6 Control Flow

6.1 Overview of Control Flow Statements

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.

6.2 Selection Statements

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

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.

Figure 6.1 Activity Diagram for if Statements

Activity Diagram for if Statements

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

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 ifs 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.

The switch Statement

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 label1<statement1>
  case label2<statement2>
  ...
  case labeln<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.

Figure 6.2 Activity Diagram for a switch Statement

Activity Diagram for a switch Statement

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!

Review Questions

Review Questions

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.

6.3 Iteration Statements

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 while Statement

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.

Figure 6.3 Activity Diagram for the while Statement

Activity Diagram for the while Statement

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 do-while Statement

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.

Figure 6.4 Activity Diagram for the do-while Statement

Activity Diagram for the 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(;;) Statement

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>
}

Figure 6.5 Activity Diagram for the for Statement

Activity Diagram for the for Statement

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 for(:) Statement

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.

Figure 6.6 Enhanced for Statement

Enhanced for Statement

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.

6.4 Transfer Statements

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.

Labeled Statements

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

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

The continue Statement

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

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.

Table 6.1 The return Statement

return Statement

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.
  }
}

Review Questions

Review Questions

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)   

{
  int sum = 0;
  for (int[] row : nums)
    for (int val : row)

      sum += val;
  System.out.println(sum);
}

(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

6.5 Stack-Based Execution and Exception Propagation

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.

Figure 6.7 Method Execution

Method 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 Exception Propagation

Exception Propagation

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

6.6 Exception Types

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.

Figure 6.9 Partial Exception Inheritance Hierarchy

Partial Exception Inheritance Hierarchy

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 Exception Class

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.

ClassNotFoundException

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.

The RuntimeException Class

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.

ArithmeticException

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.

ArrayIndexOutOfBoundsException

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.

ClassCastException

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.

IllegalArgumentException and NumberFormatException

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.

IllegalStateException

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.

NullPointerException

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 Error Class

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.

AssertionError

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.

ExceptionInInitializerError

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).

IOError

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).

NoClassDefFoundError

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.

StackOverflowError

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.

Checked and Unchecked Exceptions

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.

Defining New 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.

6.7 Exception Handling: try, catch, and finally

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.

Figure 6.10 The try-catch-finally Construct

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

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).

The catch Block

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.

Figure 6.11 Exception Handling (Scenario 1)

Exception Handling (Scenario 1)

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.

Figure 6.12 Exception Handling (Scenario 2)

Exception Handling (Scenario 2)

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.

Figure 6.13 Exception Handling (Scenario 3)

Exception Handling (Scenario 3)

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);
}
...

The finally Block

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().

6.8 The throw Statement

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().

6.9 The throws Clause

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.

Review Questions

Review Questions

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.

6.10 Assertions

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 assert Statement and the AssertionError Class

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>);

Figure 6.14 Execution of the Simple assert Statement (with Assertions Enabled)

Execution of the Simple assert Statement (with Assertions Enabled)

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 AssertionErrors 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 AssertionErrors. 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;
  }
}

Compiling Assertions

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.

Option -source 1.3

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.

Runtime Enabling and Disabling of Assertions

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.

Table 6.2 Granularities for Enabling and Disabling Assertions at Runtime

Granularities for Enabling and Disabling Assertions at Runtime

Assertion Execution for All Non-System Classes

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.

Assertion Execution at the Package Level

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

Figure 6.15 Package Hierarchy

Package Hierarchy

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.

Assertion Execution at the Class Level

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

Assertion Execution for All System Classes

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

Table 6.3 Enabling and Disabling Assertions in All System Classes at Runtime

Enabling and Disabling Assertions in All System Classes at Runtime

Using Assertions

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.

Internal Invariants

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

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.

Preconditions and Postconditions

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.

Other Uses

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.

Review Questions

Review Questions

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

Chapter Summary

Chapter Summary

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

Programming Exercises

Programming Exercises

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 ...
  }
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset