Chapter 3. Implementing Classes

CHAPTER GOALS

  • To become familiar with the process of implementing classes

  • To be able to implement simple methods

  • To understand the purpose and use of constructors

  • To understand how to access instance variables and local variables

  • To be able to write javadoc comments

    G To implement classes for drawing graphical shapes

In this chapter, you will learn how to implement your own classes. You will start with a given design that specifies the public interface of the class—that is, the methods through which programmers can manipulate the objects of the class. Then you will learn the steps to completing the class. You need to implement the methods, which requires that you find a data representation for the objects, and supply the instructions for each method. You need to document your efforts so that other programmers can understand and use your creation. And you need to provide a tester to validate that your class works correctly.

Instance Variables

In Chapter 2, you learned how to use objects from existing classes. In this chapter, you will start implementing your own classes. We begin with a very simple example that shows you how objects store their data, and how methods access the data of an object. You will then learn a systematic process for implementing classes.

Our first example is a class that models a tally counter, a mechanical device that is used to count people—for example, to find out how many people attend a concert or board a bus (see Figure 1).

Whenever the operator pushes a button, the counter value advances by one. We model this operation with a count method. A physical counter has a display to show the current value. In our simulation, we use a getValue method instead. For example,

Counter tally = new Counter();
tally.count();
tally.count();
int result = tally.getValue(); // Sets result to 2

When implementing the Counter class, we need to determine the data that each counter object contains. In this simple example, that is very straightforward. Each counter needs to store a variable that keeps track of how many times the counter has been advanced.

A Tally Counter

Figure 3.1. A Tally Counter

An object stores its data in instance variables. An instance of a class is an object of the class. Thus, an instance variable is a storage location that is present in each object of the class.

You specify instance variables in the class declaration:

public class Counter
{
   private int value;
   ...
}

Note

An object's instance variables store the data required for executing its methods.

An instance variable declaration consists of the following parts:

  • An access specifier (private)

  • The type of the instance variable (such as int)

  • The name of the instance variable (such as value)

Each object of a class has its own set of instance variables. For example, if concert-Counter and boardingCounter are two objects of the Counter class, then each object has its own value variable (see Figure 2). As you will see in Section 3.7, the instance variable value is set to 0 when a Counter object is constructed.

Note

Each object of a class has its own set of instance variables.

In order to gain a better understanding of how methods affect instance variables, we will have a quick look at the implementation of the methods of the Counter class.

Instance Variables

Figure 3.2. Instance Variables

The count method advances the counter value by 1. We will cover the syntax of the method header in Section 3.3. For now, focus on the body of the method inside the braces:

public void count()
{
   value = value + 1;
}

Note how the count method accesses the instance variable value. Which instance variable? The one belonging to the object on which the method is invoked. For example, consider the call

concertCounter.count();

This call advances the value variable of the concertCounter object.

The getValue method returns the current value:

public int getValue()
{
   return value;
}

The return statement is a special statement that terminates the method call and returns a result to the method's caller.

Instance variables are generally declared with the access specifier private. That specifier means that they can be accessed only by the methods of the same class, not by any other method. For example, the value variable can be accessed by the count and getValue methods of the Counter class but not a method of another class. Those other methods need to use the Counter class methods if they want to manipulate a counter's value.

Note

Private instance variables can only be accessed by methods of the same class.

In the next section, we discuss the reason for making instance variables private.

Instance Variables

2. Suppose you use a class Clock with private instance variables hours and minutes. How can you access these variables in your program?

Encapsulation

In the preceding section, you learned that you should hide instance variables by making them private. Why would a programmer want to hide something? In this section we discuss the benefits of information hiding.

The strategy of information hiding is not unique to computer programming—it is used in many engineering disciplines. Consider the electronic control module that is present in every modern car. It is a device that controls the timing of the spark plugs and the flow of gasoline into the motor. If you ask your mechanic what is inside the electronic control module, you will likely get a shrug.

The module is a black box, something that magically does its thing. A car mechanic would never open the control module—it contains electronic parts that can only be serviced at the factory. In general, engineers use the term "black box" to describe any device whose inner workings are hidden. Note that a black box is not totally mysterious. Its interface with the outside world is well-defined. For example, the car mechanic understands how the electronic control module must be connected with sensors and engine parts.

The process of hiding implementation details while publishing an interface is called encapsulation. In Java, the class construct provides encapsulation. The public methods of a class are the interface through which the private implementation is manipulated.

Note

Encapsulation is the process of hiding implementation details and providing methods for data access.

Why do car manufacturers put black boxes into cars? The black box greatly simplifies the work of the car mechanic. Before engine control modules were invented, gasoline flow was regulated by a mechanical device called a carburetor, and car mechanics had to know how to adjust the springs and latches inside. Nowadays, a mechanic no longer needs to know what is inside the module.

Similarly, a programmer using a class is not burdened by unnecessary detail, as you know from your own experience. In Chapter 2, you used classes for strings, streams, and windows without worrying how these classes are implemented.

Encapsulation also helps with diagnosing errors. A large program may consist of hundreds of classes and thousands of methods, but if there is an error with the internal data of an object, you only need to look at the methods of one class. Finally, encapsulation makes it possible to change the implementation of a class without having to tell the programmers who use the class.

Note

Encapsulation allows a programmer to use a class without having to know its implementation.

In Chapter 2, you learned to be an object user. You saw how to obtain objects, how to manipulate them, and how to assemble them into a program. In that chapter, your treated objects as black boxes. Your role was roughly analogous to the car mechanic who fixed a car by hooking up a new engine control module.

Note

Information hiding makes it simpler for the implementor of a class to locate errors and change implementations.

In this chapter, you will move on to implementing classes. In these sections, your role is analogous to the car parts manufacturer who puts together an engine control module from transistors, capacitors, and other electronic parts. You will learn the necessary Java programming techniques that enable your objects to carry out the desired behavior.

Encapsulation

4. In Chapters 1 and 2, you used System.out as a black box to cause output to appear on the screen. Who designed and implemented System.out?

5. Suppose you are working in a company that produces personal finance software. You are asked to design and implement a class for representing bank accounts. Who will be the users of your class?

Specifying the Public Interface of a Class

