Control flow statements govern the flow of control in a program during execution, i.e., the order in which statements are executed in a running program. There are three main categories of control flow statements:
• Selection statements: if
, if-else
, and switch
.
• Iteration statements: while
, do-while
, basic for
, and enhanced for
.
• Transfer statements: break
, continue
, return
, try-catch-finally
, throw
, and assert
.
Java provides selection statements that allow the program to choose between alternative actions during execution. The choice is based on criteria specified in the selection statement. These selection statements are
The simple if
statement has the following syntax:
if
(<conditional expression>)
<statement>
It is used to decide whether an action is to be performed or not, based on a condition. The condition is specified by <conditional expression> and the action to be performed is specified by <statement>, which can be a single statement or a code block. The <conditional expression> must evaluate to a boolean
or a Boolean
value. In the latter case, the Boolean
value is unboxed to the corresponding boolean
value.
The semantics of the simple if
statement are straightforward. The <conditional expression> is evaluated first. If its value is true
, <statement> (called the if
block) is executed and execution continues with the rest of the program. If the value is false
, the if
block is skipped and execution continues with the rest of the program. The semantics are illustrated by the activity diagram in Figure 6.1a.
In the following examples of the if
statement, it is assumed that the variables and the methods have been appropriately defined:
if (emergency) // emergency is a boolean variable
operate();
if (temperature > critical)
soundAlarm();
if (isLeapYear() && endOfCentury())
celebrate();
if (catIsAway()) { // Block
getFishingRod();
goFishing();
}
Note that <statement> can be a block, and the block notation is necessary if more than one statement is to be executed when the <conditional expression>
is true
.
Since the <conditional expression>
evaluates to a boolean
value, it avoids a common programming error: using an expression of the form (a=b)
as the condition, where inadvertently an assignment operator is used instead of a relational operator. The compiler will flag this as an error, unless both a
and b
are boolean.
Note that the if
block can be any valid statement. In particular, it can be the empty statement (;
) or the empty block ({}
). A common programming error is an inadvertent use of the empty statement.
if (emergency); // Empty if block
operate(); // Executed regardless of whether it was an emergency or not.
The if-else
statement is used to decide between two actions, based on a condition. It has the following syntax:
if
(<conditional expression>)
<statement1>else
<statement2>
The <conditional expression> is evaluated first. If its value is true
(or unboxed to true
), <statement1
> (the if
block) is executed and execution continues with the rest of the program. If the value is false
(or unboxed to false
), <statement2
> (the else
block) is executed and execution continues with the rest of the program. In other words, one of two mutually exclusive actions is performed. The else
clause is optional; if omitted, the construct is equivalent to the simple if
statement. The semantics are illustrated by the activity diagram in Figure 6.1b.
In the following examples of the if-else
statement, it is assumed that all variables and methods have been appropriately defined:
if (emergency)
operate();
else
joinQueue();
if (temperature > critical)
soundAlarm();
else
businessAsUsual();
if (catIsAway()) {
getFishingRod();
goFishing();
} else
playWithCat();
Since actions can be arbitrary statements, the if
statements can be nested.
if (temperature >= upperLimit) { // (1)
if (danger) // (2) Simple if.
soundAlarm();
if (critical) // (3)
evacuate();
else // Goes with if at (3).
turnHeaterOff();
} else // Goes with if at (1).
turnHeaterOn();
The use of the block notation, {}
, can be critical to the execution of if
statements. The if
statements (A) and (B) in the following examples do not have the same meaning. The if
statements (B) and (C) are the same, with extra indentation used in (C) to make the meaning evident. Leaving out the block notation in this case could have catastrophic consequences: the heater could be turned on when the temperature is above the upper limit.
// (A):
if (temperature > upperLimit) { // (1) Block notation.
if (danger) soundAlarm(); // (2)
} else // Goes with if at (1).
turnHeaterOn();
// (B):
if (temperature > upperLimit) // (1) Without block notation.
if (danger) soundAlarm(); // (2)
else turnHeaterOn(); // Goes with if at (2).
// (C):
if (temperature > upperLimit) // (1)
if (danger) // (2)
soundAlarm();
else // Goes with if at (2).
turnHeaterOn();
The rule for matching an else
clause is that an else
clause always refers to the nearest if
that is not already associated with another else
clause. Block notation and proper indentation can be used to make the meaning obvious.
Cascading if-else
statements are a sequence of nested if-else
statements where the if
of the next if-else
statement is joined to the else
clause of the previous one. The decision to execute a block is then based on all the conditions evaluated so far.
if (temperature >= upperLimit) { // (1)
soundAlarm();
turnHeaterOff();
} else if (temperature < lowerLimit) { // (2)
soundAlarm();
turnHeaterOn();
} else if (temperature == (upperLimit-lowerLimit)/2) { // (3)
doingFine();
} else // (4)
noCauseToWorry();
The block corresponding to the first if
condition that evaluates to true
is executed, and the remaining if
s are skipped. In the example given above, the block at (3) will execute only if the conditions at (1) and (2) are false
and the condition at (3) is true
. If none of the conditions are true
, the block associated with the last else
clause is executed. If there is no last else
clause, no actions are performed.
Conceptually, the switch
statement can be used to choose one among many alternative actions, based on the value of an expression. Its general form is as follows:
switch
(<switch expression>) {
case label
1: <statement1>
case label
2: <statement2>
...
case label
n: <statementn>
default
: <statement>
} // end switch
The syntax of the switch
statement comprises a switch
expression followed by the switch
body, which is a block of statements. The type of the switch
expression is either an enumerated type or one of the following types: char
, byte
, short
, int
, or the corresponding wrapper type for these primitive types. The statements in the switch
body can be labeled, this defines entry points in the switch
body where control can be transferred depending on the value of the switch
expression. The execution of the switch
statement is as follows:
• The switch
expression is evaluated first. If the value is a wrapper type, an unboxing conversion is performed.
• The value of the switch
expression is compared with the case
labels. Control is transferred to the <statementi
> associated with the case
label that is equal to the value of the switch
expression. After execution of the associated statement, control falls through to the next statement unless appropriate action is taken.
• If no case
label is equal to the value of the switch
expression, the statement associated with the default
label is executed.
Figure 6.2 illustrates the flow of control through a switch
statement where the default
label is declared last.
All labels (including the default
label) are optional, and can be defined in any order in the switch
body. There can be at the most one default
label in a switch
statement. If no valid case
labels are found and the default
label is left out, the whole switch
statement is skipped.
The case
labels are constant expressions whose values must be unique, meaning no duplicate values are allowed. As a matter of fact, a case
label must be a compile-time constant expression whose value must be assignable to the type of the switch
expression (see Section 5.2, p. 163). In particular, all the case
label values must be in the range of the type of the switch
expression. Note that the type of the case
label cannot be boolean
, long
, or floating-point.
Example 6.1 Fall Through in a switch
Statement
public class Advice {
public final static int LITTLE_ADVICE = 0;
public final static int MORE_ADVICE = 1;
public final static int LOTS_OF_ADVICE = 2;
public static void main(String[] args) {
dispenseAdvice(LOTS_OF_ADVICE);
}
public static void dispenseAdvice(int howMuchAdvice) {
switch(howMuchAdvice) { // (1)
case LOTS_OF_ADVICE:
System.out.println("See no evil."); // (2)
case MORE_ADVICE:
System.out.println("Speak no evil."); // (3)
case LITTLE_ADVICE:
System.out.println("Hear no evil."); // (4)
break; // (5)
default:
System.out.println("No advice."); // (6)
}
}
}
Output from the program:
See no evil.
Speak no evil.
Hear no evil.
In Example 6.1, depending on the value of the howMuchAdvice
parameter, different advice is printed in the switch
statement at (1) in the method dispenseAdvice()
. The example shows the output when the value of the howMuchAdvice
parameter is LOTS_OF_ADVICE
. In the switch
statement, the associated statement at (2) is executed, giving one advice. Control then falls through to the statement at (3), giving the second advice. Control falls through to (4), dispensing the third advice, and finally, executing the break
statement at (5) causes control to exit the switch
statement. Without the break
statement at (5), control would continue to fall through the remaining statements, if there were any. Execution of the break
statement in a switch
body transfers control out of the switch
statement (see Section 6.4, p. 224). If the parameter howMuchAdvice
has the value MORE_ADVICE
, then the advice at (3) and (4) are given. The value LITTLE_ADVICE
results in only one advice at (4) being given. Any other value results in the default
action, which announces that there is no advice.
The associated statement of a case
label can be a list of statements (which need not be a statement block). The case
label is prefixed to the first statement in each case
. This is illustrated by the associated statement for the case
label LITTLE_ADVICE
in Example 6.1, which comprises statements (4) and (5).
Example 6.2 makes use of a break
statement inside a switch
statement to convert a char
value representing a digit to its corresponding word in English. Note that the break
statement is the last statement in the list of statements associated with each case
label. It is easy to think that the break
statement is a part of the switch
statement syntax, but technically it is not.
Example 6.2 Using break
in a switch
Statement
public class Digits {
public static void main(String[] args) {
System.out.println(digitToString('7') + " " + digitToString('8') + " " +
digitToString('6'));
}
public static String digitToString(char digit) {
String str = "";
switch(digit) {
case '1': str = "one"; break;
case '2': str = "two"; break;
case '3': str = "three"; break;
case '4': str = "four"; break;
case '5': str = "five"; break;
case '6': str = "six"; break;
case '7': str = "seven"; break;
case '8': str = "eight"; break;
case '9': str = "nine"; break;
case '0': str = "zero"; break;
default: System.out.println(digit + " is not a digit!");
}
return str;
}
}
Output from the program:
seven eight six
Several case
labels can prefix the same statement. They will all result in the associated statement being executed. This is illustrated in Example 6.3 for the switch
statement at (1).
The first statement in the switch
body must have a case
or default
label, otherwise it is unreachable. This statement will never be executed, since control can never be transferred to it. The compiler will flag this as an error.
Since each action associated with a case
label can be an arbitrary statement, it can be another switch
statement. In other words, switch
statements can be nested. Since a switch
statement defines its own local block, the case
labels in an inner block do not conflict with any case
labels in an outer block. Labels can be redefined in nested blocks, unlike variables which cannot be redeclared in nested blocks (see Section 4.6, p. 131). In Example 6.3, an inner switch
statement is defined at (2). This allows further refinement of the action to take on the value of the switch
expression, in cases where multiple labels are used in the outer switch
statement. A break
statement terminates the innermost switch
statement in which it is executed.
Example 6.3 Nested switch
Statement
public class Seasons {
public static void main(String[] args) {
int monthNumber = 11;
switch(monthNumber) { // (1) Outer
case 12: case 1: case 2:
System.out.println("Snow in the winter.");
break;
case 3: case 4: case 5:
System.out.println("Green grass in the spring.");
break;
case 6: case 7: case 8:
System.out.println("Sunshine in the summer.");
break;
case 9: case 10: case 11: // (2)
switch(monthNumber) { // Nested switch (3) Inner
case 10:
System.out.println("Halloween.");
break;
case 11:
System.out.println("Thanksgiving.");
break;
} // end nested switch
// Always printed for case labels 9, 10, 11
System.out.println("Yellow leaves in the fall."); // (4)
break;
default:
System.out.println(monthNumber + " is not a valid month.");
}
}
}
Output from the program:
Thanksgiving.
Yellow leaves in the fall.
Example 6.4 illustrates using enum
types in a switch
statement. The enum
type SPICE_DEGREE
is defined at (1). The type of the switch
expression is SPICE_DEGREE
. Note that the enum constants are not specified with their fully qualified name (see (2a). Using the fully qualified name results in a compile-time error, as shown at (2b). Only enum
constants that have the same enum
type as the switch
expression can be specified as case
label values. The semantics of the switch
statement are the same as described earlier.
Example 6.4 Enums in switch
Statement
public class SwitchingFun {
enum SPICE_DEGREE { // (1)
MILD, MEDIUM, HOT, SUICIDE;
}
public static void main(String[] args) {
SPICE_DEGREE spiceDegree = SPICE_DEGREE.HOT;
switch (spiceDegree) {
case HOT: // (2a) OK!
// case SPICE_LEVEL.HOT: // (2b) COMPILE-TIME ERROR!
System.out.println("Have fun!");
break;
case SUICIDE:
System.out.println("Good luck!");
break;
default:
System.out.println("Enjoy!");
}
}
}
Output from the program:
Have fun!
6.1 What will be the result of attempting to compile and run the following class?
public class IfTest {
public static void main(String[] args) {
if (true)
if (false)
System.out.println("a");
else
System.out.println("b");
}
}
Select the one correct answer.
(a) The code will fail to compile because the syntax of the if
statement is incorrect.
(b) The code will fail to compile because the compiler will not be able to determine which if
statement the else
clause belongs to.
(c) The code will compile correctly and display the letter a
, when run.
(d) The code will compile correctly and display the letter b
, when run.
(e) The code will compile correctly, but will not display any output.
6.2 Which statements are true?
Select the three correct answers.
(a) The conditional expression in an if
statement can have method calls.
(b) If a
and b
are of type boolean
, the expression (a = b)
can be the conditional expression of an if
statement.
(c) An if
statement can have either an if
clause or an else
clause.
(d) The statement if (false) ; else ;
is illegal.
(e) Only expressions which evaluate to a boolean
value can be used as the condition in an if
statement.
6.3 What, if anything, is wrong with the following code?
void test(int x) {
switch (x) {
case 1:
case 2:
case 0:
default:
case 4:
}
}
Select the one correct answer.
(a) The variable x
does not have the right type for a switch
expression.
(b) The case
label 0
must precede case
label 1
.
(c) Each case
section must end with a break
statement.
(d) The default
label must be the last label in the switch
statement.
(e) The body of the switch
statement must contain at least one statement.
(f) There is nothing wrong with the code.
6.4 Which of these combinations of switch
expression types and case
label value types are legal within a switch
statement?
Select the two correct answers.
(a) switch
expression of type int
and case
label value of type char
.
(b) switch
expression of type float
and case
label value of type int
.
(c) switch
expression of type byte
and case
label value of type float
.
(d) switch
expression of type char
and case
label value of type long
.
(e) switch
expression of type boolean
and case
label value of type boolean
.
(f) switch
expression of type Byte
and case
label value of type byte
.
(g) switch
expression of type byte
and case
label value of type Byte
.
6.5 What will be the result of attempting to compile and run the following program?
public class Switching {
public static void main(String[] args) {
final int iLoc = 3;
switch (6) {
case 1:
case iLoc:
case 2 * iLoc:
System.out.println("I am not OK.");
default:
System.out.println("You are OK.");
case 4:
System.out.println("It's OK.");
}
}
}
Select the one correct answer.
(a) The code will fail to compile because of the case
label value 2 * iLoc
.
(b) The code will fail to compile because the default
label is not specified last in the switch
statement.
(c) The code will compile correctly and will only print the following, when run:
I am not OK.
You are OK.
It's OK.
(d) The code will compile correctly and will only print the following, when run:
You are OK.
It's OK.
(e) The code will compile correctly and will only print the following, when run:
It's OK.
6.6 What will be the result of attempting to compile and run the following program?
public class MoreSwitching {
public static void main(String[] args) {
final int iLoc = 3;
Integer iRef = 5;
switch (iRef) {
default:
System.out.println("You are OK.");
case 1:
case iLoc:
case 2 * iLoc:
System.out.println("I am not OK.");
break;
case 4:
System.out.println("It's OK.");
}
}
}
Select the one correct answer.
(a) The code will fail to compile because the type of the switch
expression is not valid.
(b) The code will compile correctly and will only print the following, when run:
You are OK.
I am not OK.
(c) The code will compile correctly and will only print the following, when run:
You are OK.
I am not OK.
It's OK.
(d) The code will compile correctly and will only print the following, when run:
It's OK.
6.7 What will be the result of attempting to compile and run the following program?
public class KeepOnSwitching {
public static void main(String[] args) {
final int iLoc = 3;
final Integer iFour = 4;
Integer iRef = 4;
switch (iRef) {
case 1:
case iLoc:
case 2 * iLoc:
System.out.println("I am not OK.");
default:
System.out.println("You are OK.");
case iFour:
System.out.println("It's OK.");
}
}
}
Select the one correct answer.
(a) The code will fail to compile because of the value of one of the case
labels.
(b) The code will fail to compile because of the type of the switch
expression.
(c) The code will compile correctly and will only print the following, when run:
You are OK.
It's OK.
(d) The code will compile correctly and will only print the following, when run:
It's OK.
6.8 What will be the result of attempting to compile and run the following code?
public enum Scale5 {
GOOD, BETTER, BEST;
public char getGrade() {
char grade = 'u0000';
switch(this){
case GOOD:
grade = 'C';
break;
case BETTER:
grade = 'B';
break;
case BEST:
grade = 'A';
break;
}
return grade;
}
public static void main (String[] args) {
System.out.println(GOOD.getGrade());
}
}
Select the one correct answer.
(a) The program will not compile because of the switch
expression.
(b) The program will not compile, as enum constants cannot be used as case
labels.
(c) The case
labels must be qualified with the enum type name.
(d) The program compiles and only prints the following, when run:
C
(e) The program compiles and only prints the following, when run:
GOOD
(f) None of the above.
Loops allow a block of statements to be executed repeatedly (that is, iterated). A boolean condition (called the loop condition) is commonly used to determine when to terminate the loop. The statements executed in the loop constitute the loop body. The loop body can be a single statement or a block.
Java provides three language constructs for loop construction:
These loops differ in the order in which they execute the loop body and test the loop condition. The while
loop and the basic for
loop test the loop condition before executing the loop body, while the do-while
loop tests the loop condition after execution of the loop body.
In addition to the basic for
loop, there is a specialized one called the enhanced for
loop (also called the for-each loop) that simplifies iterating over arrays and collections. We will use the notations for(;;)
and for(:)
to designate the basic for
loop and the enhanced for
loop, respectively.
The syntax of the while
loop is
while (<loop condition>)
<loop body>
The <loop condition> is evaluated before executing the <loop body>. The while
statement executes the <loop body> as long as the <loop condition> is true
. When the <loop condition> becomes false
, the loop is terminated and execution continues with the statement immediately following the loop. If the <loop condition> is false
to begin with, the <loop body> is not executed at all. In other words, a while
loop can execute zero or more times. The <loop condition> must evaluate to a boolean
or a Boolean
value. In the latter case, the reference value is unboxed to a boolean
value. The flow of control in a while
statement is shown in Figure 6.3.
The while
statement is normally used when the number of iterations is not known.
while (noSignOfLife())
keepLooking();
Since the <loop body> can be any valid statement, inadvertently terminating each line with the empty statement (;
) can give unintended results. Always using a block statement, { ... }
, as the <loop body> helps to avoid such problems.
while (noSignOfLife()); // Empty statement as loop body!
keepLooking(); // Statement not in the loop body.
The syntax of the do-while
loop is
do
<loop body>
while (<loop condition>);
The <loop condition> is evaluated after executing the <loop body>. The value of the <loop condition> is subjected to unboxing if it is of the type Boolean
. The do-while
statement executes the <loop body> until the <loop condition> becomes false
. When the <loop condition> becomes false
, the loop is terminated and execution continues with the statement immediately following the loop. Note that the <loop body> is executed at least once. Figure 6.4 illustrates the flow of control in a do-while
statement.
The <loop body> in a do-while
loop is invariably a statement block. It is instructive to compare the while
and the do-while
loops. In the examples below, the mice might never get to play if the cat is not away, as in the loop at (1). The mice do get to play at least once (at the peril of losing their life) in the loop at (2).
while (cat.isAway()) { // (1)
mice.play();
}
do { // (2)
mice.play();
} while (cat.isAway());
The for(;;)
loop is the most general of all the loops. It is mostly used for counter-controlled loops, i.e., when the number of iterations is known beforehand.
The syntax of the loop is as follows:
for (<initialization>; <loop condition>; <increment expression>)
<loop body>
The <initialization> usually declares and initializes a loop variable that controls the execution of the <loop body>. The <loop condition> must evaluate to a boolean
or a Boolean
value. In the latter case, the reference value is converted to a boolean
value by unboxing. The <loop condition> usually involves the loop variable, and if the loop condition is true
, the loop body is executed; otherwise, execution continues with the statement following the for(;;)
loop. After each iteration (that is, execution of the loop body), the <increment expression> is executed. This usually modifies the value of the loop variable to ensure eventual loop termination. The loop condition is then tested to determine whether the loop body should be executed again. Note that the <initialization> is only executed once on entry to the loop. The semantics of the for(;;)
loop are illustrated in Figure 6.5, and can be summarized by the following equivalent while
loop code template:
<initialization>while
(<loop condition>) {
<loop body>
<increment expression>
}
The following code creates an int
array and sums the elements in the array.
int sum = 0;
int[] array = {12, 23, 5, 7, 19};
for (int index = 0; index < array.length; index++) // (1)
sum += array[index];
The loop variable index
is declared and initialized in the <initialization> section of the loop. It is incremented in the <increment expression> section.
The for(;;)
loop defines a local block so that the scope of this declaration is the for(;;)
block, which comprises the <initialization>, the <loop condition>, the <loop body> and the <increment expression> sections. Any variable declared in the for(;;)
block is thus not accessible after the for(;;)
loop terminates. The loop at (1) showed how a declaration statement can be specified in the <initialization> section. Such a declaration statement can also specify a comma-separated list of variables.
for (int i = 0, j = 1, k = 2; ... ; ...) ...; // (2)
The variables i
, j
, and k
in the declaration statement all have type int
. All variables declared in the <initialization> section are local variables in the for(;;)
block and obey the scope rules for local blocks. However, note that the following code will not compile, as variable declarations of different types (in this case, int
and String
) require declaration statements that are terminated by semicolons:
for (int i = 0, String str = "@"; ... ; ...) ...; // (3)
Compile time error.
The <initialization> section can also be a comma-separated list of expression statements (see Section 3.3, p. 45). For example, the loop at (2) can be rewritten by factoring out the variable declaration.
int i, j, k; // Variable declaration
for (i = 0, j = 1, k = 2; ... ; ...) ...; // (4)
Only initialization
The <initialization> section is now a comma-separated list of three expressions. The expressions in such a list are always evaluated from left to right. Note that the variables i
, j
, and k
at (4) are not local to the loop.
Declaration statements cannot be mixed with expression statements in the <initialization> section, as is the case at (5) in the following example. Factoring out the variable declaration, as at (6), leaves a legal comma-separated list of expression statements only.
// (5) Not legal and ugly:
for (int i = 0, System.out.println("This won't do!"); flag; i++) { // Error!
// loop body
}
// (6) Legal, but still ugly:
int i; // declaration
factored out.
for (i = 0, System.out.println("This is legal!"); flag; i++) { // OK.
// loop body
}
The <increment expression> can also be a comma-separated list of expression statements. The following code specifies a for(;;)
loop that has a comma-separated list of three variables in the <initialization> section, and a comma-separated list of two expressions in the <increment expression> section:
// Legal usage but not recommended.
int[][] sqMatrix = { {3, 4, 6}, {5, 7, 4}, {5, 8, 9} };
for (int i = 0, j = sqMatrix[0].length - 1, asymDiagonal = 0; // initialization
i < sqMatrix.length; // loop condition
i++, j--) // increment
expression
asymDiagonal += sqMatrix[i][j]; // loop body
All sections in the for(;;)
header are optional. Any or all of them can be left empty, but the two semicolons are mandatory. In particular, leaving out the <loop condition> signifies that the loop condition is true
. The “crab”, (;;)
, is commonly used to construct an infinite loop, where termination is presumably achieved through code in the loop body (see next section on transfer statements):
for (;;) Java.programming(); // Infinite loop
The enhanced for
loop is convenient when we need to iterate over an array or a collection, especially when some operation needs to be performed on each element of the array or collection. In this section we discuss iterating over arrays, and in Chapter 15 we take a closer look at the for(:)
loop for iterating over collections.
Earlier in this chapter we used a for(;;)
loop to sum the values of elements in an int
array:
int sum = 0;
int[] intArray = {12, 23, 5, 7, 19};
for (int index = 0; index < intArray.length; index++) { // (1) using for(;;) loop
sum += intArray[index];
}
The for(;;)
loop at (1) above is rewritten using the for(:)
loop in Figure 6.6. The body of the loop is executed for each element in the array, where the variable element
successively denotes the current element in the array intArray
. When the loop terminates, the variable sum
will contain the sum of all elements in the array. We do not care about the position of the elements in the array, just that the loop iterates over all elements of the array.
From Figure 6.6 we see that the for(:)
loop header has two parts. The expression must evaluate to a reference value that refers to an array, i.e., the array we want to iterate over. The array can be an array of primitive values or objects, or even an array of arrays. The expression is only evaluated once. The element declaration specifies a local variable that can be assigned a value of the element type of the array. This assignment might require either a boxing or an unboxing conversion. The type of the array in the code snippet is int[]
, and the element type is int
. Therefore, the element variable is declared to be of type int
. The element variable is local to the loop block and is not accessible after the loop terminates. Also, changing the value of the current variable does not change any value in the array. The loop body, which can be a simple statement or a statement block, is executed for each element in the array and there is no danger of any out-of-bounds errors.
The for(:)
loop has its limitations. We cannot change element values and it does not provide any provision for positional access using an index. The for(:)
loop only increments by one and always in a forward direction. It does not allow iterations over several arrays simultaneously. Under such circumstances the for(;;)
loop can be more convenient.
Here are some code examples of for(:)
loops that are legal:
// Some 1-dim arrays:
int[] intArray = {10, 20, 30};
Integer[] intObjArray = {10, 20, 30};
String[] strArray = {"one", "two"};
// Some 2-dim arrays:
Object[][] objArrayOfArrays = {intObjArray, strArray};
Number[][] numArrayOfArrays = {{1.5, 2.5}, intObjArray, {100L, 200L}};
int[][] intArrayOfArrays = {{20}, intArray, {40}};
// Iterate over an array of Strings.
// Expression type is String[], and element type is String.
// String is assignable to Object.
for (Object obj : strArray) {}
// Iterate over an array of ints.
// Expression type is int[], and element type is int.
// int is assignable to Integer (boxing conversion)
for (Integer iRef : intArrayOfArrays[0]){}
// Iterate over an array of Integers.
// Expression type is Integer[], and element type is Integer.
// Integer is assignable to int (unboxing conversion)
for (int i : intObjArray){}
// Iterate over a 2-dim array of ints.
// Outer loop: expression type is int[][], and element type is int[].
// Inner loop: expression type is int[], element type is int.
for (int[] row : intArrayOfArrays)
for (int val : row) {}
// Iterate over a 2-dim array of Numbers.
// Outer loop: expression type is Number[][], and element type is Number[].
// Outer loop: Number[] is assignable to Object[].
// Inner loop: expression type is Object[], element type is Object.
for (Object[] row : numArrayOfArrays)
for (Object obj : row) {}
// Outer loop: expression type is Integer[][], and element type is Integer[].
// Outer loop: Integer[] is assignable to Number[].
// Inner loop: expression type is int[], and element type is int.
// Inner loop: int is assignable to double.
for (Number[] row : new Integer[][] {intObjArray, intObjArray, intObjArray})
for (double num : new int[] {}) {}
Here are some code examples of for(:)
loops that are not legal:
// Expression type is Number[][], element type is Number[].
// Number[] is not assignable to Number.
for (Number num : numArrayOfArrays) {} // Compile-time error.
// Expression type is Number[], element type is Number.
// Number is not assignable to int.
for (int row : numArrayOfArrays[0]) {} // Compile-time error.
// Outer loop: expression type is int[][], and element type is int[].
// int[] is not assignable to Integer[].
for (Integer[] row : intArrayOfArrays) // Compile-time error.
for (int val : row) {}
// Expression type is Object[][], and element type is Object[].
// Object[] is not assignable to Integer[].
for (Integer[] row : objArrayOfArrays) {} // Compile-time error.
// Outer loop: expression type is String[], and element type is String.
// Inner loop: expression type is String which is not legal here.
for (String str : strArray)
for (char val : str) {} // Compile-time error.
When using the for(:)
loop to iterate over an array, the two main causes of errors are: the expression in the loop header does not represent an array and/or the element type of the array is not assignable to the local variable declared in the loop header.
Java provides six language constructs for transferring control in a program:
• break
• continue
• return
• try-catch-finally
• throw
• assert
This section discusses the first three statements, and the remaining statements are discussed in subsequent sections.
Note that Java does not have a goto
statement, although goto
is a reserved word.
A statement may have a label.
<label> : <statement>
A label is any valid identifier and it always immediately precedes the statement. Label names exist in their own name space, so that they do not conflict with names of packages, classes, interfaces, methods, fields, and local variables. The scope of a label is the statement prefixed by the label, meaning that it cannot be redeclared as a label inside the labeled statement—analogous to the scope of local variables.
L1: if (i > 0) {
L1: System.out.println(i); // (1) Not OK. Label redeclared.
}
L1: while (i < 0) { // (2) OK.
L2: System.out.println(i);
}
L1: { // (3) OK. Labeled block.
int j = 10;
System.out.println(j);
}
L1: try { // (4) OK. Labeled try-catch-finally block.
int j = 10, k = 0;
L2: System.out.println(j/k);
} catch (ArithmeticException ae) {
L3: ae.printStackTrace();
} finally {
L4: System.out.println("Finally done.");
}
A statement can have multiple labels:
LabelA: LabelB: System.out.println("Mutliple labels. Use judiciously.");
A declaration statement cannot have a label:
L0: int i = 0; // Compile time error.
A labeled statement is executed as if it was unlabeled, unless it is the break
or continue
statement. This is discussed in the next two subsections.
The break
statement comes in two forms: the unlabeled and the labeled form.
break; // the unlabeled form
break <label>; // the labeled form
The unlabeled break
statement terminates loops (for(;;)
, for(:)
, while
, do-while)
and switch
statements, and transfers control out of the current context (i.e., the closest enclosing block). The rest of the statement body is skipped, and execution continues after the enclosing statement.
In Example 6.5, the break
statement at (1) is used to terminate a for
loop. Control is transferred to (2) when the value of i
is equal to 4
at (1), skipping the rest of the loop body and terminating the loop.
Example 6.5 also shows that the unlabeled break
statement only terminates the innermost loop or switch
statement that contains the break
statement. The break
statement at (3) terminates the inner for
loop when j
is equal to 2
, and execution continues in the outer switch
statement at (4) after the for
loop.
Example 6.5 The break
Statement
class BreakOut {
public static void main(String[] args) {
for (int i = 1; i <= 5; ++i) {
if (i == 4)
break; // (1) Terminate loop. Control to (2).
// Rest of loop body skipped when i gets the value 4.
System.out.printf("%d %.2f%n", i, Math.sqrt(i));
} // end for
// (2) Continue here.
int n = 2;
switch (n) {
case 1:
System.out.println(n);
break;
case 2:
System.out.println("Inner for loop: ");
for (int j = 0; j <= n; j++)
if (j == 2)
break; // (3) Terminate loop. Control to (4).
else
System.out.println(j);
default:
System.out.println("default: " + n); // (4) Continue here.
}
}
}
Output from the program:
1 1.00
2 1.41
3 1.73
Inner for loop:
0
1
default: 2
A labeled break
statement can be used to terminate any labeled statement that contains the break
statement. Control is then transferred to the statement following the enclosing labeled statement. In the case of a labeled block, the rest of the block is skipped and execution continues with the statement following the block:
out:
{ // (1) Labeled block
// ...
if (j == 10) break out; // (2) Terminate block. Control to (3).
System.out.println(j); // Rest of the block not executed if j == 10.
// ...
}
// (3) Continue here.
In Example 6.6, the program continues to add the elements below the diagonal of a square matrix until the sum is greater than 10. Two nested for
loops are defined at (1) and (2). The outer loop is labeled outer
at (1). The unlabeled break
statement at (3) transfers control to (5) when it is executed, that is, it terminates the inner loop and control is transferred to the statement after the inner loop. The labeled break
statement at (4) transfers control to (6) when it is executed (i.e., it terminates both the inner and the outer loop, transferring control to the statement after the loop labeled outer
).
Example 6.6 Labeled break
Statement
class LabeledBreakOut {
public static void main(String[] args) {
int[][] squareMatrix = {{4, 3, 5}, {2, 1, 6}, {9, 7, 8}};
int sum = 0;
outer: // label
for (int i = 0; i < squareMatrix.length; ++i){ // (1)
for (int j = 0; j < squareMatrix[i].length; ++j) { // (2)
if (j == i) break; // (3) Terminate this loop.
// Control to (5).
System.out.println("Element[" + i + ", " + j + "]: " +
squareMatrix[i][j]);
sum += squareMatrix[i][j];
if (sum > 10) break outer;// (4) Terminate both loops.
// Control to (6).
} // end inner loop
// (5) Continue with outer loop.
} // end outer loop
// (6) Continue here.
System.out.println("sum: " + sum);
}
}
Output from the program:
Element[1, 0]: 2
Element[2, 0]: 9
sum: 11
Like the break
statement, the continue
statement also comes in two forms: the unlabeled and the labeled form.
continue; // the unlabeled form
continue <label>; // the labeled form
The continue
statement can only be used in a for(;;)
, for(:)
, while
, or do-while
loop to prematurely stop the current iteration of the loop body and proceed with the next iteration, if possible. In the case of the while
and do-while
loops, the rest of the loop body is skipped, that is, stopping the current iteration, with execution continuing with the <loop condition>. In the case of the for(;;)
loop, the rest of the loop body is skipped, with execution continuing with the <<increment expression>.
In Example 6.7, an unlabeled continue
statement is used to skip an iteration in a for(;;)
loop. Control is transferred to (2) when the value of i
is equal to 4
at (1), skipping the rest of the loop body and continuing with the <increment expression> in the for
statement.
Example 6.7 continue
Statement
class Skip {
public static void main(String[] args) {
for (int i = 1; i <= 5; ++i) {
if (i == 4) continue; // (1) Control to (2).
// Rest of loop body skipped when i has the value 4.
System.out.printf("%d %.2f%n", i, Math.sqrt(i));
// (2) Continue with increment expression.
} // end for
}
}
Output from the program:
1 1.00
2 1.41
3 1.73
5 2.24
A labeled continue
statement must occur within a labeled loop that has the same label. Execution of the labeled continue
statement then transfers control to the end of that enclosing labeled loop. In Example 6.8, the unlabeled continue
statement at (3) transfers control to (5) when it is executed; i.e., the rest of the loop body is skipped and execution continues with the next iteration of the inner loop. The labeled continue
statement at (4) transfers control to (6) when it is executed (i.e., it terminates the inner loop but execution continues with the next iteration of the loop labeled outer
). It is instructive to compare the output from Example 6.6 (labeled break
) and Example 6.8 (labeled continue
).
Example 6.8 Labeled continue
Statement
class LabeledSkip {
public static void main(String[] args) {
int[][] squareMatrix = {{4, 3, 5}, {2, 1, 6}, {9, 7, 8}};
int sum = 0;
outer: // label
for (int i = 0; i < squareMatrix.length; ++i){ // (1)
for (int j = 0; j < squareMatrix[i].length; ++j) { // (2)
if (j == i) continue; // (3) Control to (5).
System.out.println("Element[" + i + ", " + j + "]: " +
squareMatrix[i][j]);
sum += squareMatrix[i][j];
if (sum > 10) continue outer; // (4) Control to (6).
// (5) Continue with inner loop.
} // end inner loop
// (6) Continue with outer loop.
} // end outer loop
System.out.println("sum: " + sum);
}
}
Output from the program:
Element[0, 1]: 3
Element[0, 2]: 5
Element[1, 0]: 2
Element[1, 2]: 6
Element[2, 0]: 9
sum: 25
The return
statement is used to stop execution of a method and transfer control back to the calling code (also called the caller). The usage of the two forms of the return
statement is dictated by whether it is used in a void
or a non-void
method (see Table 6.1). The first form does not return any value to the calling code, but the second form does. Note that the keyword void
does not represent any type.
In Table 6.1, the <expression> must evaluate to a primitive value or a reference value, and its type must be assignable to the return type specified in the method header (see Section 5.5, p. 169 and Section 7.9, p. 320). See also the discussion on covariant return in connection with method overriding in Section 7.2.
As can be seen from Table 6.1, a void
method need not have a return
statement—in which case the control normally returns to the caller after the last statement in the method’s body has been executed. However, a void
method can only specify the first form of the return
statement. This form of the return
statement can also be used in constructors, as these also do not return a value.
Table 6.1 also shows that the first form of the return
statement is not allowed in a non-void
method. The second form of the return statement is mandatory in a non-void
method, if the method execution is not terminated programmatically, for example, by throwing an exception. Example 6.9 illustrates the use of the return
statement summarized in Table 6.1.
Example 6.9 The return
Statement
public class ReturnDemo {
public static void main (String[] args) { // (1) void method can use return.
if (args.length == 0) return;
output(checkValue(args.length));
}
static void output(int value) { // (2) void method need not use return.
System.out.println(value);
return 'a'; // Not OK. Can not return a value.
}
static int checkValue(int i) { // (3) Non-void method: Any return statement
// must return a value.
if (i > 3)
return i; // OK.
else
return 2.0; // Not OK. double not assignable to int.
}
static int AbsentMinded() { // (4) Non-void method
throw new RuntimeException(); // OK: No return statement provided, but
// method terminates by throwing an exception.
}
}
6.9 What will be the result of attempting to compile and run the following code?
class MyClass {
public static void main(String[] args) {
boolean b = false;
int i = 1;
do {
i++;
b = ! b;
} while (b);
System.out.println(i);
}
}
Select the one correct answer.
(a) The code will fail to compile because b
is an invalid conditional expression for the do-while
statement.
(b) The code will fail to compile because the assignment b = ! b
is not allowed.
(c) The code will compile without error and will print 1
, when run.
(d) The code will compile without error and will print 2
, when run.
(e) The code will compile without error and will print 3
, when run.
6.10 What will be the output when running the following program?
public class MyClass {
public static void main(String[] args) {
int i=0;
int j;
for (j=0; j<10; ++j) { i++; }
System.out.println(i + " " + j);
}
}
Select the two correct answers.
(a) The first number printed will be 9
.
(b) The first number printed will be 10
.
(c) The first number printed will be 11
.
(d) The second number printed will be 9
.
(e) The second number printed will be 10
.
(f) The second number printed will be 11
.
6.11 Which one of these for
statements is valid?
Select the one correct answer.
(a) int j=10; for (int i=0, j+=90; i<j; i++) { j--; }
(b) for (int i=10; i=0; i--) {}
(c) for (int i=0, j=100; i<j; i++, --j) {;}
(d) int i, j; for (j=100; i<j; j--) { i += 2; }
(e) int i=100; for ((i>0); i--) {}
6.12 What will be the result of attempting to compile and run the following program?
class MyClass {
public static void main(String[] args) {
int i = 0;
for ( ; i<10; i++) ; // (1)
for (i=0; ; i++) break; // (2)
for (i=0; i<10; ) i++; // (3)
for ( ; ; ) ; // (4)
}
}
Select the one correct answer.
(a) The code will fail to compile because the expression in the first section of the for
statement (1) is empty.
(b) The code will fail to compile because the expression in the middle section of the for
statement (2) is empty.
(c) The code will fail to compile because the expression in the last section of the for
statement (3) is empty.
(d) The code will fail to compile because the for
statement (4) is invalid.
(e) The code will compile without error, and the program will run and terminate without any output.
(f) The code will compile without error, but will never terminate when run.
6.13 Which statements are valid when occurring on their own?
Select the three correct answers.
(a) while () break;
(b) do { break; } while (true);
(c) if (true) { break; }
(d) switch (1) { default: break; }
(e) for (;true;) break;
6.14 Given the following code fragment, which of the following lines will be a part of the output?
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
if (i == j) {
continue outer;
}
System.out.println("i=" + i + ", j=" + j);
}
}
Select the two correct answers.
(a) i=1, j=0
(b) i=0, j=1
(c) i=1, j=2
(d) i=2, j=1
(e) i=2, j=2
(f) i=3, j=3
(g) i=3, j=2
6.15 What will be the result of attempting to compile and run the following code?
class MyClass {
public static void main(String[] args) {
for (int i = 0; i<10; i++) {
switch(i) {
case 0:
System.out.println(i);
}
if (i) {
System.out.println(i);
}
}
}
}
Select the one correct answer.
(a) The code will fail to compile because of an illegal switch
expression in the switch
statement.
(b) The code will fail to compile because of an illegal conditional expression in the if
statement.
(c) The code will compile without error and will print the numbers 0
through 10
, when run.
(d) The code will compile without error and will print the number 0
, when run.
(e) The code will compile without error and will print the number 0
twice, when run.
(f) The code will compile without error and will print the numbers 1
through 10
, when run.
6.16 Which of the following implementations of a max()
method will correctly return the largest value?
// (1)
int max(int x, int y) {
return (if (x > y) { x; } else { y; });
}
// (2)
int max(int x, int y) {
return (if (x > y) { return x; } else { return y; });
}
// (3)
int max(int x, int y) {
switch (x < y) {
case true:
return y;
default:
return x;
};
}
// (4)
int max(int x, int y) {
if (x>y) return x;
return y;
}
Select the one correct answer.
(a) Implementation labeled (1).
(b) Implementation labeled (2).
(c) Implementation labeled (3).
(d) Implementation labeled (4).
6.17 Given the following code, which statement is true?
class MyClass {
public static void main(String[] args) {
int k=0;
int l=0;
for (int i=0; i <= 3; i++) {
k++;
if (i == 2) break;
l++;
}
System.out.println(k + ", " + l);
}
}
Select the one correct answer.
(a) The program will fail to compile.
(b) The program will print 3, 3
, when run.
(c) The program will print 4, 3
, when run, if break
is replaced by continue
.
(d) The program will fail to compile if break
is replaced by return
.
(e) The program will fail to compile if break
is by an empty statement.
6.18 Which statements are true?
Select the two correct answers.
(a) {{}}
is a valid statement block.
(b) { continue; }
is a valid statement block.
(c) block: { break block; }
is a valid statement block.
(d) block: { continue block; }
is a valid statement block.
(e) The break
statement can only be used in a loop (while
, do-while
or for
) or a switch
statement.
6.19 Which declaration will result in the program compiling and printing 90
, when run?
public class RQ400_10 {
public static void main(String[] args) {
// (1) INSERT DECLARATION HERE
int sum = 0;
for (int i : nums)
sum += i;
System.out.println(sum);
}
}
Select the two correct answers.
(a) Object[] nums = {20, 30, 40};
(b) Number[] nums = {20, 30, 40};
(c) Integer[] nums = {20, 30, 40};
(d) int[] nums = {20, 30, 40};
(e) None of the above.
6.20 Which method declarations, when inserted at (1), will result in the program compiling and printing 90
when run?
public class RQ400_30 {
public static void main(String[] args) {
doIt();
}
// (1) INSERT METHOD DECLARATION HERE.
}
Select the two correct answers.
(a)
public static void doIt() {
int[] nums = {20, 30, 40};
for (int sum = 0, i : nums)
sum += i;
System.out.println(sum);
}
(b)
public static void doIt() {
for (int sum = 0, i : {20, 30, 40})
sum += i;
System.out.println(sum);
}
(c)
public static void doIt() {
int sum = 0;
for (int i : {20, 30, 40})
sum += i;
System.out.println(sum);
}
(d)
public static void doIt() {
int sum = 0;
for (int i : new int[] {20, 30, 40})
sum += i;
System.out.println(sum);
}
(e)
public static void doIt() {
int[] nums = {20, 30, 40};
int sum = 0;
for (int i : nums)
sum += i;
System.out.println(sum);
}
6.21 Given the declaration:
int[][] nums = {{20}, {30}, {40}};
Which code will compile and print 90
, when run?
Select the one correct answer.
(a)
{
int sum = 0;
for (int[] row : nums[])
for (int val : nums[row])
sum += val;
System.out.println(sum);
}
(b)
{
int sum = 0;
for (int[] row : nums[][])
for (int val : nums[row])
sum += val;
System.out.println(sum);
}
(c)
{
int sum = 0;
for (int[] row : nums)
for (int val : nums[row])
sum += val;
System.out.println(sum);
}
(d)
(e)
{
int sum = 0;
for (Integer[] row : nums)
for (int val : row)
sum += val;
System.out.println(sum);
}
6.22 What will be the result of compiling and running the following program?
public class RQ200_150 {
public static void main(String[] args) {
for (Character cRef = 'A'; cRef < 'F'; cRef++)
switch(cRef) {
default: System.out.print((char)('a' + cRef - 'A')); break;
case 'B': System.out.print(cRef); break;
case 68: System.out.print(cRef); // 68 == 'D'
}
}
}
Select the one correct answer.
(a) The code will fail to compile because of errors in the for
loop header.
(b) The code will fail to compile because of errors in the switch
statement.
(c) The code will compile, but throws a NullPointerException
.
(d) The code will compile and print: aBcDe
An exception in Java signals the occurrence of some unexpected error situation during execution, e.g., a requested file cannot be found, an array index is out of bounds, or a network link failed. Explicit checks in the code for such situations can easily result in incomprehensible code. Java provides an exception handling mechanism for dealing with such error situations systematically.
The exception mechanism is built around the throw-and-catch paradigm. To throw an exception is to signal that an unexpected condition has occurred. To catch an exception is to take appropriate action to deal with the exception. An exception is caught by an exception handler, and the exception need not be caught in the same context that it was thrown in. The runtime behavior of the program determines which exceptions are thrown and how they are caught. The throw-and-catch principle is embedded in the try
-catch
-finally
construct.
Several threads can be executing in the JVM (see Chapter 13). Each thread has its own runtime stack (also called the call stack or the invocation stack) that is used to handle execution of methods. Each element on the stack (called an activation record or a stack frame) corresponds to a method call. Each new call results in a new activation record being pushed on the stack, which stores all the pertinent information such as storage for the local variables. The method with the activation record on the top of the stack is the one currently executing. When this method finishes executing, its record is popped from the stack. Execution then continues in the method corresponding to the activation record which is now uncovered on the top of the stack. The methods on the stack are said to be active, as their execution has not completed. At any given time, the active methods on a runtime stack comprise what is called the stack trace of a thread’s execution.
Example 6.10 is a simple program to illustrate method execution. It calculates the average for a list of integers, given the sum of all the integers and the number of integers. It uses three methods:
• The method main()
which calls the method printAverage()
with parameters giving the total sum of the integers and the total number of integers, (1a).
• The method printAverage()
which in turn calls the method computeAverage()
, (3).
• The method computeAverage()
which uses integer division to calculate the average and returns the result, (7).
Example 6.10 Method Execution
public class Average1 {
public static void main(String[] args) {
printAverage(100, 20); // (1a)
System.out.println("Exit main()."); // (2)
}
public static void printAverage(int totalSum, int totalNumber) {
int average = computeAverage(totalSum, totalNumber); // (3)
System.out.println("Average = " + // (4)
totalSum + " / " + totalNumber + " = " + average);
System.out.println("Exit printAverage()."); // (5)
}
public static int computeAverage(int sum, int number) {
System.out.println("Computing average."); // (6)
return sum/number; // (7)
}
}
Output of program execution:
Computing average.
Average = 100 / 20 = 5
Exit printAverage().
Exit main().
Execution of Example 6.10 is illustrated in Figure 6.7. Each method execution is shown as a box with the local variables declared in the method. The height of the box indicates how long a method is active. Before the call to the method System.out.println()
at (6) in Figure 6.7, the stack trace comprises the three active methods: main()
, printAverage()
and computeAverage()
. The result 5 from the method computeAverage()
is returned at (7) in Figure 6.7. The output from the program is in correspondence with the sequence of method calls in Figure 6.7. The program terminates normally, therefore this program behavior is called normal execution.
If the method call at (1) in Example 6.10
printAverage(100, 20); // (1a)
is replaced with
printAverage(100, 0); // (1b)
and the program is run again, the output is as follows:
Computing average.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Average1.computeAverage(Average1.java:18)
at Average1.printAverage(Average1.java:10)
at Average1.main(Average1.java:5)
Figure 6.8 illustrates the program execution. All goes well until the return
statement at (7) in the method computeAverage()
is executed. An error condition occurs in calculating the expression sum/number
, because integer division by 0 is an illegal operation. This error condition is signaled by the JVM by throwing an ArithmeticException
(see “6.6 Exception Types” on page 239). This exception is propagated by the JVM through the runtime stack as explained next.
Figure 6.8 illustrates the case where an exception is thrown and the program does not take any explicit action to deal with the exception. In Figure 6.8, execution of the computeAverage()
method is suspended at the point where the exception is thrown. The execution of the return
statement at (7) never gets completed. Since this method does not have any code to deal with the exception, its execution is likewise terminated abruptly and its activation record popped. We say that the method completes abruptly. The exception is then offered to the method whose activation is now on the top of the stack (method printAverage()
). This method does not have any code to deal with the exception either, so its execution completes abruptly. The statements at (4) and (5) in the method printAverage()
never get executed. The exception now propagates to the last active method (method main()
). This does not deal with the exception either. The main()
method also completes abruptly. The statement at (2) in the main()
method never gets executed. Since the exception is not caught by any of the active methods, it is dealt with by the main thread’s default exception handler. The default exception handler usually prints the name of the exception, with an explanatory message, followed by a printout of the stack trace at the time the exception was thrown. An uncaught exception results in the death of the thread in which the exception occurred.
If an exception is thrown during the evaluation of the left-hand operand of a binary expression, then the right-hand operand is not evaluated. Similarly, if an exception is thrown during the evaluation of a list of expressions (e.g., a list of actual parameters in a method call), evaluation of the rest of the list is skipped.
If the line numbers in the stack trace are not printed in the output as shown previously, use the following command to run the program:
>java -Djava.compiler=NONE Average1
Exceptions in Java are objects. All exceptions are derived from the java.lang. Throwable
class. Figure 6.9 shows a partial hierarchy of classes derived from the Throwable
class. The two main subclasses Exception
and Error
constitute the main categories of throwables, the term used to refer to both exceptions and errors. Figure 6.9 also shows that not all exception classes are found in the same package.
The Throwable
class provides a String
variable that can be set by the subclasses to provide a detail message. The purpose of the detail message is to provide more information about the actual exception. All classes of throwables define a one-parameter constructor that takes a string as the detail message.
The class Throwable
provides the following common methods to query an exception:
String getMessage()
Returns the detail message.
void printStackTrace()
Prints the stack trace on the standard error stream. The stack trace comprises the method invocation sequence on the runtime stack when the exception was thrown. The stack trace can also be written to a PrintStream
or a PrintWriter
by supplying such a destination as an argument to one of the two overloaded printStackTrace()
methods.
String toString()
Returns a short description of the exception, which typically comprises the class name of the exception together with the string returned by the getMessage()
method.
In dealing with throwables, it is important to recognize situations under which particular throwables can occur, and the source that is responsible for throwing them. By source here we mean:
• either it is the JVM that is responsible for throwing the throwable, or
• that the throwable is explicitly thrown programmatically by the code in the application or any API used by the application.
In further discussion on exception types, we provide an overview of situations under which selected throwables can occur and the source responsible for throwing them.
The class Exception
represents exceptions that a program would normally want to catch. Its subclass RuntimeException
represents many common programming errors that can manifest at runtime (see the next subsection). Other subclasses of the Exception
class define other categories of exceptions, e.g., I/O-related exceptions in the java.io
package (IOException
, FileNotFoundException
, EOFException
, IOError
). Usage of I/O-related exceptions can be found in Chapter 11 on files and streams.
The subclass ClassNotFoundException
signals that the JVM tried to load a class by its string name, but the class could not be found. A typical example of this situation is when the class name is misspelled while starting program execution with the java
command. The source in this case is the JVM throwing the exception to signal that the class cannot be found and therefore execution cannot be started.
Runtime exceptions are all subclasses of the java.lang.RuntimeException
class, which is a subclass of the Exception
class. As these runtime exceptions are usually caused by program bugs that should not occur in the first place, it is usually more appropriate to treat them as faults in the program design and let them be handled by the default exception handler.
This exception represents situations where an illegal arithmetic operation is attempted, e.g., integer division by 0. It is typically thrown by the JVM. See Chapter 5 on operators for more details.
Java provides runtime checking of the array index value, i.e., out-of-bounds array indices. The subclass ArrayIndexOutOfBoundsException
represents exceptions thrown by the JVM that signal out-of-bound errors specifically for arrays, i.e., an invalid index is used to access an element in the array. The index value must satisfy the relation 0 ≤ index value length of the array. See Section 3.6, p. 69, covering arrays.
This exception is thrown by the JVM to signal that an attempt was made to cast a reference value to a type that was not legal, e.g., casting the reference value of an Integer
object to the Long
type. Casting reference values is discussed in Section 7.11, p. 327.
The IllegalArgumentException
is thrown programmatically to indicate that a method was called with an illegal or inappropriate argument. For example, the classes Pattern
and Matcher
in the java.util.regex
package and the Scanner
class in the java.util
package have methods that throw this exception. See Chapter 12 on locales, regular expressions, and formatting for more details.
The class NumberFormatException
is a subclass of the IllegalArgumentException
class, and is specialized to signal problems when converting a string to a numeric value if the format of the characters in the string is not appropriate for the conversion. This exception is also thrown programmatically. The numeric wrapper classes all have methods that throw this exception if things go wrong when converting a string to a numeric value. See Section 10.3, p. 428, on wrapper classes, for more details.
This exception is thrown programmatically when an operation is attempted, but the runtime environment or the application is not in an appropriate state for the requested operation. The Scanner
class in the java.util
package has methods that throw this exception if they are called and the scanner has been closed. See Section 12.7, p. 593, on formatting, for more details.
This exception is typically thrown by the JVM when an attempt is made to use the null
value as a reference value to refer to an object. This might involve calling an instance method using a reference that has the null
value, or accessing a field using a reference that has the null
value. This programming error has made this exception one of the most profusely thrown by the JVM.
The class Error
and its subclasses define errors that are invariably never explicitly caught and are usually irrecoverable. Not surprisingly, most such errors are signalled by the JVM. Apart from the subclasses mentioned below, other subclasses of the java.lang.Error
class define errors that indicate class linkage (LinkageError
), thread (ThreadDeath
), and virtual machine (VirtualMachineError
) problems.
The subclass AssertionError
of the java.lang.Error
class is used by the Java assertion facility. This error is thrown by the JVM in response to the condition in the assert
statement evaluating to false
. Section 6.10 discusses the assertion facility.
The JVM throws this error to signal that an unexpected problem occurred during the evaluation of a static initializer block or an initializer expression in a static variable declaration (see Section 9.7, p. 406).
This error in the java.io
package is thrown programmatically by the methods of the java.io.Console
class to indicate that a serious, irrecoverable I/O error has occurred (see Section 11.5, p. 500).
This error is thrown by the JVM when an application needs a class, but no definition of the class could be found. For instance, the application might want to use the class as part of a method call or create a new instance. The class existed when the application was compiled, but it cannot be found at runtime. The reason for this problem might be that the name of the class might be misspelled in the command line, the CLASSPATH might not specify the correct path, or the class file with the byte code is no longer available.
This error occurs when the runtime stack has no more room for new method activation records. We say that the stack has overflowed. This situation can occur when method execution in an application recurses too deeply. Here is a recursive method to illustrate stack overflow:
public void callMe() {
System.out.println("Don't do this at home!");
callMe();
}
Once this method is called, it will keep on calling itself until the runtime stack is full, resulting in the StackOverflowError
being thrown by the JVM.
Except for RuntimeException
, Error
, and their subclasses, all exceptions are called checked exceptions. The compiler ensures that if a method can throw a checked exception, directly or indirectly, the method must explicitly deal with it. The method must either catch the exception and take the appropriate action, or pass the exception on to its caller (see Section 6.9, p. 257).
Exceptions defined by Error
and RuntimeException
classes and their subclasses are known as unchecked exceptions, meaning that a method is not obliged to deal with these kinds of exceptions (shown with grey color in Figure 6.9). They are either irrecoverable (exemplified by the Error
class) and the program should not attempt to deal with them, or they are programming errors (exemplified by the RuntimeException
class) and should usually be dealt with as such, and not as exceptions.
New exceptions are usually defined to provide fine-grained categorization of error situations, instead of using existing exception classes with descriptive detail messages to differentiate between the situations. New exceptions can either extend the Exception
class directly or one of its checked subclasses, thereby making the new exceptions checked, or the RuntimeException
class to create new unchecked exceptions.
As exceptions are defined by classes, they can declare fields and methods, thus providing more information as to their cause and remedy when they are thrown and caught. The super()
call can be used to set the detail message for the exception. Note that the exception class must be instantiated to create an exception object that can be thrown and subsequently caught and dealt with. The code below sketches a class declaration for an exception that can include all pertinent information about the exception.
public class EvacuateException extends Exception {
// Data
Date date;
Zone zone;
TransportMode transport;
// Constructor
public EvacuateException(Date d, Zone z, TransportMode t) {
// Call the constructor of the superclass
super("Evacuation of zone " + z);
// ...
}
// Methods
// ...
}
Several examples illustrate exception handling in the subsequent sections.
The mechanism for handling exceptions is embedded in the try
-catch
-finally
construct, which has the following general form:
try { // try block
<statements>
} catch (<exception type1> <parameter1>) { // catch block
<statements>
}
...
catch (<exception typen> <parametern>) { // catch block
<statements>
} finally { // finally block
<statements>
}
Exceptions thrown during execution of the try
block can be caught and handled in a catch
block. A finally
block is guaranteed to be executed, regardless of the cause of exit from the try
block, or whether any catch
block was executed. Figure 6.10 shows three typical scenarios of control flow through the try-catch-finally
construct.
A few aspects about the syntax of this construct should be noted. The block notation is mandatory. For each try
block there can be zero or more catch
blocks, but only one finally
block. The catch
blocks and the finally
block must always appear in conjunction with a try
block, and in the right order. A try
block must be followed by at least one catch
block or a finally
block must be specified. Each catch
block defines an exception handler. The header of the catch
block takes exactly one argument, which is the exception the block is willing to handle. The exception must be of the Throwable
class or one of its subclasses.
Each block (try
, catch
, or finally
) of a try-catch-finally
construct can contain arbitrary code, which means that a try-catch-finally
construct can also be nested in any such block. However, such nesting can easily make the code difficult to read and is best avoided.
The try
block establishes a context for exception handling. Termination of a try
block occurs as a result of encountering an exception, or from successful execution of the code in the try
block.
The catch
blocks are skipped for all normal exits from the try
block where no exceptions were raised, and control is transferred to the finally
block if one is specified (see (1) in Figure 6.10).
For all exits from the try
block resulting from exceptions, control is transferred to the catch
blocks—if any such blocks are specified—to find a matching catch
block ((2) in Figure 6.10). If no catch
block matches the thrown exception, control is transferred to the finally
block if one is specified (see (3) in Figure 6.10).
Only an exit from a try
block resulting from an exception can transfer control to a catch
block. A catch
block can only catch the thrown exception if the exception is assignable to the parameter in the catch
block (see Section 7.8, p. 319). The code of the first such catch
block is executed and all other catch
blocks are ignored.
On exit from a catch
block, normal execution continues unless there is any pending exception that has been thrown and not handled. If this is the case, the method is aborted and the exception is propagated up the runtime stack as explained earlier.
After a catch
block has been executed, control is always transferred to the finally
block if one is specified. This is always true as long as there is a finally
block, regardless of whether the catch
block itself throws an exception.
In Example 6.11, the method printAverage()
calls the method computeAverage()
in a try
-catch
construct at (4). The catch
block is declared to catch exceptions of type ArithmeticException
. The catch
block handles the exception by printing the stack trace and some additional information at (7) and (8), respectively. Normal execution of the program is illustrated in Figure 6.11, which shows that the try
block is executed but no exceptions are thrown, with normal execution continuing after the try
-catch
construct. This corresponds to Scenario 1 in Figure 6.10.
Example 6.11 The try-catch
Construct
public class Average2 {
public static void main(String[] args) {
printAverage(100, 20); // (1)
System.out.println("Exit main()."); // (2)
}
public static void printAverage(int totalSum, int totalNumber) {
try { // (3)
int average = computeAverage(totalSum, totalNumber);// (4)
System.out.println("Average = " + // (5)
totalSum + " / " + totalNumber + " = " + average);
} catch (ArithmeticException ae) { // (6)
ae.printStackTrace(); // (7)
System.out.println("Exception handled in " +
"printAverage()."); // (8)
}
System.out.println("Exit printAverage()."); // (9)
}
public static int computeAverage(int sum, int number) {
System.out.println("Computing average."); // (10)
return sum/number; // (11)
}
}
Output from the program, with call printAverage(100, 20)
at (1):
Computing average.
Average = 100 / 20 = 5
Exit printAverage().
Exit main().
Output from the program, with call printAverage(100, 0)
at (1):
Computing average.
java.lang.ArithmeticException: / by zero
at Average2.computeAverage(Average2.java:24)
at Average2.printAverage(Average2.java:11)
at Average2.main(Average2.java:5)
Exception handled in printAverage().
Exit printAverage().
Exit main().
However, if we run the program in Example 6.11 with the following call in (1):
printAverage(100, 0)
an ArithmeticException
is thrown by the integer division in the method computeAverage()
. From Figure 6.12 we see that the execution of the method computeAverage()
is stopped and the exception propagated to method printAverage()
, where it is handled by the catch
block at (6). Normal execution of the method continues at (9) after the try
-catch
construct, as witnessed by the output from the statements at (9) and (2). This corresponds to Scenario 2 in Figure 6.10.
In Example 6.12, the main()
method calls the printAverage()
method in a try
-catch
construct at (1). The catch
block at (3) is declared to catch exceptions of type ArithmeticException
. The printAverage()
method calls the computeAverage()
method in a try
-catch
construct at (7), but here the catch
block is declared to catch exceptions of type IllegalArgumentException
. Execution of the program is illustrated in Figure 6.13, which shows that the ArithmeticException
is first propagated to the catch
block in the printAverage()
method. But since this catch
block cannot handle this exception, it is propagated further to the catch
block in the main()
method, where it is caught and handled. Normal execution continues at (6) after the exception is handled.
Note that the execution of the try
block at (7) in the printAverage()
method is never completed: the statement at (9) is never executed. The catch
block at (10) is skipped. The execution of the printAverage()
method is aborted: the statement at (13) is never executed, and the exception is propagated. This corresponds to Scenario 3 in Figure 6.10.
Example 6.12 Exception Propagation
public class Average3 {
public static void main(String[] args) {
try { // (1)
printAverage(100, 0); // (2)
} catch (ArithmeticException ae) { // (3)
ae.printStackTrace(); // (4)
System.out.println("Exception handled in " +
"main()."); // (5)
}
System.out.println("Exit main()."); // (6)
}
public static void printAverage(int totalSum, int totalNumber) {
try { // (7)
int average = computeAverage(totalSum, totalNumber);// (8)
System.out.println("Average = " + // (9)
totalSum + " / " + totalNumber + " = " + average);
} catch (IllegalArgumentException iae) { // (10)
iae.printStackTrace(); // (11)
System.out.println("Exception handled in " +
"printAverage()."); // (12)
}
System.out.println("Exit printAverage()."); // (13)
}
public static int computeAverage(int sum, int number) {
System.out.println("Computing average."); // (14)
return sum/number; // (15)
}
}
Output from the program:
Computing average.
java.lang.ArithmeticException: / by zero
at Average3.computeAverage(Average3.java:30)
at Average3.printAverage(Average3.java:17)
at Average3.main(Average3.java:6)
Exception handled in main().
Exit main().
The scope of the argument name in the catch
block is the block itself. As mentioned earlier, the type of the exception object must be assignable to the type of the argument in the catch
block (see Section 7.8, p. 319). In the body of the catch
block, the exception object can be queried like any other object by using the argument name. The javac
compiler also complains if a catch
block for a superclass exception shadows the catch
block for a subclass exception, as the catch
block of the subclass exception will never be executed. The following example shows incorrect order of the catch
blocks at (1) and (2), which will result in a compile time error: the superclass Exception
will shadow the subclass ArithmeticException
.
...
// Compiler complains
catch (Exception e) { // (1) superclass
System.out.println(e);
} catch (ArithmeticException e) { // (2) subclass
System.out.println(e);
}
...
If the try
block is executed, then the finally
block is guaranteed to be executed, regardless of whether any catch
block was executed. Since the finally
block is always executed before control transfers to its final destination, the finally
block can be used to specify any clean-up code (e.g., to free resources such as files and net connections).
A try
-finally
construct can be used to control the interplay between two actions that must be executed in the correct order, possibly with other intervening actions. In the following code, the operation in the calculateAverage()
method is dependent on the success of the sumNumbers()
method, this is checked by the value of the sum
variable before calling the calculateAverage()
method.
int sum = -1;
try {
sum = sumNumbers();
// other actions
} finally {
if (sum >= 0) calculateAverage();
}
The code above guarantees that if the try
block is entered, the sumNumbers()
method will be executed first, and later the calculateAverage()
method will be executed in the finally
block, regardless of how execution proceeds in the try
block. We can, if desired, include any catch
blocks to handle any exceptions.
If the finally
block neither throws an exception nor executes a control transfer statement like a return
or a labeled break
, the execution of the try
block or any catch
block determines how execution proceeds after the finally
block (see Figure 6.10, p. 246).
• If there is no exception thrown during execution of the try
block or the exception has been handled in a catch
block, normal execution continues after the finally
block.
• If there is any pending exception that has been thrown and not handled (either due to the fact that no catch
block was found or the catch
block threw an exception), the method is aborted and the exception is propagated after the execution of the finally
block.
If the finally
block throws an exception, this exception is propagated with all its ramifications—regardless of how the try
block or any catch
block were executed. In particular, the new exception overrules any previously unhandled exception.
If the finally
block executes a control transfer statement, such as a return
or a labeled break
, this control transfer statement determines how the execution will proceed—regardless of how the try
block or any catch
block were executed. In particular, a value returned by a return
statement in the finally
block will supercede any value returned by a return
statement in the try
block or a catch
block.
The output of Example 6.13 shows that the finally
block at (9) is executed, regardless of whether an exception is thrown in the try
block at (3) or not. If an exception is thrown, it is caught and handled by the catch
block at (6). After the execution of the finally
block at (9), normal execution continues at (10).
Example 6.13 The try-catch-finally
Construct
public class Average4 {
public static void main(String[] args) {
printAverage(100, 20); // (1)
System.out.println("Exit main()."); // (2)
}
public static void printAverage(int totalSum, int totalNumber) {
try { // (3)
int average = computeAverage(totalSum, totalNumber);// (4)
System.out.println("Average = " + // (5)
totalSum + " / " + totalNumber + " = " + average);
} catch (ArithmeticException ae) { // (6)
ae.printStackTrace(); // (7)
System.out.println("Exception handled in " +
"printAverage()."); // (8)
} finally { // (9)
System.out.println("Finally done.");
}
System.out.println("Exit printAverage()."); // (10)
}
public static int computeAverage(int sum, int number) {
System.out.println("Computing average."); // (11)
return sum/number; // (12)
}
}
Output from the program, with the call printAverage(100, 20)
at (1):
Computing average.
Average = 100 / 20 = 5
Finally done.
Exit printAverage().
Exit main().
Output from the program, with the call printAverage(100, 0)
at (1):
Computing average.
java.lang.ArithmeticException: / by zero
at Average4.computeAverage(Average4.java:26)
at Average4.printAverage(Average4.java:11)
at Average4.main(Average4.java:5)
Exception handled in printAverage().
Finally done.
Exit printAverage().
Exit main().
On exiting from the finally
block, if there is any pending exception, the method is aborted and the exception propagated as explained earlier. This is illustrated in Example 6.14. The method printAverage()
is aborted after the finally
block at (6) has been executed, as the ArithmeticException
thrown at (9) is not handled by any method. In this case, the exception is handled by the default exception handler. Notice the difference in the output from Example 6.13 and Example 6.14.
Example 6.14 The try-finally
Construct
public class Average5 {
public static void main(String[] args) {
printAverage(100, 0); // (1)
System.out.println("Exit main()."); // (2)
}
public static void printAverage(int totalSum, int totalNumber) {
try { // (3)
int average = computeAverage(totalSum, totalNumber);// (4)
System.out.println("Average = " + // (5)
totalSum + " / " + totalNumber + " = " + average);
} finally { // (6)
System.out.println("Finally done.");
}
System.out.println("Exit printAverage()."); // (7)
}
public static int computeAverage(int sum, int number) {
System.out.println("Computing average."); // (8)
return sum/number; // (9)
}
}
Output from the program:
Computing average.
Finally done.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Average5.computeAverage(Average5.java:21)
at Average5.printAverage(Average5.java:10)
at Average5.main(Average5.java:4)
Example 6.15 shows how the execution of a control transfer statement such as a return
in the finally
block affects the program execution. The first output from the program shows that the average is computed but the value returned is from the return
statement at (8) in the finally
block, not from the return
statement at (6) in the try
block. The second output shows that the ArithmeticException
thrown in the computeAverage()
method and propagated to the printAverage()
method is nullified by the return
statement in the finally
block. Normal execution continues after the return
statement at (8), with the value 0 being returned from the printAverage()
method.
Example 6.15 The finally
Block and the return
Statement
public class Average6 {
public static void main(String[] args) {
System.out.println("Average: " + printAverage(100, 20)); // (1)
System.out.println("Exit main()."); // (2)
}
public static int printAverage(int totalSum, int totalNumber) {
int average = 0;
try { // (3)
average = computeAverage(totalSum, totalNumber); // (4)
System.out.println("Average = " + // (5)
totalSum + " / " + totalNumber + " = " + average);
return average; // (6)
} finally { // (7)
System.out.println("Finally done.");
return average*2; // (8)
}
}
public static int computeAverage(int sum, int number) {
System.out.println("Computing average."); // (9)
return sum/number; // (10)
}
}
Output from the program, with call printAverage(100, 20)
in (1):
Computing average.
Average = 100 / 20 = 5
Finally done.
Average: 10
Exit main().
Output from the program, with call printAverage(100, 0)
in (1):
Computing average.
Finally done.
Average: 0
Exit main().
Earlier examples in this chapter have shown how an exception can be thrown implicitly by the JVM during execution. Now we look at how an application can programmatically throw an exception using the throw
statement. The general format of the throw
statement is as follows:
throw <object reference expression>;
The compiler ensures that the <object reference expression> is of the type Throwable
class or one of its subclasses. At runtime a NullPointerException
is thrown by the JVM if the <object reference expression> is null
. This ensures that a Throwable
will always be propagated. A detail message is often passed to the constructor when the exception object is created.
throw new ArithmeticException("Integer division by 0");
When an exception is thrown, normal execution is suspended. The runtime system proceeds to find a catch
block that can handle the exception. The search starts in the context of the current try
block, propagating to any enclosing try
blocks and through the runtime stack to find a handler for the exception. Any associated finally
block of a try
block encountered along the search path is executed. If no handler is found, then the exception is dealt with by the default exception handler at the top level. If a handler is found, normal execution resumes after the code in its catch
block has been executed, barring any rethrowing of an exception.
In Example 6.16, an exception is thrown using a throw
statement at (17). This exception is propagated to the main()
method where it is caught and handled by the catch
block at (3). Note that the finally
blocks at (6) and (14) are executed. Execution continues normally from (7).
Example 6.16 Throwing Exceptions
public class Average7 {
public static void main(String[] args) {
try { // (1)
printAverage(100, 0); // (2)
} catch (ArithmeticException ae) { // (3)
ae.printStackTrace(); // (4)
System.out.println("Exception handled in " + // (5)
"main().");
} finally {
System.out.println("Finally in main()."); // (6)
}
System.out.println("Exit main()."); // (7)
}
public static void printAverage(int totalSum, int totalNumber) {
try { // (8)
int average = computeAverage(totalSum, totalNumber); // (9)
System.out.println("Average = " + // (10)
totalSum + " / " + totalNumber + " = " + average);
} catch (IllegalArgumentException iae) { // (11)
iae.printStackTrace(); // (12)
System.out.println("Exception handled in " + // (13)
"printAverage().");
} finally {
System.out.println("Finally in printAverage()."); // (14)
}
System.out.println("Exit printAverage()."); // (15)
}
public static int computeAverage(int sum, int number) {
System.out.println("Computing average.");
if (number == 0) // (16)
throw new ArithmeticException("Integer division by 0"); // (17)
return sum/number; // (18)
}
}
Output from the program:
Computing average.
Finally in printAverage().
java.lang.ArithmeticException: Integer division by 0
at Average7.computeAverage(Average7.java:35)
at Average7.printAverage(Average7.java:19)
at Average7.main(Average7.java:6)
Exception handled in main().
Finally in main().
Exit main().
A throws
clause can be specified in the method header.
... someMethod(...)
throws <ExceptionType1>, <ExceptionType2>,..., <ExceptionTypen> { ... }
Each <ExceptionTypei
> declares an exception, normally only checked exceptions are declared. The compiler enforces that the checked exceptions thrown by a method are limited to those specified in its throws
clause. Of course, the method can throw exceptions that are subclasses of the checked exceptions in the throws
clause. This is permissible since exceptions are objects and a subclass object can polymorphically act as an object of its superclass (see Section 7.1, p. 284). The throws
clause can specify unchecked exceptions, but this is seldom done and the compiler does not verify them.
In a method, a checked exception can be thrown directly by using the throw
statement, or indirectly by calling other methods that can throw a checked exception. If a checked exception is thrown in a method, it must be handled in one of three ways:
• By using a try
block and catching the exception in a handler and dealing with it
• By using a try
block and catching the exception in a handler, but throwing another exception that is either unchecked or declared in its throws
clause
• By explicitly allowing propagation of the exception to its caller by declaring it in the throws
clause of its method header
This mechanism ensures that a checked exception will be dealt with, regardless of the path of execution. This aids development of robust programs, as allowance can be made for many contingencies. Native methods can also declare checked exceptions in their throws
clause, but the compiler is not able to check them for consistency.
In Example 6.17, a new checked exception is defined. The checked exception class IntegerDivisionByZero
is defined at (11) by extending the Exception
class. The method main()
calls the method printAverage()
in a try
block at (1). In the if
statement at (9), the method computeAverage()
throws the checked exception IntegerDivisionByZero
defined at (11). Neither the computeAverage()
method nor the printAverage()
method catch the exception, but instead throw it to their caller, as declared in the throws
clause in their headers at (6) and (8). The exception propagates to the main()
method. Since the printAverage()
method was called from the context of the try
block at (1) in the main()
method, the exception is successfully matched with its catch
block at (3). The exception is handled and the finally
block at (4) executed, with normal execution proceeding at (5). If the method main()
did not catch the exception, it would have to declare this exception in a throws
clause. In that case, the exception would end up being taken care of by the default exception handler.
Example 6.17 The throws
Clause
public class Average8 {
public static void main(String[] args) {
try { // (1)
printAverage(100, 0); // (2)
} catch (IntegerDivisionByZero idbze) { // (3)
idbze.printStackTrace();
System.out.println("Exception handled in " +
"main().");
} finally { // (4)
System.out.println("Finally done in main().");
}
System.out.println("Exit main()."); // (5)
}
public static void printAverage(int totalSum, int totalNumber)
throws IntegerDivisionByZero { // (6)
int average = computeAverage(totalSum, totalNumber);
System.out.println("Average = " +
totalSum + " / " + totalNumber + " = " + average);
System.out.println("Exit printAverage()."); // (7)
}
public static int computeAverage(int sum, int number)
throws IntegerDivisionByZero { // (8)
System.out.println("Computing average.");
if (number == 0) // (9)
throw new IntegerDivisionByZero("Integer Division By Zero");
return sum/number; // (10)
}
}
class IntegerDivisionByZero extends Exception { // (11)
IntegerDivisionByZero(String str) { super(str); } // (12)
}
Output from the program:
Computing average.
IntegerDivisionByZero: Integer Division By Zero
at Average8.computeAverage(Average8.java:33)
at Average8.printAverage(Average8.java:22)
at Average8.main(Average8.java:7)
Exception handled in main().
Finally done in main().
Exit main().
The exception type specified in the throws
clause in the method header can be a superclass type of the actual exceptions thrown, i.e., the exceptions thrown must be assignable to the type of the exceptions specified in the throws
clause. If a method can throw exceptions of the type A
, B
, and C
where these are subclasses of type D
, then the throws
clause can either specify A
, B
, and C
or just specify D
. In the printAverage()
method, the method header could specify the superclass Exception
of the subclass IntegerDivisionByZero
in a throws
clause.
public static void printAverage(int totalSum, int totalNumber) throws Exception {
/* ... */
}
It is generally considered bad programming style to specify exception superclasses in the throws
clause of the method header when the actual exceptions thrown in the method are instances of their subclasses. Programmers will be deprived of information about which specific subclass exceptions can be thrown in, unless they have access to the source code.
A subclass can override a method defined in its superclass by providing a new implementation (see Section 7.2, p. 288). What happens when an inherited method with a list of exceptions in its throws
clause is overridden in a subclass? The method definition in the subclass can omit the throws
clause, or it can specify checked exception classes in a throws
clause that covers the checked exceptions from the throws
clause of the inherited method. This means that an overriding method cannot allow more checked exceptions in its throws
clause than the inherited method does. Allowing more checked exceptions in the overriding method would create problems for clients who already deal with the exceptions specified in the inherited method. Such clients would be ill prepared if an object of the subclass (under the guise of polymorphism) threw a checked exception they were not prepared for.
In the following code, the method superclassMethodX
in superclass A
is overridden in subclass B
. The throws
clause of the method in subclass B
at (2) is a subset of the exceptions specified for the method in the superclass at (1). However, there are no restrictions on specifying unchecked exceptions in the throws
clause of the overriding method.
class A {
// ...
protected void superclassMethodX()
throws FirstException, SecondException, ThirdException {/* ... */} // (1)
// ...
}
class B extends A {
// ...
protected void superclassMethodX()
throws FirstException, ThirdException { /* ... */ } // (2)
// ...
}
Handling of checked exceptions in initializers is covered in Section 9.7 on page 406.
6.23 Which digits, and in what order, will be printed when the following program is run?
public class MyClass {
public static void main(String[] args) {
int k=0;
try {
int i = 5/k;
} catch (ArithmeticException e) {
System.out.println("1");
} catch (RuntimeException e) {
System.out.println("2");
return;
} catch (Exception e) {
System.out.println("3");
} finally {
System.out.println("4");
}
System.out.println("5");
}
}
Select the one correct answer.
(a) The program will only print 5
.
(b) The program will only print 1
and 4
, in that order.
(c) The program will only print 1
, 2
, and 4
, in that order.
(d) The program will only print 1
, 4
, and 5
, in that order.
(e) The program will only print 1
, 2
, 4
, and 5
, in that order.
(f) The program will only print 3
and 5
, in that order.
6.24 Given the following program, which statements are true?
public class Exceptions {
public static void main(String[] args) {
try {
if (args.length == 0) return;
System.out.println(args[0]);
} finally {
System.out.println("The end");
}
}
}
Select the two correct answers.
(a) If run with no arguments, the program will produce no output.
(b) If run with no arguments, the program will print "The end"
.
(c) The program will throw an ArrayIndexOutOfBoundsException
.
(d) If run with one argument, the program will simply print the given argument.
(e) If run with one argument, the program will print the given argument followed by "The end"
.
6.25 What will be the result of attempting to compile and run the following program?
public class MyClass {
public static void main(String[] args) {
RuntimeException re = null;
throw re;
}
}
Select the one correct answer.
(a) The code will fail to compile because the main()
method does not declare that it throws RuntimeException
in its declaration.
(b) The program will fail to compile because it cannot throw re
.
(c) The program will compile without error and will throw java.lang.RuntimeException
when run.
(d) The program will compile without error and will throw java.lang.NullPointerException
when run.
(e) The program will compile without error and will run and terminate without any output.
6.26 Which statements are true?
Select the two correct answers.
(a) If an exception is not caught in a method, the method will terminate and normal execution will resume.
(b) An overriding method must declare that it throws the same exception classes as the method it overrides.
(c) The main()
method of a program can declare that it throws checked exceptions.
(d) A method declaring that it throws a certain exception class may throw instances of any subclass of that exception class.
(e) finally
blocks are executed if, and only if, an exception gets thrown while inside the corresponding try
block.
6.27 Which digits, and in what order, will be printed when the following program is compiled and run?
public class MyClass {
public static void main(String[] args) {
try {
f();
} catch (InterruptedException e) {
System.out.println("1");
throw new RuntimeException();
} catch (RuntimeException e) {
System.out.println("2");
return;
} catch (Exception e) {
System.out.println("3");
} finally {
System.out.println("4");
}
System.out.println("5");
}
// InterruptedException is a direct subclass of Exception.
static void f() throws InterruptedException {
throw new InterruptedException("Time for lunch.");
}
}
Select the one correct answer.
(a) The program will print 5
.
(b) The program will print 1
and 4
, in that order.
(c) The program will print 1
, 2
, and 4
, in that order.
(d) The program will print 1
, 4
, and 5
, in that order.
(e) The program will print 1
, 2
, 4
, and 5
, in that order.
(f) The program will print 3
and 5
, in that order.
6.28 Which digits, and in what order, will be printed when the following program is run?
public class MyClass {
public static void main(String[] args) throws InterruptedException {
try {
f();
System.out.println("1");
} finally {
System.out.println("2");
}
System.out.println("3");
}
// InterruptedException is a direct subclass of Exception.
static void f() throws InterruptedException {
throw new InterruptedException("Time to go home.");
}
}
Select the one correct answer.
(a) The program will print 2
and throw InterruptedException.
(b) The program will print 1
and 2
, in that order.
(c) The program will print 1
, 2
, and 3
, in that order.
(d) The program will print 2
and 3
, in that order.
(e) The program will print 3
and 2
, in that order.
(f) The program will print 1
and 3
, in that order.
6.29 What is wrong with the following code?
public class MyClass {
public static void main(String[] args) throws A {
try {
f();
} finally {
System.out.println("Done.");
} catch (A e) {
throw e;
}
}
public static void f() throws B {
throw new B();
}
}
class A extends Throwable {}
class B extends A {}
Select the one correct answer.
(a) The main()
method must declare that it throws B
.
(b) The finally
block must follow the catch
block in the main()
method.
(c) The catch
block in the main()
method must declare that it catches B
rather than A
.
(d) A single try
block cannot be followed by both a finally
and a catch
block.
(e) The declaration of class A
is illegal.
6.30 What is the minimal list of exception classes that the overriding method f()
in the following code must declare in its throws
clause before the code will compile correctly?
class A {
// InterruptedException is a direct subclass of Exception.
void f() throws ArithmeticException, InterruptedException {
div(5, 5);
}
int div(int i, int j) throws ArithmeticException {
return i/j;
}
}
public class MyClass extends A {
void f() /* throws [...list of exceptions...] */ {
try {
div(5, 0);
} catch (ArithmeticException e) {
return;
}
throw new RuntimeException("ArithmeticException was expected.");
}
}
Select the one correct answer.
(a) Does not need to specify any exceptions.
(b) Needs to specify that it throws ArithmeticException
.
(c) Needs to specify that it throws InterruptedException
.
(d) Needs to specify that it throws RuntimeException
.
(e) Needs to specify that it throws both ArithmeticException
and InterruptedException
.
6.31 What, if anything, would cause the following code not to compile?
class A {
void f() throws ArithmeticException {
//...
}
}
public class MyClass extends A {
public static void main(String[] args) {
A obj = new MyClass();
try {
obj.f();
} catch (ArithmeticException e) {
return;
} catch (Exception e) {
System.out.println(e);
throw new RuntimeException("Something wrong here");
}
}
// InterruptedException is a direct subclass of Exception.
void f() throws InterruptedException {
//...
}
}
Select the one correct answer.
(a) The main()
method must declare that it throws RuntimeException
.
(b) The overriding f()
method in MyClass
must declare that it throws ArithmeticException
, since the f()
method in class A
declares that it does.
(c) The overriding f()
method in MyClass
is not allowed to throw InterruptedException
, since the f()
method in class A
does not throw this exception.
(d) The compiler will complain that the catch(ArithmeticException)
block shadows the catch(Exception)
block.
(e) You cannot throw exceptions from a catch
block.
(f) Nothing is wrong with the code, it will compile without errors.
In Java, assertions can be used to document and validate assumptions made about the state of the program at designated locations in the code. Each assertion contains a boolean expression that is expected to be true
when the assertion is executed. If this assumption is false
, the JVM throws a special error represented by the AssertionError
class. The assertion facility uses the exception handling mechanism to propagate the error. Since the assertion error signals failure in program behavior, it should not be caught programmatically, but allowed to propagate to the top level. As we shall see later in this section, the assertion facility can be enabled or disabled at runtime, i.e., we can choose whether assertions should be executed or not at runtime.
The assertion facility is an invaluable aid in implementing correct programs (i.e., programs that adhere to their specifications). It should not be confused with the exception handling mechanism that aids in developing robust programs (i.e., programs that handle unexpected situations gracefully). Used judiciously, the two mechanisms facilitate programs that are reliable.
The following two forms of the assert
statement can be used to specify assertions:
assert <boolean expression> ; // the simple form
assert <boolean expression> : <message expression> ; // the augmented form
The <boolean expression> can be a boolean
or a Boolean
expression. In the latter case, its value is unboxed to a boolean
value. If assertions are enabled (see p. 269), the execution of an assert
statement proceeds as shown in Figure 6.14. The two forms are essentially equivalent to the following code, respectively:
if (<assertions enabled> && !<boolean expression>) // the simple form
throw new AssertionError();
if (<assertions enabled> && !<boolean expression>) // the augmented form
throw new AssertionError(<message expression>);
If assertions are enabled, then the <boolean expression> is evaluated. If its value is true
, execution continues normally after the assert
statement. However, if it is false
, an AssertionError
is thrown and propagated. In the simple form, the AssertionError
does not provide any detailed message about the assertion failure.
The augmented form specifies a <message expression> that can be used to provide a detailed error message. In the augmented form, if the assertion is false
, the <message expression> is evaluated and its value passed to the appropriate AssertionError
constructor. The <message expression> must evaluate to a value (i.e., either a primitive or a reference value). The AssertionError
constructor invoked converts the value to a textual representation. In particular, the <message expression> cannot call a method that is declared void
. The compiler will flag this as an error. The augmented form is recommended, as it allows a detailed error message to be included in reporting the assertion failure.
Example 6.18 illustrates using assertions. Statements at (2), (3), and (4) in class Speed
are all assert
statements. In this particular context of calculating the speed, it is required that the values satisfy the assumptions at (2), (3), and (4) in the private method calcSpeed()
. The simple form of the assert
statement is used at (2) and (4).
assert distance >= 0.0; // (2)
...
assert speed >= 0.0; // (4)
The augmented form is used at (3).
assert time > 0.0 : "Time is not a positive value: " + time; // (3)
The augmented form at (3) is equivalent to the following line of code, assuming assertions have been enabled at runtime:
if (time <= 0.0) throw new AssertionError("Time is not a positive value: " + time);
The java.lang.AssertionError
class is a subclass of java.lang.Error
(see Figure 6.9). Thus AssertionError
s are unchecked. They can be explicitly caught and handled using the try-catch
construct, and the execution continues normally, as one would expect. However, Errors
are seldom caught and handled by the program, and the same applies to AssertionError
s. Catching these errors would defeat the whole purpose of the assertion facility.
In addition to the default constructor (invoked by the simple assert
form), the AssertionError
class provides seven single-parameter constructors: six for the primitive data types (byte
and short
being promoted to int
) and one for object references (type Object
). The type of the <message expression> used in the augmented assertion statement determines which of the overloaded constructors is invoked. It is not possible to query the AssertionError
object for the actual value passed to the constructor. However, the method getMessage()
will return the textual representation of the value.
Example 6.18 Using Assertions
public class Speed {
public static void main(String[] args) {
Speed objRef = new Speed();
double speed = objRef.calcSpeed(-12.0, 3.0); // (1a)
// double speed = objRef.calcSpeed(12.0, -3.0); // (1b)
// double speed = objRef.calcSpeed(12.0, 2.0); // (1c)
// double speed = objRef.calcSpeed(12.0, 0.0); // (1d)
System.out.println("Speed (km/h): " + speed);
}
/** Requires distance >= 0.0 and time > 0.0 */
private double calcSpeed(double distance, double time) {
assert distance >= 0.0; // (2)
assert time > 0.0 : "Time is not a positive value: " + time; // (3)
double speed = distance / time;
assert speed >= 0.0; // (4)
return speed;
}
}
The assertion facility was introduced in Java 1.4. Prior to Java 1.4, assert
was an identifier and not a keyword. Starting with Java 1.4, it could only be used as a keyword in the source code. Also starting with Java 1.5, the javac
compiler will compile assertions by default. This means that incorrect use of the keyword assert
will be flagged as a compile time error, e.g., if assert
is used as an identifier.
Source code that uses assert
as an identifier can still be compiled, but then it cannot use assert
as a keyword. The option -source 1.3
must be specified with the javac
command. This option instructs the compiler that the source code is compatible to the Java release 1.3. (Other Java releases from 1.3 onwards can be specified with this option.)
The following program uses assert
as an identifier:
// File: Legacy.java
public class Legacy {
public static void main(String[] args) {
double assert = 1.3;
System.out.println("Not assertive enough with " + assert);
}
}
Compiling the file Legacy.java
and running the Legacy
class above gives the following results:
>javac -source 1.3 Legacy.java
Legacy.java:4: warning: as of release 1.4, 'assert' is a keyword, and may not be
used as an identifier
(use -source 1.4 or higher to use 'assert' as a keyword)
double assert = 1.3;
^
Legacy.java:5: warning: as of release 1.4, 'assert' is a keyword, and may not be
used as an identifier
(use -source 1.4 or higher to use 'assert' as a keyword)
System.out.println("Not assertive enough with " + assert);
^
2 warnings
>java Legacy
Not assertive enough with 1.3
The class Legacy
compiles with warnings, but the code runs without any problem. However, compiling the file Speed.java
(Example 6.18, p. 267) gives the following result:
>javac -source 1.3 Speed.java
Speed.java:15: warning: as of release 1.4, 'assert' is a keyword, and may not be
used as an identifier
(use -source 1.4 or higher to use 'assert' as a keyword)
assert distance >= 0.0; // (2)
^
Speed.java:15: ';' expected
assert distance >= 0.0; // (2)
^
...
4 errors
3 warnings
The compiler rejects assert
statements in the source. It will also warn about the use of the keyword assert
as an identifier. In other words, source code that contains the keyword assert
as an identifier will compile (barring any other errors), but it will also result in a warning.
Enabling assertions means they will be executed at runtime. By default, assertions are disabled. Their execution is then effectively equivalent to empty statements. This means that disabled assertions carry an insignificant performance penalty, although they add storage overhead to the byte code of a class. Typically, assertions are enabled during development and left disabled once the program is deployed. Since assertions are already in the compiled code, they can be turned on whenever needed.
Two options are provided by the java
command to enable and disable assertions with various granularities. The option -enableassertions
, or its short form -ea
, enables assertions, and the option -disableassertions
, or its short form -da
, disables assertions at various granularities. The granularities that can be specified are shown in Table 6.2.
The -ea
option means that all non-system classes loaded during the execution of the program have their assertions enabled. A system class is a class that is in the Java platform libraries. For example, classes in the java.*
packages are system classes. A system class is loaded directly by the JVM.
Note that class files not compiled with an assertion-aware compiler are not affected, whether assertions are enabled or disabled. Also, once a class has been loaded and initialized at runtime, its assertion status cannot be changed.
Assuming that the file Speed.java
(Example 6.18, p. 267) has been compiled, all assertions in non-system classes required for execution (of which Speed
class is one) can be enabled, and the program run as follows:
>java -ea Speed
Exception in thread "main" java.lang.AssertionError
at Speed.calcSpeed(Speed.java:15)
at Speed.main(Speed.java:6)
Since the distance is negative at (1a), the assertion at (2) fails in Example 6.18. An AssertionError
is thrown, which is propagated, being finally caught by the default exception handler and resulting in the stack trace being printed on the console.
All assertions (in all non-system classes) can be disabled during the execution of the Speed
class.
>java -da Speed
Speed (km/h): -4.0
In this case, this is effectively equivalent to running the program with neither the -ea
nor the -da
options.
>java Speed
Speed (km/h): -4.0
If we comment-out (1a) and uncomment (1b) in Example 6.18 and run the program with the options enabled, we get the following behavior from the program.
>java -ea Speed
Exception in thread "main" java.lang.AssertionError: Time is not a positive value:
-3.0
at Speed.calcSpeed(Speed.java:16)
at Speed.main(Speed.java:7)
We see that the value of the <message expression> in the augmented assertion at (3) is written on the console, together with the stack trace, because this assertion failed.
Assume that we have a program called Trickster
in the unnamed package, that uses the wizard
package shown in Figure 6.15 (same as Figure 4.2 on page 105).
The following command line will only enable assertions for all classes in the package wizard.pandorasBox
and its subpackage wizard.pandorasBox.artifacts
. The assertions in the class Trickster
are not enabled.
>java -ea:wizard.pandorasBox... Trickster
Without the ...
notation, the package name will be interpreted as a class name. Non-existent package names specified in the command line are silently accepted, but simply have no consequences during execution.
The following command line will only enable assertions in the unnamed package and, thereby, the assertions in the class Trickster
, since this class resides in the unnamed package.
>java -ea:... Trickster
Note that the package option applies to the package specified and all its subpackages, recursively.
The following command line will only enable assertions in the Trickster
class.
>java -ea:Trickster Trickster
The following command line will only enable assertions in the specified class wizard.pandorasBox.artifacts.Ailment
, and no other class.
>java -ea:wizard.pandorasBox.artifacts.Ailment Trickster
The java
command can contain multiple instances of the options, each specifying its own granularity. The options are then processed in order of their specification from left to right, before any classes are loaded. The latter options take priority over former options. This allows a fine-grained control of what assertions are enabled at runtime. The following command line will enable assertions for all classes in the package wizard.pandorasBox
and its subpackage wizard.pandorasBox.artifacts
, but disable them in the class wizard.pandorasBox.artifacts.Ailment
.
>java -ea:wizard.pandorasBox... -da:wizard.pandorasBox.artifacts.Ailment Trickster
The following commands all enable assertions in the class wizard.spells.Baldness
.
>java -ea Trickster
>java -ea:wizard... Trickster
>java -ea:wizard.spells... Trickster
>java -ea:wizard.spells.Baldness Trickster
It is worth noting that inheritance (see Section 7.1, p. 284) has no affect on the execution of assertions. Assertions are enabled or disabled on a per-class basis. Whether assertions in the superclass will be executed through code inherited by the subclass depends entirely on the superclass. In the following command line, assertions from the superclass wizard.pandorasBox.artifacts.Ailment
will not be executed, although assertions for the subclass wizard.spells.Baldness
are enabled:
>java -ea -da:wizard.pandorasBox.artifacts.Ailment Trickster
In order to enable or disable assertions in all system classes, we can use the options shown in Table 6.3. Enabling assertions in system classes can be useful to shed light on internal errors reported by the JVM. In the following command line, the first option -esa
will enable assertions for all system classes. The second option -ea:wizard...
will enable assertions in the package wizard
and its subpackages wizard.pandorasBox
, wizard.pandorasBox.artifacts
and wizard.spells
, but the third option -da:wizard.pandorasBox.artifacts...
will disable them in the package wizard.pandorasBox.artifacts
.
>java -esa -ea:wizard... -da:wizard.pandorasBox.artifacts... Trickster
Assertions should have no side effects that can produce adverse behavior in the code, whether enabled or not. The assertion facility is a defensive mechanism, meaning that it should only be used to test the code, and should not be employed after the code is delivered. The program should exhibit the same behavior whether assertions are enabled or disabled. The program should not rely on any computations done within an assertion statement. With assertions enabled, the following statement would be executed, but if assertions were disabled, it could have dire consequences.
assert reactor.controlCoreTemperature();
Assertions should also not be used to validate information supplied by a client. A typical example is argument checking in public methods. Argument checking is part of such a method’s contract, which could be violated if the assertions were disabled. A special case is program arguments on the command line. Their validation should be enforced by exception handling, and not by assertions. Another drawback is that assertion failures can only provide limited information about the cause of any failure, in the form of an AssertionError
. Appropriate argument checking can provide more suitable information about erroneous arguments, in the form of specific exceptions such as IllegalArgumentException
, IndexOutOfBoundsException
, or NullPointerException.
The rest of this section illustrates useful idioms that employ assertions.
Very often assumptions about the program are documented as comments in the code. The following code at (1) makes the assumption that the variable status
must be negative for the last else
clause to be executed.
int status = ref1.compareTo(ref2);
if (status == 0) {
...
} else if (status > 0) {
...
} else { // (1) status must be negative.
...
}
This assumption is an internal invariant and can be verified using an assertion, as shown at (2) below.
int status = ref1.compareTo(ref2);
if (status == 0) {
...
} else if (status > 0) {
...
} else {
assert status < 0 : status; // (2)
...
}
Often an alternative action is chosen, based on a value that is guaranteed to be one of a small set of predefined values. A switch
statement with no default
clause is a typical example. The value of the switch
expression is guaranteed to be one of the case
labels and the default
case is omitted, as the following code shows.
switch (trinityMember) {
case THE_FATHER:
...
break;
case THE_SON:
...
break;
case THE_HOLY_GHOST:
...
break;
}
A default
clause that executes an assertion can be used to formulate this invariant.
default:
assert false : trinityMember;
If assertions are enabled, an AssertionError
will signal the failure in case the trinity no longer holds. Note that using enum
constants in the switch
statement above makes the default
clause unnecessary.
However, the previous code causes a compile-time error in a non-void
method if all case
labels return a value and no return
statement follows the switch
statement.
switch (trinityMember) {
case THE_FATHER:
return psalm101;
case THE_SON:
return psalm102;
case THE_HOLY_GHOST:
return psalm103;
default:
assert false: trinityMember;
}
return psalm100; // (3) Compile time error if omitted.
Without the return
statement at (3) and with assertions disabled, the method could return without a value, violating the fact that it is a non-void
method. Explicitly throwing an AssertionError
rather than using an assert
statement in the default
clause, would be a better option in this case.
default:
throw new AssertionError(trinityMember);
Control flow invariants can be used to test assumptions about the flow of control in the program. The following idiom can be employed to explicitly test that certain locations in the code will never be reached.
assert false : "This line should never be reached.";
If program control does reach this statement, assertion failure will detect it.
In the following code, the assumption is that execution never reaches the end of the method declaration indicated by (1).
private void securityMonitor() {
// ...
while (alwaysOnDuty) {
// ...
if (needMaintenance)
return;
// ...
}
// (1) This line should never be reached.
}
The previous assertion can be inserted after (1) to check the assumption.
Care should be taken in using this idiom, as the compiler can flag the assert
statement at this location as being unreachable. For example, if the compiler can deduce that the while
condition will always be true
, it will flag the assert
statement as being unreachable.
The assertion facility can be used to practice a limited form of programming-by-contract. For example, the assertion facility can be used to check that methods comply with their contract. Preconditions define assumptions for the proper execution of a method when it is invoked. As discussed earlier, assertions should not be used to check arguments in public methods. For non-public methods, preconditions can be checked at the start of method execution.
private void adjustReactorThroughput(int increment) {
// Precondition:
assert isValid(increment) : "Throughput increment invalid.";
// Proceed with the adjustment.
// ...
}
Postconditions define assumptions about the successful completion of a method. Postconditions in any method can be checked by assertions executed just before returning from the method. For example, if the method adjustReactorThroughPut()
guarantees that the reactor core is in a stable state after its completion, we can check this postcondition using an assertion.
private void adjustReactorThroughput(int increment) {
// Precondition:
assert isValid(increment) : "Throughput increment invalid.";
// Proceed with the adjustment.
// ...
// Postcondition -- the last action performed before returning.
assert isCoreStable() : "Reactor core not stable.";
}
Section 8.4 (p. 371) provides an example using a local class where data can be saved before doing a computation, so that it can later be used to check a postcondition.
If minimizing the size of the class file is crucial, then the following conditional compilation idiom should be used to insert assertions in the source code:
final static boolean COMPILE_ASSERTS = false;
...
if (COMPILE_ASSERTS)
assert whatEverYouWant; // Not compiled if COMPILE_ASSERTS is false.
...
It is possible to enforce that a class be loaded and initialized only if its assertions are enabled. The idiom for this purpose uses a static initializer block (see Section 9.7, p. 406).
static { // Static initializer
boolean assertsAreEnabled = false; // (1)
assert assertsAreEnabled = true; // (2) utilizing side effect
if (!assertsAreEnabled) // (3)
throw new AssertionError("Enable assertions!");
}
The declaration statement at (1) sets the local variable assertsAreEnabled
to false
. If assertions are enabled, the assert
statement at (2) is executed. The assignment operator sets the variable assertsAreEnabled
to true
as a side effect of evaluating the boolean expression that has the value true
. The assertion at (2) is, of course, true
. No exception is thrown by the if
statement at (3). However, if assertions are disabled, the assert
statement at (2) is never executed. As the variable assertsAreEnabled
is false
, the if
statement at (3) throws an exception. The static initializer is placed first in the class declaration, so that it is executed first during class initialization.
6.32 Assuming assertions are enabled, which of these assertion statements will throw an error?
Select the two correct answers.
(a) assert true : true;
(b) assert true : false;
(c) assert false : true;
(d) assert false : false;
6.33 Which of the following are valid runtime options?
Select the two correct answers.
(a) -ae
(b) -enableassertions
(c) -source 1.6
(d) -disablesystemassertions
(e) -dea
6.34 What is the class name of the exception thrown by an assertion statement?
Select the one correct answer.
(a) Depends on the assertion statement.
(b) FailedAssertion
(c) AssertionException
(d) RuntimeException
(e) AssertionError
(f) Error
6.35 What can cause an assertion statement to be ignored?
Select the one correct answer.
(a) Nothing.
(b) Using appropriate compiler options.
(c) Using appropriate runtime options.
(d) Using both appropriate compiler and runtime options.
6.36 Given the following method, which statements will throw an exception, assuming assertions are enabled?
static int inv(int value) {
assert value > -50 : value < 100;
return 100/value;
}
Select the two correct answers.
(a) inv(-50);
(b) inv(0);
(c) inv(50);
(d) inv(100);
(e) inv(150);
6.37 Which runtime options would cause assertions to be enabled for the class org.example.ttp.Bottle
?
Select the two correct answers.
(a) -ea
(b) -ea:Bottle
(c) -ea:org.example
(d) -ea:org...
(e) -enableexceptions:org.example.ttp.Bottle
(f) -ea:org.example.ttp
6.38 What will be the result of compiling and running the following code with assertions enabled?
public class TernaryAssertion {
public static void assertBounds(int low, int high, int value) {
assert ( value > low ? value < high : false )
: (value < high ? "too low" : "too high" );
}
public static void main(String[] args) {
assertBounds(100, 200, 150);
}
}
Select the one correct answer.
(a) The compilation fails because the method name assertBounds
cannot begin with the keyword assert
.
(b) The compilation fails because the assert
statement is invalid.
(c) The compilation succeeds and the program runs without errors.
(d) The compilation succeeds and an AssertionError
with the error message "too low"
is thrown.
(e) The compilation succeeds and an AssertionError
with the error message "too high"
is thrown.
6.39 Which statements are true about the AssertionError
class?
Select the two correct answers.
(a) It is a checked exception.
(b) It has a method named toString
.
(c) It has a method named getErrorMessage
.
(d) It can be caught by a try
-catch
construct.
6.40 Which of these classes is the direct superclass of AssertionError
?
Select the one correct answer.
(a) Object
(b) Throwable
(c) Exception
(d) Error
(e) RuntimeError
6.41 Given the following command, which classes would have assertions enabled?
java -ea -da:com... net.example.LaunchTranslator
Select the two correct answers.
(a) com.example.Translator
(b) java.lang.String
(c) dot.com.Boom
(d) net.example.LaunchTranslator
(e) java.lang.AssertionError
The following information was included in this chapter:
• discussion of the selection statements: if
, if-else
, switch
• discussion of the iteration statements: for(;;)
, for(:)
, while
, do-while
• discussion of the transfer statements: break
, continue
, return
• discussion of exception handling and exception classes in the core APIs
• defining new exception types
• discussion of the try
-catch
-finally
construct and control flow paths through the construct
• throwing exceptions programmatically with the throw
statement
• using the throws
clause to specify checked exceptions
• discussion of the assert
statement
• using, compiling, and executing assertions
6.1 Create different versions of a program that finds all the primes below 100. Create one version that only uses the for(;;)
loop (i.e., no while
or do-while
). Create another version that only uses the while
loop.
6.2 Here is a skeleton of a system for simulating a nuclear power plant. Implement the methods in the class named Control
. Modify the method declarations if necessary. The Javadoc comments for each method give a description of what the implementation should do. Some of the methods in the other classes have unspecified implementations. Assume that these methods have been properly implemented and provide hooks to the rest of the system.
package energy;
/** A PowerPlant with a reactor core. */
public class PowerPlant {
/** Each power plant has a reactor core. This has package
accessibility so that the Control class that is defined in
the same package can access it. */
Reactor core;
/** Initializes the power plant, creates a reactor core. */
PowerPlant() {
core = new Reactor();
}
/** Sound the alarm to evacuate the power plant. */
public void soundEvacuateAlarm() {
// ... implementation unspecified ...
}
/** Get the level of reactor output that is most desirable at this time.
(Units are unspecified.) */
public int getOptimalThroughput() {
// ... implementation unspecified ...
return 0;
}
/** The main entry point of the program: sets up a PowerPlant
object and a Control object and lets the Control object run the
power plant. */
public static void main(String[] args) {
PowerPlant plant = new PowerPlant();
Control ctrl = new Control(plant);
ctrl.runSystem();
}
}
/** A reactor core that has a throughput that can be either decreased or
increased. */
class Reactor {
/** Get the current throughput of the reactor. (Units are unspecified.) */
public int getThroughput() {
// ... implementation unspecified ...
return 0;
}
/** @returns true if the reactor status is critical, false otherwise. */
public boolean isCritical() {
// ... implementation unspecified ...
return false;
}
/** Ask the reactor to increase throughput. */
void increaseThroughput() throws ReactorCritical {
// ... implementation unspecified ...
}
/** Ask the reactor to decrease throughput. */
void decreaseThroughput() {
// ... implementation unspecified ...
}
}
/** This exception class should be used to report that the reactor status is
critical. */
class ReactorCritical extends Exception {}
/** A controller that will manage the power plant and make sure that the reactor
runs with optimal throughput. */
class Control {
PowerPlant thePlant;
final static int TOLERANCE = 10;
public Control(PowerPlant p) {
thePlant = p;
}
/** Run the power plant by continuously monitoring the
optimalThroughput and the actual throughput of the reactor. If
the throughputs differ by more than 10 units, i.e. tolerance,
adjust the reactor throughput.
If the reactor status becomes critical, the evacuate alarm is
sounded and the reactor is shut down.
The runSystem() method can handle the reactor core directly
but calls methods needAdjustment(), adjustThroughput(), and shutdown()
instead. */
public void runSystem() {
// ... provide implementation here ...
}
/** Reports whether the throughput of the reactor needs adjusting,
given the target throughput.
This method should also monitor and report if the reactor status becomes
critical.
@return true if the optimal and actual throughput values
differ by more than 10 units. */
public boolean needAdjustment(int target) {
// ... provide implementation here ...
return true;
}
/** Adjust the throughput of the reactor by calling increaseThroughput() and
decreaseThroughput() methods until the actual throughput is within 10
units of the target throughput. */
public void adjustThroughput(int target) {
// ... provide implementation here ...
}
/** Shut down the reactor by lowering the throughput to 0. */
public void shutdown() {
// ... provide implementation here ...
}
}