0% found this document useful (0 votes)
104 views44 pages

More About Methods: Chapter Goals

This document discusses method parameters and return values. It explains that parameters are copied into a method's formal parameters when called, and the method can modify the copies but not the original values. Accessor methods return information about an object without modifying it, while mutator methods change the object's state. Immutable classes like String have only accessors and no mutators, making their objects safe to share freely without risk of unexpected changes.

Uploaded by

Ani Ani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
104 views44 pages

More About Methods: Chapter Goals

This document discusses method parameters and return values. It explains that parameters are copied into a method's formal parameters when called, and the method can modify the copies but not the original values. Accessor methods return information about an object without modifying it, while mutator methods change the object's state. Immutable classes like String have only accessors and no mutators, making their objects safe to share freely without risk of unexpected changes.

Uploaded by

Ani Ani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

More about Methods

7
CHAPTER

Chapter Goals
◆ To understand how parameters are passed into methods and how return values
are returned from methods

◆ To minimize the use of side effects

◆ To understand the difference between instance methods and static methods

◆ To introduce the concept of static variables

◆ To be able to determine the scope of variables

◆ To appreciate the importance of method comments

◆ To document the responsibilities of methods and their callers with preconditions

◆ To be able to program recursive methods

269
270 Chapter 7. More about Methods

In this chapter you will learn more about methods. You have already implemented
several simple methods and are familiar with the basic concepts. We will go over
parameters, return values, and variable scope in a more systematic fashion. You will
also learn about several more technical issues, such as static methods and variables.
Finally, we will discuss several concepts that will enable you to implement methods
correctly.

7.1 Method Parameters

In the implementation of a method you define the parameters of the method. For
example, consider the deposit method of the BankAccount class:
public class BankAccount
{ . . .
public void deposit(double amount)
{ . . .
}
. . .
}
This method has two parameters:

1. The implicit parameter this, the bank account to which money is deposited
2. An explicit parameter amount, the amount of the deposit

These values are sometimes called the formal parameters.


In each method call, you supply a set of actual parameters or arguments, the values
that you use for a particular call. For example, consider the call

harrysChecking.deposit(allowance - 200);

This method call has two actual parameters:

1. The object reference stored in the object variable harrysChecking


2. The value of the expression allowance - 200

When a method is called, the actual parameter values are computed and copied into
the formal parameter variables (see Figure 1).
this = harrysChecking;
amount = allowance - 200;
When the method returns, the formal parameter variables are abandoned, and
their values are lost.
7.1 Method Parameters 271

harrysChecking
allowance 800
BankAccount
allowance - 200
balance 500
this
amount 600

Figure 1

Parameter Passing

Explicit parameter variables are no different from other variables. You can modify
them during the execution of the method:

public void deposit(double amount)


{ amount = amount + balance; // bad style
balance = amount;
}

However, it is considered bad style to modify the contents of a parameter variable.


In the preceding example, the explicit parameter of the method was a number
value. Let’s consider a slightly more complex example, with a parameter that is an
object reference. The following method can be used to transfer money from one
account to another:

public class BankAccount


{ . . .
public void transfer(BankAccount other, double amount)
{ withdraw(amount);
other.deposit(amount);
}
. . .
}

For example,

double allowance = 800;


momsSavings.transfer(harrysChecking, allowance);

Now there are three formal parameter variables: this, other, and amount. Figure 2
shows how they are initialized. Note that both the object references and the numbers
are copied into the method. After the method exits, the two bank account balances
272 Chapter 7. More about Methods

momsSavings
harrysChecking
BankAccount
allowance 800
balance 1000

this
other
BankAccount
amount 800

balance 500

Figure 2

Object References and


Number Parameters

have changed. The method was able to change the accounts because it received
copies of the object references. Of course the contents of the allowance variable
was not changed. In Java, no method can modify the contents of a number variable
that is passed as a parameter.

Quality Tip 7.1


◆ Use Meaningful Names for Parameters

◆ You can give any name you like to method parameters. Choose explicit names for parameters
◆ that have specific roles; choose simple names for those that are completely generic. The goal
◆ is to make the reader understand the purpose of the parameter without having to read the
◆ description.
◆ For example, double sin(double x) is not as good as double sin(double radian).
◆ Naming the parameter radian gives additional information: namely, that the angle cannot be
◆ given in degrees.
◆ The Math class contains a method that is declared as
◆ double atan2(double a, double b)

7.2 Accessor Methods, Mutator Methods, and Side Effects 273

◆ I can never remember where to put the x-value and where to put the y-value, or whether it
◆ computes tan⫺1 (a/b) or tan⫺1 (b/a). I wish they had named the parameters more sensibly:
◆ double atan2(double yNumerator, double xDenominator)

Common Error 7.1


◆ Type Mismatch

◆ The compiler takes the types of the method parameters and return values very seriously. It is
◆ an error to call a method with a value of an incompatible type. The compiler automatically
◆ converts from int to double and from ordinary classes to superclasses (see Chapter 9). It does
◆ not convert, however, when there is a possibility of information loss, and it doesn’t convert
◆ between numbers and strings or objects. For this reason, Java is called a strongly typed language.
◆ This is a useful feature, because it lets the compiler find programming errors before they create
◆ havoc when the program runs.
◆ For example, you cannot give a string to a numerical method, even if the string contains
◆ only digits:

String num = "100";

double x = Math.sqrt(num); // Error

◆ You cannot store a numerical return value in a string variable:

◆ String root = Math.sqrt(2); // Error

7.2 Accessor Methods, Mutator


Methods, and Side Effects

A method that accesses an object and returns some information about it, without
changing the object, is called an accessor method. In contrast, a method that modifies
the state of an object is called a mutator method. For example, in the BankAccount
class, getBalance is an accessor method and the deposit/withdraw methods are
mutator methods.
In other words, an accessor method does not modify the object to which the this
parameter refers, whereas a mutator method does.
You can call an accessor method as many times as you like—you always get the
same answer, and it does not change the state of your object. That is clearly a desir-
able property, because it makes the behavior of such a method very predictable.
As a rule of thumb, it is best to separate accessors and mutators. If a method re-
turns a value, then it should not modify the object. Conversely, mutators should have
a return type of void. This is not a rule of the Java language, but just a recommen-
dation to make it easy to differentiate between mutators and accessors.
274 Chapter 7. More about Methods

Some classes have been designed to have only accessor methods and no mu-
tator methods at all. Such classes are called immutable. An example is the String
class. Once a string has been constructed, its contents never change. No method in
the String class can modify the contents of a string. For example, the substring
method does not remove characters from the original string. Instead, it constructs a
new string that contains the substring characters.
An immutable class has a major advantage: It is safe to give out references to its
objects freely. If no method can change the object’s value, then no code can mod-
ify the object at an unexpected time. In contrast, if you give out a BankAccount
reference to any other method, you have to be aware that the state of your object
may change—the other method can call the deposit and withdraw methods on the
reference that you gave it.
Even though an accessor method can change parameter objects, you generally
don’t expect it to. Here is an equals method that compares whether two bank ac-
counts have the same balance.

public class BankAccount


{ public boolean equals(BankAccount other)
{ . . .
}
}

Consider the comparison

if (account1.equals(account2)) . . .

You would be, to say the least, surprised if this call withdrew some money from
either account1 or account2. Although we all have come to expect unreasonable
bank charges, an “equality testing charge” would be disturbing.
In other words, there is the expectation that accessor methods do not modify any
parameters, and that mutator methods do not modify any parameters beyond this.
That ideal situation is not always the case. For example, the transfer method that
we discussed in the last section does update the other account. Such a method is
said to have a side effect. A side effect of a method is any kind of observable behavior
outside the object.
Consider this example. You want to print the balance of a bank account.

System.out.println("The balance is now $" +


momsSavings.getBalance());

Why don’t you simply have the getBalance method print the value as it is getting
it?

public double getBalance()


{ System.out.println("The balance is now $" + balance);
return balance();
}
7.2 Accessor Methods, Mutator Methods, and Side Effects 275

That would be more convenient when you actually want to print the value. But
of course there are cases when you want the value for some other purpose. You
don’t want to have the output littered with balance reports every time the balance is
accessed. Therefore, a method that computes a value and, “while it is at it”, has the
side effect of printing output, is undesirable.
One particularly reprehensible practice is printing error messages inside methods.
You should never do that:
public void deposit(double amount)
{ if (amount <= 0)
System.out.println("Bad value of amount"); // bad style
else
balance = balance + amount;
}
Printing an error message severely limits the reusability of a method. Such a method
can be used only in programs that can print to System.out—eliminating applets
and embedded systems such as the computer inside an automatic teller machine.
The method can be used only in applications where the user can understand an error
message in the English language—eliminating the majority of your potential cus-
tomers. Let the methods do just the computation, not the error report to the user.
You will learn later in this chapter and in Chapter 13 how a method can use excep-
tions to indicate problems.

Quality Tip 7.2


◆ Minimize Side Effects

◆ In an ideal world, all methods would be accessors that simply return an answer without chang-
◆ ing any value at all. (In fact, programs that are written in so-called functional programming
◆ languages such as Scheme and ML come close to this ideal.) Of course, in an object-oriented
◆ programming language, we use objects to remember state changes. Therefore, a method that
◆ just changes the state of its implicit parameter is certainly acceptable. A method that does any-
◆ thing else is said to have a side effect. While side effects cannot be completely eliminated, they
◆ can be the cause of surprises and problems and should be minimized. Here is a classification
◆ of method behavior.

◆ Best: Accessor methods with no changes to any explicit parameters—no side effects.

Example: getBalance.

◆ ◆ Good: Mutator methods with no changes to any explicit parameters, no side effects:
◆ Example: withdraw.

◆ Fair: Methods that change an explicit parameter. Example: transfer.

◆ ◆ Poor: Methods that change a static variable (see Section 7.4) or print messages to
◆ System.out.
276 Chapter 7. More about Methods

7.3 Static Methods

Sometimes you write methods that don’t need an implicit parameter. Such a method
is called a static method or a class method. In contrast, the methods that you saw
in the preceding sections are often called instance methods because they operate on
a particular instance of an object. You have seen static method calls in Chapter 2.
For example, the sqrt method in the Math class is a static method. When you call
Math.sqrt(x), you don’t supply any implicit parameter. (Recall that Math is the
name of a class, not an object.) And, of course, every application has a static main
method (however, applets do not).
Why would you want to write a method without an implicit parameter? The
most common reason is that you want to encapsulate some computations that in-
volve only numbers. Since numbers aren’t objects, you can’t pass them as implicit
parameters.
Here is a typical example of a static method that carries out some simple algebra.
Recall from Chapter 5 that two floating-point numbers x and y are approximately
equal if
兩x ⫺ y兩
ⱕ⑀
max(兩x兩, 兩y兩)

where ⑀ is a small number, typically chosen to be 10⫺14 .


Of course, this formula is just complex enough that it makes a lot of sense to
encapsulate it in a method. Since the parameters are numbers, the method doesn’t
operate on any objects at all, and we make it into a static method:
public static boolean approxEqual(double x, double y)
{ final double EPSILON = 1E-14;
double xymax = Math.max(Math.abs(x), Math.abs(y));
return Math.abs(x - y) <= EPSILON * xymax;
}

You need to find a home for this method. You have two choices. You can simply add
this method to a class whose methods need to call it. Or you can come up with a new
class (similar to the Math class of the standard Java library) to contain this method.
In this book, we will generally use the latter approach. Since this method has to do
with numbers, we’ll design a class Numeric to hold the approxEqual method. Here
is the class:

class Numeric
{ public static boolean approxEqual(double x, double y)
{ final double EPSILON = 1E-14;
double xymax = Math.max(Math.abs(x), Math.abs(y));
return Math.abs(x - y) <= EPSILON * xymax;
}
// more numeric methods can be added here
}
7.3 Static Methods 277

When calling the static method, you supply the name of the class containing the
method so that the compiler can find it. For example,
double r = Math.sqrt(2);
if (Numeric.approxEqual(r * r, 2))
System.out.println("Math.sqrt(2) squared is approximately 2");
Note that you do not supply an object of type Numeric when you call the method.
Static methods have no implicit parameter—in other words, they don’t have a this
parameter.
Now we can tell you why the main method is static. When the program starts,
there aren’t any objects yet. Therefore, the first method in the program must be a
static method.
You may well wonder why these methods are called static methods. The nor-
mal meaning of the word static (“staying fixed at one place”) does not seem to have
anything to do with what static methods do. That is indeed the case. Java uses
the static keyword because C++ uses it in the same context. C++ uses static
to denote class methods because the inventors of C++ did not want to invent an-
other keyword. Someone noted that there was a relatively rarely used keyword,
static, that denotes certain variables that stay in a fixed location for multiple
method calls. (Java does not have this feature, nor does it need it.) It turned out
that the keyword could be reused to denote class methods without confusing the
compiler. The fact that it can confuse humans was apparently not a big concern.
You’ll just have to live with the fact that “static method” means “class method”:
a method that does not operate on an object and that has only explicit parameters.

Common Error 7.2


◆ Trying to Modify Numeric Parameters

◆ Let us try to write a method that updates a number, representing the balance of a bank account,
◆ by applying an interest rate.
◆ public static void updateBalance(double balance,
◆ double interestRate)
◆ { double interest = balance * interestRate / 100;
◆ balance = balance + interest;
◆ }
◆ public static void main(String[] args)

{ double savings = 10000;

double rate = 5;

updateBalance(savings, rate);

// savings is not updated

◆ . . .
◆ }
◆ This doesn’t work. Let’s walk through the method call. As the method starts, the parameter
◆ variable balance is set to the same value as savings, and interestRate is set to rate. Then
278 Chapter 7. More about Methods

◆ Figure 3 savings 10000



rate 5

◆ A Method Cannot Modify
◆ Numeric Parameters

balance 10000

◆ interestRate 5




◆ Figure 4 collegefund

rate 5 BankAccount

◆ A Method Can
◆ Modify the balance 1000

◆ State of Object account
◆ Parameters interestRate 5





◆ balance is modified, but that modification had no effect on savings, because balance is a
◆ separate variable (see Figure 3). When the method exits, balance is forgotten, and savings
◆ isn’t increased.
◆ In Java, a method can never modify the values of numbers that are passed to it. It is there-
◆ fore plainly impossible to write a method updateBalance that actually updates a double
◆ parameter passed to it.
◆ This sounds like a serious limitation, but in practice it is not a big problem. You already
◆ know the solution. If the bank balance is contained inside an object, you can pass an object
◆ reference to a method, and the method can modify the bank balance.

◆ public static void updateBalance(BankAccount account,
◆ double interestRate)
◆ { double interest = account.getBalance * interestRate / 100;
◆ account.deposit(interest);
◆ }

◆ public static void main(String[] args)
◆ { BankAccount collegeFund = new BankAccount(10000);
◆ double rate = 5;
◆ updateBalance(collegeFund, rate);
◆ . . .
◆ }

◆ This method does update the balance of collegeFund. Figure 4 shows the reason. The object
◆ reference account is a copy of the object reference collegeFund, and both refer to the same
◆ object.
7.3 Static Methods 279

Advanced Topic 7.1


◆ Call by Value and Call by Reference

◆ In Java, method parameters are copied into the parameter variables when a method starts.
◆ Computer scientists call this call mechanism “call by value”. There are some limitations to the
◆ “call by value” mechanism. As you saw in Common Error 7.2, it is not possible to implement
◆ methods that modify the contents of number variables. Other programming languages such as
◆ C++ support an alternate mechanism, called “call by reference”. For example, in C++ it would
◆ be an easy matter to write a method that modifies a number, by using a so-called reference
◆ parameter. Here is the C++ code, for those of you who know C++:

◆ // this is C++
◆ void updateBalance(double& balance, double interestRate)
◆ // balance is a double&, a reference to a double
◆ { double interest = balance * interestRate / 100;
◆ balance = balance + interest;

}

◆ int main()
◆ { double savings = 10000;
◆ double rate = 5;
◆ updateBalance(savings, rate);
◆ // in C++, savings is updated

. . .

}


◆ You will sometimes read in Java books that “numbers are passed by value, objects are passed
◆ by reference”. That is technically not quite correct. In Java, both numbers and object references
◆ are copied by value. To see this clearly, let us consider another scenario. This method tries to
◆ set the betterAccount parameter to the account with the better balance:

◆ public static void chooseAccount(BankAccount betterAccount,
◆ BankAccount candidate1, BankAccount candidate2)
◆ { if (candidate1.getBalance() > candidate2.getBalance())
◆ betterAccount = candidate1;
◆ else
◆ betterAccount = candidate2;
◆ }

◆ public static void main(String[] args)
◆ { BankAccount collegeFund = new BankAccount(10000);
◆ BankAccount momsSavings = new BankAccount(8000);
◆ BankAccount myAccount = null;

◆ chooseAccount(myAccount, momsSavings, collegeFund); // NO
◆ . . .
◆ }

280 Chapter 7. More about Methods

