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;
Recall some of the common primitives in Java: int
s, boolean
s, float
s, chars
s.
When we assign a value to the primitive, we fill in the box with the value of the primitive.
x = 3;
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:
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();
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";
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 discussionSome students might incorrectly draw the result of the code
char c;
c = 'c';
as follows:
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.
|
Incorrect. Notice the assignment statement is assigning the
references
equal to each other, not the objects.
|
|
|
Incorrect. References can only point to objects, not other references.
|
|
|
Correct! The assignment statement sets the references to point to the same value.
|
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
|
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
|
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
|
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
.
|
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:
- First access the online documentation for the
Point
class here. This web page contains information about the different methods that the class has. Get familar with this website! You will be returning to it many times throughout the class to see the documentation on different Java classes that you might want to use. - Then fill in the blanks in the
printLength
andprintAngle
methods, and supply code in themain
method that matches the corresponding comments, as you did in the preceding activity.
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);
}
}
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:
- It doesn't have a
main
method. You test it by using a tester class, provided for you inlab03/AccountTester.java
. - The code follows the convention of prefixing the word "my" onto instance variables, to reinforce the notion that each object has its own copy. This convention also deals with the difficulty of naming closely related variables. For instance, the argument to the constructor and the instance variable each represents a balance, but it leads to confusing if they're both named "balance". An alternative would be to name the instance variable "balance" and the constructor argument "initialBalance".
- The instance variable is listed after the methods. This is a controversial choice. Defenders of this practice argue that a prospective user of the class should not be concerned with or distracted by the class's "private parts". On the other hand, someone reading the code will have to know what the instance variables are to make any sense of the methods, so for that purpose it's better to have them up front.
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.
- Add a
parentAccount
instance variable to theAccount
class; this is the account that will provide the overdraft protection, and it may have overdraft protection of its own. - 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. - In the one-argument constructor, set the parent account to
null
. - 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, thewithdraw
method should merely print an error message as before and not change any account balances. Here's an example of the desired behavior, with theAccount
objectkathy
providing overdraft protection for theAccount
objectmegan
. 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 |
- Add tests to
AccountTester.java
sufficient to exercise all cases in the modifiedwithdraw
method.
Discussion: Merging Revisited
Link to the discussionOne 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
.
- Describe all pairs of arguments to check for which
contains1MoreThan
correctly returns true. - Describe all pairs of arguments to check for which
contains1MoreThan
correctly returns false. - 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. - 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. - 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
:
- the completed
Line1.java
andLine2.java
- your
Account.java
andAccountTester.java
that provide overdraft protection and account merging; - your
Debug.java
program with whatever extra calls to check you added while analyzing it; - your file
bug.info
.
Reading
- HFJ chapter 5