Lab 3: Java Objects

A. Intro

Learning Goals

This lab focuses on three rather different aspects of programming. One is the declaration and use of reference-typed variables. Exercises provide some practice, asking you to identify common usage errors and to predict the output of a complicated code segment involving reference variables. A visual representation using box-and-pointer diagrams seems to help most programmers keep track of what points to what; there are also exercises in identifying what diagram represents a given code segment and vice versa.

Today you'll also be working with a few activities in a less artificial context, that of a bank account class. You enhance it in several ways, testing your enhancements by adding code to a separate "tester" class. (In the next lab, we will formalize this process by using the JUnit testing tool.)

Finally, you do some debugging. Many inexperienced programmers approach the problem of finding a bug rather haphazardly, immediately implementing what they think might be a fix. We model a more organized procedure: identify when the program works correctly and when it crashes or fails (this probably requires more test runs), then implement a fix that corrects the incorrect code without disturbing the correct code.

Beginning the Lab

You'll be working with a partner as usual, someone different from your partners in the first two labs. Again, be alert to differences between your approaches to today's activities and your partner's.

Before you begin the exercises in this lab, make sure to download the code and create a new Eclipse project out of it.

B. Objects and References

Boxes and Pointers

Throughout this class it will be extraordinarily helpful to draw pictures of the variables in our program to help us with debugging. The diagrams we'll teach you to use in this class are often referred to as box and pointer diagrams, or sometimes box and arrow diagrams.

Let's start off with something simple. When we declare a primitive, we draw a box for it, and label the box with the type of primitive, and the name of the variable. Here, primitives will be in red boxes. For example,

int x;

EmptyInt

Recall some of the common primitives in Java: ints, booleans, floats, charss.

When we assign a value to the primitive, we fill in the box with the value of the primitive.

x = 3;

FullInt

The real power of Java, of course, is that your variables can store more complicated types of data other than numbers and characters. Variables can also refer to objects, which are more complicated types. For example, consider the Dog class defined below.

public class Dog {
    public int myAge;
    public String myName;
}

The Dog class can contain information both about the age of a dog and its name. This makes it a more complicated type than an int, which just stores a number.

We can create a variable that will refer to an object of type Dog, similarly to how we create a variable that refers to an int:

Dog d;

This variable is called a reference, because it will refer to an object. When we first declare the reference but don't assign an object to it, we say the reference contains nothing, or null. Here's how we draw it:

NullRef

Here we're drawing references in green to emphasize that they are different from primitives.

Now let's assign the reference to a Dog object, similarly to how we assigned the value 3 to our int.

d = new Dog();

NewObj

Here an object is drawn in blue, to emphasize that it is different from a primitive and a reference. What is special about an object is that it can store primitives and references inside of it, allowing it to organize data together.

If and only if we see the new keyword, we are creating an object, so we draw a blue box for it.

Notice one critical thing about the object: unlike the 3, which is drawn inside the box for x, the Dog object is not drawn inside the variable d. Instead d simply contains an arrow that points to the Dog object. This is why d is called a reference or pointer, because it just refers to the object but doesn't contain it.

Finally, let's see how the picture updates when we assign values to the variables inside the object.

d.myAge = 1;
d.myName = "Guau";

TwoObjects

Is it what you expected?

A String is an object, not a primitive. Objects are not drawn inside other objects, so when we initialize myName, we make sure the reference points outside the object.

Discussion: Intuition for Drawing Objects

Link to the discussion

Discuss with your partner to see if you can come up with intuition as to why these diagrams are drawn the way they are. Why does it make sense that objects are not stored inside variables, but are only referred to them? Why does it make sense that objects are not drawn inside other objects? Why isn't the blue object box labeled with the name of the variable (in the example, d )? There aren't necessarily correct answers to these question, so just see if you can come up with explanations that makes sense to you. Then read over your classmates' posts to see if their intuition makes sense to you as well.

Discussion: Drawing a char Variable

Link to the discussion

Some students might incorrectly draw the result of the code

char c;
c = 'c';

as follows:

Char

Explain these students' misconception.

Self-test: Assignment Statements

Consider a main program for the Counter class mentioned in the readings:

Counter c1 = new Counter ( );
c1.increment ( );
// c1 now represents the value 1
Counter c2 = new Counter ( );
// c2 now represents the value 0

Indicate which of the box-and-pointer diagrams best represents the effect of the assignment statement

c1 = c2;

If you get this wrong, consult with your partner.

refasgt1
Incorrect. Notice the assignment statement is assigning the references equal to each other, not the objects.
refasgt2
Incorrect. References can only point to objects, not other references.
refasgt3
Correct! The assignment statement sets the references to point to the same value.
Check Solution

Self-test: Error Messages

Before you try it for yourself, answer the following question: What error message is caused by the following code?

public class Counter {
    int myCount = 0;

    void increment ( ) {
        myCount = myCount + 1;
    }

    public static void main (String [ ] args) {
        Counter c1 = new Counter ( );
        increment ( );
        c1.myCount = 0;
    }
}
c1 cannot be resolved
Incorrect. Try it out for yourself and see!
Cannot make a static reference to the non-static method increment() from the type Counter
Correct! Increment must be called on the object pointed to by c1
The constructor Counter(int) is undefined
Incorrect. Nowhere do we try to construct a counter using an argument.
The method increment() in the type Counter is not applicable for the arguments (int)
Incorrect. We never try to pass in a value to increment
Cannot make a static reference to the non-static field myCount .
Incorrect. We only reference myCount by calling it from c1 , which is a non-static reference
Check Solution

Self-test: Error Messages 2

Before you try it yourself, answer the question: What error message is caused by the following code?

public class Counter {

    int myCount = 0;

    void increment ( ) {
        myCount = myCount + 1;
    }

    public static void main (String [ ] args) {
        Counter c1 = new Counter ( );
        c1.increment ( );
        myCount = 0;
    }
}
c1 cannot be resolved
Incorrect. Try it out for yourself and see!
Cannot make a static reference to the non-static method increment() from the type Counter
Incorrect. We correctly call increment on an instance of a Counter object
The constructor Counter(int) is undefined
Incorrect. Nowhere do we try to construct a counter using an argument.
The method increment() in the type Counter is not applicable for the arguments (int)
Incorrect. We never try to pass in a value to increment
Cannot make a static reference to the non-static field myCount .
Correct! myCount must be accessed from an instance of a Counter object
Check Solution

Self-test: Error Messages 3

Before you try it yourself, answer the question: What error message is caused by the following code?

public class Counter {
 int myCount = 0;

 void increment ( ) {
    myCount = myCount + 1;
 }

 public static void main (String [ ] args) {
    Counter c1 = new Counter ( );
    c1.increment (2);
    c1.myCount = 0;
 }
c1 cannot be resolved
Incorrect. Try it out for yourself and see!
Cannot make a static reference to the non-static method increment() from the type Counter
Incorrect. We correctly call increment on an instance of a Counter object
The constructor Counter(int) is undefined
Incorrect. Nowhere do we try to construct a counter using an argument.
The method increment() in the type Counter is not applicable for the arguments (int)
Correct! Nowhere did we define a method increment that takes in an int
Cannot make a static reference to the non-static field myCount .
Incorrect. We only reference myCount by calling it from c1 , which is a non-static reference
Check Solution

Self-test: Error Messages 4

Before you try it yourself, answer the question: What error message is caused by the following code?

public class Counter {
    int myCnt = 0;

    void increment ( ) {
        myCount = myCount + 1;
    }