◆ In this situation, we are not trying to change the state of the object to which the parame-
◆ ter variable betterAccount refers; We are trying to replace the object with a different one
◆ (see Figure 5). Now the parameter variable betterAccount is replaced with a reference to
◆ collegeFund, but that change does not affect the myAccount variable that is supplied in the
◆ call.
◆ As you can see, a Java method can update an object’s state, but it cannot replace the contents
◆ of an object reference. This shows that object references are passed by value in Java.
◆ Of course, there is a simple remedy: Make the method return the better account:


◆ public static BankAccount chooseAccount(BankAccount candidate1,
◆ BankAccount candidate2);
◆ { BankAccount betterAccount;
◆ if (candidate1.getBalance() > candidate2.getBalance())
◆ betterAccount = candidate1;
◆ else
◆ betterAccount = candidate2;
◆ return betterAccount;
◆ }





◆ collegeFund

◆ momsSavings
◆ BankAccount
◆ myAccount
◆ balance 1000





◆ candidate1

◆ candidate2 BankAccount

◆ betterAccount balance 8000






◆ Figure 5


◆ A Method Cannot Replace


Object Parameters
7.4 The return Statement 281

◆ public static void main(String[] args)


◆ { BankAccount collegeFund = new BankAccount(10000);
◆ BankAccount momsSavings = new BankAccount(8000);
◆ BankAccount myAccount;

◆ myAccount = chooseAccount(momsSavings, collegeFund);
◆ . . .
◆ }

7.4 The return Statement

A method that has a return type other than void must return a value, by executing
a statement of the form
return expression ;
Note that you can return the value of any expression. For example, consider the
following method, a simplified version of the getTax method from the preceding
chapter:

public double getTax()


{ double tax;

if (income <= CUTOFF1)


tax = RATE1 * income;
else if (income <= CUTOFF2)
tax = BASE2 + RATE2 * (income - CUTOFF1);
else
tax = BASE3 + RATE3 * (income - CUTOFF2);
return tax;
}
You don’t need to store the result in a variable and then return the variable. If you
like, you can simply return the value of an expression:

public double getTax()


{ if (income <= CUTOFF1)
return RATE1 * income;
else if (income <= CUTOFF2)
return BASE2 + RATE2 * (income - CUTOFF1);
else
return BASE3 + RATE3 * (income - CUTOFF2);
}
When the return statement is processed, the method exits immediately. This is con-
venient for handling exceptional cases at the beginning:
282 Chapter 7. More about Methods

public double getTax()


{ if (income < 0) return 0;
// never gets here if income < 0
double tax;

if (income <= CUTOFF1)


tax = RATE1 * income;
else if (income <= CUTOFF2)
tax = BASE2 + RATE2 * (income - CUTOFF1);
else
tax = BASE3 + RATE3 * (income - CUTOFF2);
return tax;
}

If the method is called when income is less than zero, the method returns 0 and the
remainder of the method is not executed.
It is important that every branch of a method return a value. Consider the follow-
ing incorrect version of the getTax method:
public double getTax()
{ if (income > CUTOFF2)
return BASE3 + RATE3 * (income - CUTOFF2);
else if (income > CUTOFF1)
return BASE2 + RATE2 * (income - CUTOFF1);
else if (income >= 0)
return RATE1 * income;
// Error
}

If income is less than zero, no return value is specified. The Java compiler will flag
this as an error. The remedy is, of course, to return a value in every case.

Common Error 7.3


◆ Missing Return Value

◆ A method whose return type is not void always needs to return something. If the code of the
◆ method contains several if/else branches, make sure that each one of them returns a value:
◆ public static int sign(double x)
◆ { if (x < 0) return -1;
◆ if (x > 0) return +1;
◆ // Error: missing return value if x equals 0
◆ }

◆ This method computes the sign of a number: ⫺1 for negative numbers and +1 for positive
◆ numbers. If the parameter x is zero, however, no value is returned. The Java compiler will
◆ refuse to compile a method that does not return a value under some circumstances.
7.5 Static Variables 283

7.5 Static Variables

Consider a slight variation of our BankAccount class: a bank account has both a
balance and an account number:
public class BankAccount
{ . . .
private double balance;
private int accountNumber;
}
We want to assign account numbers sequentially. That is, we want the bank account
constructor to construct the first account with number 1, the next with number 2,
and so on. Therefore, we must store the last assigned account number somewhere.
It makes no sense, though, to make this value into an instance variable:
public class BankAccount
{ . . .
private double balance;
private int accountNumber;
private int lastAssignedNumber; // NO—won’t work
}
In that case each instance of the BankAccount class would have its own value of
lastAssignedNumber. Instead, we need to have a single value of lastAssigned-
Number that is the same for the entire class. Such a variable is called a class variable
or, in Java, a static variable, because you declare it using the static keyword.
public class BankAccount
{ . . .
private double balance;
private int accountNumber;
private static int lastAssignedNumber;
}
Every BankAccount object has its own balance and accountNumber instance varia-
bles, but there is only a single copy of the lastAssignedNumber variable (see Figure 6).
Every method of a class can access its static variables. Here is the constructor of
the BankAccount class, which increments the last assigned number and then uses it
to initialize the account number of the object to be constructed:
class BankAccount
{ public BankAccount()
{ // generate next account number to be assigned
lastAssignedNumber++;
// updates the class variable
// assign to account number of this bank account
accountNumber = lastAssignedNumber;
// updates the instance variable
}
. . .
}
284 Chapter 7. More about Methods

How do you initialize static variables? You can’t initialize them in the class con-
structor:

public BankAccount()
{ lastAssignedNumber = 0; // NO—would reset to 0 each time
. . .
}

BankAccount.lastAssignedNumber 3

collegeFund BankAccount

momsSavings balance 1000


accountNumber 1
myAccount

BankAccount

balance 8000
accountNumber 2

BankAccount

balance 0
accountNumber 3

Figure 6

Instance Variables and a


Static Variable
7.5 Static Variables 285

Then the initialization would occur each time a new instance is constructed. There
are three ways to initialize a static variable:

1. Do nothing. The static variable is then initialized with 0 (for numbers), false
(for boolean values) or null (for objects).
2. Use an explicit initializer:

public class BankAccount


{ . . .
private static lastAssignedNumber = 0;
}

The initialization is executed once before the first object of the class is con-
structed.
3. Use a static initialization block:
public class BankAccount
{ . . .
private static lastAssignedNumber;
static
{ lastAssignedNumber = 0;
}
}
All statements in the static initialization block are executed once before
the first object of the class is constructed. This construct is rarely used in prac-
tice.
In general, static variables are considered undesirable. Methods that read and modify
static variables have side effects. That is, the behavior of such methods does not simply
depend on their inputs. If you call such a method twice, with the exact same inputs,
it may still act differently, because the settings of the static variables are different.
In fact, if you call the BankAccount constructor in the preceding example twice in a
row, you will get two different results—that was the point of introducing the static
lastAssignedNumber variable. However, in practical programs, static variables are
rarely useful. If your program relies on a static variable, you should think carefully
whether you merely used that static variable as a momentary convenience to “park”
a value so that it can be picked up by another method. That is a bad strategy, because
many other methods can also access and change that static variable.
Like instance variables, static variables, when used at all, should always be de-
clared as private to ensure that methods of other classes do not change their val-
ues. However, static constants are often declared public. For example, the Math class
defines several constant values, such as
public class Math
{ . . .
public static final double PI = 3.14159265358979323846;
}

You can refer to such a constant in any method as Math.PI.


286 Chapter 7. More about Methods

Why are these class variables called static variables? As with static methods, the
static keyword itself is just a meaningless holdover from C++. But static variables
and static methods have much in common: they apply to the entire class, not to
specific instances of the class.

Quality Tip 7.3


◆ Do Not Abuse Static Variables

◆ We have purposefully not covered static variables earlier in this book. They make it possible to
◆ write programs that are hard to understand and maintain. Consider the following bad example.
◆ We want to sort three numbers in increasing order and print them. The setMinMax “helper”
◆ method sorts two numbers by setting the static variables min and max to hold the smaller and
◆ the larger values.

◆ class Sort // bad code
◆ { public static void setMinMax(double a, double b)
◆ { if (a < b) { min = a; max = b; }
◆ else { min = b; max = a; }
◆ }

◆ public static void sortAndPrint3(double a, double b, double c)
◆ { setMinMax(a, b);
◆ if (c < min)
◆ System.out.println(c); // c is the smallest of the three
◆ else
◆ { System.out.print(min); // min is the smallest of the three
◆ setMinMax(c, max); //sort c and max
◆ }

System.out.println(" " + min + " " + max);


//print the other two
◆ }
◆ private static double min;
◆ private static double max;

}


◆ The writer of this class had a problem. The setMinMax method computes both the smaller
◆ and the larger of its input values, but it can’t return both. Therefore, the programmer chose to
◆ return neither value and instead stored them in static variables. In this example, the code will
◆ work correctly, but there is a danger. If you don’t immediately retrieve the answers from the
◆ static variables, some other part of the program may overwrite them. Accidental overwriting
◆ happens with distressing regularity when static variables are used in larger programs, and
◆ therefore they are best avoided.
7.6 Variable Lifetime, Initialization, and Scope 287

7.6 Variable Lifetime, Initialization,


and Scope

You have now encountered the four kinds of variables that Java supports:
1. Instance variables
2. Static variables
3. Local variables
4. Parameter variables

The lifetime of a variable defines when the variable is created and how long it stays
around.
When an object is constructed, all its instance variables are created. As long as any
part of the program can access the object, it stays alive. Once the garbage collector has
determined that no part of the program can access the object any more, it is recycled.
That is, as long as you can reach an object, its instance variables stay intact.
A static variable is created when its class is first loaded, and it lives until the class
is unloaded.
A local variable is created when the program enters the statement that defines it. It
stays alive until the block that encloses the variable definition is exited. For example,
consider the following method:
public void withdraw(double amount)
{ if (amount <= balance)
{ double newBalance = balance - amount;
// local variable newBalance created
balance = newBalance;
} // end of lifetime of local variable newBalance
}
The newBalance variable is created when the declaration
double newBalance = balance - amount;
is executed. It stays alive until the end of the enclosing block.
Finally, when a method is called, its parameter variables are created. They stay
alive until the method returns to its caller:
public void deposit(double amount)
// parameter variable amount created
{ balance = balance + amount;
} // end of lifetime of parameter variable amount
Next, let us summarize what we know about the initialization of these four types
of variables. Instance variables and static variables are automatically initialized with
a default value (0 for numbers, false for boolean, null for objects) unless you spec-
ify another initial value. Parameter variables are initialized with copies of the actual
parameters. Local variables are not initialized by default; you must supply an initial
288 Chapter 7. More about Methods

value, and the compiler complains if you try to use a local variable that you never
initialized.
The scope of a variable is the part of the program in which you can access it. As
you know, instance and static variables are usually declared as private, and you
can access them only in the methods of their own class. The scope of a local variable
extends from the point of its definition to the end of the enclosing block. The scope
of a parameter variable is the entire body of its method.
It sometimes happens that the same variable name is used in two methods. Con-
sider the variables r in the following example:
public static double area(Rectangle rect)
{ double r = rect.getWidth() * rect.getHeight();
return r;
}
public static void main(String[] args)
{ Rectangle r = new Rectangle(5, 10, 20, 30);
double a = area(r);
. . .
}
These variables are independent from each other. You can have variables with the
same name r in different methods, just as you can have different motels with the
same name “Bates Motel” in different cities.
In this situation, the scopes of the two variables named r are disjoint. Problems
arise, however, if you have two or more variable names with overlapping scope.
There are Java language rules that tell you which of the variables is accessed when
you use the ambiguous name. The other variables are then shadowed. Here is a pur-
posefully bad example. Suppose you use the same name for an instance variable and
a local variable:
public class Coin
{ . . .
public void draw(Graphics2D g2)
{ String name = "SansSerif"; // local variable
int size = 18;
g2.setFont(new Font(name, Font.BOLD, size));
. . .
}
private String name; // instance variable
private double value;
}
Inside the draw method, the variable name name could potentially have two meanings:
the local variable or the instance variable. The Java language specifies that in this
situation the local variable wins out. This sounds pretty arbitrary, but there is actually
a good reason: You can still refer to the instance variable as this.name. Some people
use this trick on purpose so that they don’t have to come up with new variable names:
public Coin(String name, double value)
{ this.name = name;
7.6 Variable Lifetime, Initialization, and Scope 289

this.value = value;
}
It isn’t actually a good idea to write code like this. You can easily change the name
of the local variable to something else, such as fontName or aName. Then you, and
the other readers of your code, don’t have to remember the arcane language rule that
local variables shadow instance variables.

Common Error 7.4


◆ Shadowing

◆ Using the same name accidentally for a local variable and an instance variable is a surprisingly
◆ common error. As you saw in the preceding section, the local variable then shadows the in-
◆ stance variable. Even though you may have meant to access the instance variable, the local
◆ variable is quietly accessed. For some reason, this problem is most common in constructors.
◆ Look at this example of a wrong constructor:

◆ public class Coin
◆ { public Coin(double aValue, String aName)
◆ { value = aValue;

String name = aName; // oops...

}


◆ . . .

◆ private double value;
◆ private String name;
◆ }

◆ The programmer declared a local variable name in the constructor. In all likelihood, that was
◆ just a typo—the programmer’s fingers were on autopilot and typed the keyword String, even
◆ though the programmer all the time intended to access the instance variable. Unfortunately,
◆ the compiler gives no warning in this situation and quietly sets the local variable to the value
◆ of aName. The instance variable of the object that is being constructed is never touched, and
◆ remains null.

Advanced Topic 7.2


◆ Alternative Forms of
◆ Instance Variable Initializations
◆ As you have seen, instance variables are initialized with a default value (0, false, or null,
◆ depending on their type). You can then set them to any desired value in a constructor, and that
◆ is the style that we prefer in this book.
290 Chapter 7. More about Methods

◆ However, there are two other mechanisms to specify an initial value for instance variables.
◆ Just as with local variables, you can specify initialization values for instance variables. For
◆ example,

◆ public class Coin
◆ { . . .
◆ private double value = 1;
◆ private String name = "Dollar";
◆ }

These default values are used for every object that is being constructed.

There is also another, much less common, syntax, which is analogous to the static initial-

ization blocks that you saw in Section 7.5. You can place one or more initialization blocks inside

the class definition. All statements in that block are executed whenever an object is being

constructed. Here is an example:

◆ public class Coin
◆ { . . .
◆ { value = 1;
◆ name = "Dollar";
◆ }
◆ private double value;
◆ private String name;

}

◆ Since the rules for the alternative initialization mechanisms are somewhat complex, we rec-
◆ ommend that you simply use constructors to do the job of construction.

Advanced Topic 7.3


◆ Calling One Constructor from Another

◆ Consider the BankAccount class. It has two constructors: a default constructor to initialize
◆ the balance with zero, and another constructor to supply an initial balance. In our case,
◆ the default constructor is only one line long. But in general, if the default constructor needs
◆ to initialize several instance variables, it can be convenient to have the default constructor
◆ call another constructor of the same class instead. There is a shorthand notation to achieve
◆ this:

public class BankAccount

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

◆ public BankAccount()
◆ { this(0);
◆ }
◆ . . .
◆ }

7.7 Comments 291

◆ The command this(0); means “Call another constructor of this class and supply the value
◆ 0.” Such a constructor call can occur only as the first line in another constructor.
◆ This syntax is a minor convenience, and we will not use it in this book. Actually, the use
◆ of the this keyword is a little confusing, because normally this denotes a reference to the
◆ implicit parameter. However, if this is followed by parentheses, it denotes a call to another
◆ constructor of this class.

7.7 Comments

As you progress to implementing more complex classes and methods, you must get
into the habit of thoroughly commenting their behavior. In Java there is a very useful
standard form for documentation comments, and there are tools to extract class and
method documentation automatically. In fact, the online class library documentation
has been automatically extracted from the class library code.
A documentation comment starts with a /**, a special comment delimiter used by
the javadoc utility, which automatically extracts and formats documentation com-
ments. (See Productivity Hint 7.1 for a description of this 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.
Here is a typical example.
/**
Tests whether two floating-point numbers are
equal, except for a roundoff error.
@param x a floating-point number
@param y a floating-point number
@return true if x and y are approximately equal
*/
public static boolean approxEqual(double x, double y)
{ final double EPSILON = 1E-14;
double xymax = Math.max(Math.abs(x), Math.abs(y));
return Math.abs(x - y) <= EPSILON * xymax;
}
Whoa! The comment is longer than the method! Indeed it is, but that is irrelevant.
We were just lucky this particular method was easy to compute. The method com-
ment documents not the implementation but the idea—ultimately a more valuable
property.
According to the standard Java documentation style, every method (except main)
should have a comment explaining its purpose. If a method doesn’t have parameters,
you omit the @param tag. If the return type is void, omit the @return tag.
Occasionally, you will find that these comments are silly to write. That is partic-
ularly true for general-purpose methods:
292 Chapter 7. More about Methods

/**
Computes the maximum of two integers.
@param x an integer
@param y another integer
@return the larger of the two inputs
*/
public static int max(int x, int y)
{ if (x > y)
return x;
else
return y;
}

It should be pretty clear that max computes the maximum, and it is perfectly obvious
that the method receives two integers x and y. Indeed, in this case the comment is
somewhat overblown. We nevertheless strongly recommend writing the comment
for every method. It is easy to spend more time pondering whether the comment
is too trivial to write than it takes just 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.
If you override a method of the class that your class extends (see Chapter 9),
and you do not do any more than that method’s comment already indicates, you
do not have to add a comment. For example, if you override the paint method of
Applet, then you don’t have to explain what the paint method does. However, if
your paint method does something interesting, of which the people who read and
maintain your code should be aware, then by all means add the comment.
It is always a good idea to write the method comment first, before writing the
method code. This is an excellent test to see that you firmly understand what you
need to program. If you can’t explain the method’s inputs and outputs, you aren’t
ready to implement it.
The comments you have just seen explain individual methods. As you begin to
write programs that are composed of multiple classes, you should also get into the
habit of supplying a brief comment for each class, explaining its purpose.
The comment syntax for class comments is very simple: Just place the documen-
tation comment above the class.
/**
A bank account for depositing and withdrawing money.
*/
public class BankAccount
{ . . .
}

The javadoc utility, described in Productivity Hint 7.1, copies the first sentence of
each class and method comment to summary tables. Therefore, it is best to write that
first sentence with some care. It should start with an uppercase letter and end with
7.7 Comments 293

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.

Quality Tip 7.4


◆ Keep Methods Short

◆ There is a certain cost for writing a method. You need to write the documentation; you need
◆ to pass parameters when you call the method; you should test the method in isolation; you
◆ should think how to make the method reusable. To avoid this cost, it is always tempting just
◆ to stuff more and more code in one place rather than going through the trouble of breaking
◆ up the code into separate methods. It is quite common to see inexperienced programmers
◆ produce methods that are several hundred lines long.
◆ Ideally, each method should contain no more than one screenful of text. That makes it
◆ easy to read the code in the text editor. As a rule of thumb, a method longer than 30 lines (not
◆ counting comments) is usually suspect and should probably be broken up. Of course, there
◆ are exceptions to this rule, but they are surprisingly rare. Most programmers, when forced to
◆ break up long methods into smaller ones, find the result superior to their original version.

Productivity Hint 7.1


◆ The javadoc Utility

◆ The Java Development Kit from Sun Microsystems provides a utility called javadoc to gen-
◆ erate documentation that can be inspected by a Web browser for your programs (see Figure
◆ 7). You must use the exact format for method comments that was described in the preceding
◆ section. That is, you must write comments of the form
◆ /**

purpose

@param name description

◆ @param name description
◆ @return description
◆ */

Every comment must come immediately before the class or method to which it applies. The

first line must be /**. (In early versions of the javadoc program, each subsequent line had to

◆ start with a *; the designers of those versions apparently were unaware of Productivity Hint
◆ 2.1. That requirement has now been removed.)
◆ From a command shell, you invoke the javadoc utility with the command
◆ javadoc MyProg.java

◆ The javadoc utility then produces a file MyProg.html in HTML format, which you can in-
◆ spect in a browser. If you know HTML (see Chapter 4), you can embed HTML tags into the
◆ comments to specify fonts or add images. Perhaps most importantly, javadoc automatically
◆ provides hyperlinks to other classes and methods.
294 Chapter 7. More about Methods
























◆ Figure 7


◆ An HTML Page Produced by the
◆ javadoc Utility



◆ The javadoc tool is wonderful because it does one thing right: it lets you put the docu-
◆ mentation together with your code. That way, when you update your programs, you can see right
◆ away which documentation needs to be updated. Hopefully, you will then update it right then
◆ and there. Afterwards, run javadoc again and get a nicely formatted HTML page.

Productivity Hint 7.2


◆ Global Search and Replace

Suppose you chose an unfortunate name for a method—say ae instead of approxEqual—and

you regret your choice. Of course, you can locate all occurrences of ae in your code and replace

them manually. However, most programming editors have a command to search for the ae’s

automatically and replace them with approxEqual.

You need to specify some details about the search:

◆ ◆ Do you want it to ignore case? That is, should Ae be a match? In Java you usually don’t
◆ want that.
7.7 Comments 295

◆ ◆ Do you want it to match whole words only? If not, the ae in maelstrom is also a
◆ match. In Java you usually want to match whole words.
◆ ◆ Is this a regular-expression search? No, but regular expressions can make searches even
◆ more powerful—see Productivity Hint 7.3.

◆ ◆ Do you want to confirm each replace, or simply go ahead and replace all matches?
◆ I usually confirm the first three or four, and when I see that it works as expected, I
◆ give the go-ahead to replace the rest. (By the way, a global replace means to replace
◆ all occurrences in the document.) Good text editors can undo a global replace that has
◆ gone awry. Find out whether yours will.
◆ ◆ Do you want the search to go from the cursor to the rest of the program file, or should
◆ it search the currently selected text? Restricting replacement to a portion of the file can
◆ be very useful, but in this example you would want to move the cursor to the top of
◆ the file and then replace until the end of the file.

◆ Not every editor has all these options. You should investigate what your editor offers.

Productivity Hint 7.3


◆ Regular Expressions

◆ Regular expressions describe character patterns. For example, numbers have a simple form.
◆ They contain one or more digits. The regular expression describing numbers is [0-9]+. The
◆ set [0-9] denotes any digit between 0 and 9, and the + means “one or more”.
◆ What good is it? Several utility programs use regular expressions to locate matching text.
◆ Also, the search commands of some programming editors understand regular expressions.
◆ The most popular program that uses regular expressions is grep (which stands for “generalized
◆ regular expression pattern”). You can run grep from a command prompt or from inside some
◆ compilation environments. It needs a regular expression and one or more files to search. When
◆ grep runs, it displays a set of lines that match the regular expression.
◆ Suppose you want to look for all magic numbers (see Quality Tip 2.2) in a file. The com-
◆ mand


grep [0-9]+ Homework.java


◆ lists all lines in the file Homework.java that contain sequences of digits. That isn’t terribly
◆ useful; lines with variable names x1 will be listed. OK, you want sequences of digits that do
◆ not immediately follow letters:

◆ grep [^A-Za-z][0-9]+ Homework.java

◆ The set [^A-Za-z] denotes any characters that are not in the ranges A ro Z and a to z. This
◆ works much better, and it shows only lines that contain actual numbers.
◆ There are a bewildering number of symbols (sometimes called wildcards) with special mean-
◆ ings in the regular expression syntax, and unfortunately, different programs use different styles
◆ of regular expressions. It is best to consult the program documentation for details.
296 Chapter 7. More about Methods

Productivity Hint 7.4


◆ Empty Stubs

◆ Some people first write all code and then start compiling and testing. Others prefer to see some
◆ results quickly. If you are among the impatient, you will like the technique of stubs.
◆ A stub is a method that is completely empty and returns a trivial value. The stub can be
◆ used to test that the code compiles and to debug the logic of other parts of the program.

◆ /**
◆ Gives a description of the
◆ effects of an earthquake.
◆ @return the description
◆ */
◆ public getDescription String ()
◆ { return "mumble";
◆ }

◆ If you combine the stub with the remainder of the program and test it, you may get an output
◆ of "Effect of earthquake:", which shows you that you are on the right track. You can
◆ then flesh out one stub at a time.
◆ This method is particularly helpful if you like composing your programs directly on the
◆ computer. Of course, the initial planning requires thought, not typing, and is best done at a
◆ desk. Once you know what methods you need, however, you can enter their interface de-
◆ scriptions and stubs, compile, implement one method, compile and test, implement the next
◆ method, compile and test, until you are done.

7.8 Preconditions

What should a method do when it is called with inappropriate inputs? For example,
how should Math.sqrt(-1) react? What should account.deposit(-1000) do?
There are two choices.

◆ A method can fail safely and return to its caller. For example, the deposit
method can simply do nothing when called with an unexpected input.
◆ A method can terminate the program. For example, the deposit method can
call
System.exit(1) when called with an unexpected method.

Java has a third, very sophisticated mechanism that allows a method to terminate
and send an exception, which signals to an appropriate receiver that something has
gone very wrong. As long as such a receiver is in place, it can handle the problem
and avoid termination of the program. Right now, we’ll show you how to terminate
a method by throwing an exception.
7.8 Preconditions 297

public double deposit(double amount)


{ if (amount <= 0)
throw new IllegalArgumentException();
balance = balance + amount;
}

When the method is called with an illegal argument, the program aborts with an
error message

java.lang.IllegalArgumentException
at BankAccount.deposit(BankAccount.java:14)