In this section, we will discuss the process of specifying the public interface of a class. Imagine that you are a member of a team that works on banking software. A fundamental concept in banking is a bank account. Your task is to understand the design of a BankAccount class so that you can implement it, which in turn allows other programmers on the team to use it.

You need to know exactly what features of a bank account need to be implemented. Some features are essential (such as deposits), whereas others are not important (such as the gift that a customer may receive for opening a bank account). Deciding which features are essential is not always an easy task. We will revisit that issue in Chapters 8 and 12. For now, we will assume that a competent designer has decided that the following are considered the essential operations of a bank account:

  • Deposit money

  • Withdraw money

  • Get the current balance

Note

In order to implement a class, you first need to know which methods are required.

In Java, operations are expressed as method calls. To figure out the exact specification of the method calls, imagine how a programmer would carry out the bank account operations. We'll assume that the variable harrysChecking contains a reference to an object of type BankAccount. We want to support method calls such as the following:

harrysChecking.deposit(2240.59);
harrysChecking.withdraw(500);
double currentBalance = harrysChecking.getBalance();

The first two methods are mutators. They modify the balance of the bank account and don't return a value. The third method is an accessor. It returns a value that you store in a variable or pass to a method.

As you can see from the sample calls, the BankAccount class should declare three methods:

  • public void deposit(double amount)

  • public void withdraw(double amount)

  • public double getBalance()

Recall from Chapter 2 that double denotes the double-precision floating-point type, and void indicates that a method does not return a value.

Here we only give the method headers. When you declare a method, you also need to provide the method body, consisting of statements that are executed when the method is called.

public void deposit(double amount)
{
   implementation--filled in later
}

We will supply the method bodies in Section 3.5.

Every method header contains the following parts:

  • An access specifier (usually public)

  • The return type (the type of the value returned, such as void or double)

  • The name of the method (such as deposit)

  • A list of the parameter variables of the method (if any), enclosed in parentheses (such as double amount)

Note

In a method header, you specify the return type, method name, and the types and names of the parameters.

The access specifier controls which other methods can call this method. Most methods should be declared as public. That way, all other methods in a program can call them. (Occasionally, it can be useful to have private methods. They can only be called from other methods of the same class.)

The return type is the type of the value that the method returns. The deposit method does not return a value, whereas the getBalance method returns a value of type double.

Each parameter of the method has both a type and a name that describes its purpose. For example, the deposit method has a single parameter named amount of type double.

Next, you need to supply constructors. A constructor initializes the instance variables of an object. In Java, a constructor is very similar to a method, with two important differences.

  • The name of the constructor is always the same as the name of the class (e.g., BankAccount).

  • Constructors have no return type (not even void).

We want to construct bank accounts that initially have a zero balance, as well as accounts that have a given initial balance. For this purpose, we specify two constructors.

  • public BankAccount()

  • public BankAccount(double initialBalance)

They are used as follows:

BankAccount harrysChecking = new BankAccount();
BankAccount momsSavings = new BankAccount(5000);

Just like a method, a constructor also has a body—a sequence of statements that is executed when a new object is constructed.

public BankAccount()
{
   implementation--filled in later
}

Note

Constructors set the initial data for objects. The constructor name is always the same as the class name.

The statements in the constructor body will set the instance variables of the object that is being constructed—see Section 3.5.

Don't worry about the fact that there are two constructors with the same name—all constructors of a class have the same name, that is, the name of the class. The compiler can tell them apart because they take different parameters.

When declaring a class, you place all constructor and method declarations inside, like this:

public class BankAccount
{
   private instance variables--filled in later

   // Constructors
   public BankAccount()
   {
      implementation--filled in later
   }

   public BankAccount(double initialBalance)
   {
      implementation--filled in later
   }
// Methods
   public void deposit(double amount)
   {
      implementation--filled in later
   }

   public void withdraw(double amount)
   {
      implementation--filled in later
   }

   public double getBalance()
   {
      implementation--filled in later
   }
}

The public constructors and methods of a class form the public interface of the class. These are the operations that any programmer can use to create and manipulate BankAccount objects.

Our BankAccount class is simple, but it allows programmers to carry out all of the important operations that commonly occur with bank accounts. For example, consider this program segment, authored by a programmer who uses the BankAccount class. These statements transfer an amount of money from one bank account to another:

// Transfer from one account to another
double transferAmount = 500;
momsSavings.withdraw(transferAmount);
harrysChecking.deposit(transferAmount);

And here is a program segment that adds interest to a savings account:

double interestRate = 5; // 5% interest
double interestAmount
      = momsSavings.getBalance() * interestRate / 100;
momsSavings.deposit(interestAmount);

As you can see, programmers can use objects of the BankAccount class to carry out meaningful tasks, without knowing how the BankAccount objects store their data or how the BankAccount methods do their work.

Of course, as implementors of the BankAccount class, we will need to supply the private implementation. We will do so in Section 3.5. First, however, an important step remains: documenting the public interface. That is the topic of the next section.

Class Declaration

7. What is wrong with this sequence of statements? BankAccount harrysChecking = new BankAccount(10000); System.out.println(harrysChecking.withdraw(500));

8. Suppose you want a more powerful bank account abstraction that keeps track of an account number in addition to the balance. How would you change the public interface to accommodate this enhancement?

Commenting the Public Interface

When you implement classes and methods, you should get into the habit of thoroughly commenting their behaviors. In Java there is a very useful standard form for documentation comments. If you use this form in your classes, a program called javadoc can automatically generate a neat set of HTML pages that describe them. (See Productivity Hint 3.1 on page 92 for a description of this utility.)

A documentation comment is placed before the class or method declaration that is being documented. It starts with a /**, a special comment delimiter used by the javadoc utility. Then you describe the method's purpose. Then, for each method parameter, you supply a line that starts with @param, followed by the parameter name and a short explanation. Finally, you supply a line that starts with @return, describing the return value. You omit the @param tag for methods that have no parameters, and you omit the @return tag for methods whose return type is void.

Note

Use documentation comments to describe the classes and public methods of your programs.

The javadoc utility copies the first sentence of each comment to a summary table in the HTML documentation. Therefore, it is best to write that first sentence with some care. It should start with an uppercase letter and end with a period. It does not have to be a grammatically complete sentence, but it should be meaningful when it is pulled out of the comment and displayed in a summary.

Here are two typical examples.

/**
   Withdraws money from the bank account.
   @param amount the amount to withdraw
*/
public void withdraw(double amount)
{
   implementation--filled in later
}

/**
   Gets the current balance of the bank account.
   @return the current balance
*/
public double getBalance()
{
   implementationfilled in later
}

The comments you have just seen explain individual methods. Supply a brief comment for each class, explaining its purpose. The comment syntax for class comments is very simple: Just place the documentation comment above the class.

/**
   A bank account has a balance that can be changed by
   deposits and withdrawals.
*/
public class BankAccount
{
   ...
}

Your first reaction may well be "Whoa! Am I supposed to write all this stuff?" These comments do seem pretty repetitive. But you should take the time to write them, even if it feels silly.

It is always a good idea to write the method comment first, before writing the code in the method body. This is an excellent test to see that you firmly understand what you need to program. If you can't explain what a class or method does, you aren't ready to implement it.

What about very simple methods? You can easily spend more time pondering whether a comment is too trivial to write than it takes to write it. In practical programming, very simple methods are rare. It is harmless to have a trivial method overcommented, whereas a complicated method without any comment can cause real grief to future maintenance programmers. According to the standard Java documentation style, every class, every method, everyparameter, and every return value should have a comment.

Note

Provide documentation comments for every class, every method, every parameter, and every return value.

The javadoc utility formats your comments into a neat set of documents that you can view in a web browser. It makes good use of the seemingly repetitive phrases. The first sentence of the comment is used for a summary table of all methods of your class (see Figure 3). The @param and @return comments are neatly formatted in the detail description of each method (see Figure 4). If you omit any of the comments, then javadoc generates documents that look strangely empty.

This documentation format should look familiar. The programmers who implement the Java library use javadoc themselves. They too document every class, every method, every parameter, and every return value, and then use javadoc to extract the documentation in HTML format.

A Method Summary Generated by javadoc

Figure 3.3. A Method Summary Generated by javadoc

Method Detail Generated by javadoc

Figure 3.4. Method Detail Generated by javadoc

Method Detail Generated by javadoc

10. Suppose we enhance the BankAccount class so that each account has an account number. Supply a documentation comment for the constructor public BankAccount(int accountNumber, double initialBalance)

11. Why is the following documentation comment questionable?

/**
   Each account has an account number.
   @return the account number of this account
*/
public int getAccountNumber()

Providing the Class Implementation

Now that you understand the specification of the public interface of the BankAccount class, let's provide the implementation.

First, we need to determine the data that each bank account object contains. In the case of our simple bank account class, each object needs to store a single value, the current balance. (A more complex bank account class might store additional data—perhaps an account number, the interest rate paid, the date for mailing out the next statement, and so on.)

public class BankAccount
{
   private double balance;
   ...
}

Note

The private implementation of a class consists of instance variables, and the bodies of constructors and methods.

Now that we have determined the instance variables, let's complete the BankAccount class by supplying the bodies of the constructors and methods. Each body contains a sequence of statements. We'll start with the constructors because they are very straightforward. A constructor has a simple job: to initialize the instance variables of an object.

Recall that we designed the BankAccount class to have two constructors. The first constructor simply sets the balance to zero:

public BankAccount()
{
   balance = 0;
}

The second constructor sets the balance to the value supplied as the construction parameter:

public BankAccount(double initialBalance)
{
   balance = initialBalance;
}

To see how these constructors work, let us trace the statement

BankAccount harrysChecking = new BankAccount(1000);

one step at a time.

Here are the steps that are carried out when the statement executes.

  • Create a new object of type BankAccount.

  • Call the second constructor (because a parameter value is supplied in the constructor call).

  • Set the parameter variable initialBalance to 1000.

  • Set the balance instance variable of the newly created object to initialBalance.

  • Return an object reference, that is, the memory location of the object, as the value of the new expression.

  • Store that object reference in the harrysChecking variable.

Let's move on to implementing the BankAccount methods. Here is the deposit method:

public void deposit(double amount)
{
   balance = balance + amount;
}

To understand exactly what the method does, consider this statement:

harrysChecking.deposit(500);

This statement carries out the following steps:

  • Set the parameter variable amount to 500.

  • Fetch the balance instance variable of the object whose location is stored in harrysChecking.

  • Add the value of amount to balance

  • Store the sum in the balance instance variable, overwriting the old value.

The withdraw method is very similar to the deposit method:

public void withdraw(double amount)
{
   balance = balance - amount;
}

There is only one method left, getBalance. Unlike the deposit and withdraw methods, which modify the instance variables of the object on which they are invoked, the getBalance method returns a value:

public double getBalance()
{
   return balance;
}

We have now completed the implementation of the BankAccount class—see the code listing below. There is only one step remaining: testing that the class works correctly. That is the topic of the next section.

ch03/account/BankAccount.java

1  /**
2    A bank account has a balance that can be changed by
3    deposits and withdrawals.
4  */
5  public class BankAccount
6  {
7    private double balance;
8
9    /**
10       Constructs a bank account with a zero balance.
11    */
12    public BankAccount()
13    {
14       balance = 0;
15    }
16
17       /**
18          Constructs a bank account with a given balance.
19          @param initialBalance the initial balance
20       */
21       public BankAccount(double initialBalance)
22       {
23          balance = initialBalance;
24       }
25
26       /**
27          Deposits money into the bank account.
28          @param amount the amount to deposit
29       */
30       public void deposit(double amount)
31       {
32            balance = balance + amount;
33       }
34
35       /**
36          Withdraws money from the bank account.
37          @param amount the amount to withdraw
38       */
39       public void withdraw(double amount)
40       {
41            balance = balance - amount;
42       }
43
44       /**
45          Gets the current balance of the bank account.
46          @return the current balance
47       */
48       public double getBalance()
49       {
50          return balance;
51       }
52    }
Method Declaration

13. Why does the following code not succeed in robbing mom's bank account?

public class BankRobber
{
   public static void main(String[] args)
    {
      BankAccount momsSavings = new BankAccount(1000);
      momsSavings.balance = 0;
    }
}