    public static void main (String [ ] args) {
        Counter c1 = new Counter ( );
        c1.increment ();
        c1.myCount = 0;
    }
}
c1 cannot be resolved
Incorrect. Try it out for yourself and see!
Cannot make a static reference to the non-static method increment() from the type Counter
Incorrect. We correctly call increment on an instance of a Counter object
The constructor Counter(int) is undefined
Incorrect. Nowhere do we try to construct a counter using an argument.
Cannot make a static reference to the non-static field myCount .
Incorrect. We only reference myCount by calling it from c1 , which is a non-static reference
c1.myCount cannot be resolved or is not a field.
Correct! Notice the declared instance variable is myCnt not myCount .
Check Solution

Many Representations of a Line Segment

In the next two labs you'll see four different ways of representing a line segment in Java. Recall that a line segment can be defined by a pair of numbers for one end point and a pair of numbers for the other end point. We're going to look at different ways of organizing these numbers using objects. The reasons to have you work with many representations are to help you get comfortable with working with objects, making design decisions in creating classes, and relating a class definition to the box-and-arrow representation of an object from that class.

An Exercise with a Line Class.

For the upcoming exercise, one of the partners should take the keyboard, and the other monitor what's being typed.

The file lab03/Line1.java contains a framework for a class representing a line segment as a pair of points (x1, y1) and (x2, y2). Each point is represented by a pair of integer instance variables.

Complete the framework by filling in the blanks in the printLength and printAngle methods, and by supplying code in the main method that matches the corresponding comments. Recall that the length of the line segment connecting points (x1, y1) and (x2, y2) is the square root of (x2–x1)2 + (y2–y1)2, and the angle is the arctangent2 of (y2–y1, x2–x1).

One more thing to note about the code. It uses static methods and the static variable PI declared in the Math class.

Another Version of the Line Class

Switch places with your partner.

The file lab03/Line2.java is like Line1.java except that it represents a line segment as a pair of points. It uses the Point class supplied in the java.awt class library. Complete the framework, rename either the file or the class, then test the resulting program. This requires two steps:

Note: The first line of this program is important: import java.awt.Point;. We import the Point class so that we can refer to it easily.

Self-test: More Assignment Statements

What gets printed by the following program? You and your partner should figure out the answer without using the computer. Then check your answer below.

import java.awt.Point;

public class Test1 {

    public static void main (String [ ] args) {
        Point p1 = new Point ( );
        p1.x = 1;
        p1.y = 2;
        Point p2 = new Point ( );
        p2.x = 3;
        p2.y = 4;
        // now the fun begins
        p2.x = p1.y;
        p1 = p2;
        p2.x = p1.y;
        System.out.println (p1.x + " " + p1.y
            + " " + p2.x + " " + p2.y);
    }
}
Toggle Solution

4 4 4 4

C. Typed Methods and Method Parameters

Bank Account Management

The next several exercises involve modifications to an Account class, which models a bank account. The file you're modifying is lab03/Account.java.

The Account class allows deposits and withdrawals. Instead of warning about a balance that's too low, however, it merely disallows a withdrawal request for more money than the account contains.

A few notes about the code:

Exercise: Modifying Withdrawal Behavior

The withdraw method is currently defined as a void method. Modify it to return a boolean: true if the withdrawal succeeds (along with actually doing the withdrawal) and false if it fails.

Also add tests of this feature to AccountTester.java.

Exercise: Merging Accounts

Switch places with your partner.

Define a merge method on accounts that takes the balance of the argument account, adds it to the current balance of this account, and sets the argument's balance to zero. Here is a skeleton for the new method:

/**
 * Merge account "anotherAcct" into this one by
 * removing all the money from anotherAcct and
 * adding that amount to this one.
 */
public void merge (Account anotherAcct) {
    // TODO Put your own code here
}

Add appropriate tests to those in AccountTester.java.

The code for this method demonstrates the limits of private access. Inside the merge method, you have access to two objects, one named this and the other named anotherAcct. Moreover, you can also access both your own myBalance variable and that of the other argument account! (This surprises some students who, reasoning from real life, think that an object should have private information that not even another object of the same class can access. So, this exercise is not a great example of how a real bank account should work.)

Exercise: Overdraft Protection

A convenient feature of some bank accounts is overdraft protection; rather than bouncing a check when the balance would go negative, the bank would deduct the necessary funds from a second account. One might imagine such a setup for a student account, provided the student's parents are willing to cover any overdrafts (!). Another use is to have a checking account that is tied to a saving account, where the savings account covers overdrafts on the checking account.

Implement and test overdraft protection for Account objects, by completing the following steps.