Of course, for realistic programs, aborting the program is not desirable. The Java
exception-handling mechanism allows programmers to catch the exception and take
corrective action, as we saw in Section 2.7.2. Turn to Chapter 13 for more informa-
tion on this topic.
When writing a method, how should you handle bad inputs? Should you termi-
nate the method or should you fail safely? Consider Math.sqrt. It would be an easy
matter to implement a square root method that returns 0 for negative values and the
actual square root for positive values. Suppose you use that method to compute the
intersection points of a circle and a line. Suppose they don’t intersect, but you forgot
to take that possibility into account. Now the square root of a negative number will
return a wrong value, namely 0, and you will obtain two bogus intersection points.
(Actually, you will get the same point twice.) You may miss that during testing, and
the faulty program may make it into production. This isn’t a big deal for our graphics
program, but suppose the program directs a dental drill robot. It would start drilling
somewhere outside the tooth. This makes termination an attractive alternative. It is
hard to overlook termination during testing, and it is better if the drill stops rather
than boring through the patient’s gums.
In general, it is best if you don’t fudge a return value, or quietly do nothing, when
presented with an improper input value. Thus, you should expect that some methods
have conditions that describe what is a valid input and what is not. Such a condition
is called a precondition of a method.
Here is what you should do when writing a method:

◆ Establish clear preconditions for all inputs. Write in the @param comment what
values you are not willing to handle.
◆ Throw an exception if a precondition is not satisfied.
◆ Be sure to supply correct results for all inputs that fulfill the precondition.

Let us apply that strategy to the deposit method:

/**
Deposits money into this account.
@param amount the amount of money to deposit; must be ⬎ 0
*/
298 Chapter 7. More about Methods

public void deposit(double amount)


{ if (amount <= 0)
throw new IllegalArgumentException();
balance = balance + amount;
}
We advertised that amount must be greater than 0. This is a precondition of the
deposit method. The method is responsible only for handling inputs that con-
form to the precondition. It is free to do anything if the precondition is not fulfilled.
It would be perfectly legal if the method reformatted the hard disk every time it
was called with a wrong input. Naturally, that isn’t reasonable. Instead, we check
the precondition and throw an exception if it is not fulfilled. In the absence of a
handler for this exception, the program terminates. That may not be “nice”, but
it is legal. Remember that the method can do anything if the precondition is not
fulfilled.
Another alternative is to let the method fail safely by doing nothing when called
with improper arguments:
/**
Deposits money into this account.
@param amount the amount of money to deposit; must be ⬎ 0
*/
public void deposit(double amount)
{ if (amount <= 0)
return;
balance = balance + amount;
}
That is not as good. If the program calling the deposit method has a few bugs that
cause it to pass a negative amount as an input value, then the version that throws the
exception will make the bugs very obvious during testing—it is hard to ignore when
the program aborts. The fail-safe version, on the other hand, will quietly do nothing,
and you may not notice that it performs some wrong calculations as a consequence.
Bertrand Meyer [1] compares preconditions to contracts. The method promises
to compute the correct answer for all inputs that fulfill the precondition. The caller
promises never to call the method with illegal inputs. If the caller fulfills its promise
and gets a wrong answer, it can take the method to programmer’s court. If the caller
doesn’t fulfill its promise and something terrible happens as a consequence, it has no
recourse.

7.9 Recursion

An important mathematical function is the factorial. The value n! (read n factorial) is


defined to be the product 1 ⫻ 2 ⫻ 3 ⫻ ⭈⭈⭈ ⫻ n. Also, by convention, 0! ⳱ 1. Factorials
7.9 Recursion 299

for negative numbers are not defined. Here are the first few values of the factorial
function:

n n!
0 1
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
8 40320

As you can see, these values get large very quickly. The factorial function is inter-
esting, because it describes how many ways one can scramble or permute n distinct
objects. For example, there are 3! ⳱ 6 rearrangements of the letters in the string
"rum": namely mur, mru, umr, urm, rmu, and rum itself. There are 24 permutations of
the string "drum".
How can you program a method that computes the factorial function? Of course
you can’t write
public static int factorial(int n)
{ return 1 * 2 * 3 * ⭈⭈⭈ * n;
}

There is no magic “⭈⭈⭈” operation in Java that fills in the details.


Consider how I filled in the table of factorials. When I computed 6! for the table, I
did not multiply 1 ⫻ 2 ⫻ 3 ⫻ 4 ⫻ 5 ⫻ 6. I took the preceding entry, 120, and multiplied
that by 6. That is, 6! ⳱ 6 ⫻ 5!. In general, n! ⳱ n ⫻ (n ⫺ 1)! You can implement that.

public static int factorial(int n)


{ int result = n * factorial(n - 1);
return result;
}

In Java, a method can call itself, as long as it calls itself with a simpler value. Let
us walk through this method, computing factorial(6). The first line asks to com-
pute 6 * factorial(5). You don’t know what that is, so you temporarily sus-
pend that thought and walk through the computation of factorial(5). It needs to
compute 5 * factorial(4). You don’t know what that is either, so you tempo-
rarily suspend that thought and work out factorial(4), which needs factorial(3).
Eventually you are down to factorial(2), factorial(1), and factorial(0).
Now you should be getting nervous. That call returns 0 * factorial(-1), so
300 Chapter 7. More about Methods

something must be wrong. You really must handle 0! separately and have it re-
turn 1.

Program Fac.java
public class Fac
{ public static void main(String[] args)
{ ConsoleReader console = new ConsoleReader(System.in);
System.out.println("Please enter a number:");
int n = console.readInt();
System.out.println(n + "! = " + factorial(n));
}

/**
Computes the factorial of an integer.
@param n an integer >= 0
@return n!
*/
public static int factorial(int n)
{ if (n == 0)
return 1;
else
{ int result = n * factorial(n - 1);
return result;
}
}
}

With that fix, everything goes well. (It helps that 1! ⳱ 1 ⫻ 0! ⳱ 1 ⫻ 1.) Here is an
illustration of the sequence of calls and return values.
factorial(6) calls factorial(5)
factorial(5) calls factorial(4)
factorial(4) calls factorial(3)
factorial(3) calls factorial(2)
factorial(2) calls factorial(1)
factorial(1) calls factorial(0)
factorial(0) returns 1
factorial(1) returns 1 (1⫻1)
factorial(2) returns 2 (2⫻1)
factorial(3) returns 6 (3⫻2)
factorial(4) returns 24 (4⫻6)
factorial(5) returns 120 (5⫻24)
factorial(6) returns 720 (6⫻120)
The process of having a method call itself over and over is called recursion. The call
pattern of a recursive method looks complicated, and the key to the successful design
of a recursive method is not to think about it.
7.9 Recursion 301

Instead, let us look at the factorial method again. The first part is utterly rea-
sonable.

if (n == 0)
return 1;

That just sets 0! to 1. The next part is actually also reasonable,

else
{ int result = n * factorial(n - 1);
return result;
}

as long as you are willing to believe that the method works for simpler inputs. If factorial
works as advertised, then factorial(n - 1) is 1 ⫻ 2 ⫻ 3 ⫻ ⭈⭈⭈⫻(n⫺ 1). Multiplying
that number by n yields n ⫻ 1 ⫻ 2 ⫻ 3 ⫻ ⭈⭈⭈ ⫻ (n ⫺ 1) ⳱ n!.
There are two key requirements to make sure that the recursion is successful:

◆ Every recursive call must simplify the computation in some way.


◆ There must be special cases to handle the simplest computations.

For the factorial method, “simpler” means “smaller parameter”. In general, though,
“simpler” does not necessarily mean smaller. It might mean shorter strings, or curves
with fewer wiggles.
The factorial method calls itself again with smaller and smaller integers. Even-
tually the parameter value must reach 0, and there is a special case for computing 0!.
Thus, the factorial method always succeeds.
Actually, you have to be careful. What happens when you call factorial(-1)?
It calls -1 * factorial(-2), so the parameter value gets bigger in magnitude. We
never wanted to permit this case in the first place. Now is a good time to apply the
lesson from Section 7.7 and spell out the precondition of the method:

/**
Computes the factorial of an integer.
@param n an integer ⱖ 0
@return n!
*/
public static int factorial(int n)
{ if (n < 0) throw new IllegalArgumentException();

if (n == 0)
return 1;
else
{ int result = n * factorial(n - 1);
return result;
}
}
302 Chapter 7. More about Methods

Common Error 7.5


◆ Infinite Recursion

◆ A common programming error is an infinite recursion: a method calling itself over and over
◆ with no end in sight. The computer needs some amount of memory for bookkeeping for each
◆ call. After some number of calls, all memory that is available for this purpose is exhausted.
◆ Your program shuts down and reports a “stack fault”.
◆ Infinite recursion happens either because the parameter values don’t get simpler or because
◆ a special terminating case is missing.