14. The Rectangle class has four instance variables: x, y, width, and height. Give a possible implementation of the getWidth method.

15. Give a possible implementation of the translate method of the Rectangle class.

Unit Testing

In the preceding section, we completed the implementation of the BankAccount class. What can you do with it? Of course, you can compile the file BankAccount.java. However, you can't execute the resulting BankAccount.class file. It doesn't contain a main method. That is normal—most classes don't contain a main method.

In the long run, your class may become a part of a larger program that interacts with users, stores data in files, and so on. However, before integrating a class into a program, it is always a good idea to test it in isolation. Testing in isolation, outside a complete program, is called unit testing.

Note

A unit test verifies that a class works correctly in isolation, outside a complete program.

The Return Value of the getBalance Method in BlueJ

Figure 3.5. The Return Value of the getBalance Method in BlueJ

To test your class, you have two choices. Some interactive development environments have commands for constructing objects and invoking methods (see Special Topic 2.1). Then you can test a class simply by constructing an object, calling methods, and verifying that you get the expected return values. Figure 5 shows the result of calling the getBalance method on a BankAccount object in BlueJ.

Alternatively, you can write a tester class. A tester class is a class with a main method that contains statements to run methods of another class. As discussed in Section 2.9, a tester class typically carries out the following steps:

  1. Construct one or more objects of the class that is being tested.

  2. Invoke one or more methods.

  3. Print out one or more results.

  4. Print the expected results.

The MoveTester class in Section 2.9 is a good example of a tester class. That class runs methods of the Rectangle class—a class in the Java library.

Here is a class to run methods of the BankAccount class. The main method constructs an object of type BankAccount, invokes the deposit and withdraw methods, and then displays the remaining balance on the console.

We also print the value that we expect to see. In our sample program, we deposit $2,000 and withdraw $500. We therefore expect a balance of $1,500.

ch03/account/BankAccountTester.java

1    /**
2       A class to test the BankAccount class.
3    */
4    public class BankAccountTester
5    {
6       /**
7          Tests the methods of the BankAccount class.
8          @param args not used
9       */
10       public static void main(String[] args)
11       {
12          BankAccount harrysChecking = new BankAccount();
13          harrysChecking.deposit(2000);
14          harrysChecking.withdraw(500);
15          System.out.println(harrysChecking.getBalance());
16          System.out.println("Expected: 1500");
17       }
18    }

Program Run

1500
Expected: 1500

To produce a program, you need to combine the BankAccount and the BankAccount-Tester classes. The details for building the program depend on your compiler and development environment. In most environments, you need to carry out these steps:

  1. Make a new subfolder for your program.

  2. Make two files, one for each class.

  3. Compile both files.

  4. Run the test program.

Many students are surprised that such a simple program contains two classes. However, this is normal. The two classes have entirely different purposes. The Bank-Account class describes objects that compute bank balances. The BankAccountTester class runs a test that puts a BankAccount object through its paces.

The Return Value of the getBalance Method in BlueJ

17. Why is the BankAccountTester class unnecessary in development environments that allow interactive testing, such as BlueJ?

Local Variables

In this section, we discuss the behavior of local variables. A local variable is a variable that is declared in the body of a method. For example, the giveChange method in How To 3.1 on page 96 declares a local variable change:

public double giveChange()
{
   double change = payment - purchase;
   purchase = 0;
   payment = 0;
   return change;
}

Note

Local variables are declared in the body of a method.

Parameter variables are similar to local variables, but they are declared in method headers. For example, the following method declares a parameter variable amount:

public void enterPayment(double amount)

Local and parameter variables belong to methods. When a method runs, its local and parameter variables come to life. When the method exits, they are removed immediately. For example, if you call register.giveChange(), then a variable change is created. When the method exits, that variable is removed.

Note

When a method exits, its local variables are removed.

In contrast, instance variables belong to objects, not methods. When an object is constructed, its instance variables are created. The instance variables stay alive until no method uses the object any longer. (The Java virtual machine contains an agent called a garbage collector that periodically reclaims objects when they are no longer used.)

An important difference between instance variables and local variables is initialization. You must initialize all local variables. If you don't initialize a local variable, the compiler complains when you try to use it. (Note that parameter variables are initialized when the method is called.)

Note

Instance variables are initialized to a default value, but you must initialize local variables.

Instance variables are initialized with a default value before a constructor is invoked. Instance variables that are numbers are initialized to 0. Object references are set to a special value called null. If an object reference is null, then it refers to no object at all. We will discuss the null value in greater detail in Section 5.2.5.

Local Variables

19. Why was it necessary to introduce the local variable change in the giveChange method? That is, why didn't the method simply end with the statement return payment - purchase;

Implicit Parameters

In Section 2.4, you learned that a method has an implicit parameter (the object on which the method is invoked) in addition to the explicit parameters, which are enclosed in parentheses. In this section, we will examine implicit parameters in greater detail.

Have a look at a particular invocation of the deposit method:

momsSavings.deposit(500);

Here, the implicit parameter is momsSavings and the explicit parameter is 500.

Now look again at the code of the deposit method:

public void deposit(double amount)
{
   balance = balance + amount;
}

What does balance mean exactly? After all, our program may have multiple Bank-Account objects, and each of them has its own balance.

Of course, since we are depositing the money into momsSavings, balance must mean momsSavings.balance. In general, when you refer to an instance variable inside a method, it means the instance variable of the implicit parameter.

Note

Use of an instance variable name in a method denotes the instance variable of the implicit parameter.

If you need to, you can access the implicit parameter—the object on which the method is called—with the reserved word this. For example, in the preceding method invocation, this refers to the same object as momsSavings (see Figure 6).

Note

The this reference denotes the implicit parameter.

The statement

balance = balance + amount;

actually means

this.balance = this.balance + amount;
The Implicit Parameter of a Method Call

Figure 3.6. The Implicit Parameter of a Method Call

When you refer to an instance variable in a method, the compiler automatically applies it to the this reference. Some programmers actually prefer to manually insert the this reference before every instance variable because they find it makes the code clearer. Here is an example:

public BankAccount(double initialBalance)
{
   this.balance = initialBalance;
}

You may want to try it out and see if you like that style.

The this reference can also be used to distinguish between instance variables and local or parameter variables. Consider the constructor

public BankAccount(double balance)
{
   this.balance = balance;
}

The expression this.balance clearly refers to the balance instance variable. However, the expression balance by itself seems ambiguous. It could denote either the parameter variable or the instance variable. In Java, local and parameter variables are considered first when looking up variable names. Therefore,

this.balance = balance;

means: "Set the instance variable balance to the parameter variable balance".

There is another situation in which it is important to understand the implicit parameter. Consider the following modification to the BankAccount class. We add a method to apply the monthly account fee:

public class BankAccount
{
   ...
   public void monthlyFee()
   {
      withdraw(10); // Withdraw $10 from this account
   }
}

That means to withdraw from the same bank account object that is carrying out the monthlyFee operation. In other words, the implicit parameter of the withdraw method is the (invisible) implicit parameter of the monthlyFee method.

Note

A method call without an implicit parameter is applied to the same object.

If you find it confusing to have an invisible parameter, you can use the this reference to make the method easier to read:

public class BankAccount
{
   ...
   public void monthlyFee()
   {
      this.withdraw(10); // Withdraw $10 from this account
   }
}

You have now seen how to use objects and implement classes, and you have learned some important technical details about variables and method parameters. The remainder of this chapter continues the optional graphics track. In the next chapter, you will learn more about the most fundamental data types of the Java language.

The Implicit Parameter of a Method Call

BankAccount class have, and what are their names and types?

21. In the deposit method, what is the meaning of this.amount? Or, if the expression has no meaning, why not?

22. How many implicit and explicit parameters does the main method of the Bank-AccountTester class have, and what are they called?

Shape Classes

In this section, we continue the optional graphics track by discussing how to organize complex drawings in a more object-oriented fashion.

When you produce a drawing that is composed of complex parts, such as the one in Figure 7, it is a good idea to make a separate class for each part. Provide a draw method that draws the shape, and provide a constructor to set the position of the shape. For example, here is the outline of the Car class.

public class Car
{
   public Car(int x, int y)
   {
      // Remember position
       ...
   }

   public void draw(Graphics2D g2)
   {
      // Drawing instructions
       ...
   }
}

Note

It is a good idea to make a class for any part of a drawing that can occur more than once.

You will find the complete class declaration at the end of this section. The draw method contains a rather long sequence of instructions for drawing the body, roof, and tires. The coordinates of the car parts seem a bit arbitrary. To come up with suitable values, draw the image on graph paper and read off the coordinates (Figure 8).

Note

To figure out how to draw a complex shape, make a sketch on graph paper.

The Car Component Draws Two Car Shapes

Figure 3.7. The Car Component Draws Two Car Shapes

The program that produces Figure 7 is composed of three classes.

  • The Car class is responsible for drawing a single car. Two objects of this class are constructed, one for each car.

  • The CarComponent class displays the drawing.

  • The CarViewer class shows a frame that contains a CarComponent.

Let us look more closely at the CarComponent class. The paintComponent method draws two cars. We place one car in the top-left corner of the window, and the other car in the bottom right. To compute the bottom right position, we call the getWidth and getHeight methods of the JComponent class. These methods return the dimensions of the component. We subtract the dimensions of the car to determine the position of car2:

Using Graph Paper to Find Shape Coordinates

Figure 3.8. Using Graph Paper to Find Shape Coordinates

Car car1 = new Car(0, 0);
int x = getWidth() - 60;
int y = getHeight() - 30;
Car car2 = new Car(x, y);

Pay close attention to the call to getWidth inside the paintComponent method of CarComponent. The method call has no implicit parameter, which means that the method is applied to the same object that executes the paintComponent method. The component simply obtains its own width.

Run the program and resize the window. Note that the second car always ends up at the bottom-right corner of the window. Whenever the window is resized, the paintComponent method is called and the car position is recomputed, taking the current component dimensions into account.

ch03/car/CarComponent.java

1  import java.awt.Graphics;
2  import java.awt.Graphics2D;
3  import javax.swing.JComponent;
4
5  /**
6     This component draws two car shapes.
7  */
8  public class CarComponent extends JComponent
9  {
10     public void paintComponent(Graphics g)
11     {
12        Graphics2D g2 = (Graphics2D) g;
13
14        Car car1 = new Car(0, 0);
15
16        int x = getWidth() - 60;
17        int y = getHeight() - 30;
18
19        Car car2 = new Car(x, y);
20
21        car1.draw(g2);
22        car2.draw(g2);
23     }
24  }

ch03/car/Car.java

1    import java.awt.Graphics2D;
2    import java.awt.Rectangle;
3    import java.awt.geom.Ellipse2D;
4    import java.awt.geom.Line2D;
5    import java.awt.geom.Point2D;
6
7    /**
8       A car shape that can be positioned anywhere on the screen.
9    */
10    public class Car
11    {
12        private int xLeft;
13        private int yTop;
14
15        /**
16          Constructs a car with a given top left corner.
17          @param x  the x coordinate of the top left corner
18          @param y  the y coordinate of the top left corner
19        */
20         public Car(int x, int y)
21        {
22          xLeft = x;
23          yTop = y;
24        }
25
26        /**
27          Draws the car.
28          @param g2 the graphics context
29        */
30          public void draw(Graphics2D g2)
31        {
32            Rectangle body
33                  = new Rectangle(xLeft, yTop + 10, 60, 10);
34            Ellipse2D.Double frontTire
35                  = new Ellipse2D.Double(xLeft + 10, yTop + 20, 10, 10);
36            Ellipse2D.Double rearTire
37                  = new Ellipse2D.Double(xLeft + 40, yTop + 20, 10, 10);
38
39            // The bottom of the front windshield
40            Point2D.Double r1
41                  = new Point2D.Double(xLeft + 10, yTop + 10);
42            // The front of the roof
43            Point2D.Double r2
44                  = new Point2D.Double(xLeft + 20, yTop);
45            // The rear of the roof
46            Point2D.Double r3
47                  = new Point2D.Double(xLeft + 40, yTop);
48            // The bottom of the rear windshield
49            Point2D.Double r4
50                  = new Point2D.Double(xLeft + 50, yTop + 10);
51
52            Line2D.Double frontWindshield
53                  = new Line2D.Double(r1, r2);
54            Line2D.Double roofTop
55                  = new Line2D.Double(r2, r3);
56            Line2D.Double rearWindshield
57                  = new Line2D.Double(r3, r4);
58
59            g2.draw(body);
60            g2.draw(frontTire);
61            g2.draw(rearTire);
62            g2.draw(frontWindshield);
63            g2.draw(roofTop);
64            g2.draw(rearWindshield);
65        }
66    }

ch03/car/CarViewer.java

1   import javax.swing.JFrame;
2
3   public class CarViewer
4   {
5      public static void main(String[] args)
6       {
7         JFrame frame = new JFrame();
8
9         frame.setSize(300, 400);
10         frame.setTitle("Two cars");
11         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
12
13         CarComponent component = new CarComponent();
14         frame.add(component);
15
16         frame.setVisible(true);
17       }
18   }
Using Graph Paper to Find Shape Coordinates

24. Which class needs to be modified to have the car tires painted in black, and what modification do you need to make?

25. How do you make the cars twice as big?

Summary of Learning Objectives

Understand instance variables and the methods that access them.

  • An object's instance variables store the data required for executing its methods.

  • Each object of a class has its own set of instance variables.

  • Private instance variables can only be accessed by methods of the same class.

Explain the concept and benefits of encapsulation.

  • Encapsulation is the process of hiding implementation details and providing methods for data access.

  • Encapsulation allows a programmer to use a class without having to know its implementation.

  • Information hiding makes it simpler for the implementor of a class to locate errors and change implementations.

Write method and constructor headers that describe the public interface of a class.

  • In order to implement a class, you first need to know which methods are required.

  • In a method header, you specify the return type, method name, and the types and names of the parameters.

  • Constructors set the initial data for objects. The constructor name is always the same as the class name.

Write class documentation in javadoc format.

  • Use documentation comments to describe the classes and public methods of your programs.

  • Provide documentation comments for every class, every method, every parameter, and every return value.

Provide the private implementation of a class.

  • The private implementation of a class consists of instance variables, and the bodies of constructors and methods.

Write tests that verify that a class works correctly.

  • A unit test verifies that a class works correctly in isolation, outside a complete program.

  • Compare lifetime and initialization of instance, local, and parameter variables.

  • Local variables are declared in the body of a method.

  • When a method exits, its local variables are removed.

  • Instance variables are initialized to a default value, but you must initialize local variables.

Recognize the use of the implicit parameter in method declarations.

  • Use of an instance variable name in a method denotes the instance variable of the implicit parameter.

  • The this reference denotes the implicit parameter.

  • A method call without an implicit parameter is applied to the same object.

Implement classes that draw graphical shapes.

  • It is a good idea to make a class for any part of a drawing that can occur more than once.

  • To figure out how to draw a complex shape, make a sketch on graph paper.

Media Resources

  • Worked Example Making a Simple Menu

  • • Lab Exercises

  • Media Resources
  • Media Resources
  • Media Resources

Review Exercises

R3.1 What is the interface of a class? How does it differ from the implementation of a class?

R3.2 What is encapsulation? Why is it useful?

R3.3 Instance variables are a part of the hidden implementation of a class, but they aren't actually hidden from programmers who have the source code of the class. Explain to what extent the private reserved word provides information hiding.

R3.4 Consider a class Grade that represents a letter grade, such as A+ or B. Give two choices of instance variables that can be used for implementing the Grade class.

R3.5 Consider a class Time that represents a point in time, such as 9 A.M. or 3:30 P.M. Give two different sets of instance variables that can be used for implementing the Time class.

R3.6 Suppose the implementor of the Time class of Exercise R3.5 changes from one implementation strategy to another, keeping the public interface unchanged. What do the programmers who use the Time class need to do?

R3.7 You can read the value instance variable of the Counter class with the getValue accessor method. Should there be a setValue mutator method to change it? Explain why or why not.

R3.8

  1. Show that the BankAccount(double initialBalance) constructor is not strictly necessary. That is, if we removed that constructor from the public interface, how could a programmer still obtain BankAccount objects with an arbitrary balance?

  2. Conversely, could we keep only the BankAccount(double initialBalance) constructor and remove the BankAccount() constructor?

R3.9 Why does the BankAccount class not have a reset method?

R3.10 What happens in our implementation of the BankAccount class when more money is withdrawn from the account than the current balance?

R3.11 What is the this reference? Why would you use it?

R3.12 What does the following method do? Give an example of how you can call the method.

public class BankAccount
{
   public void mystery(BankAccount that, double amount)
   {
       this.balance = this.balance - amount;
       that.balance = that.balance + amount;
   }
   ... // Other bank account methods
}

R3.13 Suppose you want to implement a class TimeDepositAccount. A time deposit account has a fixed interest rate that should be set in the constructor, together with the initial balance. Provide a method to get the current balance. Provide a method to add the earned interest to the account. This method should have no parameters because the interest rate is already known. It should have no return value because you already provided a method for obtaining the current balance. It is not possible to deposit additional funds into this account. Provide a withdraw method that removes the entire balance. Partial withdrawals are not allowed.

R3.14 Consider the following implementation of a class Square:

public class Square
{
   private int sideLength;
   private int area; // Not a good idea

   public Square(int length)
   {
       sideLength = length;
   }

   public int getArea()
   {
      area = sideLength * sideLength;
      return area;
   }
}

Why is it not a good idea to introduce an instance variable for the area? Rewrite the class so that area is a local variable.

R3.15 Consider the following implementation of a class Square:

public class Square
{
   private int sideLength;
   private int area;

   public Square(int initialLength)
   {
      sideLength = initialLength;
      area = sideLength * sideLength;
   }

   public int getArea() { return area; }
   public void grow() { sideLength = 2 * sideLength(); }
}

What error does this class have? How would you fix it?

R3.16 Provide a unit test class for the Counter class in Section 3.1.

R3.17 Read Exercise P3.7, but do not implement the Car class yet. Write a tester class that tests a scenario in which gas is added to the car, the car is driven, more gas is added, and the car is driven again. Print the actual and expected amount of gas in the tank.

R3.18 Suppose you want to extend the car viewer program in Section 3.9 to show a suburban scene, with several cars and houses. Which classes do you need?

R3.19 Explain why the calls to the getWidth and getHeight methods in the CarComponent class have no explicit parameter.

R3.20 How would you modify the Car class in order to show cars of varying sizes?

Programming Exercises

P3.1 Write a BankAccountTester class whose main method constructs a bank account, deposits $1,000, withdraws $500, withdraws another $400, and then prints the remaining balance. Also print the expected result.

P3.2 Add a method

public void addInterest(double rate)

to the BankAccount class that adds interest at the given rate. For example, after the statements

BankAccount momsSavings = new BankAccount(1000);
momsSavings.addInterest(10); // 10% interest

the balance in momsSavings is $1,100. Also supply a BankAccountTester class that prints the actual and expected balance.

P3.3 Write a class SavingsAccount that is similar to the BankAccount class, except that it has an added instance variable interest. Supply a constructor that sets both the initial balance and the interest rate. Supply a method addInterest (with no explicit parameter) that adds interest to the account. Write a SavingsAccountTester class that constructs a savings account with an initial balance of $1,000 and an interest rate of 10%. Then apply the addInterest method and print the resulting balance. Also compute the expected result by hand and print it.

P3.4 Add a feature to the CashRegister class for computing sales tax. The tax rate should be supplied when constructing a CashRegister object. Add recordTaxablePurchase and getTotalTax methods. (Amounts added with recordPurchase are not taxable.) The giveChange method should correctly reflect the sales tax that is charged on taxable items.

P3.5 After closing time, the store manager would like to know how much business was transacted during the day. Modify the CashRegister class to enable this functionality. Supply methods getSalesTotal and getSalesCount to get the total amount of all sales and the number of sales. Supply a method reset that resets any counters and totals so that the next day's sales start from zero.

P3.6 Implement a class Employee. An employee has a name (a string) and a salary (a double). Provide a constructor with two parameters

public Employee(String employeeName, double currentSalary)

and methods

public String getName()
public double getSalary()
public void raiseSalary(double byPercent)

These methods return the name and salary, and raise the employee's salary by a certain percentage. Sample usage:

Employee harry = new Employee("Hacker, Harry", 50000);
harry.raiseSalary(10); // Harry gets a 10% raise

Supply an EmployeeTester class that tests all methods.

P3.7 Implement a class Car with the following properties. A car has a certain fuel efficiency (measured in miles/gallon or liters/km—pick one) and a certain amount of fuel in the gas tank. The efficiency is specified in the constructor, and the initial fuel level is 0. Supply a method drive that simulates driving the car for a certain distance, reducing the amount of gasoline in the fuel tank. Also supply methods getGasInTank, returning the current amount of gasoline in the fuel tank, and addGas, to add gasoline to the fuel tank. Sample usage:

Car myHybrid = new Car(50); // 50 miles per gallon
myHybrid.addGas(20); // Tank 20 gallons
myHybrid.drive(100); // Drive 100 miles
double gasLeft = myHybrid.getGasInTank(); // Get gas remaining in tank

You may assume that the drive method is never called with a distance that consumes more than the available gas. Supply a CarTester class that tests all methods.

P3.8 Implement a class Student. For the purpose of this exercise, a student has a name and a total quiz score. Supply an appropriate constructor and methods getName(), addQuiz(int score), getTotalScore(), and getAverageScore(). To compute the latter, you also need to store the number of quizzes that the student took.

Supply a StudentTester class that tests all methods.

P3.9 Implement a class Product. A product has a name and a price, for example new Product("Toaster", 29.95). Supply methods getName, getPrice, and reducePrice. Supply a program ProductPrinter that makes two products, prints the name and price, reduces their prices by $5.00, and then prints the prices again.

P3.10 Provide a class for authoring a simple letter. In the constructor, supply the names of the sender and the recipient:

public Letter(String from, String to)

Supply a method

public void addLine(String line)

to add a line of text to the body of the letter.

Supply a method

public String getText()

that returns the entire text of the letter. The text has the form:

Dear recipient name:
blank line
first line of the body
second line of the body
...
last line of the body
blank line
Sincerely,
blank line
sender name

Also supply a class LetterPrinter that prints this letter.

Dear John:

I am sorry we must part.
I wish you all the best.

Sincerely,

Mary

Construct an object of the Letter class and call addLine twice.

Hints: (1) Use the concat method to form a longer string from two shorter strings.

(2) The special string " " represents a new line. For example, the statement