  1. Add a parentAccount instance variable to the Account class; this is the account that will provide the overdraft protection, and it may have overdraft protection of its own.
  2. Add a two-argument constructor. The first argument will be the initial balance as in the existing code. The second argument will be an Account reference with which to initialize the instance variable you defined in step 1.
  3. In the one-argument constructor, set the parent account to null.
  4. Modify the withdraw method so that, if the requested withdrawal can't be covered by this account, the difference is withdrawn from the parent account. This may trigger overdraft protection for the parent account, and then its parent, and so on. You are not allowed to assume a limit on the number of accounts connected in this way. If the account doesn't have a parent and it can't cover the withdrawal, the withdraw method should merely print an error message as before and not change any account balances. Here's an example of the desired behavior, with the Account object kathy providing overdraft protection for the Account object megan. We recommend you use recursion, not a loop, to implement this feature.
kathy balance megan balance attempted withdrawl from megan desired result
500 100 50 megan has 50, kathy has 500
500 100 200 megan has 0, kathy has 400
500 100 700 return false without changing either balance
  1. Add tests to AccountTester.java sufficient to exercise all cases in the modified withdraw method.

Discussion: Merging Revisited

Link to the discussion

One proposed solution for merging accounts is the following:

public void merge (Account otherAccount) {
    this.myBalance = this.myBalance + otherAccount.myBalance;
    otherAccount = new Account (0);
}

This doesn't work. Explain why not.

D. Debug Some Recursive Code

contains1MoreThan

contains1MoreThan is a method that returns true when myString is the result of inserting exactly one character into an argument s, and returns false otherwise.

Consider the code in lab03/Debug.java. It uses Java's String class. (If you'd like to check out the documentation for String to see all of its methods, check it out here). There is a bug in the contains1MoreThan method; you'll work with it for the rest of lab.

Exercise: When It Works, and When It Doesn't

Experiment with the Debug class in Eclipse by adding more calls to check. (Don't delete any of the calls you try; we'd like to see them in the file you submit.) Determine answers to the following questions, either by experiment or by analyzing the code. Put these answers into a file named bug.info.

  1. Describe all pairs of arguments to check for which contains1MoreThan correctly returns true.
  2. Describe all pairs of arguments to check for which contains1MoreThan correctly returns false.
  3. Describe all pairs of arguments to check for which contains1MoreThan incorrectly returns true, that is, when the first string argument to check is not the result of inserting exactly one character into the second.
  4. Describe all pairs of arguments to check for which contains1MoreThan incorrectly returns false, that is, when the first string argument to check is the result of inserting exactly one character into the second.
  5. Describe all pairs of arguments to check for which contains1MoreThan crashes.

Note that the answer for any category may be "all pairs of strings" or "no pairs of strings".

Explanation of the Bug

Determine what's wrong with the contains1MoreThan method, and how you figured it out. Add this information to your file bug.info.

E. Conclusion

Summary

We suspect that several of this lab's activities proved difficult for many of you: keeping track of what references point to what; modifying code (which you first have to understand); and systematically finding bugs. If this applies to you, get your partner or another classmate involved. Have them generate variants of the lab exercises to provide extra practice. The exercises on complicated uses of references are pretty easy to produce and easy to check once solved. Methods for counting the deposits and withdrawals can be added to the Account class. And bugs in any program are generally easy to generate. (You might want to write down any particularly problematic bugs that you encounter in lab! Keeping track of the bugs you yourself make and reveal what your own weaknesses as a programmer are)

We'll be covering arrays and testing in the next lab.

Submission

Submit the following as lab03:

Reading