The mechanism for handling execeptions 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 5.9 shows three typical scenarios of control flow through the try-catch-finally construct.
A few aspects about the syntax of this construct should be noted. The block notation is mandatory. For each try block there can be zero or more catch blocks, but only one finally block. The catch blocks and finally block must always appear in conjunction with a try block, and in the above order. A try block must be followed by either at least one catch block or one finally block. Each catch block defines an exception handler. The header of the catch block takes exactly one argument, which is the exception its block is willing to handle. The exception must be of the Throwable class or one of its subclasses.
Each block (try, catch, or finally) of a try-catch-finally construct can contain arbitrary code, which means that a try-catch-finally construct can also be nested in any such block. However, such nesting can easily make the code difficult to read and is best avoided.
The try block establishes a context that wants its termination to be handled. Termination occurs as a result of encountering an exception, or from successful execution of the code in the try block.
For all exits from the try block, except those due to exceptions, the catch blocks are skipped and control is transferred to the finally block, if one is specified (see (1) in Figure 5.9).
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 5.9). If no catch block matches the thrown exception, control is transferred to the finally block, if one is specified (see (3) in Figure 5.9).
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 6.6, p. 260). 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 5.10, 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. Execution of the program is illustrated in Figure 5.10, 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 5.9.
public class Average2 { 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); } 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 5.10 with the following call in (1):
printAverage(100, 0)
an ArithmeticException is thrown by the integer division in method computeAverage(). From Figure 5.11 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 5.9.
In Example 5.11, 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 5.12, which shows that the ArithmeticException is first propagated to the catch block in the printAverage() method. But since this catch block cannot handle this exception, it is propagated further to the catch block in the main() method, where it is caught and handled. Normal execution continues at (6) after the exception is handled.
Note that the execution of try block at (7) in the printAverage() method is never completed: the statment at (9) is never executed. The catch block at (10) is skipped. The execution of the printAverage() method is aborted: the statment at (13) is never executed, and the exception is propagated. This corresponds to Scenario 3 in Figure 5.9.
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 6.6, p. 260). In the body of the catch block, the exception object can be queried like any other object by using the argument name. The javac compiler also complains if a catch block for a superclass exception shadows the catch block for a subclass exception, as the catch block of the subclass exception will never be executed. The following example shows incorrect order of the catch blocks at (1) and (2), which will result in a compile time error: the superclass Exception will shadow the subclass ArithmeticException.
... // Compiler complains catch (Exception e) { // (1) superclass System.out.println(e); } catch (ArithmeticException e) { // (2) subclass System.out.println(e); } ...
If the try block is executed, then the finally block is guaranteed to be executed, regardless of whether any catch block was executed. Since the finally block is always executed before control transfers to its final destination, it can be used to specify any clean-up code (for example, to free resources such as, files, net connections).
A try-finally construct can be used to control the interplay between two actions that must be executed in the right order, possibly with other intervening actions.
int sum = -1; try { sum = sumNumbers(); // other actions } finally { if (sum >= 0) calculateAverage(); }
The code above guarantees that if the try block is entered sumNumbers() will be executed first and then later calculateAverage() will be executed in the finally block, regardless of how execution proceeds in the try block. As the operation in calculateAverage() is dependent on the success of sumNumbers(), this is checked by the value of the sum variable before calling calculateAverage(). catch blocks can, of course, be included 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, then the execution of the try block or any catch block determines how execution proceeds after the finally block (see Figure 5.9, p. 189).
If there is no exception thrown during execution of the try block or the exception has been handled in a catch block, then 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, then 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, then 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 supersede any value returned by a return statement in the try block or a catch block.
Output of Example 5.12 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).
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 call printAverage(100, 20) at (1):
Computing average. Average = 100 / 20 = 5 Finally done. Exit printAverage(). Exit main().
Output from the program, with 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 5.13. 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 5.12 and Example 5.13.
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 5.14 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.
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().