body = body.concat("Sincerely,").concat("
");

adds a line containing the string "Sincerely," to the body.

P3.11 Write a class Bug that models a bug moving along a horizontal line. The bug moves either to the right or left. Initially, the bug moves to the right, but it can turn to change its direction. In each move, its position changes by one unit in the current direction. Provide a constructor

public Bug(int initialPosition)

and methods

public void turn()
public void move()
public int getPosition()

Sample usage:

Bug bugsy = new Bug(10);
bugsy.move(); // now the position is 11
bugsy.turn();
bugsy.move(); // now the position is 10

Your BugTester should construct a bug, make it move and turn a few times, and print the actual and expected position.

P3.12 Implement a class Moth that models a moth flying across a straight line. The moth has a position, the distance from a fixed origin. When the moth moves toward a point of light, its new position is halfway between its old position and the position of the light source. Supply a constructor

public Moth(double initialPosition)

and methods

public void moveToLight(double lightPosition)
public double getPosition()

Your MothTester should construct a moth, move it toward a couple of light sources, and check that the moth's position is as expected.

P3.13 Implement a class RoachPopulation that simulates the growth of a roach population. The constructor takes the size of the initial roach population. The breed method simulates a period in which the roaches breed, which doubles their population. The spray method simulates spraying with insecticide, which reduces the population by 10 percent. The getRoaches method returns the current number of roaches. A program called RoachSimulation simulates a population that starts out with 10 roaches. Breed, spray, and print the roach count. Repeat three more times.

P3.14 Implement a VotingMachine class that can be used for a simple election. Have methods to clear the machine state, to vote for a Democrat, to vote for a Republican, and to get the tallies for both parties. Extra credit if your program gives the nod to your favored party if the votes are tallied after 8 P.M. on the first Tuesday in November, but acts normally on all other dates. (Hint: Use the GregorianCalendar class—see Programming Project 2.1.)

P3.15 Draw a "bull's eye"—a set of concentric rings in alternating black and white colors. Hint: Fill a black circle, then fill a smaller white circle on top, and so on.

Programming Exercises

Your program should be composed of classes BullsEye, BullsEyeComponent, and Bulls-EyeViewer.

P3.16 Write a program that draws a picture of a house. It could be as simple as the accompanying figure, or if you like, make it more elaborate (3-D, skyscraper, marble columns in the entryway, whatever).

Programming Exercises

Implement a class House and supply a method draw(Graphics2D g2) that draws the house.

P3.17 Extend Exercise P3.16 by supplying a House constructor for specifying the position and size. Then populate your screen with a few houses of different sizes.

P3.18 Change the car viewer program in Section 3.9 to make the cars appear in different colors. Each Car object should store its own color. Supply modified Car and Car-Component classes.

P3.19 Change the Car class so that the size of a car can be specified in the constructor. Change the CarComponent class to make one of the cars appear twice the size of the original example.

P3.20 Write a program to plot the string "HELLO", using only lines and circles. Do not call drawString, and do not use System.out. Make classes LetterH, LetterE, LetterL, and LetterO.

P3.21 Write a program that displays the Olympic rings. Color the rings in the Olympic colors.

Programming Exercises

Provide a class OlympicRingViewer and a class OlympicRingComponent.

P3.22 Make a bar chart to plot the following data set. Label each bar. Make the bars horizontal for easier labeling.

Bridge Name

Longest Span (ft)

Golden Gate

4,200

Brooklyn

1,595

Delaware Memorial

2,150

Mackinac

3,800

Provide a class BarChartViewer and a class BarChartComponent.

Programming Projects

Project 3.1 In this project, you will enhance the BankAccount class and see how abstraction and encapsulation enable evolutionary changes to software.

Begin with a simple enhancement: charging a fee for every deposit and withdrawal. Supply a mechanism for setting the fee and modify the deposit and withdraw methods so that the fee is levied. Test your resulting class and check that the fee is computed correctly.

Now make a more complex change. The bank will allow a fixed number of free transactions (deposits or withdrawals) every month, and charge for transactions exceeding the free allotment. The charge is not levied immediately but at the end of the month.

Supply a new method deductMonthlyCharge to the BankAccount class that deducts the monthly charge and resets the transaction count. (Hint: Use Math.max(actual transaction count, free transaction count) in your computation.)

Produce a test program that verifies that the fees are calculated correctly over several months.

Project 3.2 In this project, you will explore an object-oriented alternative to the "Hello, World" program in Chapter 1.

Begin with a simple Greeter class that has a single method, sayHello. That method should return a string, not print it. Use BlueJ to create two objects of this class and invoke their sayHello methods.

That is boring—of course, both objects return the same answer.

Enhance the Greeter class so that each object produces a customized greeting. For example, the object constructed as new Greeter("Dave") should say "Hello, Dave". (Use the concat method to combine strings to form a longer string, or peek ahead at Section 4.6 to see how you can use the + operator for the same purpose.)

Add a method sayGoodbye to the Greeter class.

Finally, add a method refuseHelp to the Greeter class. It should return a string such as

"I am sorry, Dave. I am afraid I can't do that."

Test your class in BlueJ. Make objects that greet the world and Dave, and invoke methods on them.

Answers to Self-Check Questions

  1. public void reset()

    {
       value = 0;
    }
  2. You can only access them by invoking the methods of the Clock class.

  3. In one of the methods of the Counter class.

  4. The programmers who designed and implemented the Java library.

  5. Other programmers who work on the personal finance application.

  6. harrysChecking.withdraw(harrysChecking.getBalance())

  7. The withdraw method has return type void. It doesn't return a value. Use the

    getBalance method to obtain the balance after the withdrawal.
  8. Add an accountNumber parameter to the constructors, and add a getAccount-Number method. There is no need for a setAccountNumber method—the account number never changes after construction.

  9. /**
       This class models a tally counter.
    */
    public class Counter
    {
       private int value;
    
       /**
          Gets the current value of this counter.
          @return the current value
       */
       public int getValue()
       {
          return value;
       }
    
       /**
          Advances the value of this counter by 1.
       */
       public void count()
       {
           value = value + 1;
    }
    }
  10. /**

    Constructs a new bank account with a given initial balance.

    @param accountNumber the account number for this account

    @param initialBalance the initial balance for this account

    */
  11. The first sentence of the method description should describe the method—it is displayed in isolation in the summary table.

  12. An instance variable

    private int accountNumber;

    needs to be added to the class.

  13. Because the balance instance variable is accessed from the main method of BankRobber. The compiler will report an error because it is not a method of the BankAccount class.

  14. public int getWidth()
    {
       return width;
    }
  15. There is more than one correct answer. One possible implementation is as follows:

    public void translate(int dx, int dy)
    {
        int newx = x + dx;
        x = newx;
        int newy = y + dy;
        y = newy;
    }
  16. One BankAccount object, no BankAccountTester object. The purpose of the BankAccount-Tester class is merely to hold the main method.

  17. In those environments, you can issue interactive commands to construct BankAccount objects, invoke methods, and display their return values.

  18. Variables of both categories belong to methods—they come alive when the method is called, and they die when the method exits. They differ in their initialization. Parameter variables are initialized with the call values; local variables must be explicitly initialized.

  19. After computing the change due, payment and purchase were set to zero. If the method returned payment - purchase, it would always return zero.

  20. One implicit parameter, called this, of type BankAccount, and one explicit parameter, called amount, of type double.

  21. It is not a legal expression. this is of type BankAccount and the BankAccount class has no instance variable named amount.

  22. No implicit parameter—the main method is not invoked on any object—and one explicit parameter, called args.

  23. CarComponent

  24. In the draw method of the Car class, call

    g2.fill(frontTire);
    g2.fill(rearTire);
  25. Double all measurements in the draw method of the Car class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.157.142