Javanotes5 102 151
Javanotes5 102 151
CONTROL
This section has been weighed down with lots of examples of numerical processing. For our
next example, let’s do some text processing. Consider the problem of finding which of the 26
letters of the alphabet occur in a given string. For example, the letters that occur in “Hello
World” are D, E, H, L, O, R, and W. More specifically, we will write a program that will list all
the letters contained in a string and will also count the number of different letters. The string
will be input by the user. Let’s start with a pseudocode algorithm for the program.
Ask the user to input a string
Read the response into a variable, str
Let count = 0 (for counting the number of different letters)
for each letter of the alphabet:
if the letter occurs in str:
Print the letter
Add 1 to count
Output the count
Since we want to process the entire line of text that is entered by the user, we’ll use
TextIO.getln() to read it. The line of the algorithm that reads “for each letter of the al-
phabet” can be expressed as “for (letter=’A’; letter<=’Z’; letter++)”. But the body
of this for loop needs more thought. How do we check whether the given letter, letter, occurs
in str? One idea is to look at each character in the string in turn, and check whether that
character is equal to letter. We can get the i-th character of str with the function call
str.charAt(i), where i ranges from 0 to str.length() - 1. One more difficulty: A letter
such as ’A’ can occur in str in either upper or lower case, ’A’ or ’a’. We have to check for both
of these. But we can avoid this difficulty by converting str to upper case before processing
it. Then, we only have to check for the upper case letter. We can now flesh out the algorithm
fully. Note the use of break in the nested for loop. It is required to avoid printing or counting
a given letter more than once (in the case where it occurs more than once in the string). The
break statement breaks out of the inner for loop, but not the outer for loop. Upon executing
the break, the computer continues the outer loop with the next value of letter.
Ask the user to input a string
Read the response into a variable, str
Convert str to upper case
Let count = 0
for letter = ’A’, ’B’, ..., ’Z’:
for i = 0, 1, ..., str.length()-1:
if letter == str.charAt(i):
Print letter
Add 1 to count
break // jump out of the loop
Output the count
In fact, there is actually an easier way to determine whether a given letter occurs in a string,
str. The built-in function str.indexOf(letter) will return -1 if letter does not occur in
the string. It returns a number greater than or equal to zero if it does occur. So, we could
check whether letter occurs in str simply by checking “if (str.indexOf(letter) >= 0)”.
If we used this technique in the above program, we wouldn’t need a nested for loop. This gives
you a preview of how subroutines can be used to deal with complexity.
The enhanced for loop can be used to perform the same processing on each of the enum
constants that are the possible values of an enumerated type. The syntax for doing this is:
for ( henum-type-name i hvariable-name i : henum-type-name i.values() )
hstatement i
or
for ( henum-type-name i hvariable-name i : henum-type-name i.values() ) {
hstatements i
}
If MyEnum is the name of any enumerated type, then MyEnum.values() is a function call that
returns a list containing all of the values of the enum. (values() is a static member function
in MyEnum and of any other enum.) For this enumerated type, the for loop would have the
form:
for ( MyEnum hvariable-name i : MyEnum.values() )
hstatement i
The intent of this is to execute the hstatementi once for each of the possible values of the
MyEnum type. The hvariable-namei is the loop control variable. In the hstatementi, it repre-
sents the enumerated type value that is currently being processed. This variable should not be
declared before the for loop; it is essentially being declared in the loop itself.
To give a concrete example, suppose that the following enumerated type has been defined
to represent the days of the week:
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
Then we could write:
for ( Day d : Day.values() ) {
System.out.print( d );
System.out.print(" is day number ");
System.out.println( d.ordinal() );
}
Day.values() represents the list containing the seven constants that make up the enumerated
type. The first time through this loop, the value of d would be the first enumerated type value
Day.MONDAY, which has ordinal number 0, so the output would be “MONDAY is day number 0”.
The second time through the loop, the value of d would be Day.TUESDAY, and so on through
Day.SUNDAY. The body of the loop is executed once for each item in the list Day.values(),
with d taking on each of those values in turn. The full output from this loop would be:
MONDAY is day number 0
TUESDAY is day number 1
WEDNESDAY is day number 2
THURSDAY is day number 3
FRIDAY is day number 4
SATURDAY is day number 5
SUNDAY is day number 6
Since the intent of the enhanced for loop is to do something “for each” item in a data
structure, it is often called a for-each loop. The syntax for this type of loop is unfortunate. It
would be better if it were written something like “foreach Day d in Day.values()”, which
conveys the meaning much better and is similar to the syntax used in other programming
languages for similar types of loops. It’s helpful to think of the colon (:) in the loop as meaning
“in.”
3.5. THE IF STATEMENT 89
if (hboolean-expression-1 i)
hstatement-1 i
else
if (hboolean-expression-2 i)
hstatement-2 i
else
hstatement-3 i
However, since the computer doesn’t care how a program is laid out on the page, this is almost
always written in the format:
if (hboolean-expression-1 i)
hstatement-1 i
else if (hboolean-expression-2 i)
hstatement-2 i
else
hstatement-3 i
You should think of this as a single statement representing a three-way branch. When the
computer executes this, one and only one of the three statements—hstatement-1 i, hstatement-
2 i, or hstatement-3 i—will be executed. The computer starts by evaluating hboolean-expression-
1 i. If it is true, the computer executes hstatement-1 i and then jumps all the way to the end of
the outer if statement, skipping the other two hstatementis. If hboolean-expression-1 i is false,
the computer skips hstatement-1 i and executes the second, nested if statement. To do this,
it tests the value of hboolean-expression-2 i and uses it to decide between hstatement-2 i and
hstatement-3 i.
Here is an example that will print out one of three different messages, depending on the
value of a variable named temperature:
if (temperature < 50)
System.out.println("It’s cold.");
else if (temperature < 80)
System.out.println("It’s nice.");
else
System.out.println("It’s hot.");
If temperature is, say, 42, the first test is true. The computer prints out the message “It’s
cold”, and skips the rest—without even evaluating the second condition. For a temperature of
75, the first test is false, so the computer goes on to the second test. This test is true, so
the computer prints “It’s nice” and skips the rest. If the temperature is 173, both of the tests
evaluate to false, so the computer says “It’s hot” (unless its circuits have been fried by the
heat, that is).
You can go on stringing together “else-if’s” to make multi-way branches with any number
of cases:
if (hboolean-expression-1 i)
hstatement-1 i
else if (hboolean-expression-2 i)
hstatement-2 i
else if (hboolean-expression-3 i)
hstatement-3 i
.
. // (more cases)
.
3.5. THE IF STATEMENT 91
else if (hboolean-expression-N i)
hstatement-N i
else
hstatement-(N+1) i
The computer evaluates boolean expressions one after the other until it comes to one that is
true. It executes the associated statement and skips the rest. If none of the boolean expressions
evaluate to true, then the statement in the else part is executed. This statement is called
a multi-way branch because only one of the statements will be executed. The final else part
can be omitted. In that case, if all the boolean expressions are false, none of the statements is
executed. Of course, each of the statements can be a block, consisting of a number of statements
enclosed between { and }. (Admittedly, there is lot of syntax here; as you study and practice,
you’ll become comfortable with it.)
Determining the relative order of y and z requires another if statement, so this becomes
if (x < y && x < z) { // x comes first
if (y < z)
System.out.println( x + " " + y + " " + z );
else
System.out.println( x + " " + z + " " + y );
}
else if (x > y && x > z) { // x comes last
if (y < z)
System.out.println( y + " " + z + " " + x );
else
System.out.println( z + " " + y + " " + x );
}
else { // x in the middle
if (y < z)
System.out.println( y + " " + x + " " + z);
else
92 CHAPTER 3. CONTROL
You might check that this code will work correctly even if some of the values are the same. If
the values of two variables are the same, it doesn’t matter which order you print them in.
Note, by the way, that even though you can say in English “if x is less than y and z,”,
you can’t say in Java “if (x < y && z)”. The && operator can only be used between boolean
values, so you have to make separate tests, x<y and x<z, and then combine the two tests with
&&.
There is an alternative approach to this problem that begins by asking, “which order should
x and y be printed in?” Once that’s known, you only have to decide where to stick in z. This
line of thought leads to different Java code:
if ( x < y ) { // x comes before y
if ( z < x ) // z comes first
System.out.println( z + " " + x + " " + y);
else if ( z > y ) // z comes last
System.out.println( x + " " + y + " " + z);
else // z is in the middle
System.out.println( x + " " + z + " " + y);
}
else { // y comes before x
if ( z < y ) // z comes first
System.out.println( z + " " + y + " " + x);
else if ( z > x ) // z comes last
System.out.println( y + " " + x + " " + z);
else // z is in the middle
System.out.println( y + " " + z + " " + x);
}
Once again, we see how the same problem can be solved in many different ways. The two
approaches to this problem have not exhausted all the possibilities. For example, you might
start by testing whether x is greater than y. If so, you could swap their values. Once you’ve
done that, you know that x should be printed before y.
∗ ∗ ∗
Finally, let’s write a complete program that uses an if statement in an interesting way. I
want a program that will convert measurements of length from one unit of measurement to
another, such as miles to yards or inches to feet. So far, the problem is extremely under-
specified. Let’s say that the program will only deal with measurements in inches, feet, yards,
and miles. It would be easy to extend it later to deal with other units. The user will type in
a measurement in one of these units, such as “17 feet” or “2.73 miles”. The output will show
the length in terms of each of the four units of measure. (This is easier than asking the user
which units to use in the output.) An outline of the process is
Read the user’s input measurement and units of measure
Express the measurement in inches, feet, yards, and miles
Display the four results
The program can read both parts of the user’s input from the same line by using
TextIO.getDouble() to read the numerical measurement and TextIO.getlnWord() to read
the unit of measure. The conversion into different units of measure can be simplified by first
3.5. THE IF STATEMENT 93
converting the user’s input into inches. From there, the number of inches can easily be con-
verted into feet, yards, and miles. Before converting into inches, we have to test the input to
determine which unit of measure the user has specified:
Let measurement = TextIO.getDouble()
Let units = TextIO.getlnWord()
if the units are inches
Let inches = measurement
else if the units are feet
Let inches = measurement * 12 // 12 inches per foot
else if the units are yards
Let inches = measurement * 36 // 36 inches per yard
else if the units are miles
Let inches = measurement * 12 * 5280 // 5280 feet per mile
else
The units are illegal!
Print an error message and stop processing
Let feet = inches / 12.0
Let yards = inches / 36.0
Let miles = inches / (12.0 * 5280.0)
Display the results
Since units is a String, we can use units.equals("inches") to check whether the spec-
ified unit of measure is “inches”. However, it would be nice to allow the units to be spec-
ified as “inch” or abbreviated to “in”. To allow these three possibilities, we can check if
(units.equals("inches") || units.equals("inch") || units.equals("in")). It would
also be nice to allow upper case letters, as in “Inches” or “IN”. We can do this by converting
units to lower case before testing it or by substituting the function units.equalsIgnoreCase
for units.equals.
In my final program, I decided to make things more interesting by allowing the user to enter
a whole sequence of measurements. The program will end only when the user inputs 0. To do
this, I just have to wrap the above algorithm inside a while loop, and make sure that the loop
ends when the user inputs a 0. Here’s the complete program:
/*
* This program will convert measurements expressed in inches,
* feet, yards, or miles into each of the possible units of
* measure. The measurement is input by the user, followed by
* the unit of measure. For example: "17 feet", "1 inch",
* "2.73 mi". Abbreviations in, ft, yd, and mi are accepted.
* The program will continue to read and convert measurements
* until the user enters an input of 0.
*/
public class LengthConverter {
public static void main(String[] args) {
double measurement; // Numerical measurement, input by user.
String units; // The unit of measure for the input, also
// specified by the user.
double inches, feet, yards, miles; // Measurement expressed in
// each possible unit of
// measure.
94 CHAPTER 3. CONTROL
TextIO.putln(" yards");
TextIO.putf("%12.5g", miles);
TextIO.putln(" miles");
TextIO.putln();
} // end while
TextIO.putln();
TextIO.putln("OK! Bye for now.");
} // end main()
} // end class LengthConverter
(Note that this program uses formatted output with the “g” format specifier. In this pro-
gram, we have no control over how large or how small the numbers might be. It could easily
make sense for the user to enter very large or very small measurements. The “g” format will
print a real number in exponential form if it is very large or very small, and in the usual decimal
form otherwise. Remember that in the format specification %12.5g, the 5 is the total number
of significant digits that are to be printed, so we will always get the same number of signifant
digits in the output, no matter what the size of the number. If we had used an “f” format
specifier such as %12.5f, the output would be in decimal form with 5 digits after the decimal
point. This would print the number 0.0000000007454 as 0.00000, with no significant digits
at all! With the “g” format specifier, the output would be 7.454e-10.)
does nothing when the boolean variable done is true, and prints out “Not done yet” when
it is false. You can’t just leave out the semicolon in this example, since Java syntax requires
an actual statement between the if and the else. I prefer, though, to use an empty block,
consisting of { and } with nothing between, for such cases.
Occasionally, stray empty statements can cause annoying, hard-to-find errors in a program.
For example, the following program segment prints out “Hello” just once, not ten times:
for (int i = 0; i < 10; i++);
System.out.println("Hello");
96 CHAPTER 3. CONTROL
Why? Because the “;” at the end of the first line is a statement, and it is this statement
that is executed ten times. The System.out.println statement is not really inside the for
statement at all, so it is executed just once, after the for loop has completed.
The break statements are technically optional. The effect of a break is to make the computer
jump to the end of the switch statement. If you leave out the break statement, the computer
will just forge ahead after completing one case and will execute the statements associated with
the next case label. This is rarely what you want, but it is legal. (I will note here—although
you won’t understand it until you get to the next chapter—that inside a subroutine, the break
statement is sometimes replaced by a return statement.)
Note that you can leave out one of the groups of statements entirely (including the break).
You then have two case labels in a row, containing two different constants. This just means
3.6. THE SWITCH STATEMENT 97
that the computer will jump to the same place and perform the same action for each of the two
constants.
Here is an example of a switch statement. This is not a useful example, but it should be
easy for you to follow. Note, by the way, that the constants in the case labels don’t have to be
in any particular order, as long as they are all different:
switch ( N ) { // (Assume N is an integer variable.)
case 1:
System.out.println("The number is 1.");
break;
case 2:
case 4:
case 8:
System.out.println("The number is 2, 4, or 8.");
System.out.println("(That’s a power of 2!)");
break;
case 3:
case 6:
case 9:
System.out.println("The number is 3, 6, or 9.");
System.out.println("(That’s a multiple of 3!)");
break;
case 5:
System.out.println("The number is 5.");
break;
default:
System.out.println("The number is 7 or is outside the range 1 to 9.");
}
The switch statement is pretty primitive as control structures go, and it’s easy to make mis-
takes when you use it. Java takes all its control structures directly from the older programming
languages C and C++. The switch statement is certainly one place where the designers of Java
should have introduced some improvements.
TextIO.putln(" 1. inches");
TextIO.putln(" 2. feet");
TextIO.putln(" 3. yards");
TextIO.putln(" 4. miles");
TextIO.putln();
TextIO.putln("Enter the number of your choice: ");
optionNumber = TextIO.getlnInt();
/* Read user’s measurement and convert to inches. */
switch ( optionNumber ) {
case 1:
TextIO.putln("Enter the number of inches: ");
measurement = TextIO.getlnDouble();
inches = measurement;
break;
case 2:
TextIO.putln("Enter the number of feet: ");
measurement = TextIO.getlnDouble();
inches = measurement * 12;
break;
case 3:
TextIO.putln("Enter the number of yards: ");
measurement = TextIO.getlnDouble();
inches = measurement * 36;
break;
case 4:
TextIO.putln("Enter the number of miles: ");
measurement = TextIO.getlnDouble();
inches = measurement * 12 * 5280;
break;
default:
TextIO.putln("Error! Illegal option number! I quit!");
System.exit(1);
} // end switch
/* Now go on to convert inches to feet, yards, and miles... */
switch ( currentSeason ) {
case WINTER: // ( NOT Season.WINTER ! )
System.out.println("December, January, February");
break;
case SPRING:
System.out.println("March, April, May");
break;
case SUMMER:
System.out.println("June, July, August");
break;
case FALL:
System.out.println("September, October, November");
break;
}
You probably haven’t spotted the error, since it’s not an error from a human point of view.
The computer reports the last line to be an error, because the variable computerMove might
not have been assigned a value. In Java, it is only legal to use the value of a variable if a
value has already been definitely assigned to that variable. This means that the computer
must be able to prove, just from looking at the code when the program is compiled, that the
variable must have been assigned a value. Unfortunately, the computer only has a few simple
rules that it can apply to make the determination. In this case, it sees a switch statement in
which the type of expression is int and in which the cases that are covered are 0, 1, and 2. For
other values of the expression, computerMove is never assigned a value. So, the computer thinks
computerMove might still be undefined after the switch statement. Now, in fact, this isn’t true:
0, 1, and 2 are actually the only possible values of the expression (int)(3*Math.random()),
but the computer isn’t smart enough to figure that out. The easiest way to fix the problem is
100 CHAPTER 3. CONTROL
to replace the case label case 2 with default. The computer can see that a value is assigned
to computerMove in all cases.
More generally, we say that a value has been definitely assigned to a variable at a given
point in a program if every execution path leading from the declaration of the variable to that
point in the code includes an assignment to the variable. This rule takes into account loops
and if statements as well as switch statements. For example, the following two if statements
both do the same thing as the switch statement given above, but only the one on the right
definitely assigns a value to computerMove:
String computerMove; String computerMove;
int rand; int rand;
rand = (int)(3*Math.random()); rand = (int)(3*Math.random());
if ( rand == 0 ) if ( rand == 0 )
computerMove = "Rock"; computerMove = "Rock";
else if ( rand == 1 ) else if ( rand == 1 )
computerMove = "Scissors"; computerMove = "Scissors";
else if ( rand == 2 ) else
computerMove = "Paper"; computerMove = "Paper";
In the code on the left, the test “if ( rand == 2 )” in the final else clause is unnecessary
because if rand is not 0 or 1, the only remaining possibility is that rand == 2. The computer,
however, can’t figure that out.
3.7.1 Exceptions
The term exception is used to refer to the type of error that one might want to handle with
a try..catch. An exception is an exception to the normal flow of control in the program.
The term is used in preference to “error” because in some cases, an exception might not be
considered to be an error at all. You can sometimes think of an exception as just another way
to organize a program.
Exceptions in Java are represented as objects of type Exception. Actual exceptions are de-
fined by subclasses of Exception. Different subclasses represent different types of exceptions We
will look at only two types of exception in this section: NumberFormatException and IllegalArgu-
mentException.
A NumberFormatException can occur when an attempt is made to convert a string
into a number. Such conversions are done by the functions Integer.parseInt
and Integer.parseDouble. (See Subsection 2.5.7.) Consider the function call
Integer.parseInt(str) where str is a variable of type String. If the value of str is the
string "42", then the function call will correctly convert the string into the int 42. However,
3.7. EXCEPTIONS AND TRY..CATCH 101
if the value of str is, say, "fred", the function call will fail because "fred" is not a legal
string representation of an int value. In this case, an exception of type NumberFormatException
occurs. If nothing is done to handle the exception, the program will crash.
An IllegalArgumentException can occur when an illegal value is passed as a parameter to a
subroutine. For example, if a subroutine requires that a parameter be greater than or equal to
zero, an IllegalArgumentException might occur when a negative value is passed to the subroutine.
How to respond to the illegal value is up to the person who wrote the subroutine, so we
can’t simply say that every illegal parameter value will result in an IllegalArgumentException.
However, it is a common response.
One case where an IllegalArgumentException can occur is in the valueOf function of an
enumerated type. Recall from Subsection 2.3.3 that this function tries to convert a string into
one of the values of the enumerated type. If the string that is passed as a parameter to valueOf
is not the name of one of the enumerated type’s values, then an IllegalArgumentException occurs.
For example, given the enumerated type
enum Toss { HEADS, TAILS }
Toss.valueOf("HEADS") correctly returns the value Toss.HEADS, while Toss.valueOf("FEET")
results in an IllegalArgumentException.
3.7.2 try..catch
When an exception occurs, we say that the exception is “thrown”. For example, we say that
Integer.parseInt(str) throws an exception of type NumberFormatException when the value
of str is illegal. When an exception is thrown, it is possible to “catch” the exception and
prevent it from crashing the program. This is done with a try..catch statement. In somewhat
simplified form, the syntax for a try..catch is:
try {
hstatements-1 i
}
catch ( hexception-class-name i hvariable-name i ) {
hstatements-2 i
}
The hexception-class-namei could be NumberFormatException, IllegalArgumentException, or
some other exception class. When the computer executes this statement, it executes the state-
ments in the try part. If no error occurs during the execution of hstatements-1 i, then the
computer just skips over the catch part and proceeds with the rest of the program. However,
if an exception of type hexception-class-namei occurs during the execution of hstatements-1 i,
the computer immediately jumps to the catch part and executes hstatements-2 i, skipping any
remaining statements in hstatements-1 i. During the execution of hstatements-2 i, the hvariable-
namei represents the exception object, so that you can, for example, print it out. At the end
of the catch part, the computer proceeds with the rest of the program; the exception has been
caught and handled and does not crash the program. Note that only one type of exception is
caught; if some other type of exception occurs during the execution of hstatements-1 i, it will
crash the program as usual.
(By the way, note that the braces, { and }, are part of the syntax of the try..catch
statement. They are required even if there is only one statement between the braces. This is
different from the other statements we have seen, where the braces around a single statement
are optional.)
102 CHAPTER 3. CONTROL
As an example, suppose that str is a variable of type String whose value might or might
not represent a legal real number. Then we could say:
try {
double x;
x = Double.parseDouble(str);
System.out.println( "The number is " + x );
}
catch ( NumberFormatException e ) {
System.out.println( "Not a legal number." );
}
If an error is thrown by the call to Double.parseDouble(str), then the output statement in
the try part is skipped, and the statement in the catch part is executed.
It’s not always a good idea to catch exceptions and continue with the program. Often that
can just lead to an even bigger mess later on, and it might be better just to let the exception
crash the program at the point where it occurs. However, sometimes it’s possible to recover
from an error. For example, suppose that we have the enumerated type
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
and we want the user to input a value belonging to this type. TextIO does not know about
this type, so we can only read the user’s response as a string. The function Day.valueOf can
be used to convert the user’s response to a value of type Day. This will throw an exception
of type IllegalArgumentException if the user’s response is not the name of one of the values of
type Day, but we can respond to the error easily enough by asking the user to enter another
response. Here is a code segment that does this. (Converting the user’s response to upper case
will allow responses such as “Monday” or “monday” in addition to “MONDAY”.)
Day weekday; // User’s response as a value of type Day.
while ( true ) {
String response; // User’s response as a String.
TextIO.put("Please enter a day of the week: ");
response = TextIO.getln();
response = response.toUpperCase();
try {
weekday = Day.valueOf(response);
break;
}
catch ( IllegalArgumentException e ) {
TextIO.putln( response + " is not the name of a day of the week." );
}
}
The break statement will be reached only if the user’s response is acceptable, and so the loop
will end only when a legal value has been assigned to weekday.
count = 0;
try {
while (true) { // Loop ends when an exception occurs.
number = TextIO.getDouble();
count++; // This is skipped when the exception occurs
sum += number;
}
}
catch ( IllegalArgumentException e ) {
// We expect this to occur when the end-of-file is encountered.
// We don’t consider this to be an error, so there is nothing to do
// in this catch clause. Just proceed with the rest of the program.
}
// At this point, we’ve read the entire file.
TextIO.putln();
TextIO.putln("Number of data values read: " + count);
TextIO.putln("The sum of the data values: " + sum);
if ( count == 0 )
TextIO.putln("Can’t compute an average of 0 values.");
else
TextIO.putln("The average of the values: " + (sum/count));
}
}
small! Applets can do other things besides draw themselves, such as responding when the user
clicks the mouse on the applet. Each of the applet’s behaviors is defined by a subroutine.
The programmer specifies how the applet behaves by filling in the bodies of the appropriate
subroutines.
A very simple applet, which does nothing but draw itself, can be defined by a class that
contains nothing but a paint() routine. The source code for the class would then have the
form:
import java.awt.*;
import java.applet.*;
public class hname-of-applet i extends Applet {
public void paint(Graphics g) {
hstatements i
}
}
where hname-of-appleti is an identifier that names the class, and the hstatementsi are the code
that actually draws the applet. This looks similar to the definition of a stand-alone program,
but there are a few things here that need to be explained, starting with the first two lines.
When you write a program, there are certain built-in classes that are available for you to
use. These built-in classes include System and Math. If you want to use one of these classes,
you don’t have to do anything special. You just go ahead and use it. But Java also has a large
number of standard classes that are there if you want them but that are not automatically
available to your program. (There are just too many of them.) If you want to use these
classes in your program, you have to ask for them first. The standard classes are grouped
into so-called “packages.” Two of these packages are called “java.awt” and “java.applet”. The
directive “import java.awt.*;” makes all the classes from the package java.awt available for
use in your program. The java.awt package contains classes related to graphical user interface
programming, including a class called Graphics. The Graphics class is referred to in the
paint() routine above. The java.applet package contains classes specifically related to applets,
including the class named Applet.
The first line of the class definition above says that the class “extends Applet.” Applet is
a standard class that is defined in the java.applet package. It defines all the basic properties
and behaviors of applet objects. By extending the Applet class, the new class we are defining
inherits all those properties and behaviors. We only have to define the ways in which our class
differs from the basic Applet class. In our case, the only difference is that our applet will draw
itself differently, so we only have to define the paint() routine that does the drawing. This is
one of the main advantages of object-oriented programming.
(Actually, in the future, our applets will be defined to extend JApplet rather than Applet.
The JApplet class is itself an extension of Applet. The Applet class has existed since the
original version of Java, while JApplet is part of the newer “Swing” set of graphical user
interface components. For the moment, the distinction is not important.)
One more thing needs to be mentioned—and this is a point where Java’s syntax gets un-
fortunately confusing. Applets are objects, not classes. Instead of being static members of a
class, the subroutines that define the applet’s behavior are part of the applet object. We say
that they are “non-static” subroutines. Of course, objects are related to classes because every
object is described by a class. Now here is the part that can get confusing: Even though a
non-static subroutine is not actually part of a class (in the sense of being part of the behavior
106 CHAPTER 3. CONTROL
of the class), it is nevertheless defined in a class (in the sense that the Java code that defines
the subroutine is part of the Java code that defines the class). Many objects can be described
by the same class. Each object has its own non-static subroutine. But the common definition
of those subroutines—the actual Java source code—is physically part of the class that describes
all the objects. To put it briefly: static subroutines in a class definition say what the class does;
non-static subroutines say what all the objects described by the class do. An applet’s paint()
routine is an example of a non-static subroutine. A stand-alone program’s main() routine is an
example of a static subroutine. The distinction doesn’t really matter too much at this point:
When working with stand-alone programs, mark everything with the reserved word, “static”;
leave it out when working with applets. However, the distinction between static and non-static
will become more important later in the course.
∗ ∗ ∗
Let’s write an applet that draws something. In order to write an applet that draws some-
thing, you need to know what subroutines are available for drawing, just as in writing text-
oriented programs you need to know what subroutines are available for reading and writing
text. In Java, the built-in drawing subroutines are found in objects of the class Graphics, one
of the classes in the java.awt package. In an applet’s paint() routine, you can use the Graphics
object g for drawing. (This object is provided as a parameter to the paint() routine when
that routine is called.) Graphics objects contain many subroutines. I’ll mention just three of
them here. You’ll encounter more of them in Chapter 6.
• g.setColor(c), is called to set the color that is used for drawing. The parameter, c is
an object belonging to a class named Color, another one of the classes in the java.awt
package. About a dozen standard colors are available as static member variables in
the Color class. These standard colors include Color.BLACK, Color.WHITE, Color.RED,
Color.GREEN, and Color.BLUE. For example, if you want to draw in red, you would say
“g.setColor(Color.RED);”. The specified color is used for all subsequent drawing oper-
ations up until the next time setColor is called.
• g.drawRect(x,y,w,h) draws the outline of a rectangle. The parameters x, y, w, and h
must be integer-valued expressions. This subroutine draws the outline of the rectangle
whose top-left corner is x pixels from the left edge of the applet and y pixels down from
the top of the applet. The width of the rectangle is w pixels, and the height is h pixels.
• g.fillRect(x,y,w,h) is similar to drawRect except that it fills in the inside of the rect-
angle instead of just drawing an outline.
This is enough information to write an applet that will draw the following image on a Web
page:
3.8. GUI PROGRAMMING 107
The applet first fills its entire rectangular area with red. Then it changes the drawing color
to black and draws a sequence of rectangles, where each rectangle is nested inside the previous
one. The rectangles can be drawn with a while loop. Each time through the loop, the rectangle
gets smaller and it moves down and over a bit. We’ll need variables to hold the width and height
of the rectangle and a variable to record how far the top-left corner of the rectangle is inset
from the edges of the applet. The while loop ends when the rectangle shrinks to nothing. In
general outline, the algorithm for drawing the applet is
Set the drawing color to red (using the g.setColor subroutine)
Fill in the entire applet (using the g.fillRect subroutine)
Set the drawing color to black
Set the top-left corner inset to be 0
Set the rectangle width and height to be as big as the applet
while the width and height are greater than zero:
draw a rectangle (using the g.drawRect subroutine)
increase the inset
decrease the width and the height
In my applet, each rectangle is 15 pixels away from the rectangle that surrounds it, so the
inset is increased by 15 each time through the while loop. The rectangle shrinks by 15 pixels
on the left and by 15 pixels on the right, so the width of the rectangle shrinks by 30 each time
through the loop. The height also shrinks by 30 pixels each time through the loop.
It is not hard to code this algorithm into Java and use it to define the paint() method of
an applet. I’ve assumed that the applet has a height of 160 pixels and a width of 300 pixels.
The size is actually set in the source code of the Web page where the applet appears. In order
for an applet to appear on a page, the source code for the page must include a command that
specifies which applet to run and how big it should be. (We’ll see how to do that later.) It’s
not a great idea to assume that we know how big the applet is going to be. On the other hand,
it’s also not a great idea to write an applet that does nothing but draw a static picture. I’ll
address both these issues before the end of this section. But for now, here is the source code
for the applet:
import java.awt.*;
import java.applet.Applet;
public class StaticRects extends Applet {
public void paint(Graphics g) {
// Draw a set of nested black rectangles on a red background.
// Each nested rectangle is separated by 15 pixels on
// all sides from the rectangle that encloses it.
int inset; // Gap between borders of applet
// and one of the rectangles.
int rectWidth, rectHeight; // The size of one of the rectangles.
g.setColor(Color.red);
g.fillRect(0,0,300,160); // Fill the entire applet with red.
g.setColor(Color.black); // Draw the rectangles in black.
inset = 0;
rectWidth = 299; // Set size of first rect to size of applet.
108 CHAPTER 3. CONTROL
rectHeight = 159;
while (rectWidth >= 0 && rectHeight >= 0) {
g.drawRect(inset, inset, rectWidth, rectHeight);
inset += 15; // Rects are 15 pixels apart.
rectWidth -= 30; // Width decreases by 15 pixels
// on left and 15 on right.
rectHeight -= 30; // Height decreases by 15 pixels
// on top and 15 on bottom.
}
} // end paint()
} // end class StaticRects
(You might wonder why the initial rectWidth is set to 299, instead of to 300, since the
width of the applet is 300 pixels. It’s because rectangles are drawn as if with a pen whose nib
hangs below and to the right of the point where the pen is placed. If you run the pen exactly
along the right edge of the applet, the line it draws is actually outside the applet and therefore
is not seen. So instead, we run the pen along a line one pixel to the left of the edge of the
applet. The same reasoning applies to rectHeight. Careful graphics programming demands
attention to details like these.)
∗ ∗ ∗
When you write an applet, you get to build on the work of the people who wrote the Applet
class. The Applet class provides a framework on which you can hang your own work. Any
programmer can create additional frameworks that can be used by other programmers as a basis
for writing specific types of applets or stand-alone programs. I’ve written a small framework
that makes it possible to write applets that display simple animations. One example that we
will consider is an animated version of the nested rectangles applet from earlier in this section.
You can see the applet in action at the bottom of the on-line version of this page.
A computer animation is really just a sequence of still images. The computer displays the
images one after the other. Each image differs a bit from the preceding image in the sequence.
If the differences are not too big and if the sequence is displayed quickly enough, the eye is
tricked into perceiving continuous motion.
In the example, rectangles shrink continually towards the center of the applet, while new
rectangles appear at the edge. The perpetual motion is, of course, an illusion. If you think
about it, you’ll see that the applet loops through the same set of images over and over. In each
image, there is a gap between the borders of the applet and the outermost rectangle. This gap
gets wider and wider until a new rectangle appears at the border. Only it’s not a new rectangle.
What has really happened is that the applet has started over again with the first image in the
sequence.
The problem of creating an animation is really just the problem of drawing each of the still
images that make up the animation. Each still image is called a frame. In my framework for
animation, which is based on a non-standard class called SimpleAnimationApplet2, all you
have to do is fill in the code that says how to draw one frame. The basic format is as follows:
import java.awt.*;
public class hname-of-class i extends SimpleAnimationApplet2 {
public void drawFrame(Graphics g) {
hstatements i // to draw one frame of the animation
3.8. GUI PROGRAMMING 109
}
}
height = getHeight();
g.setColor(Color.red); // Fill the frame with red.
g.fillRect(0,0,width,height);
g.setColor(Color.black); // Switch color to black.
inset = getFrameNumber() % 15; // Get the inset for the
// outermost rect.
rectWidth = width - 2*inset - 1; // Set size of outermost rect.
rectHeight = height - 2*inset - 1;
while (rectWidth >= 0 && rectHeight >= 0) {
g.drawRect(inset,inset,rectWidth,rectHeight);
inset += 15; // Rects are 15 pixels apart.
rectWidth -= 30; // Width decreases by 15 pixels
// on left and 15 on right.
rectHeight -= 30; // Height decreases by 15 pixels
// on top and 15 on bottom.
}
} // end drawFrame()
} // end class MovingRects
The main point here is that by building on an existing framework, you can do interesting
things using the type of local, inside-a-subroutine programming that was covered in Chapter 2
and Chapter 3. As you learn more about programming and more about Java, you’ll be able
to do more on your own—but no matter how much you learn, you’ll always be dependent on
other people’s work to some extent.
Exercises 111
1. How many times do you have to roll a pair of dice before they come up snake eyes? You
could do the experiment by rolling the dice by hand. Write a computer program that
simulates the experiment. The program should report the number of rolls that it makes
before the dice come up snake eyes. (Note: “Snake eyes” means that both dice show a
value of 1.) Exercise 2.2 explained how to simulate rolling a pair of dice.
2. Which integer between 1 and 10000 has the largest number of divisors, and how many
divisors does it have? Write a program to find the answers and print out the results. It is
possible that several integers in this range have the same, maximum number of divisors.
Your program only has to print out one of them. Subsection 3.4.2 discussed divisors. The
source code for that example is CountDivisors.java.
You might need some hints about how to find a maximum value. The basic idea is
to go through all the integers, keeping track of the largest number of divisors that you’ve
seen so far. Also, keep track of the integer that had that number of divisors.
3. Write a program that will evaluate simple expressions such as 17 + 3 and 3.14159 * 4.7.
The expressions are to be typed in by the user. The input always consist of a number,
followed by an operator, followed by another number. The operators that are allowed are
+, -, *, and /. You can read the numbers with TextIO.getDouble() and the operator
with TextIO.getChar(). Your program should read an expression, print its value, read
another expression, print its value, and so on. The program should end when the user
enters 0 as the first number on the line.
4. Write a program that reads one line of input text and breaks it up into words. The
words should be output one per line. A word is defined to be a sequence of letters. Any
characters in the input that are not letters should be discarded. For example, if the user
inputs the line
He said, "That’s not a good idea."
An improved version of the program would list “that’s” as a single word. An apostrophe
can be considered to be part of a word if there is a letter on each side of the apostrophe.
To test whether a character is a letter, you might use (ch >= ’a’ && ch <= ’z’) ||
(ch >= ’A’ && ch <= ’Z’). However, this only works in English and similar languages.
A better choice is to call the standard function Character.isLetter(ch), which returns
a boolean value of true if ch is a letter and false if it is not. This works for any Unicode
character.
112 CHAPTER 3. CONTROL
5. Suppose that a file contains information about sales figures for a company in various cities.
Each line of the file contains a city name, followed by a colon (:) followed by the data for
that city. The data is a number of type double. However, for some cities, no data was
available. In these lines, the data is replaced by a comment explaining why the data is
missing. For example, several lines from the file might look like:
San Francisco: 19887.32
Chicago: no report received
New York: 298734.12
Write a program that will compute and print the total sales from all the cities together.
The program should also report the number of cities for which data was not available.
The name of the file is “sales.dat”.
To complete this program, you’ll need one fact about file input with TextIO that was
not covered in Subsection 2.4.5. Since you don’t know in advance how many lines there
are in the file, you need a way to tell when you have gotten to the end of the file. When
TextIO is reading from a file, the function TextIO.eof() can be used to test for end of
file. This boolean-valued function returns true if the file has been entirely read and
returns false if there is more data to read in the file. This means that you can read the
lines of the file in a loop while (TextIO.eof() == false).... The loop will end when
all the lines of the file have been read.
Suggestion: For each line, read and ignore characters up to the colon. Then read the
rest of the line into a variable of type String. Try to convert the string into a number, and
use try..catch to test whether the conversion succeeds.
6. Write an applet that draws a checkerboard. Assume that the size of the applet is 160
by 160 pixels. Each square in the checkerboard is 20 by 20 pixels. The checkerboard
contains 8 rows of squares and 8 columns. The squares are red and black. Here is a tricky
way to determine whether a given square is red or black: If the row number and the
column number are either both even or both odd, then the square is red. Otherwise, it is
black. Note that a square is just a rectangle in which the height is equal to the width, so
you can use the subroutine g.fillRect() to draw the squares. Here is an image of the
checkerboard:
(To run an applet, you need a Web page to display it. A very simple page will do.
Assume that your applet class is called Checkerboard, so that when you compile it you
get a class file named Checkerboard.class Make a file that contains only the lines:
Exercises 113
7. Write an animation applet that shows a checkerboard pattern in which the even numbered
rows slide to the left while the odd numbered rows slide to the right. You can assume that
the applet is 160 by 160 pixels. Each row should be offset from its usual position by the
amount getFrameNumber() % 40. Hints: Anything you draw outside the boundaries of
the applet will be invisible, so you can draw more than 8 squares in a row. You can use
negative values of x in g.fillRect(x,y,w,h). (Before trying to do this exercise, it would
be a good idea to look at a working applet, which can be found in the on-line version of
this book.)
Your applet will extend the non-standard class, SimpleAnimationApplet2, which was
introduced in Section 3.8. The compiled class files, SimpleAnimationApplet2.class and
SimpleAnimationApplet2$1.class, must be in the same directory as your Web-page
source file along with the compiled class file for your own class. These files are produced
when you compile SimpleAnimationApplet2.java. Assuming that the name of your class
is SlidingCheckerboard, then the source file for the Web page should contain the lines:
<applet code="SlidingCheckerboard.class" width=160 height=160>
</applet>
114 CHAPTER 3. CONTROL
Quiz on Chapter 3
1. What is an algorithm?
2. Explain briefly what is meant by “pseudocode” and how is it useful in the development
of algorithms.
3. What is a block statement? How are block statements used in Java programs?
4. What is the main difference between a while loop and a do..while loop?
5. What does it mean to prime a loop?
8. Fill in the following main() routine so that it will ask the user to enter an integer, read
the user’s response, and tell the user whether the number entered is even or odd. (You can
use TextIO.getInt() to read the integer. Recall that an integer n is even if n % 2 == 0.)
public static void main(String[] args) {
// Fill in the body of this subroutine!
}
9. Suppose that s1 and s2 are variables of type String, whose values are expected to be
string representations of values of type int. Write a code segment that will compute and
print the integer sum of those values, or will print an error message if the values cannot
successfully be converted into integers. (Use a try..catch statement.)
10. Show the exact output that would be produced by the following main() routine:
public static void main(String[] args) {
int N;
N = 1;
while (N <= 32) {
N = 2 * N;
System.out.println(N);
}
}
11. Show the exact output produced by the following main() routine:
public static void main(String[] args) {
int x,y;
x = 5;
y = 1;
while (x > 0) {
x = x - 1;
y = y * x;
System.out.println(y);
}
}
Quiz 115
12. What output is produced by the following program segment? Why? (Recall that
name.charAt(i) is the i-th character in the string, name.)
String name;
int i;
boolean startWord;
name = "Richard M. Nixon";
startWord = true;
for (i = 0; i < name.length(); i++) {
if (startWord)
System.out.println(name.charAt(i));
if (name.charAt(i) == ’ ’)
startWord = true;
else
startWord = false;
}
116 CHAPTER 3. CONTROL
Chapter 4
One way to break up a complex program into manageable pieces is to use subroutines.
A subroutine consists of the instructions for carrying out a certain task, grouped together and
given a name. Elsewhere in the program, that name can be used as a stand-in for the whole set
of instructions. As a computer executes a program, whenever it encounters a subroutine name,
it executes all the instructions necessary to carry out the task associated with that subroutine.
Subroutines can be used over and over, at different places in the program. A subroutine
can even be used inside another subroutine. This allows you to write simple subroutines and
then use them to help write more complex subroutines, which can then be used in turn in other
subroutines. In this way, very complex programs can be built up step-by-step, where each step
in the construction is reasonably simple.
As mentioned in Section 3.8, subroutines in Java can be either static or non-static. This
chapter covers static subroutines only. Non-static subroutines, which are used in true object-
oriented programming, will be covered in the next chapter.
117
118 CHAPTER 4. SUBROUTINES
The contract of a subroutine says, essentially, “Here is what you have to do to use me,
and here is what I will do for you, guaranteed.” When you write a subroutine, the comments
that you write for the subroutine should make the contract very clear. (I should admit that
in practice, subroutines’ contracts are often inadequately specified, much to the regret and
annoyance of the programmers who have to use them.)
For the rest of this chapter, I turn from general ideas about black boxes and subroutines
in general to the specifics of writing and using subroutines in Java. But keep the general ideas
and principles in mind. They are the reasons that subroutines exist in the first place, and they
are your guidelines for using them. This should be especially clear in Section 4.6, where I will
discuss subroutines as a tool in program development.
∗ ∗ ∗
You should keep in mind that subroutines are not the only example of black boxes in
programming. For example, a class is also a black box. We’ll see that a class can have a
“public” part, representing its interface, and a “private” part that is entirely inside its hidden
implementation. All the principles of black boxes apply to classes as well as to subroutines.
as discussed in the previous section. They are the instructions that the computer executes when
the method is called. Subroutines can contain any of the statements discussed in Chapter 2
and Chapter 3.
The hmodifiersi that can occur at the beginning of a subroutine definition are words that
set certain characteristics of the subroutine, such as whether it is static or not. The modifiers
that you’ve seen so far are “static” and “public”. There are only about a half-dozen possible
modifiers altogether.
If the subroutine is a function, whose job is to compute some value, then the hreturn-typei is
used to specify the type of value that is returned by the function. We’ll be looking at functions
and return types in some detail in Section 4.4. If the subroutine is not a function, then the
hreturn-typei is replaced by the special value void, which indicates that no value is returned.
The term “void” is meant to indicate that the return value is empty or non-existent.
Finally, we come to the hparameter-listi of the method. Parameters are part of the interface
of a subroutine. They represent information that is passed into the subroutine from outside,
to be used by the subroutine’s internal computations. For a concrete example, imagine a class
named Television that includes a method named changeChannel(). The immediate question
is: What channel should it change to? A parameter can be used to answer this question. Since
the channel number is an integer, the type of the parameter would be int, and the declaration
of the changeChannel() method might look like
public void changeChannel(int channelNum) { ... }
This declaration specifies that changeChannel() has a parameter named channelNum of type
int. However, channelNum does not yet have any particular value. A value for channelNum is
provided when the subroutine is called; for example: changeChannel(17);
The parameter list in a subroutine can be empty, or it can consist of one or more parameter
declarations of the form htypei hparameter-namei. If there are several declarations, they are
separated by commas. Note that each declaration can name only one parameter. For example,
if you want two parameters of type double, you have to say “double x, double y”, rather
than “double x, y”.
Parameters are covered in more detail in the next section.
Here are a few examples of subroutine definitions, leaving out the statements that define
what the subroutines do:
public static void playGame() {
// "public" and "static" are modifiers; "void" is the
// return-type; "playGame" is the subroutine-name;
// the parameter-list is empty.
. . . // Statements that define what playGame does go here.
}
int getNextN(int N) {
// There are no modifiers; "int" in the return-type
// "getNextN" is the subroutine-name; the parameter-list
// includes one parameter whose name is "N" and whose
// type is "int".
. . . // Statements that define what getNextN does go here.
}
static boolean lessThan(double x, double y) {
// "static" is a modifier; "boolean" is the
// return-type; "lessThan" is the subroutine-name; the
4.2. STATIC SUBROUTINES AND VARIABLES 121
The use of the class name here tells the computer which class to look in to find the method. It
also lets you distinguish between Poker.playGame() and other potential playGame() methods
defined in other classes, such as Roulette.playGame() or Blackjack.playGame().
More generally, a subroutine call statement for a static subroutine takes the form
hsubroutine-name i(hparameters i);
if the subroutine that is being called is in the same class, or
hclass-name i.hsubroutine-name i(hparameters i);
if the subroutine is defined elsewhere, in a different class. (Non-static methods belong to objects
rather than classes, and they are called using object names instead of class names. More on
that later.) Note that the parameter list can be empty, as in the playGame() example, but the
parentheses must be there even if there is nothing between them.
boolean playAgain;
do {
playGame(); // call subroutine to play one game
TextIO.put("Would you like to play again? ");
playAgain = TextIO.getlnBoolean();
} while (playAgain);
TextIO.putln("Thanks for playing. Goodbye.");
} // end of main()
static void playGame() {
int computersNumber; // A random number picked by the computer.
int usersGuess; // A number entered by user as a guess.
int guessCount; // Number of guesses the user has made.
computersNumber = (int)(100 * Math.random()) + 1;
// The value assigned to computersNumber is a randomly
// chosen integer between 1 and 100, inclusive.
guessCount = 0;
TextIO.putln();
TextIO.put("What is your first guess? ");
while (true) {
usersGuess = TextIO.getInt(); // Get the user’s guess.
guessCount++;
if (usersGuess == computersNumber) {
TextIO.putln("You got it in " + guessCount
+ " guesses! My number was " + computersNumber);
break; // The game is over; the user has won.
}
if (guessCount == 6) {
TextIO.putln("You didn’t get the number in 6 guesses.");
TextIO.putln("You lose. My number was " + computersNumber);
break; // The game is over; the user has lost.
}
// If we get to this point, the game continues.
// Tell the user if the guess was too high or too low.
if (usersGuess < computersNumber)
TextIO.put("That’s too low. Try again: ");
else if (usersGuess > computersNumber)
TextIO.put("That’s too high. Try again: ");
}
TextIO.putln();
} // end of playGame()
} // end of class GuessingGame
Take some time to read the program carefully and figure out how it works. And try to
convince yourself that even in this relatively simple case, breaking up the program into two
methods makes the program easier to understand and probably made it easier to write each
piece.
distinguish such variables from local variables, we call them member variables, since they
are members of a class.
Just as with subroutines, member variables can be either static or non-static. In this
chapter, we’ll stick to static variables. A static member variable belongs to the class itself, and
it exists as long as the class exists. Memory is allocated for the variable when the class is first
loaded by the Java interpreter. Any assignment statement that assigns a value to the variable
changes the content of that memory, no matter where that assignment statement is located in
the program. Any time the variable is used in an expression, the value is fetched from that
same memory, no matter where the expression is located in the program. This means that the
value of a static member variable can be set in one subroutine and used in another subroutine.
Static member variables are “shared” by all the static subroutines in the class. A local variable
in a subroutine, on the other hand, exists only while that subroutine is being executed, and is
completely inaccessible from outside that one subroutine.
The declaration of a member variable looks just like the declaration of a local variable
except for two things: The member variable is declared outside any subroutine (although it
still has to be inside a class), and the declaration can be marked with modifiers such as static,
public, and private. Since we are only working with static member variables for now, every
declaration of a member variable in this chapter will include the modifier static. They might
also be marked as public or private. For example:
static String usersName;
public static int numberOfPlayers;
private static double velocity, time;
A static member variable that is not declared to be private can be accessed from outside
the class where it is defined, as well as inside. When it is used in some other class, it must be
referred to with a compound identifier of the form hclass-namei.hvariable-namei. For example,
the System class contains the public static member variable named out, and you use this
variable in your own classes by referring to System.out. If numberOfPlayers is a public
static member variable in a class named Poker, then subroutines in the Poker class would
refer to it simply as numberOfPlayers, while subroutines in another class would refer to it as
Poker.numberOfPlayers.
As an example, let’s add a static member variable to the GuessingGame class that we wrote
earlier in this section. This variable will be used to keep track of how many games the user wins.
We’ll call the variable gamesWon and declare it with the statement “static int gamesWon;”.
In the playGame() routine, we add 1 to gamesWon if the user wins the game. At the end of the
main() routine, we print out the value of gamesWon. It would be impossible to do the same
thing with a local variable, since we need access to the same variable from both subroutines.
When you declare a local variable in a subroutine, you have to assign a value to that variable
before you can do anything with it. Member variables, on the other hand are automatically
initialized with a default value. For numeric variables, the default value is zero. For boolean
variables, the default is false. And for char variables, it’s the unprintable character that has
Unicode code number zero. (For objects, such as Strings, the default initial value is a special
value called null, which we won’t encounter officially until later.)
Since it is of type int, the static member variable gamesWon automatically gets assigned an
initial value of zero. This happens to be the correct initial value for a variable that is being
used as a counter. You can, of course, assign a different value to the variable at the beginning
of the main() routine if you are not satisfied with the default initial value.
126 CHAPTER 4. SUBROUTINES
Here’s a revised version of GuessingGame.java that includes the gamesWon variable. The
changes from the above version are shown in italic:
public class GuessingGame2 {
static int gamesWon; // The number of games won by
// the user.
public static void main(String[] args) {
gamesWon = 0; // This is actually redundant, since 0 is
// the default initial value.
TextIO.putln("Let’s play a game. I’ll pick a number between");
TextIO.putln("1 and 100, and you try to guess it.");
boolean playAgain;
do {
playGame(); // call subroutine to play one game
TextIO.put("Would you like to play again? ");
playAgain = TextIO.getlnBoolean();
} while (playAgain);
TextIO.putln();
TextIO.putln("You won " + gamesWon + " games.");
TextIO.putln("Thanks for playing. Goodbye.");
} // end of main()
static void playGame() {
int computersNumber; // A random number picked by the computer.
int usersGuess; // A number entered by user as a guess.
int guessCount; // Number of guesses the user has made.
computersNumber = (int)(100 * Math.random()) + 1;
// The value assigned to computersNumber is a randomly
// chosen integer between 1 and 100, inclusive.
guessCount = 0;
TextIO.putln();
TextIO.put("What is your first guess? ");
while (true) {
usersGuess = TextIO.getInt(); // Get the user’s guess.
guessCount++;
if (usersGuess == computersNumber) {
TextIO.putln("You got it in " + guessCount
+ " guesses! My number was " + computersNumber);
gamesWon++; // Count this game by incrementing gamesWon.
break; // The game is over; the user has won.
}
if (guessCount == 6) {
TextIO.putln("You didn’t get the number in 6 guesses.");
TextIO.putln("You lose. My number was " + computersNumber);
break; // The game is over; the user has lost.
}
// If we get to this point, the game continues.
// Tell the user if the guess was too high or too low.
if (usersGuess < computersNumber)
TextIO.put("That’s too low. Try again: ");
else if (usersGuess > computersNumber)
TextIO.put("That’s too high. Try again: ");
}
4.3. PARAMETERS 127
TextIO.putln();
} // end of playGame()
} // end of class GuessingGame2
4.3 Parameters
If a subroutine is a black box, then a parameter provides a mechanism for passing infor-
mation from the outside world into the box. Parameters are part of the interface of a subroutine.
They allow you to customize the behavior of a subroutine to adapt it to a particular situation.
As an analogy, consider a thermostat—a black box whose task it is to keep your house
at a certain temperature. The thermostat has a parameter, namely the dial that is used to
set the desired temperature. The thermostat always performs the same task: maintaining a
constant temperature. However, the exact task that it performs—that is, which temperature
it maintains—is customized by the setting on its dial.
A formal parameter must be a name, that is, a simple identifier. A formal parameter is
very much like a variable, and—like a variable—it has a specified type such as int, boolean, or
String. An actual parameter is a value, and so it can be specified by any expression, provided
that the expression computes a value of the correct type. The type of the actual parameter must
be one that could legally be assigned to the formal parameter with an assignment statement.
For example, if the formal parameter is of type double, then it would be legal to pass an int as
the actual parameter since ints can legally be assigned to doubles. When you call a subroutine,
you must provide one actual parameter for each formal parameter in the subroutine’s definition.
Consider, for example, a subroutine
static void doTask(int N, double x, boolean test) {
// statements to perform the task go here
}
This subroutine might be called with the statement
doTask(17, Math.sqrt(z+1), z >= 10);
When the computer executes this statement, it has essentially the same effect as the block of
statements:
{
int N; // Allocate memory locations for the formal parameters.
double x;
boolean test;
N = 17; // Assign 17 to the first formal parameter, N.
x = Math.sqrt(z+1); // Compute Math.sqrt(z+1), and assign it to
// the second formal parameter, x.
test = (z >= 10); // Evaluate "z >= 10" and assign the resulting
// true/false value to the third formal
// parameter, test.
// statements to perform the task go here
}
(There are a few technical differences between this and “doTask(17,Math.sqrt(z+1),z>=10);”
—besides the amount of typing—because of questions about scope of variables and what hap-
pens when several variables or parameters have the same name.)
Beginning programming students often find parameters to be surprisingly confusing. Call-
ing a subroutine that already exists is not a problem—the idea of providing information to the
subroutine in a parameter is clear enough. Writing the subroutine definition is another matter.
A common mistake is to assign values to the formal parameters at the beginning of the subrou-
tine, or to ask the user to input their values. This represents a fundamental misunderstanding.
When the statements in the subroutine are executed, the formal parameters will already have
values. The values come from the subroutine call statement. Remember that a subroutine is
not independent. It is called by some other routine, and it is the calling routine’s responsibility
to provide appropriate values for the parameters.
4.3.3 Overloading
In order to call a subroutine legally, you need to know its name, you need to know how many
formal parameters it has, and you need to know the type of each parameter. This information is
called the subroutine’s signature. The signature of the subroutine doTask, used as an example
above, can be expressed as as: doTask(int,double,boolean). Note that the signature does
130 CHAPTER 4. SUBROUTINES
not include the names of the parameters; in fact, if you just want to use the subroutine, you
don’t even need to know what the formal parameter names are, so the names are not part of
the interface.
Java is somewhat unusual in that it allows two different subroutines in the same class to
have the same name, provided that their signatures are different. (The language C++ on
which Java is based also has this feature.) When this happens, we say that the name of the
subroutine is overloaded because it has several different meanings. The computer doesn’t get
the subroutines mixed up. It can tell which one you want to call by the number and types of
the actual parameters that you provide in the subroutine call statement. You have already seen
overloading used in the TextIO class. This class includes many different methods named putln,
for example. These methods all have different signatures, such as:
putln(int) putln(double)
putln(String) putln(char)
putln(boolean) putln()
The computer knows which of these subroutines you want to use based on the type
of the actual parameter that you provide. TextIO.putln(17) calls the subroutine with
signature putln(int), while TextIO.putln("Hello") calls the subroutine with signature
putln(String). Of course all these different subroutines are semantically related, which is
why it is acceptable programming style to use the same name for them all. But as far as the
computer is concerned, printing out an int is very different from printing out a String, which is
different from printing out a boolean, and so forth—so that each of these operations requires
a different method.
Note, by the way, that the signature does not include the subroutine’s return type. It is
illegal to have two subroutines in the same class that have the same signature but that have
different return types. For example, it would be a syntax error for a class to contain two
methods defined as:
int getln() { ... }
double getln() { ... }
So it should be no surprise that in the TextIO class, the methods for reading different types
are not all named getln(). In a given class, there can only be one routine that has the name
getln and has no parameters. So, the input routines in TextIO are distinguished by having
different names, such as getlnInt() and getlnDouble().
Java 5.0 introduced another complication: It is possible to have a single subroutine that
takes a variable number of actual parameters. You have already used subroutines that do
this—the formatted output routines System.out.printf and TextIO.putf. When you call
these subroutines, the number of parameters in the subroutine call can be arbitrarily large, so
it would be impossible to have different subroutines to handle each case. Unfortunately, writing
the definition of such a subroutine requires some knowledge of arrays, which will not be covered
until Chapter 7. When we get to that chapter, you’ll learn how to write subroutines with a
variable number of parameters. For now, we will ignore this complication.
break them up into subtasks—is the other side of programming with subroutines. We’ll return
to the question of program design in Section 4.6.
As a first example, let’s write a subroutine to compute and print out all the divisors of a
given positive integer. The integer will be a parameter to the subroutine. Remember that the
syntax of any subroutine is:
hmodifiers i hreturn-type i hsubroutine-name i ( hparameter-list i ) {
hstatements i
}
Writing a subroutine always means filling out this format. In this case, the statement of the
problem tells us that there is one parameter, of type int, and it tells us what the statements
in the body of the subroutine should do. Since we are only working with static subroutines
for now, we’ll need to use static as a modifier. We could add an access modifier (public or
private), but in the absence of any instructions, I’ll leave it out. Since we are not told to
return a value, the return type is void. Since no names are specified, we’ll have to make up
names for the formal parameter and for the subroutine itself. I’ll use N for the parameter and
printDivisors for the subroutine name. The subroutine will look like
static void printDivisors( int N ) {
hstatements i
}
and all we have left to do is to write the statements that make up the body of the routine. This
is not difficult. Just remember that you have to write the body assuming that N already has
a value! The algorithm is: “For each possible divisor D in the range from 1 to N, if D evenly
divides N, then print D.” Written in Java, this becomes:
/**
* Print all the divisors of N.
* We assume that N is a positive integer.
*/
static void printDivisors( int N ) {
int D; // One of the possible divisors of N.
System.out.println("The divisors of " + N + " are:");
for ( D = 1; D <= N; D++ ) {
if ( N % D == 0 )
System.out.println(D);
}
}
I’ve added a comment before the subroutine definition indicating the contract of the
subroutine—that is, what it does and what assumptions it makes. The contract includes the
assumption that N is a positive integer. It is up to the caller of the subroutine to make sure
that this assumption is satisfied.
As a second short example, consider the problem: Write a subroutine named printRow. It
should have a parameter ch of type char and a parameter N of type int. The subroutine should
print out a line of text containing N copies of the character ch.
Here, we are told the name of the subroutine and the names of the two parameters, so we
don’t have much choice about the first line of the subroutine definition. The task in this case is
pretty simple, so the body of the subroutine is easy to write. The complete subroutine is given
by
132 CHAPTER 4. SUBROUTINES
/**
* Write one line of output containing N copies of the
* character ch. If N <= 0, an empty line is output.
*/
static void printRow( char ch, int N ) {
int i; // Loop-control variable for counting off the copies.
for ( i = 1; i <= N; i++ ) {
System.out.print( ch );
}
System.out.println();
}
Note that in this case, the contract makes no assumption about N, but it makes it clear what
will happen in all cases, including the unexpected case that N < 0.
Finally, let’s do an example that shows how one subroutine can build on another. Let’s
write a subroutine that takes a String as a parameter. For each character in the string, it will
print a line of output containing 25 copies of that character. It should use the printRow()
subroutine to produce the output.
Again, we get to choose a name for the subroutine and a name for the parameter. I’ll call
the subroutine printRowsFromString and the parameter str. The algorithm is pretty clear:
For each position i in the string str, call printRow(str.charAt(i),25) to print one line of
the output. So, we get:
/**
* For each character in str, write a line of output
* containing 25 copies of that character.
*/
static void printRowsFromString( String str ) {
int i; // Loop-control variable for counting off the chars.
for ( i = 0; i < str.length(); i++ ) {
printRow( str.charAt(i), 25 );
}
}
We could use printRowsFromString in a main() routine such as
public static void main(String[] args) {
String inputLine; // Line of text input by user.
TextIO.put("Enter a line of text: ");
inputLine = TextIO.getln();
TextIO.putln();
printRowsFromString( inputLine );
}
Of course, the three routines, main(), printRowsFromString(), and printRow(), would
have to be collected together inside the same class. The program is rather useless, but it does
demonstrate the use of subroutines. You’ll find the program in the file RowsOfChars.java, if
you want to take a look.
parameters. The question arises, though, what should the subroutine do when the caller violates
the contract by providing bad parameter values?
We’ve already seen that some subroutines respond to bad parameter values by throw-
ing exceptions. (See Section 3.7.) For example, the contract of the built-in subroutine
Double.parseDouble says that the parameter should be a string representation of a num-
ber of type double; if this is true, then the subroutine will convert the string into the equivalent
numeric value. If the caller violates the contract by passing an invalid string as the actual
parameter, the subroutine responds by throwing an exception of type NumberFormatException.
Many subroutines throw IllegalArgumentExceptions in response to bad parameter values.
You might want to take this response in your own subroutines. This can be done with a throw
statement. An exception is an object, and in order to throw an exception, you must create
an exception object. You won’t officially learn how to do this until Chapter 5, but for now, you
can use the following syntax for a throw statement that throws an IllegalArgumentException:
throw new IllegalArgumentException( herror-message i );
where herror-messagei is a string that describes the error that has been detected. (The word
“new” in this statement is what creates the object.) To use this statement in a subroutine,
you would check whether the values of the parameters are legal. If not, you would throw the
exception. For example, consider the print3NSequence subroutine from the beginning of this
section. The parameter of print3NSequence is supposed to be a positive integer. We can
modify the subroutine definition to make it throw an exception when this condition is violated:
static void print3NSequence(int startingValue) {
if (startingValue <= 0) // The contract is violated!
throw new IllegalArgumentException( "Starting value must be positive." );
.
. // (The rest of the subroutine is the same as before.)
.
If the start value is bad, the computer executes the throw statement. This will immediate
terminate the subroutine, without executing the rest of the body of the subroutine. Further-
more, the program as a whole will crash unless the exception is “caught” and handled elsewhere
in the program by a try..catch statement, as discussed in Section 3.7.
as opposed to the local variables defined inside the subroutine. The scope of a global variable
includes the entire class in which it is defined. Changes made to a global variable can have effects
that extend outside the subroutine where the changes are made. You’ve seen how this works
in the last example in the previous section, where the value of the global variable, gamesWon,
is computed inside a subroutine and is used in the main() routine.
It’s not always bad to use global variables in subroutines, but you should realize that the
global variable then has to be considered part of the subroutine’s interface. The subroutine
uses the global variable to communicate with the rest of the program. This is a kind of sneaky,
back-door communication that is less visible than communication done through parameters,
and it risks violating the rule that the interface of a black box should be straightforward and
easy to understand. So before you use a global variable in a subroutine, you should consider
whether it’s really necessary.
I don’t advise you to take an absolute stand against using global variables inside subroutines.
There is at least one good reason to do it: If you think of the class as a whole as being a kind
of black box, it can be very reasonable to let the subroutines inside that box be a little sneaky
about communicating with each other, if that will make the class as a whole look simpler from
the outside.
return type.) When the computer executes this return statement, it evaluates the expression,
terminates execution of the function, and uses the value of the expression as the returned value
of the function.
For example, consider the function definition
static double pythagoras(double x, double y) {
// Computes the length of the hypotenuse of a right
// triangle, where the sides of the triangle are x and y.
return Math.sqrt( x*x + y*y );
}
This function has two return statements. Exactly one of the two return statements is executed
to give the value of the function. Some people prefer to use a single return statement at the
very end of the function when possible. This allows the reader to find the return statement
easily. You might choose to write nextN() like this, for example: