0% found this document useful (0 votes)
6 views51 pages

Javanotes5 152 202

Uploaded by

mohanastudyads
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)
6 views51 pages

Javanotes5 152 202

Uploaded by

mohanastudyads
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/ 51

136 CHAPTER 4.

SUBROUTINES

static int nextN(int currentN) {


int answer; // answer will be the value returned
if (currentN % 2 == 1) // test if current N is odd
answer = 3*currentN+1; // if so, this is the answer
else
answer = currentN / 2; // if not, this is the answer
return answer; // (Don’t forget to return the answer!)
}
Here is a subroutine that uses this nextN function. In this case, the improvement from the
version of this subroutine in Section 4.3 is not great, but if nextN() were a long function that
performed a complex computation, then it would make a lot of sense to hide that complexity
inside a function:
static void print3NSequence(int startingValue) {
int N; // One of the terms in the sequence.
int count; // The number of terms found.
N = startingValue; // Start the sequence with startingValue.
count = 1;
TextIO.putln("The 3N+1 sequence starting from " + N);
TextIO.putln();
TextIO.putln(N); // print initial term of sequence
while (N > 1) {
N = nextN( N ); // Compute next term, using the function nextN.
count++; // Count this term.
TextIO.putln(N); // Print this term.
}
TextIO.putln();
TextIO.putln("There were " + count + " terms in the sequence.");
}

∗ ∗ ∗
Here are a few more examples of functions. The first one computes a letter grade corre-
sponding to a given numerical grade, on a typical grading scale:
/**
* Returns the letter grade corresponding to the numerical
* grade that is passed to this function as a parameter.
*/
static char letterGrade(int numGrade) {
if (numGrade >= 90)
return ’A’; // 90 or above gets an A
else if (numGrade >= 80)
return ’B’; // 80 to 89 gets a B
else if (numGrade >= 65)
return ’C’; // 65 to 79 gets a C
else if (numGrade >= 50)
return ’D’; // 50 to 64 gets a D
else
4.4. RETURN VALUES 137

return ’F’; // anything else gets an F


} // end of function letterGrade
The type of the return value of letterGrade() is char. Functions can return values of any
type at all. Here’s a function whose return value is of type boolean. It demonstrates some
interesting programming points, so you should read the comments:
/**
* The function returns true if N is a prime number. A prime number
* is an integer greater than 1 that is not divisible by any positive
* integer, except itself and 1. If N has any divisor, D, in the range
* 1 < D < N, then it has a divisor in the range 2 to Math.sqrt(N), namely
* either D itself or N/D. So we only test possible divisors from 2 to
* Math.sqrt(N).
*/
static boolean isPrime(int N) {
int divisor; // A number we will test to see whether it evenly divides N.
if (N <= 1)
return false; // No number <= 1 is a prime.
int maxToTry; // The largest divisor that we need to test.
maxToTry = (int)Math.sqrt(N);
// We will try to divide N by numbers between 2 and maxToTry.
// If N is not evenly divisible by any of these numbers, then
// N is prime. (Note that since Math.sqrt(N) is defined to
// return a value of type double, the value must be typecast
// to type int before it can be assigned to maxToTry.)
for (divisor = 2; divisor <= maxToTry; divisor++) {
if ( N % divisor == 0 ) // Test if divisor evenly divides N.
return false; // If so, we know N is not prime.
// No need to continue testing!
}
// If we get to this point, N must be prime. Otherwise,
// the function would already have been terminated by
// a return statement in the previous loop.
return true; // Yes, N is prime.
} // end of function isPrime
Finally, here is a function with return type String. This function has a String as parameter.
The returned value is a reversed copy of the parameter. For example, the reverse of “Hello
World” is “dlroW olleH”. The algorithm for computing the reverse of a string, str, is to
start with an empty string and then to append each character from str, starting from the last
character of str and working backwards to the first:
static String reverse(String str) {
String copy; // The reversed copy.
int i; // One of the positions in str,
// from str.length() - 1 down to 0.
copy = ""; // Start with an empty string.
for ( i = str.length() - 1; i >= 0; i-- ) {
138 CHAPTER 4. SUBROUTINES

// Append i-th char of str to copy.


copy = copy + str.charAt(i);
}
return copy;
}
A palindrome is a string that reads the same backwards and forwards, such as “radar”. The
reverse() function could be used to check whether a string, word, is a palindrome by testing
“if (word.equals(reverse(word)))”.
By the way, a typical beginner’s error in writing functions is to print out the answer, instead
of returning it. This represents a fundamental misunderstanding. The task of a function is to
compute a value and return it to the point in the program where the function was called. That’s
where the value is used. Maybe it will be printed out. Maybe it will be assigned to a variable.
Maybe it will be used in an expression. But it’s not for the function to decide.

4.4.3 3N+1 Revisited


I’ll finish this section with a complete new version of the 3N+1 program. This will give me a
chance to show the function nextN(), which was defined above, used in a complete program.
I’ll also take the opportunity to improve the program by getting it to print the terms of the
sequence in columns, with five terms on each line. This will make the output more presentable.
This idea is this: Keep track of how many terms have been printed on the current line; when
that number gets up to 5, start a new line of output. To make the terms line up into neat
columns, I use formatted output.
/**
* A program that computes and displays several 3N+1 sequences. Starting
* values for the sequences are input by the user. Terms in the sequence
* are printed in columns, with five terms on each line of output.
* After a sequence has been displayed, the number of terms in that
* sequence is reported to the user.
*/
public class ThreeN2 {

public static void main(String[] args) {


TextIO.putln("This program will print out 3N+1 sequences");
TextIO.putln("for starting values that you specify.");
TextIO.putln();
int K; // Starting point for sequence, specified by the user.
do {
TextIO.putln("Enter a starting value;");
TextIO.put("To end the program, enter 0: ");
K = TextIO.getInt(); // get starting value from user
if (K > 0) // print sequence, but only if K is > 0
print3NSequence(K);
} while (K > 0); // continue only if K > 0
} // end main

/**
* print3NSequence prints a 3N+1 sequence to standard output, using
4.4. RETURN VALUES 139

* startingValue as the initial value of N. It also prints the number


* of terms in the sequence. The value of the parameter, startingValue,
* must be a positive integer.
*/
static void print3NSequence(int startingValue) {

int N; // One of the terms in the sequence.


int count; // The number of terms found.
int onLine; // The number of terms that have been output
// so far on the current line.

N = startingValue; // Start the sequence with startingValue;


count = 1; // We have one term so far.

TextIO.putln("The 3N+1 sequence starting from " + N);


TextIO.putln();
TextIO.put(N, 8); // Print initial term, using 8 characters.
onLine = 1; // There’s now 1 term on current output line.

while (N > 1) {
N = nextN(N); // compute next term
count++; // count this term
if (onLine == 5) { // If current output line is full
TextIO.putln(); // ...then output a carriage return
onLine = 0; // ...and note that there are no terms
// on the new line.
}
TextIO.putf("%8d", N); // Print this term in an 8-char column.
onLine++; // Add 1 to the number of terms on this line.
}

TextIO.putln(); // end current line of output


TextIO.putln(); // and then add a blank line
TextIO.putln("There were " + count + " terms in the sequence.");

} // end of Print3NSequence

/**
* nextN computes and returns the next term in a 3N+1 sequence,
* given that the current term is currentN.
*/
static int nextN(int currentN) {
if (currentN % 2 == 1)
return 3 * currentN + 1;
else
return currentN / 2;
} // end of nextN()

} // end of class ThreeN2

You should read this program carefully and try to understand how it works. (Try using 27 for
the starting value!)
140 CHAPTER 4. SUBROUTINES

4.5 APIs, Packages, and Javadoc


As computers and their user interfaces have become easier to use, they have also
become more complex for programmers to deal with. You can write programs for a simple
console-style user interface using just a few subroutines that write output to the console and
read the user’s typed replies. A modern graphical user interface, with windows, buttons, scroll
bars, menus, text-input boxes, and so on, might make things easier for the user, but it forces
the programmer to cope with a hugely expanded array of possibilities. The programmer sees
this increased complexity in the form of great numbers of subroutines that are provided for
managing the user interface, as well as for other purposes.

4.5.1 Toolboxes
Someone who wants to program for Macintosh computers—and to produce programs that look
and behave the way users expect them to—must deal with the Macintosh Toolbox, a collection of
well over a thousand different subroutines. There are routines for opening and closing windows,
for drawing geometric figures and text to windows, for adding buttons to windows, and for
responding to mouse clicks on the window. There are other routines for creating menus and
for reacting to user selections from menus. Aside from the user interface, there are routines
for opening files and reading data from them, for communicating over a network, for sending
output to a printer, for handling communication between programs, and in general for doing
all the standard things that a computer has to do. Microsoft Windows provides its own set
of subroutines for programmers to use, and they are quite a bit different from the subroutines
used on the Mac. Linux has several different GUI toolboxes for the programmer to choose from.
The analogy of a “toolbox” is a good one to keep in mind. Every programming project
involves a mixture of innovation and reuse of existing tools. A programmer is given a set of
tools to work with, starting with the set of basic tools that are built into the language: things
like variables, assignment statements, if statements, and loops. To these, the programmer can
add existing toolboxes full of routines that have already been written for performing certain
tasks. These tools, if they are well-designed, can be used as true black boxes: They can be called
to perform their assigned tasks without worrying about the particular steps they go through to
accomplish those tasks. The innovative part of programming is to take all these tools and apply
them to some particular project or problem (word-processing, keeping track of bank accounts,
processing image data from a space probe, Web browsing, computer games, . . . ). This is called
applications programming .
A software toolbox is a kind of black box, and it presents a certain interface to the program-
mer. This interface is a specification of what routines are in the toolbox, what parameters they
use, and what tasks they perform. This information constitutes the API , or Applications
Programming Interface, associated with the toolbox. The Macintosh API is a specification
of all the routines available in the Macintosh Toolbox. A company that makes some hard-
ware device—say a card for connecting a computer to a network—might publish an API for
that device consisting of a list of routines that programmers can call in order to communicate
with and control the device. Scientists who write a set of routines for doing some kind of
complex computation—such as solving “differential equations,” say—would provide an API to
allow others to use those routines without understanding the details of the computations they
perform.
∗ ∗ ∗
4.5. APIS, PACKAGES, AND JAVADOC 141

The Java programming language is supplemented by a large, standard API. You’ve seen
part of this API already, in the form of mathematical subroutines such as Math.sqrt(), the
String data type and its associated routines, and the System.out.print() routines. The
standard Java API includes routines for working with graphical user interfaces, for network
communication, for reading and writing files, and more. It’s tempting to think of these routines
as being built into the Java language, but they are technically subroutines that have been
written and made available for use in Java programs.
Java is platform-independent. That is, the same program can run on platforms as diverse as
Macintosh, Windows, Linux, and others. The same Java API must work on all these platforms.
But notice that it is the interface that is platform-independent; the implementation varies
from one platform to another. A Java system on a particular computer includes implementations
of all the standard API routines. A Java program includes only calls to those routines. When
the Java interpreter executes a program and encounters a call to one of the standard routines,
it will pull up and execute the implementation of that routine which is appropriate for the
particular platform on which it is running. This is a very powerful idea. It means that you only
need to learn one API to program for a wide variety of platforms.

4.5.2 Java’s Standard Packages


Like all subroutines in Java, the routines in the standard API are grouped into classes. To
provide larger-scale organization, classes in Java can be grouped into packages, which were
introduced briefly in Subsection 2.6.4. You can have even higher levels of grouping, since
packages can also contain other packages. In fact, the entire standard Java API is implemented
in several packages. One of these, which is named “java”, contains several non-GUI packages
as well as the original AWT graphics user interface classes. Another package, “javax”, was
added in Java version 1.2 and contains the classes used by the Swing graphical user interface
and other additions to the API.
A package can contain both classes and other packages. A package that is contained in
another package is sometimes called a “sub-package.” Both the java package and the javax
package contain sub-packages. One of the sub-packages of java, for example, is called “awt”.
Since awt is contained within java, its full name is actually java.awt. This package con-
tains classes that represent GUI components such as buttons and menus in the AWT, the
older of the two Java GUI toolboxes, which is no longer widely used. However, java.awt also
contains a number of classes that form the foundation for all GUI programming, such as the
Graphics class which provides routines for drawing on the screen, the Color class which repre-
sents colors, and the Font class which represents the fonts that are used to display characters
on the screen. Since these classes are contained in the package java.awt, their full names
are actually java.awt.Graphics, java.awt.Color, and java.awt.Font. (I hope that by now
you’ve gotten the hang of how this naming thing works in Java.) Similarly, javax contains
a sub-package named javax.swing, which includes such classes as javax.swing.JButton,
javax.swing.JMenu, and javax.swing.JFrame. The GUI classes in javax.swing, together
with the foundational classes in java.awt, are all part of the API that makes it possible to
program graphical user interfaces in Java.
The java package includes several other sub-packages, such as java.io, which provides fa-
cilities for input/output, java.net, which deals with network communication, and java.util,
which provides a variety of “utility” classes. The most basic package is called java.lang. This
package contains fundamental classes such as String, Math, Integer, and Double.
It might be helpful to look at a graphical representation of the levels of nesting in the
142 CHAPTER 4. SUBROUTINES

java package, its sub-packages, the classes in those sub-packages, and the subroutines in those
classes. This is not a complete picture, since it shows only a very few of the many items in each
element:

j a v a

l a n g a w t u t i l

M a t h G r a p h i c s

s q r t ( ) d r a w R e c t ( )

s e t C o l o r ( )

r a n d o m ( )

t r i n g C o l o r

F o n t
I n t e g e r

S u b r o u t i n e s n e s t e d i n c l a s s e s n e s t e d i n t w o l a y e r s o f p a c k a g e s .

e f u l l n a m e o f s q r t ( ) i s j a v a . l a n g . M a t . s q r t ( )
T h h

The official documentation for the standard Java 5.0 API lists 165 different packages, in-
cluding sub-packages, and it lists 3278 classes in these packages. Many of these are rather
obscure or very specialized, but you might want to browse through the documentation to see
what is available. As I write this, the documentation for the complete API can be found at
http://java.sun.com/j2se/1.5.0/docs/api/index.html

Even an expert programmer won’t be familiar with the entire API, or even a majority of it. In
this book, you’ll only encounter several dozen classes, and those will be sufficient for writing a
wide variety of programs.

4.5.3 Using Classes from Packages


Let’s say that you want to use the class java.awt.Color in a program that you are writing.
Like any class, java.awt.Color is a type, which means that you can use it to declare variables
and parameters and to specify the return type of a function. One way to do this is to use the
full name of the class as the name of the type. For example, suppose that you want to declare
a variable named rectColor of type java.awt.Color. You could say:
java.awt.Color rectColor;

This is just an ordinary variable declaration of the form “htype-namei hvariable-namei;”. Of


course, using the full name of every class can get tiresome, so Java makes it possible to avoid
using the full name of a class by importing the class. If you put
import java.awt.Color;

at the beginning of a Java source code file, then, in the rest of the file, you can abbreviate the
full name java.awt.Color to just the simple name of the class, Color. Note that the import
4.5. APIS, PACKAGES, AND JAVADOC 143

line comes at the start of a file and is not inside any class. Although it is sometimes referred
to as a statement, it is more properly called an import directive since it is not a statement
in the usual sense. Using this import directive would allow you to say
Color rectColor;

to declare the variable. Note that the only effect of the import directive is to allow you to use
simple class names instead of full “package.class” names; you aren’t really importing anything
substantial. If you leave out the import directive, you can still access the class—you just have
to use its full name. There is a shortcut for importing all the classes from a given package. You
can import all the classes from java.awt by saying
import java.awt.*;

The “*” is a wildcard that matches every class in the package. (However, it does not match
sub-packages; you cannot import the entire contents of all the sub-packages of the java package
by saying import java.*.)
Some programmers think that using a wildcard in an import statement is bad style, since
it can make a large number of class names available that you are not going to use and might
not even know about. They think it is better to explicitly import each individual class that
you want to use. In my own programming, I often use wildcards to import all the classes from
the most relevant packages, and use individual imports when I am using just one or two classes
from a given package.
In fact, any Java program that uses a graphical user interface is likely to use many
classes from the java.awt and java.swing packages as well as from another package named
java.awt.event, and I usually begin such programs with
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

A program that works with networking might include the line “import java.net.*;”, while
one that reads or writes files might use “import java.io.*;”. (But when you start importing
lots of packages in this way, you have to be careful about one thing: It’s possible for two classes
that are in different packages to have the same name. For example, both the java.awt package
and the java.util package contain classes named List. If you import both java.awt.* and
java.util.*, the simple name List will be ambiguous. If you try to declare a variable of type
List, you will get a compiler error message about an ambiguous class name. The solution is
simple: Use the full name of the class, either java.awt.List or java.util.List. Another
solution, of course, is to use import to import the individual classes you need, instead of
importing entire packages.)
Because the package java.lang is so fundamental, all the classes in java.lang are auto-
matically imported into every program. It’s as if every program began with the statement
“import java.lang.*;”. This is why we have been able to use the class name String instead
of java.lang.String, and Math.sqrt() instead of java.lang.Math.sqrt(). It would still,
however, be perfectly legal to use the longer forms of the names.
Programmers can create new packages. Suppose that you want some classes that you are
writing to be in a package named utilities. Then the source code file that defines those
classes must begin with the line
package utilities;
144 CHAPTER 4. SUBROUTINES

This would come even before any import directive in that file. Furthermore, as mentioned in
Subsection 2.6.4, the source code file would be placed in a folder with the same name as the
package. A class that is in a package automatically has access to other classes in the same
package; that is, a class doesn’t have to import the package in which it is defined.
In projects that define large numbers of classes, it makes sense to organize those classes
into packages. It also makes sense for programmers to create new packages as toolboxes that
provide functionality and API’s for dealing with areas not covered in the standard Java API.
(And in fact such “toolmaking” programmers often have more prestige than the applications
programmers who use their tools.)
However, I will not be creating any packages in this textbook. For the purposes of this
book, you need to know about packages mainly so that you will be able to import the standard
packages. These packages are always available to the programs that you write. You might
wonder where the standard classes are actually located. Again, that can depend to some extent
on the version of Java that you are using, but in the standard Java 5.0, they are stored in jar
files in a subdirectory of the main Java installation directory. A jar (or “Java archive”) file
is a single file that can contain many classes. Most of the standard classes can be found in a
jar file named classes.jar. In fact, Java programs are generally distributed in the form of jar
files, instead of as individual class files.
Although we won’t be creating packages explicitly, every class is actually part of a package.
If a class is not specifically placed in a package, then it is put in something called the default
package, which has no name. All the examples that you see in this book are in the default
package.

4.5.4 Javadoc
To use an API effectively, you need good documentation for it. The documentation for most
Java APIs is prepared using a system called Javadoc. For example, this system is used to
prepare the documentation for Java’s standard packages. And almost everyone who creates a
toolbox in Java publishes Javadoc documentation for it.
Javadoc documentation is prepared from special comments that are placed in the Java
source code file. Recall that one type of Java comment begins with /* and ends with */. A
Javadoc comment takes the same form, but it begins with /** rather than simply /*. You
have already seen comments of this form in some of the examples in this book, such as this
subroutine from Section 4.3:
/**
* This subroutine prints a 3N+1 sequence to standard output, using
* startingValue as the initial value of N. It also prints the number
* of terms in the sequence. The value of the parameter, startingValue,
* must be a positive integer.
*/
static void print3NSequence(int startingValue) { ...

Note that the Javadoc comment is placed just before the subroutine that it is commenting
on. This rule is always followed. You can have Javadoc comments for subroutines, for member
variables, and for classes. The Javadoc comment always immediately precedes the thing it is
commenting on.
Like any comment, a Javadoc comment is ignored by the computer when the file is compiled.
But there is a tool called javadoc that reads Java source code files, extracts any Javadoc
4.5. APIS, PACKAGES, AND JAVADOC 145

comments that it finds, and creates a set of Web pages containing the comments in a nicely
formatted, interlinked form. By default, javadoc will only collect information about public
classes, subroutines, and member variables, but it allows the option of creating documentation
for non-public things as well. If javadoc doesn’t find any Javadoc comment for something, it
will construct one, but the comment will contain only basic information such as the name and
type of a member variable or the name, return type, and parameter list of a subroutine. This
is syntactic information. To add information about semantics and pragmatics, you have to
write a Javadoc comment.
As an example, you can look at the documentation Web page for TextIO. The documentation
page was created by applying the javadoc tool to the source code file, TextIO.java. If you
have downloaded the on-line version of this book, the documentation can be found in the
TextIO Javadoc directory, or you can find a link to it in the on-line version of this section.
In a Javadoc comment, the *’s at the start of each line are optional. The javadoc tool
will remove them. In addition to normal text, the comment can contain certain special codes.
For one thing, the comment can contain HTML mark-up commands. HTML is the language
that is used to create web pages, and Javadoc comments are meant to be shown on web pages.
The javadoc tool will copy any HTML commands in the comments to the web pages that it
creates. You’ll learn some basic HTML in Section 6.2, but as an example, you can add <p> to
indicate the start of a new paragraph. (Generally, in the absence of HTML commands, blank
lines and extra spaces in the comment are ignored.)
In addition to HTML commands, Javadoc comments can include doc tags, which are
processed as commands by the javadoc tool. A doc tag has a name that begins with the
character @. I will only discuss three tags: @param, @return, and @throws. These tags are used
in Javadoc comments for subroutines to provide information about its parameters, its return
value, and the exceptions that it might throw. These tags are always placed at the end of the
comment, after any description of the subroutine itself. The syntax for using them is:
@param hparameter-name i hdescription-of-parameter i
@return hdescription-of-return-value i
@throws hexception-class-name i hdescription-of-exception i
The hdescriptionsi can extend over several lines. The description ends at the next tag or at the
end of the comment. You can include a @param tag for every parameter of the subroutine and a
@throws for as many types of exception as you want to document. You should have a @return
tag only for a non-void subroutine. These tags do not have to be given in any particular order.
Here is an example that doesn’t do anything exciting but that does use all three types of
doc tag:
/**
* This subroutine computes the area of a rectangle, given its width
* and its height. The length and the width should be positive numbers.
* @param width the length of one side of the rectangle
* @param height the length the second side of the rectangle
* @return the area of the rectangle
* @throws IllegalArgumentException if either the width or the height
* is a negative number.
*/
public static double areaOfRectangle( double length, double width ) {
if ( width < 0 || height < 0 )
throw new IllegalArgumentException("Sides must have positive length.");
146 CHAPTER 4. SUBROUTINES

double area;
area = width * height;
return area;
}
I will use Javadoc comments for some of my examples. I encourage you to use them in your
own code, even if you don’t plan to generate Web page documentation of your work, since it’s
a standard format that other Java programmers will be familiar with.
If you do want to create Web-page documentation, you need to run the javadoc tool. This
tool is available as a command in the Java Development Kit that was discussed in Section 2.6.
You can use javadoc in a command line interface similarly to the way that the javac and
java commands are used. Javadoc can also be applied in the Eclipse integrated development
environment that was also discussed in Section 2.6: Just right-click the class or package that
you want to document in the Package Explorer, select “Export,” and select “Javadoc” in the
window that pops up. I won’t go into any of the details here; see the documentation.

4.6 More on Program Design


Understanding how programs work is one thing. Designing a program to perform some
particular task is another thing altogether. In Section 3.2, I discussed how pseudocode and
stepwise refinement can be used to methodically develop an algorithm. We can now see how
subroutines can fit into the process.
Stepwise refinement is inherently a top-down process, but the process does have a “bottom,”
that is, a point at which you stop refining the pseudocode algorithm and translate what you have
directly into proper programming language. In the absence of subroutines, the process would
not bottom out until you get down to the level of assignment statements and very primitive
input/output operations. But if you have subroutines lying around to perform certain useful
tasks, you can stop refining as soon as you’ve managed to express your algorithm in terms of
those tasks.
This allows you to add a bottom-up element to the top-down approach of stepwise re-
finement. Given a problem, you might start by writing some subroutines that perform tasks
relevant to the problem domain. The subroutines become a toolbox of ready-made tools that
you can integrate into your algorithm as you develop it. (Alternatively, you might be able to
buy or find a software toolbox written by someone else, containing subroutines that you can
use in your project as black boxes.)
Subroutines can also be helpful even in a strict top-down approach. As you refine your
algorithm, you are free at any point to take any sub-task in the algorithm and make it into a
subroutine. Developing that subroutine then becomes a separate problem, which you can work
on separately. Your main algorithm will merely call the subroutine. This, of course, is just
a way of breaking your problem down into separate, smaller problems. It is still a top-down
approach because the top-down analysis of the problem tells you what subroutines to write.
In the bottom-up approach, you start by writing or obtaining subroutines that are relevant to
the problem domain, and you build your solution to the problem on top of that foundation of
subroutines.

4.6.1 Preconditions and Postconditions


When working with subroutines as building blocks, it is important to be clear about how a
subroutine interacts with the rest of the program. This interaction is specified by the contract
4.6. MORE ON PROGRAM DESIGN 147

of the subroutine, as discussed in Section 4.1. A convenient way to express the contract of a
subroutine is in terms of preconditions and postconditions.
The precondition of a subroutine is something that must be true when the subroutine
is called, if the subroutine is to work correctly. For example, for the built-in function
Math.sqrt(x), a precondition is that the parameter, x, is greater than or equal to zero, since it
is not possible to take the square root of a negative number. In terms of a contract, a precon-
dition represents an obligation of the caller of the subroutine. If you call a subroutine without
meeting its precondition, then there is no reason to expect it to work properly. The program
might crash or give incorrect results, but you can only blame yourself, not the subroutine.
A postcondition of a subroutine represents the other side of the contract. It is something
that will be true after the subroutine has run (assuming that its preconditions were met—and
that there are no bugs in the subroutine). The postcondition of the function Math.sqrt() is
that the square of the value that is returned by this function is equal to the parameter that is
provided when the subroutine is called. Of course, this will only be true if the preconditiion—
that the parameter is greater than or equal to zero—is met. A postcondition of the built-in
subroutine System.out.print() is that the value of the parameter has been displayed on the
screen.
Preconditions most often give restrictions on the acceptable values of parameters, as in the
example of Math.sqrt(x). However, they can also refer to global variables that are used in
the subroutine. The postcondition of a subroutine specifies the task that it performs. For a
function, the postcondition should specify the value that the function returns.
Subroutines are often described by comments that explicitly specify their preconditions and
postconditions. When you are given a pre-written subroutine, a statement of its preconditions
and postconditions tells you how to use it and what it does. When you are assigned to write
a subroutine, the preconditions and postconditions give you an exact specification of what the
subroutine is expected to do. I will use this approach in the example that constitutes the rest
of this section. The comments are given in the form of Javadoc comments, but I will explicitly
label the preconditions and postconditions. (Many computer scientists think that new doc
tags @precondition and @postcondition should be added to the Javadoc system for explicit
labeling of preconditions and postconditions, but that has not yet been done.)

4.6.2 A Design Example


Let’s work through an example of program design using subroutines. In this example, we will
use prewritten subroutines as building blocks and we will also design new subroutines that we
need to complete the project.
Suppose that I have found an already-written class called Mosaic. This class allows a
program to work with a window that displays little colored rectangles arranged in rows and
columns. The window can be opened, closed, and otherwise manipulated with static member
subroutines defined in the Mosaic class. In fact, the class defines a toolbox or API that can be
used for working with such windows. Here are some of the available routines in the API, with
Javadoc-style comments:
/**
* Opens a "mosaic" window on the screen.
*
* Precondition: The parameters rows, cols, w, and h are positive integers.
* Postcondition: A window is open on the screen that can display rows and
* columns of colored rectangles. Each rectangle is w pixels
148 CHAPTER 4. SUBROUTINES

* wide and h pixels high. The number of rows is given by


* the first parameter and the number of columns by the
* second. Initially, all rectangles are black.
* Note: The rows are numbered from 0 to rows - 1, and the columns are
* numbered from 0 to cols - 1.
*/
public static void open(int rows, int cols, int w, int h)

/**
* Sets the color of one of the rectangles in the window.
*
* Precondition: row and col are in the valid range of row and column numbers,
* and r, g, and b are in the range 0 to 255, inclusive.
* Postcondition: The color of the rectangle in row number row and column
* number col has been set to the color specified by r, g,
* and b. r gives the amount of red in the color with 0
* representing no red and 255 representing the maximum
* possible amount of red. The larger the value of r, the
* more red in the color. g and b work similarly for the
* green and blue color components.
*/
public static void setColor(int row, int col, int r, int g, int b)

/**
* Gets the red component of the color of one of the rectangles.
*
* Precondition: row and col are in the valid range of row and column numbers.
* Postcondition: The red component of the color of the specified rectangle is
* returned as an integer in the range 0 to 255 inclusive.
*/
public static int getRed(int row, int col)

/**
* Like getRed, but returns the green component of the color.
*/
public static int getGreen(int row, int col)

/**
* Like getRed, but returns the blue component of the color.
*/
public static int getBlue(int row, int col)

/**
* Tests whether the mosaic window is currently open.
*
* Precondition: None.
* Postcondition: The return value is true if the window is open when this
* function is called, and it is false if the window is
* closed.
*/
public static boolean isOpen()

/**
4.6. MORE ON PROGRAM DESIGN 149

* Inserts a delay in the program (to regulate the speed at which the colors
* are changed, for example).
*
* Precondition: milliseconds is a positive integer.
* Postcondition: The program has paused for at least the specified number
* of milliseconds, where one second is equal to 1000
* milliseconds.
*/
public static void delay(int milliseconds)

Remember that these subroutines are members of the Mosaic class, so when they are called
from outside Mosaic, the name of the class must be included as part of the name of the routine.
For example, we’ll have to use the name Mosaic.isOpen() rather than simply isOpen().
∗ ∗ ∗
My idea is to use the Mosaic class as the basis for a neat animation. I want to fill the
window with randomly colored squares, and then randomly change the colors in a loop that
continues as long as the window is open. “Randomly change the colors” could mean a lot
of different things, but after thinking for a while, I decide it would be interesting to have a
“disturbance” that wanders randomly around the window, changing the color of each square
that it encounters. Here’s a picture showing what the contents of the window might look like
at one point in time:

With basic routines for manipulating the window as a foundation, I can turn to the specific
problem at hand. A basic outline for my program is
Open a Mosaic window
Fill window with random colors;
Move around, changing squares at random.

Filling the window with random colors seems like a nice coherent task that I can work on
separately, so let’s decide to write a separate subroutine to do it. The third step can be
expanded a bit more, into the steps: Start in the middle of the window, then keep moving to a
new square and changing the color of that square. This should continue as long as the mosaic
window is still open. Thus we can refine the algorithm to:
Open a Mosaic window
Fill window with random colors;
Set the current position to the middle square in the window;
As long as the mosaic window is open:
Randomly change color of the square at the current position;
Move current position up, down, left, or right, at random;
150 CHAPTER 4. SUBROUTINES

I need to represent the current position in some way. That can be done with two int variables
named currentRow and currentColumn that hold the row number and the column number of
the square where the disturbance is currently located. I’ll use 10 rows and 20 columns of squares
in my mosaic, so setting the current position to be in the center means setting currentRow to 5
and currentColumn to 10. I already have a subroutine, Mosaic.open(), to open the window,
and I have a function, Mosaic.isOpen(), to test whether the window is open. To keep the
main routine simple, I decide that I will write two more subroutines of my own to carry out
the two tasks in the while loop. The algorithm can then be written in Java as:
Mosaic.open(10,20,10,10)
fillWithRandomColors();
currentRow = 5; // Middle row, halfway down the window.
currentColumn = 10; // Middle column.
while ( Mosaic.isOpen() ) {
changeToRandomColor(currentRow, currentColumn);
randomMove();
}
With the proper wrapper, this is essentially the main() routine of my program. It turns out I
have to make one small modification: To prevent the animation from running too fast, the line
“Mosaic.delay(20);” is added to the while loop.
The main() routine is taken care of, but to complete the program, I still have to write the
subroutines fillWithRandomColors(), changeToRandomColor(int,int), and randomMove().
Writing each of these subroutines is a separate, small task. The fillWithRandomColors()
routine is defined by the postcondition that “each of the rectangles in the mosaic has been
changed to a random color.” Pseudocode for an algorithm to accomplish this task can be given
as:
For each row:
For each column:
set the square in that row and column to a random color
“For each row” and “for each column” can be implemented as for loops. We’ve already planned
to write a subroutine changeToRandomColor that can be used to set the color. (The possi-
bility of reusing subroutines in several places is one of the big payoffs of using them!) So,
fillWithRandomColors() can be written in proper Java as:
static void fillWithRandomColors() {
for (int row = 0; row < 10; row++)
for (int column = 0; column < 20; column++)
changeToRandomColor(row,column);
}
Turning to the changeToRandomColor subroutine, we already have a method in the Mosaic
class, Mosaic.setColor(), that can be used to change the color of a square. If we want a ran-
dom color, we just have to choose random values for r, g, and b. According to the precondition
of the Mosaic.setColor() subroutine, these random values must be integers in the range from
0 to 255. A formula for randomly selecting such an integer is “(int)(256*Math.random())”.
So the random color subroutine becomes:
static void changeToRandomColor(int rowNum, int colNum) {
int red = (int)(256*Math.random());
int green = (int)(256*Math.random());
int blue = (int)(256*Math.random());
4.6. MORE ON PROGRAM DESIGN 151

mosaic.setColor(rowNum,colNum,red,green,blue);
}

Finally, consider the randomMove subroutine, which is supposed to randomly move the
disturbance up, down, left, or right. To make a random choice among four directions, we
can choose a random integer in the range 0 to 3. If the integer is 0, move in one direction;
if it is 1, move in another direction; and so on. The position of the disturbance is given
by the variables currentRow and currentColumn. To “move up” means to subtract 1 from
currentRow. This leaves open the question of what to do if currentRow becomes -1, which
would put the disturbance above the window. Rather than let this happen, I decide to move
the disturbance to the opposite edge of the applet by setting currentRow to 9. (Remember
that the 10 rows are numbered from 0 to 9.) Moving the disturbance down, left, or right is
handled similarly. If we use a switch statement to decide which direction to move, the code
for randomMove becomes:
int directionNum;
directionNum = (int)(4*Math.random());
switch (directionNum) {
case 0: // move up
currentRow--;
if (currentRow < 0) // CurrentRow is outside the mosaic;
currentRow = 9; // move it to the opposite edge.
break;
case 1: // move right
currentColumn++;
if (currentColumn >= 20)
currentColumn = 0;
break;
case 2: // move down
currentRow++;
if (currentRow >= 10)
currentRow = 0;
break;
case 3: // move left
currentColumn--;
if (currentColumn < 0)
currentColumn = 19;
break;
}

4.6.3 The Program


Putting this all together, we get the following complete program. Note that I’ve added Javadoc-
style comments for the class itself and for each of the subroutines. The variables currentRow
and currentColumn are defined as static members of the class, rather than local variables,
because each of them is used in several different subroutines. This program actually depends
on two other classes, Mosaic and another class called MosaicCanvas that is used by Mosaic.
If you want to compile and run this program, both of these classes must be available to the
program.
152 CHAPTER 4. SUBROUTINES

/**
* This program opens a window full of randomly colored squares. A "disturbance"
* moves randomly around in the window, randomly changing the color of each
* square that it visits. The program runs until the user closes the window.
*/
public class RandomMosaicWalk {
static int currentRow; // Row currently containing the disturbance.
static int currentColumn; // Column currently containing disturbance.
/**
* The main program creates the window, fills it with random colors,
* and then moves the disturbances in a random walk around the window
* as long as the window is open.
*/
public static void main(String[] args) {
Mosaic.open(10,20,10,10);
fillWithRandomColors();
currentRow = 5; // start at center of window
currentColumn = 10;
while (Mosaic.isOpen()) {
changeToRandomColor(currentRow, currentColumn);
randomMove();
Mosaic.delay(20);
}
} // end main
/**
* Fills the window with randomly colored squares.
* Precondition: The mosaic window is open.
* Postcondition: Each square has been set to a random color.
*/
static void fillWithRandomColors() {
for (int row=0; row < 10; row++) {
for (int column=0; column < 20; column++) {
changeToRandomColor(row, column);
}
}
} // end fillWithRandomColors
/**
* Changes one square to a new randomly selected color.
* Precondition: The specified rowNum and colNum are in the valid range
* of row and column numbers.
* Postcondition: The square in the specified row and column has
* been set to a random color.
* @param rowNum the row number of the square, counting rows down
* from 0 at the top
* @param colNum the column number of the square, counting columns over
* from 0 at the left
*/
static void changeToRandomColor(int rowNum, int colNum) {
int red = (int)(256*Math.random()); // Choose random levels in range
int green = (int)(256*Math.random()); // 0 to 255 for red, green,
int blue = (int)(256*Math.random()); // and blue color components.
4.7. THE TRUTH ABOUT DECLARATIONS 153

Mosaic.setColor(rowNum,colNum,red,green,blue);
} // end of changeToRandomColor()

/**
* Move the disturbance.
* Precondition: The global variables currentRow and currentColumn
* are within the legal range of row and column numbers.
* Postcondition: currentRow or currentColumn is changed to one of the
* neighboring positions in the grid -- up, down, left, or
* right from the current position. If this moves the
* position outside of the grid, then it is moved to the
* opposite edge of the grid.
*/
static void randomMove() {
int directionNum; // Randomly set to 0, 1, 2, or 3 to choose direction.
directionNum = (int)(4*Math.random());
switch (directionNum) {
case 0: // move up
currentRow--;
if (currentRow < 0)
currentRow = 9;
break;
case 1: // move right
currentColumn++;
if (currentColumn >= 20)
currentColumn = 0;
break;
case 2: // move down
currentRow++;
if (currentRow >= 10)
currentRow = 0;
break;
case 3: // move left
currentColumn--;
if (currentColumn < 0)
currentColumn = 19;
break;
}
} // end randomMove

} // end class RandomMosaicWalk

4.7 The Truth About Declarations


Names are fundamental to programming, as I said a few chapters ago. There are a lot
of details involved in declaring and using names. I have been avoiding some of those details.
In this section, I’ll reveal most of the truth (although still not the full truth) about declaring
and using variables in Java. The material in the subsections “Initialization in Declarations”
and “Named Constants” is particularly important, since I will be using it regularly in future
chapters.
154 CHAPTER 4. SUBROUTINES

4.7.1 Initialization in Declarations


When a variable declaration is executed, memory is allocated for the variable. This memory
must be initialized to contain some definite value before the variable can be used in an expres-
sion. In the case of a local variable, the declaration is often followed closely by an assignment
statement that does the initialization. For example,
int count; // Declare a variable named count.
count = 0; // Give count its initial value.
However, the truth about declaration statements is that it is legal to include the initializa-
tion of the variable in the declaration statement. The two statements above can therefore be
abbreviated as
int count = 0; // Declare count and give it an initial value.
The computer still executes this statement in two steps: Declare the variable count, then assign
the value 0 to the newly created variable. The initial value does not have to be a constant. It
can be any expression. It is legal to initialize several variables in one declaration statement.
For example,
char firstInitial = ’D’, secondInitial = ’E’;
int x, y = 1; // OK, but only y has been initialized!
int N = 3, M = N+2; // OK, N is initialized
// before its value is used.

This feature is especially common in for loops, since it makes it possible to declare a loop control
variable at the same point in the loop where it is initialized. Since the loop control variable
generally has nothing to do with the rest of the program outside the loop, it’s reasonable to
have its declaration in the part of the program where it’s actually used. For example:
for ( int i = 0; i < 10; i++ ) {
System.out.println(i);
}
Again, you should remember that this is simply an abbreviation for the following, where I’ve
added an extra pair of braces to show that i is considered to be local to the for statement and
no longer exists after the for loop ends:
{
int i;
for ( i = 0; i < 10; i++ ) {
System.out.println(i);
}
}
(You might recall, by the way, that for “for-each” loops, the special type of for statement
that is used with enumerated types, declaring the variable in the for is required. See Subsec-
tion 3.4.4.)
A member variable can also be initialized at the point where it is declared, just as for a
local variable. For example:
public class Bank {
static double interestRate = 0.05;
static int maxWithdrawal = 200;
4.7. THE TRUTH ABOUT DECLARATIONS 155

.
. // More variables and subroutines.
.
}
A static member variable is created as soon as the class is loaded by the Java interpreter, and
the initialization is also done at that time. In the case of member variables, this is not simply
an abbreviation for a declaration followed by an assignment statement. Declaration statements
are the only type of statement that can occur outside of a subroutine. Assignment statements
cannot, so the following is illegal:
public class Bank {
static double interestRate;
interestRate = 0.05; // ILLEGAL:
. // Can’t be outside a subroutine!:
.
.
Because of this, declarations of member variables often include initial values. In fact, as
mentioned in Subsection 4.2.4, if no initial value is provided for a member variable, then a
default initial value is used. For example, when declaring an integer member variable, count,
“static int count;” is equivalent to “static int count = 0;”.

4.7.2 Named Constants


Sometimes, the value of a variable is not supposed to change after it is initialized. For example,
in the above example where interestRate is initialized to the value 0.05, it’s quite possible
that that is meant to be the value throughout the entire program. In this case, the programmer
is probably defining the variable, interestRate, to give a meaningful name to the otherwise
meaningless number, 0.05. It’s easier to understand what’s going on when a program says
“principal += principal*interestRate;” rather than “principal += principal*0.05;”.
In Java, the modifier “final” can be applied to a variable declaration to ensure that the
value stored in the variable cannot be changed after the variable has been initialized. For
example, if the member variable interestRate is declared with
final static double interestRate = 0.05;
then it would be impossible for the value of interestRate to change anywhere else in the
program. Any assignment statement that tries to assign a value to interestRate will be
rejected by the computer as a syntax error when the program is compiled.
It is legal to apply the final modifier to local variables and even to formal parameters,
but it is most useful for member variables. I will often refer to a static member variable that
is declared to be final as a named constant , since its value remains constant for the whole
time that the program is running. The readability of a program can be greatly enhanced by
using named constants to give meaningful names to important quantities in the program. A
recommended style rule for named constants is to give them names that consist entirely of
upper case letters, with underscore characters to separate words if necessary. For example, the
preferred style for the interest rate constant would be
final static double INTEREST RATE = 0.05;
This is the style that is generally used in Java’s standard classes, which define many named
constants. For example, we have already seen that the Math class contains a variable Math.PI.
This variable is declared in the Math class as a “public final static” variable of type double.
156 CHAPTER 4. SUBROUTINES

Similarly, the Color class contains named constants such as Color.RED and Color.YELLOW
which are public final static variables of type Color. Many named constants are created just to
give meaningful names to be used as parameters in subroutine calls. For example, the standard
class named Font contains named constants Font.PLAIN, Font.BOLD, and Font.ITALIC. These
constants are used for specifying different styles of text when calling various subroutines in the
Font class.
Enumerated type constants (See Subsection 2.3.3.) are also examples of named constants.
The enumerated type definition
enum Alignment { LEFT, RIGHT, CENTER }
defines the constants Alignment.LEFT, Alignment.RIGHT, and Alignment.CENTER. Technically,
Alignment is a class, and the three constants are public final static members of that class.
Defining the enumerated type is similar to defining three constants of type, say, int:
public static final int ALIGNMENT LEFT = 0;
public static final int ALIGNMNENT RIGHT = 1;
public static final int ALIGNMENT CENTER = 2;
In fact, this is how things were generally done before the introduction of enumerated types in
Java 5.0, and it is what is done with the constants Font.PLAIN, Font.BOLD, and Font.ITALIC
mentioned above. Using the integer constants, you could define a variable of type int and assign
it the values ALIGNMENT LEFT, ALIGNMENT RIGHT, or ALIGNMENT CENTER to represent different
types of alignment. The only problem with this is that the computer has no way of knowing
that you intend the value of the variable to represent an alignment, and it will not raise any
objection if the value that is assigned to the variable is not one of the three valid alignment
values.
With the enumerated type, on the other hand, the only values that can be assigned to
a variable of type Alignment are the constant values that are listed in the definition of the
enumerated type. Any attempt to assign an invalid value to the variable is a syntax error
which the computer will detect when the program is compiled. This extra safety is one of the
major advantages of enumerated types.
∗ ∗ ∗
Curiously enough, one of the major reasons to use named constants is that it’s easy to
change the value of a named constant. Of course, the value can’t change while the program
is running. But between runs of the program, it’s easy to change the value in the source code
and recompile the program. Consider the interest rate example. It’s quite possible that the
value of the interest rate is used many times throughout the program. Suppose that the bank
changes the interest rate and the program has to be modified. If the literal number 0.05 were
used throughout the program, the programmer would have to track down each place where
the interest rate is used in the program and change the rate to the new value. (This is made
even harder by the fact that the number 0.05 might occur in the program with other meanings
besides the interest rate, as well as by the fact that someone might have used 0.025 to represent
half the interest rate.) On the other hand, if the named constant INTEREST RATE is declared
and used consistently throughout the program, then only the single line where the constant is
initialized needs to be changed.
As an extended example, I will give a new version of the RandomMosaicWalk program from
the previous section. This version uses named constants to represent the number of rows in
the mosaic, the number of columns, and the size of each little square. The three constants are
declared as final static member variables with the lines:
4.7. THE TRUTH ABOUT DECLARATIONS 157

final static int ROWS = 30; // Number of rows in mosaic.


final static int COLUMNS = 30; // Number of columns in mosaic.
final static int SQUARE SIZE = 15; // Size of each square in mosaic.
The rest of the program is carefully modified to use the named constants. For example, in
the new version of the program, the Mosaic window is opened with the statement
Mosaic.open(ROWS, COLUMNS, SQUARE SIZE, SQUARE SIZE);
Sometimes, it’s not easy to find all the places where a named constant needs to be used. If
you don’t use the named constant consistently, you’ve more or less defeated the purpose. It’s
always a good idea to run a program using several different values for any named constants, to
test that it works properly in all cases.
Here is the complete new program, RandomMosaicWalk2, with all modifications from the
previous version shown in italic. I’ve left out some of the comments to save space.
public class RandomMosaicWalk2 {
final static int ROWS = 30; // Number of rows in mosaic.
final static int COLUMNS = 30; // Number of columns in mosaic.
final static int SQUARE SIZE = 15; // Size of each square in mosaic.
static int currentRow; // Row currently containing the disturbance.
static int currentColumn; // Column currently containing disturbance.
public static void main(String[] args) {
Mosaic.open( ROWS, COLUMNS, SQUARE SIZE, SQUARE SIZE );
fillWithRandomColors();
currentRow = ROWS / 2; // start at center of window
currentColumn = COLUMNS / 2;
while (Mosaic.isOpen()) {
changeToRandomColor(currentRow, currentColumn);
randomMove();
Mosaic.delay(20);
}
} // end main
static void fillWithRandomColors() {
for (int row=0; row < ROWS; row++) {
for (int column=0; column < COLUMNS; column++) {
changeToRandomColor(row, column);
}
}
} // end fillWithRandomColors
static void changeToRandomColor(int rowNum, int colNum) {
int red = (int)(256*Math.random()); // Choose random levels in range
int green = (int)(256*Math.random()); // 0 to 255 for red, green,
int blue = (int)(256*Math.random()); // and blue color components.
Mosaic.setColor(rowNum,colNum,red,green,blue);
} // end changeToRandomColor
static void randomMove() {
int directionNum; // Randomly set to 0, 1, 2, or 3 to choose direction.
directionNum = (int)(4*Math.random());
switch (directionNum) {
case 0: // move up
158 CHAPTER 4. SUBROUTINES

currentRow--;
if (currentRow < 0)
currentRow = ROWS - 1;
break;
case 1: // move right
currentColumn++;
if (currentColumn >= COLUMNS)
currentColumn = 0;
break;
case 2: // move down
currentRow ++;
if (currentRow >= ROWS)
currentRow = 0;
break;
case 3: // move left
currentColumn--;
if (currentColumn < 0)
currentColumn = COLUMNS - 1;
break;
}
} // end randomMove
} // end class RandomMosaicWalk2

4.7.3 Naming and Scope Rules


When a variable declaration is executed, memory is allocated for that variable. The variable
name can be used in at least some part of the program source code to refer to that memory or
to the data that is stored in the memory. The portion of the program source code where the
variable name is valid is called the scope of the variable. Similarly, we can refer to the scope
of subroutine names and formal parameter names.
For static member subroutines, scope is straightforward. The scope of a static subroutine
is the entire source code of the class in which it is defined. That is, it is possible to call the
subroutine from any point in the class, including at a point in the source code before the point
where the definition of the subroutine appears. It is even possible to call a subroutine from
within itself. This is an example of something called “recursion,” a fairly advanced topic that
we will return to later.
For a variable that is declared as a static member variable in a class, the situation is similar,
but with one complication. It is legal to have a local variable or a formal parameter that has
the same name as a member variable. In that case, within the scope of the local variable or
parameter, the member variable is hidden. Consider, for example, a class named Game that
has the form:
public class Game {
static int count; // member variable
static void playGame() {
int count; // local variable
.
. // Some statements to define playGame()
.
}
4.7. THE TRUTH ABOUT DECLARATIONS 159

.
. // More variables and subroutines.
.
} // end Game

In the statements that make up the body of the playGame() subroutine, the name “count”
refers to the local variable. In the rest of the Game class, “count” refers to the member vari-
able, unless hidden by other local variables or parameters named count. However, there is
one further complication. The member variable named count can also be referred to by the
full name Game.count. Usually, the full name is only used outside the class where count is
defined. However, there is no rule against using it inside the class. The full name, Game.count,
can be used inside the playGame() subroutine to refer to the member variable. So, the full
scope rule is that the scope of a static member variable includes the entire class in which it
is defined, but where the simple name of the member variable is hidden by a local variable
or formal parameter name, the member variable must be referred to by its full name of the
form hclassNamei.hvariableNamei. (Scope rules for non-static members are similar to those
for static members, except that, as we shall see, non-static members cannot be used in static
subroutines.)
The scope of a formal parameter of a subroutine is the block that makes up the body of the
subroutine. The scope of a local variable extends from the declaration statement that defines
the variable to the end of the block in which the declaration occurs. As noted above, it is
possible to declare a loop control variable of a for loop in the for statement, as in “for (int
i=0; i < 10; i++)”. The scope of such a declaration is considered as a special case: It is
valid only within the for statement and does not extend to the remainder of the block that
contains the for statement.
It is not legal to redefine the name of a formal parameter or local variable within its scope,
even in a nested block. For example, this is not allowed:
void badSub(int y) {
int x;
while (y > 0) {
int x; // ERROR: x is already defined.
.
.
.
}
}

In many languages, this would be legal; the declaration of x in the while loop would hide
the original declaration. It is not legal in Java; however, once the block in which a variable is
declared ends, its name does become available for reuse in Java. For example:
void goodSub(int y) {
while (y > 10) {
int x;
.
.
.
// The scope of x ends here.
}
while (y > 0) {
160 CHAPTER 4. SUBROUTINES

int x; // OK: Previous declaration of x has expired.


.
.
.
}
}
You might wonder whether local variable names can hide subroutine names. This can’t
happen, for a reason that might be surprising. There is no rule that variables and subroutines
have to have different names. The computer can always tell whether a name refers to a variable
or to a subroutine, because a subroutine name is always followed by a left parenthesis. It’s
perfectly legal to have a variable called count and a subroutine called count in the same class.
(This is one reason why I often write subroutine names with parentheses, as when I talk about
the main() routine. It’s a good idea to think of the parentheses as part of the name.) Even
more is true: It’s legal to reuse class names to name variables and subroutines. The syntax
rules of Java guarantee that the computer can always tell when a name is being used as a class
name. A class name is a type, and so it can be used to declare variables and formal parameters
and to specify the return type of a function. This means that you could legally have a class
called Insanity in which you declare a function
static Insanity Insanity( Insanity Insanity ) { ... }
The first Insanity is the return type of the function. The second is the function name, the
third is the type of the formal parameter, and the fourth is a formal parameter name. However,
please remember that not everything that is possible is a good idea!
Exercises 161

Exercises for Chapter 4

1. To “capitalize” a string means to change the first letter of each word in the string to upper
case (if it is not already upper case). For example, a capitalized version of “Now is the time
to act!” is “Now Is The Time To Act!”. Write a subroutine named printCapitalized
that will print a capitalized version of a string to standard output. The string to be printed
should be a parameter to the subroutine. Test your subroutine with a main() routine that
gets a line of input from the user and applies the subroutine to it.
Note that a letter is the first letter of a word if it is not immediately preceded in
the string by another letter. Recall that there is a standard boolean-valued function
Character.isLetter(char) that can be used to test whether its parameter is a letter.
There is another standard char-valued function, Character.toUpperCase(char), that
returns a capitalized version of the single character passed to it as a parameter. That is,
if the parameter is a letter, it returns the upper-case version. If the parameter is not a
letter, it just returns a copy of the parameter.

2. The hexadecimal digits are the ordinary, base-10 digits ’0’ through ’9’ plus the letters ’A’
through ’F’. In the hexadecimal system, these digits represent the values 0 through 15,
respectively. Write a function named hexValue that uses a switch statement to find the
hexadecimal value of a given character. The character is a parameter to the function, and
its hexadecimal value is the return value of the function. You should count lower case
letters ’a’ through ’f’ as having the same value as the corresponding upper case letters.
If the parameter is not one of the legal hexadecimal digits, return -1 as the value of the
function.
A hexadecimal integer is a sequence of hexadecimal digits, such as 34A7, FF8, 174204,
or FADE. If str is a string containing a hexadecimal integer, then the corresponding
base-10 integer can be computed as follows:
value = 0;
for ( i = 0; i < str.length(); i++ )
value = value*16 + hexValue( str.charAt(i) );
Of course, this is not valid if str contains any characters that are not hexadecimal digits.
Write a program that reads a string from the user. If all the characters in the string are
hexadecimal digits, print out the corresponding base-10 value. If not, print out an error
message.

3. Write a function that simulates rolling a pair of dice until the total on the dice comes up
to be a given number. The number that you are rolling for is a parameter to the function.
The number of times you have to roll the dice is the return value of the function. The
parameter should be one of the possible totals: 2, 3, . . . , 12. The function should throw
an IllegalArgumentException if this is not the case. Use your function in a program that
computes and prints the number of rolls it takes to get snake eyes. (Snake eyes means
that the total showing on the dice is 2.)

4. This exercise builds on Exercise 4.3. Every time you roll the dice repeatedly, trying to
get a given total, the number of rolls it takes can be different. The question naturally
arises, what’s the average number of rolls to get a given total? Write a function that
performs the experiment of rolling to get a given total 10000 times. The desired total is
162 CHAPTER 4. SUBROUTINES

a parameter to the subroutine. The average number of rolls is the return value. Each
individual experiment should be done by calling the function you wrote for Exercise 4.3.
Now, write a main program that will call your function once for each of the possible totals
(2, 3, ..., 12). It should make a table of the results, something like:
Total On Dice Average Number of Rolls
------------- -----------------------
2 35.8382
3 18.0607
. .
. .

5. The sample program RandomMosaicWalk.java from Section 4.6 shows a “disturbance”


that wanders around a grid of colored squares. When the disturbance visits a square, the
color of that square is changed. The applet at the bottom of Section 4.7 in the on-line
version of this book shows a variation on this idea. In this applet, all the squares start
out with the default color, black. Every time the disturbance visits a square, a small
amount is added to the red component of the color of that square. Write a subroutine
that will add 25 to the red component of one of the squares in the mosaic. The row and
column numbers of the square should be passed as parameters to the subroutine. Recall
that you can discover the current red component of the square in row r and column c
with the function call Mosaic.getRed(r,c). Use your subroutine as a substitute for the
changeToRandomColor() subroutine in the program RandomMosaicWalk2.java. (This is
the improved version of the program from Section 4.7 that uses named constants for the
number of rows, number of columns, and square size.) Set the number of rows and the
number of columns to 80. Set the square size to 5.

6. For this exercise, you will write another program based on the non-standard Mosaic class
that was presented in Section 4.6. While the program does not do anything particularly
interesting, it’s interesting as a programming problem. An applet that does the same
thing as the program can be seen in the on-line version of this book. Here is a picture
showing what it looks like at several different times:

The program will show a rectangle that grows from the center of the applet to the edges,
getting brighter as it grows. The rectangle is made up of the little squares of the mosaic.
You should first write a subroutine that draws a rectangle on a Mosaic window. More
specifically, write a subroutine named rectangle such that the subroutine call statement
rectangle(top,left,height,width,r,g,b);
Exercises 163

will call Mosaic.setColor(row,col,r,g,b) for each little square that lies on the outline
of a rectangle. The topmost row of the rectangle is specified by top. The number of
rows in the rectangle is specified by height (so the bottommost row is top+height-1).
The leftmost column of the rectangle is specified by left. The number of columns in the
rectangle is specified by width (so the rightmost column is left+width-1.)
The animation loops through the same sequence of steps over and over. In each step,
a rectangle is drawn in gray (that is, with all three color components having the same
value). There is a pause of 200 milliseconds so the user can see the rectangle. Then the
very same rectangle is drawn in black, effectively erasing the gray rectangle. Finally, the
variables giving the top row, left column, size, and color level of the rectangle are adjusted
to get ready for the next step. In the applet, the color level starts at 50 and increases by
10 after each step. When the rectangle gets to the outer edge of the applet, the loop ends.
The animation then starts again at the beginning of the loop. You might want to make a
subroutine that does one loop through all the steps of the animation.
The main() routine simply opens a Mosaic window and then does the animation loop
over and over until the user closes the window. There is a 1000 millisecond delay between
one animation loop and the next. Use a Mosaic window that has 41 rows and 41 columns.
(I advise you not to used named constants for the numbers of rows and columns, since
the problem is complicated enough already.)
164 CHAPTER 4. SUBROUTINES

Quiz on Chapter 4

1. A “black box” has an interface and an implementation. Explain what is meant by the
terms interface and implementation.

2. A subroutine is said to have a contract. What is meant by the contract of a subroutine?


When you want to use a subroutine, why is it important to understand its contract? The
contract has both “syntactic” and “semantic” aspects. What is the syntactic aspect?
What is the semantic aspect?

3. Briefly explain how subroutines can be a useful tool in the top-down design of programs.

4. Discuss the concept of parameters. What are parameters for? What is the difference
between formal parameters and actual parameters?

5. Give two different reasons for using named constants (declared with the final modifier).

6. What is an API? Give an example.

7. Write a subroutine named “stars” that will output a line of stars to standard output. (A
star is the character “*”.) The number of stars should be given as a parameter to the
subroutine. Use a for loop. For example, the command “stars(20)” would output
********************

8. Write a main() routine that uses the subroutine that you wrote for Question 7 to output
10 lines of stars with 1 star in the first line, 2 stars in the second line, and so on, as shown
below.
*
**
***
****
*****
******
*******
********
*********
**********

9. Write a function named countChars that has a String and a char as parameters. The
function should count the number of times the character occurs in the string, and it should
return the result as the value of the function.

10. Write a subroutine with three parameters of type int. The subroutine should determine
which of its parameters is smallest. The value of the smallest parameter should be returned
as the value of the subroutine.
Chapter 5

Programming in the Large II:


Objects and Classes

Whereas a subroutine represents a single task, an object can encapsulate both data (in
the form of instance variables) and a number of different tasks or “behaviors” related to that
data (in the form of instance methods). Therefore objects provide another, more sophisticated
type of structure that can be used to help manage the complexity of large programs.
This chapter covers the creation and use of objects in Java. Section 5.5 covers the central
ideas of object-oriented programming: inheritance and polymorphism. However, in this text-
book, we will generally use these ideas in a limited form, by creating independent classes and
building on existing classes rather than by designing entire hierarchies of classes from scratch.
Section 5.6 and Section 5.7 cover some of the many details of object oriented programming in
Java. Although these details are used occasionally later in the book, you might want to skim
through them now and return to them later when they are actually needed.

5.1 Objects, Instance Methods, and Instance Variables


Object-oriented programming (OOP) represents an attempt to make programs more
closely model the way people think about and deal with the world. In the older styles of
programming, a programmer who is faced with some problem must identify a computing task
that needs to be performed in order to solve the problem. Programming then consists of
finding a sequence of instructions that will accomplish that task. But at the heart of object-
oriented programming, instead of tasks we find objects—entities that have behaviors, that hold
information, and that can interact with one another. Programming consists of designing a set
of objects that somehow model the problem at hand. Software objects in the program can
represent real or abstract entities in the problem domain. This is supposed to make the design
of the program more natural and hence easier to get right and easier to understand.
To some extent, OOP is just a change in point of view. We can think of an object in standard
programming terms as nothing more than a set of variables together with some subroutines for
manipulating those variables. In fact, it is possible to use object-oriented techniques in any
programming language. However, there is a big difference between a language that makes OOP
possible and one that actively supports it. An object-oriented programming language such as
Java includes a number of features that make it very different from a standard language. In
order to make effective use of those features, you have to “orient” your thinking correctly.

165
166 CHAPTER 5. OBJECTS AND CLASSES

5.1.1 Objects, Classes, and Instances


Objects are closely related to classes. We have already been working with classes for several
chapters, and we have seen that a class can contain variables and subroutines. If an object is
also a collection of variables and subroutines, how do they differ from classes? And why does it
require a different type of thinking to understand and use them effectively? In the one section
where we worked with objects rather than classes, Section 3.8, it didn’t seem to make much
difference: We just left the word “static” out of the subroutine definitions!
I have said that classes “describe” objects, or more exactly that the non-static portions of
classes describe objects. But it’s probably not very clear what this means. The more usual
terminology is to say that objects belong to classes, but this might not be much clearer. (There
is a real shortage of English words to properly distinguish all the concepts involved. An object
certainly doesn’t “belong” to a class in the same way that a member variable “belongs” to a
class.) From the point of view of programming, it is more exact to say that classes are used
to create objects. A class is a kind of factory for constructing objects. The non-static parts of
the class specify, or describe, what variables and subroutines the objects will contain. This is
part of the explanation of how objects differ from classes: Objects are created and destroyed as
the program runs, and there can be many objects with the same structure, if they are created
using the same class.
Consider a simple class whose job is to group together a few static member variables. For
example, the following class could be used to store information about the person who is using
the program:
class UserData {
static String name;
static int age;
}

In a program that uses this class, there is only one copy of each of the variables UserData.name
and UserData.age. There can only be one “user,” since we only have memory space to store
data about one user. The class, UserData, and the variables it contains exist as long as the
program runs. Now, consider a similar class that includes non-static variables:
class PlayerData {
String name;
int age;
}

In this case, there is no such variable as PlayerData.name or PlayerData.age, since name and
age are not static members of PlayerData. So, there is nothing much in the class at all—
except the potential to create objects. But, it’s a lot of potential, since it can be used to create
any number of objects! Each object will have its own variables called name and age. There
can be many “players” because we can make new objects to represent new players on demand.
A program might use this class to store information about multiple players in a game. Each
player has a name and an age. When a player joins the game, a new PlayerData object can
be created to represent that player. If a player leaves the game, the PlayerData object that
represents that player can be destroyed. A system of objects in the program is being used to
dynamically model what is happening in the game. You can’t do this with “static” variables!
In Section 3.8, we worked with applets, which are objects. The reason they didn’t seem to
be any different from classes is because we were only working with one applet in each class that
we looked at. But one class can be used to make many applets. Think of an applet that scrolls
5.1. OBJECTS AND INSTANCE METHODS 167

a message across a Web page. There could be several such applets on the same page, all created
from the same class. If the scrolling message in the applet is stored in a non-static variable,
then each applet will have its own variable, and each applet can show a different message. The
situation is even clearer if you think about windows, which, like applets, are objects. As a
program runs, many windows might be opened and closed, but all those windows can belong
to the same class. Here again, we have a dynamic situation where multiple objects are created
and destroyed as a program runs.
∗ ∗ ∗
An object that belongs to a class is said to be an instance of that class. The variables that
the object contains are called instance variables. The subroutines that the object contains
are called instance methods. (Recall that in the context of object-oriented programming,
method is a synonym for “subroutine”. From now on, since we are doing object-oriented
programming, I will prefer the term “method.”) For example, if the PlayerData class, as
defined above, is used to create an object, then that object is an instance of the PlayerData
class, and name and age are instance variables in the object. It is important to remember that
the class of an object determines the types of the instance variables; however, the actual data
is contained inside the individual objects, not the class. Thus, each object has its own set of
data.
An applet that scrolls a message across a Web page might include a subroutine named
scroll(). Since the applet is an object, this subroutine is an instance method of the applet.
The source code for the method is in the class that is used to create the applet. Still, it’s better
to think of the instance method as belonging to the object, not to the class. The non-static
subroutines in the class merely specify the instance methods that every object created from the
class will contain. The scroll() methods in two different applets do the same thing in the
sense that they both scroll messages across the screen. But there is a real difference between
the two scroll() methods. The messages that they scroll can be different. You might say that
the method definition in the class specifies what type of behavior the objects will have, but
the specific behavior can vary from object to object, depending on the values of their instance
variables.
As you can see, the static and the non-static portions of a class are very different things and
serve very different purposes. Many classes contain only static members, or only non-static.
However, it is possible to mix static and non-static members in a single class, and we’ll see
a few examples later in this chapter where it is reasonable to do so. You should distiguish
between the source code for the class, and the class itself. The source code determines both
the class and the objects that are created from that class. The “static” definitions in the source
code specify the things that are part of the class itself, whereas the non-static definitions in the
source code specify things that will become part of every instance object that is created from
the class. By the way, static member variables and static member subroutines in a class are
sometimes called class variables and class methods, since they belong to the class itself,
rather than to instances of that class.

5.1.2 Fundamentals of Objects


So far, I’ve been talking mostly in generalities, and I haven’t given you much idea what you
have to put in a program if you want to work with objects. Let’s look at a specific example to
see how it works. Consider this extremely simplified version of a Student class, which could be
used to store information about students taking a course:
168 CHAPTER 5. OBJECTS AND CLASSES

public class Student {


public String name; // Student’s name.
public double test1, test2, test3; // Grades on three tests.
public double getAverage() { // compute average test grade
return (test1 + test2 + test3) / 3;
}
} // end of class Student
None of the members of this class are declared to be static, so the class exists only for
creating objects. This class definition says that any object that is an instance of the Student
class will include instance variables named name, test1, test2, and test3, and it will include an
instance method named getAverage(). The names and tests in different objects will generally
have different values. When called for a particular student, the method getAverage() will
compute an average using that student’s test grades. Different students can have different
averages. (Again, this is what it means to say that an instance method belongs to an individual
object, not to the class.)
In Java, a class is a type, similar to the built-in types such as int and boolean. So, a class
name can be used to specify the type of a variable in a declaration statement, the type of a
formal parameter, or the return type of a function. For example, a program could define a
variable named std of type Student with the statement
Student std;
However, declaring a variable does not create an object! This is an important point, which is
related to this Very Important Fact:

In Java, no variable can ever hold an object.


A variable can only hold a reference to an object.

You should think of objects as floating around independently in the computer’s memory. In
fact, there is a special portion of memory called the heap where objects live. Instead of holding
an object itself, a variable holds the information necessary to find the object in memory. This
information is called a reference or pointer to the object. In effect, a reference to an object
is the address of the memory location where the object is stored. When you use a variable of
class type, the computer uses the reference in the variable to find the actual object.
In a program, objects are created using an operator called new, which creates an object
and returns a reference to that object. For example, assuming that std is a variable of type
Student, declared as above, the assignment statement
std = new Student();
would create a new object which is an instance of the class Student, and it would store a
reference to that object in the variable std. The value of the variable is a reference to the
object, not the object itself. It is not quite true, then, to say that the object is the “value of
the variable std” (though sometimes it is hard to avoid using this terminology). It is certainly
not at all true to say that the object is “stored in the variable std.” The proper terminology
is that “the variable std refers to the object,” and I will try to stick to that terminology as
much as possible.
So, suppose that the variable std refers to an object belonging to the class Student. That
object has instance variables name, test1, test2, and test3. These instance variables can
5.1. OBJECTS AND INSTANCE METHODS 169

be referred to as std.name, std.test1, std.test2, and std.test3. This follows the usual
naming convention that when B is part of A, then the full name of B is A.B. For example, a
program might include the lines
System.out.println("Hello, " + std.name + ". Your test grades are:");
System.out.println(std.test1);
System.out.println(std.test2);
System.out.println(std.test3);

This would output the name and test grades from the object to which std refers. Simi-
larly, std can be used to call the getAverage() instance method in the object by saying
std.getAverage(). To print out the student’s average, you could say:
System.out.println( "Your average is " + std.getAverage() );

More generally, you could use std.name any place where a variable of type String is legal.
You can use it in expressions. You can assign a value to it. You can even use it to call subroutines
from the String class. For example, std.name.length() is the number of characters in the
student’s name.
It is possible for a variable like std, whose type is given by a class, to refer to no object at
all. We say in this case that std holds a null reference. The null reference is written in Java
as “null”. You can store a null reference in the variable std by saying
std = null;

and you could test whether the value of std is null by testing
if (std == null) . . .

If the value of a variable is null, then it is, of course, illegal to refer to instance variables
or instance methods through that variable—since there is no object, and hence no instance
variables to refer to. For example, if the value of the variable std is null, then it would be
illegal to refer to std.test1. If your program attempts to use a null reference illegally like this,
the result is an error called a null pointer exception.
Let’s look at a sequence of statements that work with objects:
Student std, std1, // Declare four variables of
std2, std3; // type Student.
std = new Student(); // Create a new object belonging
// to the class Student, and
// store a reference to that
// object in the variable std.
std1 = new Student(); // Create a second Student object
// and store a reference to
// it in the variable std1.
std2 = std1; // Copy the reference value in std1
// into the variable std2.
std3 = null; // Store a null reference in the
// variable std3.
std.name = "John Smith"; // Set values of some instance variables.
std1.name = "Mary Jones";
// (Other instance variables have default
// initial values of zero.)
170 CHAPTER 5. OBJECTS AND CLASSES

After the computer executes these statements, the situation in the computer’s memory looks
like this:

This picture shows variables as little boxes, labeled with the names of the variables. Objects
are shown as boxes with round corners. When a variable contains a reference to an object, the
value of that variable is shown as an arrow pointing to the object. The variable std3, with a
value of null, doesn’t point anywhere. The arrows from std1 and std2 both point to the same
object. This illustrates a Very Important Point:

When one object variable is assigned


to another, only a reference is copied.
The object referred to is not copied.

When the assignment “std2 = std1;” was executed, no new object was created. Instead, std2
was set to refer to the very same object that std1 refers to. This has some consequences that
might be surprising. For example, std1.name and std2.name are two different names for the
same variable, namely the instance variable in the object that both std1 and std2 refer to.
After the string "Mary Jones" is assigned to the variable std1.name, it is also true that the
value of std2.name is "Mary Jones". There is a potential for a lot of confusion here, but you
can help protect yourself from it if you keep telling yourself, “The object is not in the variable.
The variable just holds a pointer to the object.”
You can test objects for equality and inequality using the operators == and !=, but
here again, the semantics are different from what you are used to. When you make a test
“if (std1 == std2)”, you are testing whether the values stored in std1 and std2 are the
same. But the values are references to objects, not objects. So, you are testing whether
std1 and std2 refer to the same object, that is, whether they point to the same location
5.1. OBJECTS AND INSTANCE METHODS 171

in memory. This is fine, if its what you want to do. But sometimes, what you want to
check is whether the instance variables in the objects have the same values. To do that, you
would need to ask whether “std1.test1 == std2.test1 && std1.test2 == std2.test2 &&
std1.test3 == std2.test3 && std1.name.equals(std2.name)”.
I’ve remarked previously that Strings are objects, and I’ve shown the strings "Mary Jones"
and "John Smith" as objects in the above illustration. A variable of type String can only hold
a reference to a string, not the string itself. It could also hold the value null, meaning that
it does not refer to any string at all. This explains why using the == operator to test strings
for equality is not a good idea. Suppose that greeting is a variable of type String, and that
the string it refers to is "Hello". Then would the test greeting == "Hello" be true? Well,
maybe, maybe not. The variable greeting and the String literal "Hello" each refer to a
string that contains the characters H-e-l-l-o. But the strings could still be different objects,
that just happen to contain the same characters. The function greeting.equals("Hello")
tests whether greeting and "Hello" contain the same characters, which is almost certainly
the question you want to ask. The expression greeting == "Hello" tests whether greeting
and "Hello" contain the same characters stored in the same memory location.
∗ ∗ ∗
The fact that variables hold references to objects, not objects themselves, has a couple of
other consequences that you should be aware of. They follow logically, if you just keep in mind
the basic fact that the object is not stored in the variable. The object is somewhere else; the
variable points to it.
Suppose that a variable that refers to an object is declared to be final. This means that
the value stored in the variable can never be changed, once the variable has been initialized.
The value stored in the variable is a reference to the object. So the variable will continue to
refer to the same object as long as the variable exists. However, this does not prevent the data
in the object from changing. The variable is final, not the object. It’s perfectly legal to say
final Student stu = new Student();
stu.name = "John Doe"; // Change data in the object;
// The value stored in stu is not changed!
// It still refers to the same object.
Next, suppose that obj is a variable that refers to an object. Let’s consider what happens
when obj is passed as an actual parameter to a subroutine. The value of obj is assigned to
a formal parameter in the subroutine, and the subroutine is executed. The subroutine has no
power to change the value stored in the variable, obj. It only has a copy of that value. However,
that value is a reference to an object. Since the subroutine has a reference to the object, it can
change the data stored in the object. After the subroutine ends, obj still points to the same
object, but the data stored in the object might have changed. Suppose x is a variable of type
int and stu is a variable of type Student. Compare:
void dontChange(int z) { void change(Student s) {
z = 42; s.name = "Fred";
} }
The lines: The lines:
x = 17; stu.name = "Jane";
dontChange(x); change(stu);
System.out.println(x); System.out.println(stu.name);
172 CHAPTER 5. OBJECTS AND CLASSES

output the value 17. output the value "Fred".


The value of x is not The value of stu is not
changed by the subroutine, changed, but stu.name is.
which is equivalent to This is equivalent to
z = x; s = stu;
z = 42; s.name = "Fred";

5.1.3 Getters and Setters


When writing new classes, it’s a good idea to pay attention to the issue of access control. Recall
that making a member of a class public makes it accessible from anywhere, including from
other classes. On the other hand, a private member can only be used in the class where it is
defined.
In the opinion of many programmers, almost all member variables should be declared
private. This gives you complete control over what can be done with the variable. Even
if the variable itself is private, you can allow other classes to find out what its value is by pro-
viding a public accessor method that returns the value of the variable. For example, if your
class contains a private member variable, title, of type String, you can provide a method
public String getTitle() {
return title;
}
that returns the value of title. By convention, the name of an accessor method for a variable
is obtained by capitalizing the name of variable and adding “get” in front of the name. So, for
the variable title, we get an accessor method named “get” + “Title”, or getTitle(). Because
of this naming convention, accessor methods are more often referred to as getter methods. A
getter method provides “read access” to a variable.
You might also want to allow “write access” to a private variable. That is, you might
want to make it possible for other classes to specify a new value for the variable. This is done
with a setter method . (If you don’t like simple, Anglo-Saxon words, you can use the fancier
term mutator method .) The name of a setter method should consist of “set” followed by a
capitalized copy of the variable’s name, and it should have a parameter with the same type as
the variable. A setter method for the variable title could be written
public void setTitle( String newTitle ) {
title = newTitle;
}
It is actually very common to provide both a getter and a setter method for a private
member variable. Since this allows other classes both to see and to change the value of the
variable, you might wonder why not just make the variable public? The reason is that getters
and setters are not restricted to simply reading and writing the variable’s value. In fact, they
can take any action at all. For example, a getter method might keep track of the number of
times that the variable has been accessed:
public String getTitle() {
titleAccessCount++; // Increment member variable titleAccessCount.
return title;
}
and a setter method might check that the value that is being assigned to the variable is legal:
5.2. CONSTRUCTORS AND OBJECT INITIALIZATION 173

public void setTitle( String newTitle ) {


if ( newTitle == null ) // Don’t allow null strings as titles!
title = "(Untitled)"; // Use an appropriate default value instead.
else
title = newTitle;
}
Even if you can’t think of any extra chores to do in a getter or setter method, you might change
your mind in the future when you redesign and improve your class. If you’ve used a getter and
setter from the beginning, you can make the modification to your class without affecting any of
the classes that use your class. The private member variable is not part of the public interface
of your class; only the public getter and setter methods are. If you haven’t used get and set
from the beginning, you’ll have to contact everyone who uses your class and tell them, “Sorry
guys, you’ll have to track down every use that you’ve made of this variable and change your
code to use my new get and set methods instead.”
A couple of final notes: Some advanced aspects of Java rely on the naming convention
for getter and setter methods, so it’s a good idea to follow the convention rigorously. And
though I’ve been talking about using getter and setter methods for a variable, you can define
get and set methods even if there is no variable. A getter and/or setter method defines a
property of the class, that might or might not correspond to a variable. For example, if a class
includes a public void instance method with signature setValue(double), then the class has
a “property” named value of type double, and it has this property whether or not the class
has a member variable named value.

5.2 Constructors and Object Initialization


Object types in Java are very different from the primitive types. Simply declaring a variable
whose type is given as a class does not automatically create an object of that class. Objects
must be explicitly constructed . For the computer, the process of constructing an object means,
first, finding some unused memory in the heap that can be used to hold the object and, second,
filling in the object’s instance variables. As a programmer, you don’t care where in memory
the object is stored, but you will usually want to exercise some control over what initial values
are stored in a new object’s instance variables. In many cases, you will also want to do more
complicated initialization or bookkeeping every time an object is created.

5.2.1 Initializing Instance Variables


An instance variable can be assigned an initial value in its declaration, just like any other
variable. For example, consider a class named PairOfDice. An object of this class will represent
a pair of dice. It will contain two instance variables to represent the numbers showing on the
dice and an instance method for rolling the dice:
public class PairOfDice {
public int die1 = 3; // Number showing on the first die.
public int die2 = 4; // Number showing on the second die.
public void roll() {
// Roll the dice by setting each of the dice to be
// a random number between 1 and 6.
die1 = (int)(Math.random()*6) + 1;
174 CHAPTER 5. OBJECTS AND CLASSES

die2 = (int)(Math.random()*6) + 1;
}
} // end class PairOfDice
The instance variables die1 and die2 are initialized to the values 3 and 4 respectively. These
initializations are executed whenever a PairOfDice object is constructed. It’s important to
understand when and how this happens. There can be many PairOfDice objects. Each time one
is created, it gets its own instance variables, and the assignments “die1 = 3” and “die2 = 4”
are executed to fill in the values of those variables. To make this clearer, consider a variation
of the PairOfDice class:
public class PairOfDice {
public int die1 = (int)(Math.random()*6) + 1;
public int die2 = (int)(Math.random()*6) + 1;
public void roll() {
die1 = (int)(Math.random()*6) + 1;
die2 = (int)(Math.random()*6) + 1;
}
} // end class PairOfDice
Here, the dice are initialized to random values, as if a new pair of dice were being thrown onto
the gaming table. Since the initialization is executed for each new object, a set of random initial
values will be computed for each new pair of dice. Different pairs of dice can have different
initial values. For initialization of static member variables, of course, the situation is quite
different. There is only one copy of a static variable, and initialization of that variable is
executed just once, when the class is first loaded.
If you don’t provide any initial value for an instance variable, a default initial value is pro-
vided automatically. Instance variables of numerical type (int, double, etc.) are automatically
initialized to zero if you provide no other values; boolean variables are initialized to false; and
char variables, to the Unicode character with code number zero. An instance variable can also
be a variable of object type. For such variables, the default initial value is null. (In particular,
since Strings are objects, the default initial value for String variables is null.)

5.2.2 Constructors
Objects are created with the operator, new. For example, a program that wants to use a
PairOfDice object could say:
PairOfDice dice; // Declare a variable of type PairOfDice.
dice = new PairOfDice(); // Construct a new object and store a
// reference to it in the variable.
In this example, “new PairOfDice()” is an expression that allocates memory for the object,
initializes the object’s instance variables, and then returns a reference to the object. This
reference is the value of the expression, and that value is stored by the assignment statement in
the variable, dice, so that after the assignment statement is executed, dice refers to the newly
created object. Part of this expression, “PairOfDice()”, looks like a subroutine call, and that
is no accident. It is, in fact, a call to a special type of subroutine called a constructor . This
might puzzle you, since there is no such subroutine in the class definition. However, every class
has at least one constructor. If the programmer doesn’t write a constructor definition in a class,
5.2. CONSTRUCTORS AND OBJECT INITIALIZATION 175

then the system will provide a default constructor for that class. This default constructor
does nothing beyond the basics: allocate memory and initialize instance variables. If you want
more than that to happen when an object is created, you can include one or more constructors
in the class definition.
The definition of a constructor looks much like the definition of any other subroutine, with
three exceptions. A constructor does not have any return type (not even void). The name of
the constructor must be the same as the name of the class in which it is defined. The only
modifiers that can be used on a constructor definition are the access modifiers public, private,
and protected. (In particular, a constructor can’t be declared static.)
However, a constructor does have a subroutine body of the usual form, a block of statements.
There are no restrictions on what statements can be used. And it can have a list of formal
parameters. In fact, the ability to include parameters is one of the main reasons for using
constructors. The parameters can provide data to be used in the construction of the object.
For example, a constructor for the PairOfDice class could provide the values that are initially
showing on the dice. Here is what the class would look like in that case:
public class PairOfDice {
public int die1; // Number showing on the first die.
public int die2; // Number showing on the second die.
public PairOfDice(int val1, int val2) {
// Constructor. Creates a pair of dice that
// are initially showing the values val1 and val2.
die1 = val1; // Assign specified values
die2 = val2; // to the instance variables.
}
public void roll() {
// Roll the dice by setting each of the dice to be
// a random number between 1 and 6.
die1 = (int)(Math.random()*6) + 1;
die2 = (int)(Math.random()*6) + 1;
}
} // end class PairOfDice

The constructor is declared as “public PairOfDice(int val1, int val2) ...”, with no
return type and with the same name as the name of the class. This is how the Java com-
piler recognizes a constructor. The constructor has two parameters, and values for these
parameters must be provided when the constructor is called. For example, the expression
“new PairOfDice(3,4)” would create a PairOfDice object in which the values of the instance
variables die1 and die2 are initially 3 and 4. Of course, in a program, the value returned by
the constructor should be used in some way, as in
PairOfDice dice; // Declare a variable of type PairOfDice.
dice = new PairOfDice(1,1); // Let dice refer to a new PairOfDice
// object that initially shows 1, 1.

Now that we’ve added a constructor to the PairOfDice class, we can no longer create
an object by saying “new PairOfDice()”! The system provides a default constructor for a
class only if the class definition does not already include a constructor, so there is only one
constructor in the class, and it requires two actual parameters. However, this is not a big
176 CHAPTER 5. OBJECTS AND CLASSES

problem, since we can add a second constructor to the class, one that has no parameters. In
fact, you can have as many different constructors as you want, as long as their signatures are
different, that is, as long as they have different numbers or types of formal parameters. In the
PairOfDice class, we might have a constructor with no parameters which produces a pair of
dice showing random numbers:
public class PairOfDice {
public int die1; // Number showing on the first die.
public int die2; // Number showing on the second die.
public PairOfDice() {
// Constructor. Rolls the dice, so that they initially
// show some random values.
roll(); // Call the roll() method to roll the dice.
}
public PairOfDice(int val1, int val2) {
// Constructor. Creates a pair of dice that
// are initially showing the values val1 and val2.
die1 = val1; // Assign specified values
die2 = val2; // to the instance variables.
}
public void roll() {
// Roll the dice by setting each of the dice to be
// a random number between 1 and 6.
die1 = (int)(Math.random()*6) + 1;
die2 = (int)(Math.random()*6) + 1;
}
} // end class PairOfDice
Now we have the option of constructing a PairOfDice object either with “new PairOfDice()”
or with “new PairOfDice(x,y)”, where x and y are int-valued expressions.
This class, once it is written, can be used in any program that needs to work with one
or more pairs of dice. None of those programs will ever have to use the obscure incantation
“(int)(Math.random()*6)+1”, because it’s done inside the PairOfDice class. And the pro-
grammer, having once gotten the dice-rolling thing straight will never have to worry about it
again. Here, for example, is a main program that uses the PairOfDice class to count how many
times two pairs of dice are rolled before the two pairs come up showing the same value. This
illustrates once again that you can create several instances of the same class:
public class RollTwoPairs {
public static void main(String[] args) {
PairOfDice firstDice; // Refers to the first pair of dice.
firstDice = new PairOfDice();
PairOfDice secondDice; // Refers to the second pair of dice.
secondDice = new PairOfDice();
int countRolls; // Counts how many times the two pairs of
// dice have been rolled.
int total1; // Total showing on first pair of dice.
int total2; // Total showing on second pair of dice.
5.2. CONSTRUCTORS AND OBJECT INITIALIZATION 177

countRolls = 0;
do { // Roll the two pairs of dice until totals are the same.
firstDice.roll(); // Roll the first pair of dice.
total1 = firstDice.die1 + firstDice.die2; // Get total.
System.out.println("First pair comes up " + total1);
secondDice.roll(); // Roll the second pair of dice.
total2 = secondDice.die1 + secondDice.die2; // Get total.
System.out.println("Second pair comes up " + total2);
countRolls++; // Count this roll.
System.out.println(); // Blank line.
} while (total1 != total2);
System.out.println("It took " + countRolls
+ " rolls until the totals were the same.");
} // end main()
} // end class RollTwoPairs

∗ ∗ ∗
Constructors are subroutines, but they are subroutines of a special type. They are certainly
not instance methods, since they don’t belong to objects. Since they are responsible for creating
objects, they exist before any objects have been created. They are more like static member
subroutines, but they are not and cannot be declared to be static. In fact, according to the
Java language specification, they are technically not members of the class at all! In particular,
constructors are not referred to as “methods.”
Unlike other subroutines, a constructor can only be called using the new operator, in an
expression that has the form
new hclass-name i ( hparameter-list i )

where the hparameter-listi is possibly empty. I call this an expression because it computes and
returns a value, namely a reference to the object that is constructed. Most often, you will store
the returned reference in a variable, but it is also legal to use a constructor call in other ways,
for example as a parameter in a subroutine call or as part of a more complex expression. Of
course, if you don’t save the reference in a variable, you won’t have any way of referring to the
object that was just created.
A constructor call is more complicated than an ordinary subroutine or function call. It is
helpful to understand the exact steps that the computer goes through to execute a constructor
call:
1. First, the computer gets a block of unused memory in the heap, large enough to hold an
object of the specified type.
2. It initializes the instance variables of the object. If the declaration of an instance variable
specifies an initial value, then that value is computed and stored in the instance variable.
Otherwise, the default initial value is used.
3. The actual parameters in the constructor, if any, are evaluated, and the values are assigned
to the formal parameters of the constructor.
178 CHAPTER 5. OBJECTS AND CLASSES

4. The statements in the body of the constructor, if any, are executed.


5. A reference to the object is returned as the value of the constructor call.

The end result of this is that you have a reference to a newly constructed object. You can
use this reference to get at the instance variables in that object or to call its instance methods.
∗ ∗ ∗
For another example, let’s rewrite the Student class that was used in Section 1. I’ll add a
constructor, and I’ll also take the opportunity to make the instance variable, name, private.
public class Student {

private String name; // Student’s name.


public double test1, test2, test3; // Grades on three tests.

Student(String theName) {
// Constructor for Student objects;
// provides a name for the Student.
name = theName;
}

public String getName() {


// Getter method for reading the value of the private
// instance variable, name.
return name;
}

public double getAverage() {


// Compute average test grade.
return (test1 + test2 + test3) / 3;
}

} // end of class Student

An object of type Student contains information about some particular student. The con-
structor in this class has a parameter of type String, which specifies the name of that student.
Objects of type Student can be created with statements such as:
std = new Student("John Smith");
std1 = new Student("Mary Jones");

In the original version of this class, the value of name had to be assigned by a program after
it created the object of type Student. There was no guarantee that the programmer would
always remember to set the name properly. In the new version of the class, there is no way to
create a Student object except by calling the constructor, and that constructor automatically
sets the name. The programmer’s life is made easier, and whole hordes of frustrating bugs are
squashed before they even have a chance to be born.
Another type of guarantee is provided by the private modifier. Since the instance variable,
name, is private, there is no way for any part of the program outside the Student class to
get at the name directly. The program sets the value of name, indirectly, when it calls the
constructor. I’ve provided a function, getName(), that can be used from outside the class to
find out the name of the student. But I haven’t provided any setter method or other way to
change the name. Once a student object is created, it keeps the same name as long as it exists.
5.3. PROGRAMMING WITH OBJECTS 179

5.2.3 Garbage Collection


So far, this section has been about creating objects. What about destroying them? In Java,
the destruction of objects takes place automatically.
An object exists in the heap, and it can be accessed only through variables that hold
references to the object. What should be done with an object if there are no variables that
refer to it? Such things can happen. Consider the following two statements (though in reality,
you’d never do anything like this):
Student std = new Student("John Smith");
std = null;

In the first line, a reference to a newly created Student object is stored in the variable std.
But in the next line, the value of std is changed, and the reference to the Student object is
gone. In fact, there are now no references whatsoever to that object stored in any variable. So
there is no way for the program ever to use the object again. It might as well not exist. In fact,
the memory occupied by the object should be reclaimed to be used for another purpose.
Java uses a procedure called garbage collection to reclaim memory occupied by objects
that are no longer accessible to a program. It is the responsibility of the system, not the
programmer, to keep track of which objects are “garbage.” In the above example, it was very
easy to see that the Student object had become garbage. Usually, it’s much harder. If an
object has been used for a while, there might be several references to the object stored in several
variables. The object doesn’t become garbage until all those references have been dropped.
In many other programming languages, it’s the programmer’s responsibility to delete the
garbage. Unfortunately, keeping track of memory usage is very error-prone, and many serious
program bugs are caused by such errors. A programmer might accidently delete an object even
though there are still references to that object. This is called a dangling pointer error , and
it leads to problems when the program tries to access an object that is no longer there. Another
type of error is a memory leak , where a programmer neglects to delete objects that are no
longer in use. This can lead to filling memory with objects that are completely inaccessible,
and the program might run out of memory even though, in fact, large amounts of memory are
being wasted.
Because Java uses garbage collection, such errors are simply impossible. Garbage collection
is an old idea and has been used in some programming languages since the 1960s. You might
wonder why all languages don’t use garbage collection. In the past, it was considered too slow
and wasteful. However, research into garbage collection techniques combined with the incredible
speed of modern computers have combined to make garbage collection feasible. Programmers
should rejoice.

5.3 Programming with Objects


There are several ways in which object-oriented concepts can be applied to the process
of designing and writing programs. The broadest of these is object-oriented analysis and
design which applies an object-oriented methodology to the earliest stages of program devel-
opment, during which the overall design of a program is created. Here, the idea is to identify
things in the problem domain that can be modeled as objects. On another level, object-oriented
programming encourages programmers to produce generalized software components that
can be used in a wide variety of programming projects.
180 CHAPTER 5. OBJECTS AND CLASSES

Of course, for the most part, you will experience “generalized software components” by
using the standard classes that come along with Java. We begin this section by looking at some
built-in classes that are used for creating objects. At the end of the section, we will get back
to generalities.

5.3.1 Some Built-in Classes


Although the focus of object-oriented programming is generally on the design and implementa-
tion of new classes, it’s important not to forget that the designers of Java have already provided
a large number of reusable classes. Some of these classes are meant to be extended to produce
new classes, while others can be used directly to create useful objects. A true mastery of Java
requires familiarity with a large number of built-in classes—something that takes a lot of time
and experience to develop. In the next chapter, we will begin the study of Java’s GUI classes,
and you will encounter other built-in classes throughout the remainder of this book. But let’s
take a moment to look at a few built-in classes that you might find useful.
A string can be built up from smaller pieces using the + operator, but this is not very efficient.
If str is a String and ch is a character, then executing the command “str = str + ch;”
involves creating a whole new string that is a copy of str, with the value of ch appended
onto the end. Copying the string takes some time. Building up a long string letter by letter
would require a surprising amount of processing. The class StringBuffer makes it possible to be
efficient about building up a long string from a number of smaller pieces. To do this, you must
make an object belonging to the StringBuffer class. For example:
StringBuffer buffer = new StringBuffer();
(This statement both declares the variable buffer and initializes it to refer to a newly created
StringBuffer object. Combining declaration with initialization was covered in Subsection 4.7.1
and works for objects just as it does for primitive types.)
Like a String, a StringBuffer contains a sequence of characters. However, it is possible to
add new characters onto the end of a StringBuffer without making a copy of the data that
it already contains. If x is a value of any type and buffer is the variable defined above, then
the command buffer.append(x) will add x, converted into a string representation, onto the
end of the data that was already in the buffer. This command actually modifies the buffer,
rather than making a copy, and that can be done efficiently. A long string can be built up
in a StringBuffer using a sequence of append() commands. When the string is complete, the
function buffer.toString() will return a copy of the string in the buffer as an ordinary value
of type String. The StringBuffer class is in the standard package java.lang, so you can use its
simple name without importing it.
A number of useful classes are collected in the package java.util. For example, this package
contains classes for working with collections of objects. We will study these collection classes in
Chapter 10. Another class in this package, java.util.Date, is used to represent times. When
a Date object is constructed without parameters, the result represents the current date and
time, so an easy way to display this information is:
System.out.println( new Date() );
Of course, to use the Date class in this way, you must make it available by importing it with one
of the statements “import java.util.Date;” or “import java.util.*;” at the beginning of
your program. (See Subsection 4.5.3 for a discussion of packages and import.)
I will also mention the class java.util.Random. An object belonging to this class is a
source of random numbers (or, more precisely pseudorandom numbers). The standard function
5.3. PROGRAMMING WITH OBJECTS 181

Math.random() uses one of these objects behind the scenes to generate its random numbers.
An object of type Random can generate random integers, as well as random real numbers. If
randGen is created with the command:
Random randGen = new Random();

and if N is a positive integer, then randGen.nextInt(N) generates a random integer in the range
from 0 to N-1. For example, this makes it a little easier to roll a pair of dice. Instead of say-
ing “die1 = (int)(6*Math.random())+1;”, one can say “die1 = randGen.nextInt(6)+1;”.
(Since you also have to import the class java.util.Random and create the Random object, you
might not agree that it is actually easier.) An object of type Random can also be used to generate
so-called Gaussian distributed random real numbers.
The main point here, again, is that many problems have already been solved, and the
solutions are available in Java’s standard classes. If you are faced with a task that looks like
it should be fairly common, it might be worth looking through a Java reference to see whether
someone has already written a class that you can use.

5.3.2 Wrapper Classes and Autoboxing


We have already encountered the classes Double and Integer in Subsection 2.5.7. These classes
contain the static methods Double.parseDouble and Integer.parseInteger that are used
to convert strings to numerical values. We have also encountered the Character class in some
examples, and static methods such as Character.isLetter, which can be used to test whether
a given value of type char is a letter. There is a similar class for each of the other primitive types,
Long, Short, Byte, Float, and Boolean. These classes are called wrapper classes. Although
they contain useful static members, they have another use as well: They are used for creating
objects that represent primitive type values.
Remember that the primitive types are not classes, and values of primitive type are not
objects. However, sometimes it’s useful to treat a primitive value as if it were an object. You
can’t do that literally, but you can “wrap” the primitive type value in an object belonging to
one of the wrapper classes.
For example, an object of type Double contains a single instance variable, of type double.
The object is a wrapper for the double value. For example, you can create an object that wraps
the double value 6.0221415e23 with
Double d = new Double(6.0221415e23);

The value of d contains the same information as the value of type double, but it is an object. If
you want to retrieve the double value that is wrapped in the object, you can call the function
d.doubleValue(). Similarly, you can wrap an int in an object of type Integer, a boolean value
in an object of type Boolean, and so on. (As an example of where this would be useful, the
collection classes that will be studied in Chapter 10 can only hold objects. If you want to add
a primitive type value to a collection, it has to be put into a wrapper object first.)
In Java 5.0, wrapper classes have become easier to use. Java 5.0 introduced automatic
conversion between a primitive type and the corresponding wrapper class. For example, if
you use a value of type int in a context that requires an object of type Integer, the int will
automatically be wrapped in an Integer object. For example, you can say
Integer answer = 42;

and the computer will silently read this as if it were


182 CHAPTER 5. OBJECTS AND CLASSES

Integer answer = new Integer(42);

This is called autoboxing . It works in the other direction, too. For example, if d refers to an
object of type Double, you can use d in a numerical expression such as 2*d. The double value
inside d is automatically unboxed and multiplied by 2. Autoboxing and unboxing also apply
to subroutine calls. For example, you can pass an actual parameter of type int to a subroutine
that has a formal parameter of type Integer. In fact, autoboxing and unboxing make it possible
in many circumstances to ignore the difference between primitive types and objects.
∗ ∗ ∗
The wrapper classes contain a few other things that deserve to be mentioned. Integer, for
example, contains constants Integer.MIN VALUE and Integer.MAX VALUE, which are equal to
the largest and smallest possible values of type int, that is, to -2147483648 and 2147483647
respectively. It’s certainly easier to remember the names than the numerical values. There are
similar named constants in Long, Short, and Byte. Double and Float also have constants named
MIN VALUE and MAX VALUE. MAX VALUE still gives the largest number that can be represented
in the given type, but MIN VALUE represents the smallest possible positive value. For type
double, Double.MIN VALUE is 4.9 times 10−324 . Since double values have only a finite accuracy,
they can’t get arbitrarily close to zero. This is the closest they can get without actually being
equal to zero.
The class Double deserves special mention, since doubles are so much more complicated than
integers. The encoding of real numbers into values of type double has room for a few special val-
ues that are not real numbers at all in the mathematical sense. These values are given by named
constants in class Double: Double.POSITIVE INFINITY, Double.NEGATIVE INFINITY, and
Double.NaN. The infinite values can occur as the values of certain mathematical expressions. For
example, dividing a positive number by zero will give the result Double.POSITIVE INFINITY.
(It’s even more complicated than this, actually, because the double type includes a value
called “negative zero”, written -0.0. Dividing a positive number by negative zero gives
Double.NEGATIVE INFINITY.) You also get Double.POSITIVE INFINITY whenever the mathe-
matical value of an expression is greater than Double.MAX VALUE. For example, 1e200*1e200
is considered to be infinite. The value Double.NaN is even more interesting. “NaN” stands for
Not a Number , and it represents an undefined value such as the square root of a negative
number or the result of dividing zero by zero. Because of the existence of Double.NaN, no math-
ematical operation on real numbers will ever throw an exception; it simply gives Double.NaN
as the result.
You can test whether a value, x, of type double is infinite or undefined by calling the
boolean-valued static functions Double.isInfinite(x) and Double.isNaN(x). (It’s especially
important to use Double.isNaN() to test for undefined values, because Double.NaN has re-
ally weird behavior when used with relational operators such as ==. In fact, the values of
x == Double.NaN and x != Double.NaN are both false, no matter what the value of x, so
you really can’t use these expressions to test whether x is Double.NaN.)

5.3.3 The class “Object”


We have already seen that one of the major features of object-oriented programming is the
ability to create subclasses of a class. The subclass inherits all the properties or behaviors of
the class, but can modify and add to what it inherits. In Section 5.5, you’ll learn how to create
subclasses. What you don’t know yet is that every class in Java (with just one exception) is
a subclass of some other class. If you create a class and don’t explicitly make it a subclass of
5.3. PROGRAMMING WITH OBJECTS 183

some other class, then it automatically becomes a subclass of the special class named Object.
(Object is the one class that is not a subclass of any other class.)
Class Object defines several instance methods that are inherited by every other class. These
methods can be used with any object whatsoever. I will mention just one of them here. You
will encounter more of them later in the book.
The instance method toString() in class Object returns a value of type String that is
supposed to be a string representation of the object. You’ve already used this method implicitly,
any time you’ve printed out an object or concatenated an object onto a string. When you use
an object in a context that requires a string, the object is automatically converted to type
String by calling its toString() method.
The version of toString that is defined in Object just returns the name of the class that
the object belongs to, concatenated with a code number called the hash code of the object;
this is not very useful. When you create a class, you can write a new toString() method for
it, which will replace the inherited version. For example, we might add the following method
to any of the PairOfDice classes from the previous section:
public String toString() {
// Return a String representation of a pair of dice, where die1
// and die2 are instance variables containing the numbers that are
// showing on the two dice.
if (die1 == die2)
return "double " + die1;
else
return die1 + " and " + die2;
}

If dice refers to a PairOfDice object, then dice.toString() will return strings such as
“3 and 6”, “5 and 1”, and “double 2”, depending on the numbers showing on the dice. This
method would be used automatically to convert dice to type String in a statement such as
System.out.println( "The dice came up " + dice );

so this statement might output, “The dice came up 5 and 1” or “The dice came up double 2”.
You’ll see another example of a toString() method in the next section.

5.3.4 Object-oriented Analysis and Design


Every programmer builds up a stock of techniques and expertise expressed as snippets of code
that can be reused in new programs using the tried-and-true method of cut-and-paste: The old
code is physically copied into the new program and then edited to customize it as necessary.
The problem is that the editing is error-prone and time-consuming, and the whole enterprise is
dependent on the programmer’s ability to pull out that particular piece of code from last year’s
project that looks like it might be made to fit. (On the level of a corporation that wants to
save money by not reinventing the wheel for each new project, just keeping track of all the old
wheels becomes a major task.)
Well-designed classes are software components that can be reused without editing. A well-
designed class is not carefully crafted to do a particular job in a particular program. Instead,
it is crafted to model some particular type of object or a single coherent concept. Since objects
and concepts can recur in many problems, a well-designed class is likely to be reusable without
modification in a variety of projects.
184 CHAPTER 5. OBJECTS AND CLASSES

Furthermore, in an object-oriented programming language, it is possible to make subclasses


of an existing class. This makes classes even more reusable. If a class needs to be customized,
a subclass can be created, and additions or modifications can be made in the subclass without
making any changes to the original class. This can be done even if the programmer doesn’t
have access to the source code of the class and doesn’t know any details of its internal, hidden
implementation.
∗ ∗ ∗
The PairOfDice class in the previous section is already an example of a generalized software
component, although one that could certainly be improved. The class represents a single,
coherent concept, “a pair of dice.” The instance variables hold the data relevant to the state
of the dice, that is, the number showing on each of the dice. The instance method represents
the behavior of a pair of dice, that is, the ability to be rolled. This class would be reusable in
many different programming projects.
On the other hand, the Student class from the previous section is not very reusable. It
seems to be crafted to represent students in a particular course where the grade will be based
on three tests. If there are more tests or quizzes or papers, it’s useless. If there are two people
in the class who have the same name, we are in trouble (one reason why numerical student ID’s
are often used). Admittedly, it’s much more difficult to develop a general-purpose student class
than a general-purpose pair-of-dice class. But this particular Student class is good mostly as
an example in a programming textbook.
∗ ∗ ∗
A large programming project goes through a number of stages, starting with specification
of the problem to be solved, followed by analysis of the problem and design of a program
to solve it. Then comes coding , in which the program’s design is expressed in some actual
programming language. This is followed by testing and debugging of the program. After that
comes a long period of maintenance, which means fixing any new problems that are found
in the program and modifying it to adapt it to changing requirements. Together, these stages
form what is called the software life cycle. (In the real world, the ideal of consecutive stages
is seldom if ever achieved. During the analysis stage, it might turn out that the specifications
are incomplete or inconsistent. A problem found during testing requires at least a brief return
to the coding stage. If the problem is serious enough, it might even require a new design.
Maintenance usually involves redoing some of the work from previous stages. . . .)
Large, complex programming projects are only likely to succeed if a careful, systematic
approach is adopted during all stages of the software life cycle. The systematic approach to
programming, using accepted principles of good design, is called software engineering . The
software engineer tries to efficiently construct programs that verifiably meet their specifications
and that are easy to modify if necessary. There is a wide range of “methodologies” that can
be applied to help in the systematic design of programs. (Most of these methodologies seem to
involve drawing little boxes to represent program components, with labeled arrows to represent
relationships among the boxes.)
We have been discussing object orientation in programming languages, which is relevant to
the coding stage of program development. But there are also object-oriented methodologies for
analysis and design. The question in this stage of the software life cycle is, How can one discover
or invent the overall structure of a program? As an example of a rather simple object-oriented
approach to analysis and design, consider this advice: Write down a description of the problem.
Underline all the nouns in that description. The nouns should be considered as candidates for
becoming classes or objects in the program design. Similarly, underline all the verbs. These
5.4. PROGRAMMING EXAMPLE: CARD, HAND, DECK 185

are candidates for methods. This is your starting point. Further analysis might uncover the
need for more classes and methods, and it might reveal that subclassing can be used to take
advantage of similarities among classes.
This is perhaps a bit simple-minded, but the idea is clear and the general approach can be
effective: Analyze the problem to discover the concepts that are involved, and create classes to
represent those concepts. The design should arise from the problem itself, and you should end
up with a program whose structure reflects the structure of the problem in a natural way.

5.4 Programming Example: Card, Hand, Deck


In this section, we look at some specific examples of object-oriented design in a domain that
is simple enough that we have a chance of coming up with something reasonably reusable.
Consider card games that are played with a standard deck of playing cards (a so-called “poker”
deck, since it is used in the game of poker).

5.4.1 Designing the classes


In a typical card game, each player gets a hand of cards. The deck is shuffled and cards are
dealt one at a time from the deck and added to the players’ hands. In some games, cards can
be removed from a hand, and new cards can be added. The game is won or lost depending
on the value (ace, 2, . . . , king) and suit (spades, diamonds, clubs, hearts) of the cards that a
player receives. If we look for nouns in this description, there are several candidates for objects:
game, player, hand, card, deck, value, and suit. Of these, the value and the suit of a card are
simple values, and they will just be represented as instance variables in a Card object. In a
complete program, the other five nouns might be represented by classes. But let’s work on the
ones that are most obviously reusable: card, hand, and deck.
If we look for verbs in the description of a card game, we see that we can shuffle a deck and
deal a card from a deck. This gives use us two candidates for instance methods in a Deck class:
shuffle() and dealCard(). Cards can be added to and removed from hands. This gives two
candidates for instance methods in a Hand class: addCard() and removeCard(). Cards are
relatively passive things, but we need to be able to determine their suits and values. We will
discover more instance methods as we go along.
First, we’ll design the deck class in detail. When a deck of cards is first created, it contains
52 cards in some standard order. The Deck class will need a constructor to create a new deck.
The constructor needs no parameters because any new deck is the same as any other. There will
be an instance method called shuffle() that will rearrange the 52 cards into a random order.
The dealCard() instance method will get the next card from the deck. This will be a function
with a return type of Card, since the caller needs to know what card is being dealt. It has no
parameters—when you deal the next card from the deck, you don’t provide any information to
the deck; you just get the next card, whatever it is. What will happen if there are no more
cards in the deck when its dealCard() method is called? It should probably be considered an
error to try to deal a card from an empty deck, so the deck can throw an exception in that case.
But this raises another question: How will the rest of the program know whether the deck is
empty? Of course, the program could keep track of how many cards it has used. But the deck
itself should know how many cards it has left, so the program should just be able to ask the
deck object. We can make this possible by specifying another instance method, cardsLeft(),
that returns the number of cards remaining in the deck. This leads to a full specification of all
186 CHAPTER 5. OBJECTS AND CLASSES

the subroutines in the Deck class:


Constructor and instance methods in class Deck:
public Deck()
// Constructor. Create an unshuffled deck of cards.
public void shuffle()
// Put all the used cards back into the deck,
// and shuffle it into a random order.
public int cardsLeft()
// As cards are dealt from the deck, the number of
// cards left decreases. This function returns the
// number of cards that are still left in the deck.
public Card dealCard()
// Deals one card from the deck and returns it.
// Throws an exception if no more cards are left.
This is everything you need to know in order to use the Deck class. Of course, it doesn’t tell
us how to write the class. This has been an exercise in design, not in programming. In fact,
writing the class involves a programming technique, arrays, which will not be covered until
Chapter 7. Nevertheless, you can look at the source code, Deck.java, if you want. Even though
you won’t understand the implementation, the Javadoc comments give you all the information
that you need to understand the interface. With this information, you can use the class in your
programs without understanding the implementation.
We can do a similar analysis for the Hand class. When a hand object is first created, it
has no cards in it. An addCard() instance method will add a card to the hand. This method
needs a parameter of type Card to specify which card is being added. For the removeCard()
method, a parameter is needed to specify which card to remove. But should we specify the
card itself (“Remove the ace of spades”), or should we specify the card by its position in the
hand (“Remove the third card in the hand”)? Actually, we don’t have to decide, since we can
allow for both options. We’ll have two removeCard() instance methods, one with a parameter
of type Card specifying the card to be removed and one with a parameter of type int specifying
the position of the card in the hand. (Remember that you can have two methods in a class with
the same name, provided they have different types of parameters.) Since a hand can contain
a variable number of cards, it’s convenient to be able to ask a hand object how many cards it
contains. So, we need an instance method getCardCount() that returns the number of cards
in the hand. When I play cards, I like to arrange the cards in my hand so that cards of the
same value are next to each other. Since this is a generally useful thing to be able to do, we
can provide instance methods for sorting the cards in the hand. Here is a full specification for
a reusable Hand class:
Constructor and instance methods in class Hand:
public Hand() {
// Create a Hand object that is initially empty.
public void clear() {
// Discard all cards from the hand, making the hand empty.
public void addCard(Card c) {
// Add the card c to the hand. c should be non-null.
// If c is null, a NullPointerException is thrown.

You might also like