More About Methods: Chapter Goals
More About Methods: Chapter Goals
7
CHAPTER
Chapter Goals
◆ To understand how parameters are passed into methods and how return values
are returned from 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.
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
harrysChecking.deposit(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:
For example,
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
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.
◆ 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)
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.
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.
Why don’t you simply have the getBalance method print the value as it is getting
it?
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.
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兩)
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.
◆ 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
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:
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.
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
BankAccount
balance 8000
accountNumber 2
BankAccount
balance 0
accountNumber 3
Figure 6
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:
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;
}
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.
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.
◆ 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.
◆ 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
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆ 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.
◆
◆ ◆ 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.
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
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.
/**
Deposits money into this account.
@param amount the amount of money to deposit; must be ⬎ 0
*/
298 Chapter 7. More about Methods
7.9 Recursion
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;
}
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;
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:
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
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆
◆ 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.
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.
java.lang.IllegalArgumentException
Review Exercises
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.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)
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;
}
Exercise R7.12. How can you write a method that swaps two floating-point num-
bers? Hint: Point2D.Double.
Programming Exercises
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.
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.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
Encoding for
Five-Digit Bar Codes Digit 1 Digit 2 Digit 3 Digit 4 Digit 5 Check
Digit
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.
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?