Random Fact 7.1


◆ The Explosive Growth of Personal Computers

◆ In 1971, Marcian E. “Ted” Hoff, an engineer at Intel Corporation, was working on a chip for
◆ a manufacturer of electronic calculators. He realized that it would be a better idea to develop
◆ a general-purpose chip that could be programmed to interface with the keys and display of a
◆ calculator, rather than to do yet another custom design. Thus, the microprocessor was born. At
◆ the time, its primary application was as a controller for calculators, washing machines, and
◆ the like. It took years for the computer industry to notice that a genuine central processing
◆ unit was now available as a single chip.
◆ Hobbyists were the first to catch on. In 1974 the first computer kit, the Altair 8800, was
◆ available from MITS Electronics for about $350. The kit consisted of the microprocessor, a
◆ circuit board, a very small amount of memory, toggle switches, and a row of display lights.
◆ Purchasers had to solder and assemble it, then program it in machine language through the
◆ toggle switches. It was not a big hit.
◆ The first big hit was the Apple II. It was a real computer with a keyboard, a monitor, and a
◆ floppy disk drive. When it was first released, users had a $3000 machine that could play Space
◆ Invaders, run a primitive bookkeeping program, or let users program it in BASIC. The original
◆ Apple II did not even support lowercase letters, making it worthless for word processing. The
◆ breakthrough came in 1979 with a new spreadsheet program, VisiCalc. In a spreadsheet, you

enter financial data and their relationships into a grid of rows and columns (see Figure 8). Then

you modify some of the data and watch in real time how the others change. For example, you

can see how changing the mix of widgets in a manufacturing plant might affect estimated

◆ costs and profits. Middle managers in companies, who understood computers and were fed
◆ up with having to wait for hours or days to get their data runs back from the computing center,
◆ snapped up VisiCalc and the computer that was needed to run it. For them, the computer was
◆ a spreadsheet machine.
◆ The next big hit was the IBM Personal Computer, ever after known as the PC. It was the
◆ first widely available personal computer that used Intel’s 16-bit processor, the 8086, whose
◆ successors are still being used in personal computers today. The success of the PC was based
◆ not on any engineering breakthroughs but on the fact that it was easy to clone. IBM pub-
◆ lished specifications for plug-in cards, and it went one step further. It published the exact
◆ source code of the so-called BIOS (Basic Input/Output System), which controls the keyboard,
◆ monitor, ports, and disk drives and must be installed in ROM form in every PC. This allowed
7.9 Recursion 303
























◆ Figure 8


◆ A Spreadsheet



◆ third-party vendors of plug-in cards to ensure that the BIOS code, and third-party extensions
◆ of it, interacted correctly with the equipment. Of course, the code itself was the property of
◆ IBM and could not be copied legally. Perhaps IBM did not foresee that functionally equivalent
◆ versions of the BIOS nevertheless could be recreated by others. Compaq, one of the first clone
◆ vendors, had fifteen engineers, who certified that they had never seen the original IBM code,
◆ write a new version that conformed precisely to the IBM specifications. Other companies
◆ did the same, and soon there were a number of vendors selling computers that ran the same
◆ software as IBM’s PC but distinguished themselves by a lower price, increased portability, or
◆ better performance. In time, IBM lost its dominant position in the PC market. It is now one
◆ of many companies producing IBM PC–compatible computers.
◆ IBM never produced an operating system for its PCs—that is, the software that organizes

the interaction between the user and the computer, starts application programs, and manages

disk storage and other resources. Instead, IBM offered customers the option of three separate

◆ operating systems. Most customers couldn’t care less about the operating system. They chose
◆ the system that was able to launch most of the few applications that existed at the time. It
◆ happened to be DOS (Disk Operating System) by Microsoft. Microsoft cheerfully licensed
◆ the same operating system to other hardware vendors and encouraged software companies
◆ to write DOS applications. A huge number of useful application programs for PC-compatible
◆ machines was the result.
◆ PC applications were certainly useful, but they were not easy to learn. Every vendor de-
◆ veloped a different user interface: the collection of keystrokes, menu options, and settings that
304 Chapter 7. More about Methods

◆ a user needed to master to use a software package effectively. Data exchange between applica-
◆ tions was difficult, because each program used a different data format. The Apple Macintosh
◆ changed all that in 1984. The designers of the Macintosh had the vision to supply an intu-
◆ itive user interface with the computer and to force software developers to adhere to it. It took
◆ Microsoft and PC–compatible manufacturers years to catch up.
◆ The book [2] is highly recommended for an amusing and irreverent account of the emer-
◆ gence of personal computers.
◆ At the time of this writing, it is estimated that one in two U.S. households owns a personal
◆ computer and that one in four uses the Internet at least occasionally. Most personal computers
◆ are used for word processing, home finance (banking, budgeting, taxes), accessing information
◆ from CD-ROM and online sources, and entertainment. Some analysts predict that the personal
◆ computer will merge with the television set and cable network into an entertainment and
◆ information appliance.

Chapter Summary

1. A method receives input parameters and computes a result that depends on those
inputs.

2. Actual parameters are supplied in the method call. They are stored in the formal
parameter variables of the method. The types of the actual and formal parameters
must match.

3. Once the method result has been computed, the return statement terminates the
method and sends the result to the caller.

4. Method comments explain the purpose of the method and the meaning of the
parameters and return value, as well as any special requirements.

5. Side effects are externally observable results outside the implicit parameter caused
by a method call, for example, modifying a static variable or displaying a message.
Generally, side effects should be avoided.

6. In Java, methods can never change number parameters. Object parameters can be
modified but not replaced.

7. Preconditions are restrictions on the method parameters. If a method is called in


violation of a precondition, the method is not responsible for computing the right
result. To protect against violations of preconditions, you can throw an exception.

8. A method can call itself recursively, but it must provide a simpler parameter to it-
self in successive recursive calls. There must be special cases to handle the simplest
parameter values.
Review Exercises 305

Further Reading
[1] Bertrand Meyer, Object-Oriented Software Construction, Prentice-Hall, 1989, chapter 7.
[2] Robert X. Cringely, Accidental Empires, Addison-Wesley, 1992.

Classes, Objects, and Methods


Introduced in This Chapter

java.lang.IllegalArgumentException

Review Exercises

Exercise R7.1. Give realistic examples of the following:

◆ A method with a double parameter and a double return value


◆ A method with an int parameter and a double return value
◆ A method with an int parameter and a String return value
◆ A method with two double parameters and a boolean return value
◆ A method with no parameter and an int return value
◆ A method with an Ellipse2D.Double parameter and a double return value
◆ A method with a Line2D.Double parameter and a Point2D.Double return
value

Just describe what these methods do. Do not program them. For example, some
answers to the first question are “sine” and “square root”.

Exercise R7.2. True or false?

◆ A method has exactly one return statement.


◆ A method has at least one return statement.
◆ A method has at most one return value.
◆ A method with return value void never has a return statement.
◆ When executing a return statement, the method exits immediately.
◆ A method without parameters always has a side effect.
◆ A method with return value void always has a side effect.
◆ A method without side effects always returns the same value when called
with the same parameters.
306 Chapter 7. More about Methods

Exercise R7.3. Write detailed method comments for the following methods. Be sure
to describe those conditions under which the method cannot compute its result. Just
write the comments, not the methods.
public static double sqrt(double x)
public static Point2D.Double midpoint(Point2D.Double a,
Point2D.Double b)
public static double area(Ellipse2D.Double c)
public static String romanNumeral(int n)
public static double slope(Line2D.Double a)
public static boolean isLeapYear(int year)
public static String weekday(int day)

Exercise R7.4. Consider these methods:


public static double f(double x)
{ return g(x) + Math.sqrt(h(x));
}
public static double g(double x) { return 4 * h(x); }
public static double h(double x) { return x * x + k(x) - 1; }
public static double k(double x) { return 2 * (x + 1); }

Without actually compiling and running a program, determine the results of the fol-
lowing method calls:
double x1 = f(2);
double x2 = g(h(2));
double x3 = k(g(2) + h(2));
double x4 = f(0) + f(1) + f(2);
double x5 = f(-1) + g(-1) + h(-1) + k(-1);

Exercise R7.5. A predicate method is a method with return type boolean. Give an
example of a predicate method and an example of how to use it.

Exercise R7.6. What is the difference between a parameter value and a return value?
What is the difference between a parameter value and a parameter variable? What
is the difference between a parameter value and a value parameter?
Exercise R7.7. Ideally, a method should have no side effect. Can you write a pro-
gram in which no method has a side effect? Would such a program be useful?
Exercise R7.8. What is the difference between a method and a program? The main
method and a program?
Exercise R7.9. What preconditions do the following methods from the standard
Java library have?

Math.sqrt
Math.tan
Programming Exercises 307

Math.log
Math.exp
Math.pow
Math.abs

Exercise R7.10. When a method is called with parameters that violate its precon-
dition, it can terminate, or it can fail safely. Give two examples of library methods
(standard or the library methods used in this book) that fail safely when called with
invalid parameters, and give two examples of library methods that terminate.

Exercise R7.11. Consider the following method that is intended to swap the values
of two floating-point numbers:
public static void falseSwap(double a, double b)
{ double temp = a;
a = b;
b = temp;
}

public static void main(String[] args)


{ double x = 3;
double y = 4;
falseSwap(x, y);
System.out.println(x + " " + y);
}
Why doesn’t the method swap the contents of x and y?

Exercise R7.12. How can you write a method that swaps two floating-point num-
bers? Hint: Point2D.Double.

Programming Exercises

Exercise P7.1. Implement the BankAccount.equals method described in Section


7.2.

Exercise P7.2. Write a method printSorted(int a, int b, int c) that prints


its three inputs in sorted order.

Exercise P7.3. Write static methods


public static double sphereVolume(double r)
public static double sphereSurface(double r)
public static double cylinderVolume(double r, double h)
public static double cylinderSurface(double r, double h)
public static double coneVolume(double r, double h)
public static double coneSurface(double r, double h)
308 Chapter 7. More about Methods

that compute the volume and surface area of a sphere with radius r, a cylinder with
circular base with radius r and height h, and a cone with circular base with radius
r and height h. Place them into an appropriate class. Then write a program that
prompts the user for the values of r and h, calls the six methods, and prints the re-
sults.

Exercise P7.4. Write methods


public static double perimeter(Ellipse2D.Double c);
public static double area(Ellipse2D.Double c);
that compute the area and the perimeter of the ellipse e. Use these methods in a
graphics program that prompts the user to specify an ellipse. Then display messages
with the perimeter and area of the ellipse.

Exercise P7.5. Write a method

public static double distance(Point2D.Double p, Point2D.Double q)

that computes the distance between two points. Write a test program that asks the
user to select two points. Then display the distance.

Exercise P7.6. Write the method


public static boolean isInside(Point2D.Double p, Ellipse2D.Double e)

that tests whether a point is inside an ellipse.

Exercise P7.7. Write a method


public static double readDouble(String prompt)
that displays the prompt string, followed by a space, reads a floating-point number
in, and returns it. Here is a typical usage:
salary = readDouble("Please enter your salary:");
percRaise =
readDouble("What percentage raise would you like?");

Exercise P7.8. Write methods


public static displayH(Graphics2D g2, Point2D.Double p);
public static displayE(Graphics2D g2, Point2D.Double p);
public static displayL(Graphics2D g2, Point2D.Double p);
public static displayO(Graphics2D g2, Point2D.Double p);
that show the letters H, E, L, O on the graphics window, where the point p is the top
left corner of the letter. Fit the letter in a 1 ⫻ 1 square. Then call the methods to draw
the words “HELLO” and “HOLE” on the graphics display. Draw lines and circles. Do
not use the drawString method. Do not use System.out.
Programming Exercises 309

Exercise P7.9. Leap years. Write a predicate method


public static boolean isLeapYear(int year)
that tests whether a year is a leap year: that is, a year with 366 days. Leap years
are necessary to keep the calendar synchronized with the sun, because the earth
revolves around the sun once every 365.25 days. Actually, that figure is not entirely
precise, and for all dates after 1582 the Gregorian correction applies. Usually years that
are divisible by 4 are leap years, for example 1996. However, years that are divisible
by 100 (for example, 1900) are not leap years, but years that are divisible by 400 are
leap years (for example, 2000).

Exercise P7.10. Postal bar codes. For faster sorting of letters, the United States Postal
Service encourages companies that send large volumes of mail to use a bar code
denoting the ZIP code (see Figure 9).
The encoding scheme for a five-digit ZIP code is shown in Figure 10. There are full-
height frame bars on each side. The five encoded digits are followed by a correction
digit, which is computed as follows: Add up all digits, and choose the correction digit
to make the sum a multiple of 10. For example, the ZIP code 95014 has sum of digits
19, so the correction digit is 1 to make the sum equal to 20.
Each digit of the ZIP code, and the correction digit, is encoded according to the
following table:

7 4 2 1 0
1 0 0 0 1 1
2 0 0 1 0 1
3 0 0 1 1 0
4 0 1 0 0 1
5 0 1 0 1 0
6 0 1 1 0 0
7 1 0 0 0 1
8 1 0 0 1 0
9 1 0 1 0 0
0 1 1 0 0 0

where 0 denotes a half bar and 1 a full bar. Note that they represent all combinations
of two full and three half bars. The digit can be easily computed from the bar code

Figure 9 ECRLOT
*************** ** CO57
CODE C671RTS2
A Postal Bar Code JOHN DOE CO57
1009 FRANKLIN BLVD
SUNNYVALE CA 95014 – 5143
310 Chapter 7. More about Methods

Figure 10 Frame bars

Encoding for
Five-Digit Bar Codes Digit 1 Digit 2 Digit 3 Digit 4 Digit 5 Check
Digit

using the column weights 7, 4, 2, 1, 0. For example, 01100 is 0 ⫻ 7Ⳮ1 ⫻ 4Ⳮ1 ⫻ 2Ⳮ


0 ⫻ 1 Ⳮ 0 ⫻ 0 ⳱ 6. The only exception is 0, which would yield 11 according to the
weight formula.
Write a program that asks the user for a ZIP code and prints the bar code. Use :
for half bars, | for full bars. For example, 95014 becomes
||:|:::|:|:||::::::||:|::|:::|||

Exercise P7.11. Write a program that displays the bar code, using actual bars, on
your graphic screen. Hint: Write methods halfBar(Point2D.Double start) and
fullBar(Point2D.Double start).

Exercise P7.12. Write a program that reads in a bar code (with : denoting half bars
and | denoting full bars) and prints out the ZIP code it represents. Print an error
message if the bar code is not correct.

Exercise P7.13. Consider the following algorithm for computing x n for integer n. If
n ⬍ 0, x n is 1/x ⫺n . x 0 is 1. If n is positive and even, then x n ⳱ (x n/2 )2 . If n is positive
and odd, then x n ⳱ x n⫺1 ⭈ x. Implement a method intPower(double x, int n)
that uses this algorithm.

Exercise P7.14. Towers of Hanoi. This is a well-known puzzle. A stack of disks of


decreasing size is to be transported from the leftmost peg to the rightmost peg. The
middle peg can be used as a temporary storage. (See Figure 11.) One disk can be
moved at one time, from any peg to any other peg. You can only place smaller disks
on top of larger ones, not the other way around. Write a method that prints the
moves necessary to solve the puzzle for n disks. (Ask the user for n at the beginning
of the program.) Print moves in the form
Move disk from peg 1 to peg 3
Hint: Rather than writing a method hanoi() without parameters, write a method
public static void hanoi(int from, int to, int n)
that moves the top n disks from the peg from to the peg to. Then figure out how
you can achieve that by first moving the pile of the top n ⫺ 1 disks to the third peg,
moving the nth disk to the destination, and then moving the pile from the third peg
to the destination peg, this time using the original peg as the temporary storage.

Exercise P7.15. Write a method


public static int find(String s, String t)
Programming Exercises 311

Figure 11

Towers of Hanoi

that tests whether the string t is contained in the string s. If so, it returns the offset
of the first match. If not, it returns ⫺1. For example,
find("Mississippi", "is") returns 1.
find("Mississippi", "Miss") returns 0.
find("Mississippi", "pi")returns 9.
find("Mississippi", "hip") returns ⫺1.
Hint: If t is longer than s, you can return ⫺1 with confidence. Otherwise, compare
t and the initial substring of s with t.length() characters. If those are the same
strings, then return 0. Otherwise call the method recursively with the tail of s (that
is, s without the first character).

Exercise P7.16. A palindrome is a string that is identical to its reverse, ignoring upper-
and lowercase, spaces, and punctuation marks. Examples are “Radar”, “A man, a plan,
a canal: Panama”, and of course, the world’s first palindrome: “Madam, I’m Adam”.
Write a predicate method
public static boolean isPalindrome(String s)
Use the following logic: If the first character of s (that is, s.substring(0, 1)) is not
a letter, simply ignore it by calling isPalindrome(s.substring(1, s.length())).
Do the same for the last character. If both the first and last characters are letters, check
whether they match. If they don’t, the string is not a palindrome. If they do, it might
be one. In that case remove both the first character and the last and call the method
again. When should the recursion stop?

You might also like