Objectives
In this appendix you’ll:
Learn what exceptions are and how they’re handled.
Understand when to use exception handling.
Use try
blocks to delimit code in which exceptions might occur.
throw
exceptions to indicate a problem.
Use catch
blocks to specify exception handlers.
Use the finally
block to release resources.
Become familiar with the exception class hierarchy.
H.2 Example: Divide by Zero without Exception Handling
H.3 Example: Handling ArithmeticException
s and InputMismatchException
s
H.4 When to Use Exception Handling
H.7 Stack Unwinding and Obtaining Information from an Exception Object
Self-Review Exercises | Answers to Self-Review Exercises | Exercises
An exception
is an indication of a problem that occurs during a program’s execution. Exception handling enables you to create applications that can resolve (or handle) exceptions. In many cases, handling an exception allows a program to continute executing as if no problem had been encountered. The features presented in this appendix help you write robust programs that can deal with problems and continue executing or terminate gracefully.
First we demonstrate what happens when errors arise in an application that does not use exception handling. Figure H.1 prompts the user for two integers and passes them to method quotient
, which calculates the integer quotient and returns an int
result. In this example, you’ll see that exceptions are thrown (i.e., the exception occurs) when a method detects a problem and is unable to handle it.
1 // Fig. H.1: DivideByZeroNoExceptionHandling.java
2 // Integer division without exception handling.
3 import java.util.Scanner;
4
5 public class DivideByZeroNoExceptionHandling
6 {
7 // demonstrates throwing an exception when a divide-by-zero occurs
8 public static int quotient( int numerator, int denominator )
9 {
10 return numerator / denominator; // possible division by zero
11 } // end method quotient
12
13 public static void main( String[] args )
14 {
15 Scanner scanner = new Scanner( System.in ); // scanner for input
16
17 System.out.print( "Please enter an integer numerator: " );
18 int numerator = scanner.nextInt();
19 System.out.print( "Please enter an integer denominator: " );
20 int denominator = scanner.nextInt();
21
22 int result = quotient( numerator, denominator );
23 System.out.printf(
24 "
Result: %d / %d = %d
", numerator, denominator, result );
25 } // end main
26 } // end class DivideByZeroNoExceptionHandling
Please enter an integer numerator: 100
Please enter an integer denominator: 7
Result: 100 / 7 = 14
Please enter an integer numerator: 100
Please enter an integer denominator: 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at DivideByZeroNoExceptionHandling.quotient(
DivideByZeroNoExceptionHandling.java:10)
at DivideByZeroNoExceptionHandling.main(
DivideByZeroNoExceptionHandling.java:22)
Please enter an integer numerator: 100
Please enter an integer denominator: hello
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Unknown Source)
at java.util.Scanner.next(Unknown Source)
at java.util.Scanner.nextInt(Unknown Source)
at java.util.Scanner.nextInt(Unknown Source)
at DivideByZeroNoExceptionHandling.main(
DivideByZeroNoExceptionHandling.java:20)
The first sample execution in Fig. H.1 shows a successful division. In the second execution, the user enters the value 0
as the denominator. Several lines of information are displayed in response to this invalid input. This information is known as a stack trace, which includes the name of the exception (java.lang.ArithmeticException
) in a descriptive message that indicates the problem that occurred and the method-call stack (i.e., the call chain) at the time it occurred. The stack trace includes the path of execution that led to the exception method by method. This helps you debug the program. The first line specifies that an ArithmeticException
has occurred. The text after the name of the exception (“/ by zero
”) indicates that this exception occurred as a result of an attempt to divide by zero. Java does not allow division by zero in integer arithmetic. When this occurs, Java throws an ArithmeticException. ArithmeticException
s can arise from a number of different problems in arithmetic, so the extra data (“/ by zero
”) provides more specific information. Java does allow division by zero with floating-point values. Such a calculation results in the value positive or negative infinity, which is represented in Java as a floating-point value (but displays as the string Infinity
or -Infinity
). If 0.0 is divided by 0.0, the result is NaN (not a number), which is also represented in Java as a floating-point value (but displays as NaN
).
Starting from the last line of the stack trace, we see that the exception was detected in line 22 of method main
. Each line of the stack trace contains the class name and method (DivideByZeroNoExceptionHandling.main
) followed by the file name and line number (DivideByZeroNoExceptionHandling.java:22
). Moving up the stack trace, we see that the exception occurs in line 10, in method quotient
. The top row of the call chain indicates the throw point—the initial point at which the exception occurs. The throw point of this exception is in line 10 of method quotient
.
In the third execution, the user enters the string "hello"
as the denominator. Notice again that a stack trace is displayed. This informs us that an InputMismatchException
has occurred (package java.util
). Our prior examples that read numeric values from the user assumed that the user would input a proper integer value. However, users sometimes make mistakes and input noninteger values. An InputMismatchException occurs when Scanner
method nextInt
receives a string
that does not represent a valid integer. Starting from the end of the stack trace, we see that the exception was detected in line 20 of method main
. Moving up the stack trace, we see that the exception occurred in method nextInt
. Notice that in place of the file name and line number, we’re provided with the text Unknown Source
. This means that the so-called debugging symbols that provide the filename and line number information for that method’s class were not available to the JVM—this is typically the case for the classes of the Java API. Many IDEs have access to the Java API source code and will display file names and line numbers in stack traces.
In the sample executions of Fig. H.1 when exceptions occur and stack traces are displayed, the program also exits. This does not always occur in Java—sometimes a program may continue even though an exception has occurred and a stack trace has been printed. In such cases, the application may produce unexpected results. For example, a graphical user interface (GUI) application will often continue executing. The next section demonstrates how to handle these exceptions.
In Fig. H.1 both types of exceptions were detected in method main
. In the next example, we’ll see how to handle these exceptions to enable the program to run to normal completion.
The application in Fig. H.2, which is based on Fig. H.1, uses exception handling to process any ArithmeticException
s and InputMistmatchException
s that arise. The application still prompts the user for two integers and passes them to method quotient
, which calculates the quotient and returns an int
result. This version of the application uses exception handling so that if the user makes a mistake, the program catches and handles (i.e., deals with) the exception—in this case, allowing the user to enter the input again.
1 // Fig. H.2: DivideByZeroWithExceptionHandling.java
2 // Handling ArithmeticExceptions and InputMismatchExceptions.
3 import java.util.InputMismatchException;
4 import java.util.Scanner;
5
6 public class DivideByZeroWithExceptionHandling
7 {
8 // demonstrates throwing an exception when a divide-by-zero occurs
9 public static int quotient( int numerator, int denominator )
10 throws ArithmeticException
11 {
12 return numerator / denominator; // possible division by zero
13 } // end method quotient
14
15 public static void main( String[] args )
16 {
17 Scanner scanner = new Scanner( System.in ); // scanner for input
18 boolean continueLoop = true; // determines if more input is needed
19
20 do
21 {
22 try // read two numbers and calculate quotient
23 {
24 System.out.print( "Please enter an integer numerator: " );
25 int numerator = scanner.nextInt();
26 System.out.print( "Please enter an integer denominator: " );
27 int denominator = scanner.nextInt();
28
29 int result = quotient( numerator, denominator );
30 System.out.printf( "
Result: %d / %d = %d
", numerator,
31 denominator, result );
32 continueLoop = false; // input successful; end looping
33 } // end try
34 catch ( InputMismatchException inputMismatchException )
35 {
36 System.err.printf( "
Exception: %s
",
37 inputMismatchException );
38 scanner.nextLine(); // discard input so user can try again
39 System.out.println(
40 "You must enter integers. Please try again.
" );
41 } // end catch
42 catch ( ArithmeticException arithmeticException )
43 {
44 System.err.printf( "
Exception: %s
", arithmeticException );
45 System.out.println(
46 "Zero is an invalid denominator. Please try again.
" );
47 } // end catch
48 } while ( continueLoop ); // end do...while
49 } // end main
50 } // end class DivideByZeroWithExceptionHandling
Please enter an integer numerator: 100
Please enter an integer denominator: 7
Result: 100 / 7 = 14
Please enter an integer numerator: 100
Please enter an integer denominator: 0
Exception: java.lang.ArithmeticException: / by zero
Zero is an invalid denominator. Please try again.
Please enter an integer numerator: 100
Please enter an integer denominator: 7
Result: 100 / 7 = 14
Please enter an integer numerator: 100
Please enter an integer denominator: hello
Exception: java.util.InputMismatchException
You must enter integers. Please try again.
Please enter an integer numerator: 100
Please enter an integer denominator: 7
Result: 100 / 7 = 14
The first sample execution in Fig. H.2 is a successful one that does not encounter any problems. In the second execution the user enters a zero denominator, and an ArithmeticException
exception occurs. In the third execution the user enters the string "hello"
as the denominator, and an InputMismatchException
occurs. For each exception, the user is informed of the mistake and asked to try again, then is prompted for two new integers. In each sample execution, the program runs successfully to completion.
Class InputMismatchException
is imported in line 3. Class ArithmeticException
does not need to be imported because it’s in package java.lang
. Line 18 creates the boolean
variable continueLoop
, which is true
if the user has not yet entered valid input. Lines 20–48 repeatedly ask users for input until a valid input is received.
Lines 22–33 contain a try block, which encloses the code that might throw
an exception and the code that should not execute if an exception occurs (i.e., if an exception occurs, the remaining code in the try
block will be skipped). A try
block consists of the keyword try
followed by a block of code enclosed in curly braces. [Note: The term “try
block” sometimes refers only to the block of code that follows the try
keyword (not including the try
keyword itself). For simplicity, we use the term “try
block” to refer to the block of code that follows the try
keyword, as well as the try
keyword.] The statements that read the integers from the keyboard (lines 25 and 27) each use method nextInt
to read an int
value. Method nextInt
throws an InputMismatchException
if the value read in is not an integer.
The division that can cause an ArithmeticException
is not performed in the try
block. Rather, the call to method quotient
(line 29) invokes the code that attempts the division (line 12); the JVM throws an ArithmeticException
object when the denominator is zero.
Software Engineering Observation H.1
Exceptions may surface through explicitly mentioned code in a try block, through calls to other methods, through deeply nested method calls initiated by code in a try block or from the Java Virtual Machine as it executes Java bytecodes.
The try
block in this example is followed by two catch
blocks—one that handles an InputMismatchException
(lines 34–41) and one that handles an ArithmeticException
(lines 42–47). A catch block (also called a catch clause or exception handler) catches (i.e., receives) and handles an exception. A catch
block begins with the keyword catch
and is followed by a parameter in parentheses (called the exception parameter, discussed shortly) and a block of code enclosed in curly braces. [Note: The term “catch
clause” is sometimes used to refer to the keyword catch
followed by a block of code, whereas the term “catch
block” refers to only the block of code following the catch
keyword, but not including it. For simplicity, we use the term “catch
block” to refer to the block of code following the catch
keyword, as well as the keyword itself.]
At least one catch
block or a finally block (discussed in Section H.6) must immediately follow the try
block. Each catch
block specifies in parentheses an exception parameter that identifies the exception type the handler can process. When an exception occurs in a try
block, the catch
block that executes is the first one whose type matches the type of the exception that occurred (i.e., the type in the catch
block matches the thrown exception type exactly or is a superclass of it). The exception parameter’s name enables the catch
block to interact with a caught exception object—e.g., to implicitly invoke the caught exception’s toString
method (as in lines 37 and 44), which displays basic information about the exception. Notice that we use the System.err (standard error stream) object to output error messages. By default, System.err
’s print methods, like those of System.out
, display data to the command prompt.
Line 38 of the first catch
block calls Scanner
method nextLine
. Because an InputMismatchException
occurred, the call to method nextInt
never successfully read in the user’s data—so we read that input with a call to method nextLine
. We do not do anything with the input at this point, because we know that it’s invalid. Each catch
block displays an error message and asks the user to try again. After either catch
block terminates, the user is prompted for input. We’ll soon take a deeper look at how this flow of control works in exception handling.
It’s a syntax error to place code between a try block and its corresponding catch blocks.
Each catch block can have only a single parameter—specifying a comma-separated list of exception parameters is a syntax error.
An uncaught exception is one for which there are no matching catch
blocks. You saw uncaught exceptions in the second and third outputs of Fig. H.1. Recall that when exceptions occurred in that example, the application terminated early (after displaying the exception’s stack trace). This does not always occur as a result of uncaught exceptions. Java uses a “multithreaded” model of program execution—each thread is a parallel activity. One program can have many threads. If a program has only one thread, an uncaught exception will cause the program to terminate. If a program has multiple threads, an uncaught exception will terminate only the thread where the exception occurred. In such programs, however, certain threads may rely on others, and if one thread terminates due to an uncaught exception, there may be adverse effects to the rest of the program. Appendix J discusses these issues.
If an exception occurs in a try
block (such as an InputMismatchException
being thrown as a result of the code at line 25 of Fig. H.2), the try
block terminates immediately and program control transfers to the first of the following catch
blocks in which the exception parameter’s type matches the thrown exception’s type. In Fig. H.2, the first catch
block catches InputMismatchException
s (which occur if invalid input is entered) and the second catch
block catches ArithmeticException
s (which occur if an attempt is made to divide by zero). After the exception is handled, program control does not return to the throw point, because the try
block has expired (and its local variables have been lost). Rather, control resumes after the last catch
block. This is known as the termination model of exception handling. Some languages use the resumption model of exception handling, in which, after an exception is handled, control resumes just after the throw point.
Notice that we name our exception parameters (inputMismatchException
and arithmeticException
) based on their type. Java programmers often simply use the letter e
as the name of their exception parameters.
After executing a catch
block, this program’s flow of control proceeds to the first statement after the last catch
block (line 48 in this case). The condition in the do
...while
statement is true
(variable continueLoop
contains its initial value of true
), so control returns to the beginning of the loop and the user is once again prompted for input. This control statement will loop until valid input is entered. At that point, program control reaches line 32, which assigns false
to variable continueLoop
. The try
block then terminates. If no exceptions are thrown in the try
block, the catch
blocks are skipped and control continues with the first statement after the catch
blocks (we’ll learn about another possibility when we discuss the finally
block in Section H.6). Now the condition for the do
...while
loop is false
, and method main
ends.
The try
block and its corresponding catch
and/or finally
blocks form a try statement. Do not confuse the terms “try
block” and “try
statement”—the latter includes the try
block as well as the following catch
blocks and/or finally
block.
As with any other block of code, when a try
block terminates, local variables declared in the block go out of scope and are no longer accessible; thus, the local variables of a try
block are not accessible in the corresponding catch
blocks. When a catch
block terminates, local variables declared within the catch
block (including the exception parameter of that catch
block) also go out of scope and are destroyed. Any remaining catch
blocks in the try
statement are ignored, and execution resumes at the first line of code after the try
...catch
sequence—this will be a finally
block, if one is present.
Now let’s examine method quotient
(Fig. H.2, lines 9–13). The portion of the method declaration located at line 10 is known as a throws clause. It specifies the exceptions the method throws. This clause appears after the method’s parameter list and before the method’s body. It contains a comma-separated list of the exceptions that the method will throw if various problems occur. Such exceptions may be thrown by statements in the method’s body or by methods called from the body. A method can throw exceptions of the classes listed in its throws
clause or of their subclasses. We’ve added the throws
clause to this application to indicate to the rest of the program that this method may throw an ArithmeticException
. Clients of method quotient
are thus informed that the method may throw an ArithmeticException
. You’ll learn more about the throws
clause in Section H.5.
When line 12 executes, if the denominator
is zero, the JVM throws an ArithmeticException
object. This object will be caught by the catch
block at lines 42–47, which displays basic information about the exception by implicitly invoking the exception’s toString
method, then asks the user to try again.
If the denominator
is not zero, method quotient
performs the division and returns the result to the point of invocation of method quotient
in the try
block (line 29). Lines 30–31 display the result of the calculation and line 32 sets continueLoop
to false
. In this case, the try
block completes successfully, so the program skips the catch
blocks and fails the condition at line 48, and method main
completes execution normally.
When quotient
throws an ArithmeticException
, quotient
terminates and does not return a value, and quotient
’s local variables go out of scope (and are destroyed). If quotient
contained local variables that were references to objects and there were no other references to those objects, the objects would be marked for garbage collection. Also, when an exception occurs, the try
block from which quotient
was called terminates before lines 30–32 can execute. Here, too, if local variables were created in the try
block prior to the exception’s being thrown, these variables would go out of scope.
If an InputMismatchException
is generated by lines 25 or 27, the try
block terminates and execution continues with the catch
block at lines 34–41. In this case, method quotient
is not called. Then method main
continues after the last catch
block (line 48).
Exception handling is designed to process synchronous errors, which occur when a statement executes. Common examples we’ll see throughout the book are out-of-range array indices, arithmetic overflow (i.e., a value outside the representable range of values), division by zero, invalid method parameters, thread interruption (as we’ll see in Appendix J) and unsuccessful memory allocation (due to lack of memory). Exception handling is not designed to process problems associated with asynchronous events (e.g., disk I/O completions, network message arrivals, mouse clicks and keystrokes), which occur in parallel with, and independent of, the program’s flow of control.
All Java exception classes inherit directly or indirectly from class Exception, forming an inheritance hierarchy. You can extend this hierarchy with your own exception classes. Class Throwable (a subclass of Object
) is the superclass of class Exception
. Only Throwable
objects can be used with the exception-handling mechanism. Class Throwable
has two subclasses: Exception
and Error
. Class Exception
and its subclasses—for instance, RuntimeException
(package java.lang
) and IOException
(package java.io
)—represent exceptional situations that can occur in a Java program and that can be caught by the application. Class Error and its subclasses represent abnormal situations that happen in the JVM. Most Error
s happen infrequently and should not be caught by applications—it’s usually not possible for applications to recover from Error
s.
Java distinguishes between checked exceptions and unchecked exceptions. This distinction is important, because the Java compiler enforces a catch-or-declare requirement for checked exceptions. An exception’s type determines whether it’s checked or unchecked. All exception types that are direct or indirect subclasses of class RuntimeException (package java.lang
) are unchecked exceptions. These are typically caused by defects in your program’s code. Examples of unchecked exceptions include ArrayIndexOutOfBoundsException
s (discussed in Appendix E) and ArithmeticException
s. All classes that inherit from class Exception
but not class RuntimeException
are considered to be checked exceptions. Such exceptions are typically caused by conditions that are not under the control of the program—for example, in file processing, the program can’t open a file because the file does not exist. Classes that inherit from class Error
are considered to be unchecked.
The compiler checks each method call and method declaration to determine whether the method throws checked exceptions. If so, the compiler verifies that the checked exception is caught or is declared in a throws
clause. We show how to catch and declare checked exceptions in the next several examples. Recall from Section H.3 that the throws
clause specifies the exceptions a method throws. Such exceptions are not caught in the method’s body. To satisfy the catch part of the catch-or-declare requirement, the code that generates the exception must be wrapped in a try
block and must provide a catch
handler for the checked-exception type (or one of its superclass types). To satisfy the declare part of the catch-or-declare requirement, the method containing the code that generates the exception must provide a throws
clause containing the checked-exception type after its parameter list and before its method body. If the catch-or-declare requirement is not satisfied, the compiler will issue an error message indicating that the exception must be caught or declared. This forces you to think about the problems that may occur when a method that throws checked exceptions is called.
Software Engineering Observation H.2
You must deal with checked exceptions. This results in more robust code than would be created if you were able to simply ignore the exceptions.
A compilation error occurs if a method explicitly attempts to throw a checked exception (or calls another method that throws a checked exception) and that exception is not listed in that method’s throws
clause.
If a subclass method overrides a superclass method, it’s an error for the subclass method to list more exceptions in its throws
clause than the overridden superclass method does. However, a subclass’s throws
clause can contain a subset of a superclass’s throws
list.
Software Engineering Observation H.3
If your method calls other methods that throw checked exceptions, those exceptions must be caught or declared in your method. If an exception can be handled meaningfully in a method, the method should catch the exception rather than declare it.
Unlike checked exceptions, the Java compiler does not check the code to determine whether an unchecked exception is caught or declared. Unchecked exceptions typically can be prevented by proper coding. For example, the unchecked ArithmeticException
thrown by method quotient
(lines 9–13) in Fig. H.2 can be avoided if the method ensures that the denominator is not zero before attempting to perform the division. Unchecked exceptions are not required to be listed in a method’s throws
clause—even if they are, it’s not required that such exceptions be caught by an application.
Software Engineering Observation H.4
Although the compiler does not enforce the catch-or-declare requirement for unchecked exceptions, provide appropriate exception-handling code when it’s known that such exceptions might occur. For example, a program should process the NumberFormatException
from Integer
method parseInt
, even though NumberFormatException (an indirect subclass of RuntimeException
) is an unchecked exception type. This makes your programs more robust.
If a catch
handler is written to catch superclass-type exception objects, it can also catch all objects of that class’s subclasses. This enables catch
to handle related errors with a concise notation and allows for polymorphic processing of related exceptions. You can certainly catch each subclass type individually if those exceptions require different processing.
If there are multiple catch
blocks that match a particular exception type, only the first matching catch
block executes when an exception of that type occurs. It’s a compilation error to catch the exact same type in two different catch
blocks associated with a particular try
block. However, there may be several catch
blocks that match an exception—i.e., several catch
blocks whose types are the same as the exception type or a superclass of that type. For instance, we could follow a catch
block for type ArithmeticException
with a catch
block for type Exception
—both would match ArithmeticException
s, but only the first matching catch
block would execute.
Catching subclass types individually is subject to error if you forget to test for one or more of the subclass types explicitly; catching the superclass guarantees that objects of all subclasses will be caught. Positioning a catch
block for the superclass type after all other subclass catch
blocks ensures that all subclass exceptions are eventually caught.
Placing a catch
block for a superclass exception type before other catch
blocks that catch subclass exception types would prevent those catch
blocks from executing, so a compilation error occurs.
Programs that obtain certain types of resources must return them to the system explicitly to avoid so-called resource leaks. In programming languages such as C and C++, the most common kind of resource leak is a memory leak. Java performs automatic garbage collection of memory no longer used by programs, thus avoiding most memory leaks. However, other types of resource leaks can occur. For example, files, database connections and network connections that are not closed properly after they’re no longer needed might not be available for use in other programs.
A subtle issue is that Java does not entirely eliminate memory leaks. Java will not garbage-collect an object until there are no remaining references to it. Thus, if you erroneously keep references to unwanted objects, memory leaks can occur. To help avoid this problem, set reference-type variables to null
when they’re no longer needed.
The finally
block (which consists of the finally
keyword, followed by code enclosed in curly braces), sometimes referred to as the finally clause, is optional. If it’s present, it’s placed after the last catch
block. If there are no catch
blocks, the finally
block immediately follows the try
block.
The finally
block will execute whether or not an exception is thrown in the corresponding try
block. The finally
block also will execute if a try
block exits by using a return
, break
or continue
statement or simply by reaching its closing right brace. The finally
block will not execute if the application exits early from a try
block by calling method System.exit. This method immediately terminates an application.
Because a finally
block almost always executes, it typically contains resource-release code. Suppose a resource is allocated in a try
block. If no exception occurs, the catch
blocks are skipped and control proceeds to the finally
block, which frees the resource. Control then proceeds to the first statement after the finally
block. If an exception occurs in the try
block, the try
block terminates. If the program catches the exception in one of the corresponding catch
blocks, it processes the exception, then the finally
block releases the resource and control proceeds to the first statement after the finally
block. If the program doesn’t catch the exception, the finally
block still releases the resource and an attempt is made to catch the exception in a calling method.
The finally
block is an ideal place to release resources acquired in a try
block (such as opened files), which helps eliminate resource leaks.
Always release a resource explicitly and at the earliest possible moment at which it’s no longer needed. This makes resources available for reuse as early as possible, thus improving resource utilization.
If an exception that occurs in a try
block cannot be caught by one of that try
block’s catch
handlers, the program skips the rest of the try
block and control proceeds to the finally
block. Then the program passes the exception to the next outer try
block—normally in the calling method—where an associated catch
block might catch it. This process can occur through many levels of try
blocks. Also, the exception could go uncaught.
If a catch
block throws an exception, the finally
block still executes. Then the exception is passed to the next outer try
block—again, normally in the calling method.
Figure H.3 demonstrates that the finally
block executes even if an exception is not thrown in the corresponding try
block. The program contains static
methods main
(lines 6–18), throwException
(lines 21–44) and doesNotThrowException
(lines 47–64). Methods throwException
and doesNotThrowException
are declared static
, so main
can call them directly without instantiating a UsingExceptions
object.
1 // Fig. H.3: UsingExceptions.java
2 // try...catch...finally exception handling mechanism.
3
4 public class UsingExceptions
5 {
6 public static void main( String[] args )
7 {
8 try
9 {
10 throwException(); // call method throwException
11 } // end try
12 catch ( Exception exception ) // exception thrown by throwException
13 {
14 System.err.println( "Exception handled in main" );
15 } // end catch
16
17 doesNotThrowException();
18 } // end main
19
20 // demonstrate try...catch...finally
21 public static void throwException() throws Exception
22 {
23 try // throw an exception and immediately catch it
24 {
25 System.out.println( "Method throwException" );
26 throw new Exception(); // generate exception
27 } // end try
28 catch ( Exception exception ) // catch exception thrown in try
29 {
30 System.err.println(
31 "Exception handled in method throwException" );
32 throw exception; // rethrow for further processing
33
34 // code here would not be reached; would cause compilation errors
35
36 } // end catch
37 finally // executes regardless of what occurs in try...catch
38 {
39 System.err.println( "Finally executed in throwException" );
40 } // end finally
41
42 // code here would not be reached; would cause compilation errors
43
44 } // end method throwException
45
46 // demonstrate finally when no exception occurs
47 public static void doesNotThrowException()
48 {
49 try // try block does not throw an exception
50 {
51 System.out.println( "Method doesNotThrowException" );
52 } // end try
53 catch ( Exception exception ) // does not execute
54 {
55 System.err.println( exception );
56 } // end catch
57 finally // executes regardless of what occurs in try...catch
58 {
59 System.err.println(
60 "Finally executed in doesNotThrowException" );
61 } // end finally
62
63 System.out.println( "End of method doesNotThrowException" );
64 } // end method doesNotThrowException
65 } // end class UsingExceptions
Method throwException
Exception handled in method throwException
Finally executed in throwException
Exception handled in main
Method doesNotThrowException
Finally executed in doesNotThrowException
End of method doesNotThrowException
System.out
and System.err
are streams—sequences of bytes. While System.out
(known as the standard output stream) displays a program’s output, System.err
(known as the standard error stream) displays a program’s errors. Output from these streams can be redirected (i.e., sent to somewhere other than the command prompt, such as to a file). Using two different streams enables you to easily separate error messages from other output. For instance, data output from System.err
could be sent to a log file, while data output from System.out
can be displayed on the screen. For simplicity, this appendix will not redirect output from System.err
, but will display such messages to the command prompt. You’ll learn more about streams in Appendix J.
Method main
(Fig. H.3) begins executing, enters its try
block and immediately calls method throwException
(line 10). Method throwException
throws an Exception
. The statement at line 26 is known as a throw statement—it’s executed to indicate that an exception has occurred. So far, you’ve only caught exceptions thrown by called methods. You can throw exceptions yourself by using the throw
statement. Just as with exceptions thrown by the Java API’s methods, this indicates to client applications that an error has occurred. A throw
statement specifies an object to be thrown. The operand of a throw
can be of any class derived from class Throwable
.
Software Engineering Observation H.5
When toString
is invoked on any Throwable
object, its resulting string
includes the descriptive string
that was supplied to the constructor, or simply the class name if no string
was supplied.
Software Engineering Observation H.6
An object can be thrown without containing information about the problem that occurred. In this case, simply knowing that an exception of a particular type occurred may provide sufficient information for the handler to process the problem correctly.
Software Engineering Observation H.7
Exceptions can be thrown from constructors. When an error is detected in a constructor, an exception should be thrown to avoid creating an improperly formed object.
Line 32 of Fig. H.3 rethrows the exception. Exceptions are rethrown when a catch
block, upon receiving an exception, decides either that it cannot process that exception or that it can only partially process it. Rethrowing an exception defers the exception handling (or perhaps a portion of it) to another catch
block associated with an outer try
statement. An exception is rethrown by using the throw keyword, followed by a reference to the exception object that was just caught. Exceptions cannot be rethrown from a finally
block, as the exception parameter (a local variable) from the catch
block no longer exists.
When a rethrow occurs, the next enclosing try
block detects the rethrown exception, and that try
block’s catch
blocks attempt to handle it. In this case, the next enclosing try
block is found at lines 8–11 in method main
. Before the rethrown exception is handled, however, the finally
block (lines 37–40) executes. Then method main
detects the rethrown exception in the try
block and handles it in the catch
block (lines 12–15).
Next, main
calls method doesNotThrowException
(line 17). No exception is thrown in doesNotThrowException
’s try
block (lines 49–52), so the program skips the catch
block (lines 53–56), but the finally
block (lines 57–61) nevertheless executes. Control proceeds to the statement after the finally
block (line 63). Then control returns to main
and the program terminates.
If an exception has not been caught when control enters a finally
block and the finally
block throws an exception that’s not caught in the finally
block, the first exception will be lost and the exception from the finally
block will be returned to the calling method.
Avoid placing code that can throw
an exception in a finally
block. If such code is required, enclose the code in a try
...catch
within the finally
block.
Assuming that an exception thrown from a catch
block will be processed by that catch
block or any other catch
block associated with the same try
statement can lead to logic errors.
Exception handling is intended to remove error-processing code from the main line of a program’s code to improve program clarity. Do not place try
...catch
... finally
around every statement that may throw an exception. This makes programs difficult to read. Rather, place one try
block around a significant portion of your code, follow that try
block with catch
blocks that handle each possible exception and follow the catch
blocks with a single finally
block (if one is required).
When an exception is thrown but not caught in a particular scope, the method-call stack is “unwound,” and an attempt is made to catch
the exception in the next outer try
block. This process is called stack unwinding. Unwinding the method-call stack means that the method in which the exception was not caught terminates, all local variables in that method go out of scope and control returns to the statement that originally invoked that method. If a try
block encloses that statement, an attempt is made to catch
the exception. If a try
block does not enclose that statement or if the exception is not caught, stack unwinding occurs again. Figure H.4 demonstrates stack unwinding, and the exception handler in main
shows how to access the data in an exception object.
1 // Fig. H.4: UsingExceptions.java
2 // Stack unwinding and obtaining data from an exception object.
3
4 public class UsingExceptions
5 {
6 public static void main( String[] args )
7 {
8 try
9 {
10 method1(); // call method1
11 } // end try
12 catch ( Exception exception ) // catch exception thrown in method1
13 {
14 System.err.printf( "%s
", exception.getMessage() );
15 exception.printStackTrace(); // print exception stack trace
16
17 // obtain the stack-trace information
18 StackTraceElement[] traceElements = exception.getStackTrace();
19
20 System.out.println( "
Stack trace from getStackTrace:" );
21 System.out.println( "Class File Line Method" );
22
23 // loop through traceElements to get exception description
24 for ( StackTraceElement element : traceElements )
25 {
26 System.out.printf( "%s ", element.getClassName() );
27 System.out.printf( "%s ", element.getFileName() );
28 System.out.printf( "%s ", element.getLineNumber() );
29 System.out.printf( "%s
", element.getMethodName() );
30 } // end for
31 } // end catch
32 } // end main
33
34 // call method2; throw exceptions back to main
35 public static void method1() throws Exception
36 {
37 method2();
38 } // end method method1
39
40 // call method3; throw exceptions back to method1
41 public static void method2() throws Exception
42 {
43 method3();
44 } // end method method2
45
46 // throw Exception back to method2
47 public static void method3() throws Exception
48 {
49 throw new Exception( "Exception thrown in method3" );
50 } // end method method3
51 } // end class UsingExceptions
Exception thrown in method3
java.lang.Exception: Exception thrown in method3
at UsingExceptions.method3(UsingExceptions.java:49)
at UsingExceptions.method2(UsingExceptions.java:43)
at UsingExceptions.method1(UsingExceptions.java:37)
at UsingExceptions.main(UsingExceptions.java:10)
Stack trace from getStackTrace:
Class File Line Method
UsingExceptions UsingExceptions.java 49 method3
UsingExceptions UsingExceptions.java 43 method2
UsingExceptions UsingExceptions.java 37 method1
UsingExceptions UsingExceptions.java 10 main
In main
, the try
block (lines 8–11) calls method1
(declared at lines 35–38), which in turn calls method2
(declared at lines 41–44), which in turn calls method3
(declared at lines 47–50). Line 49 of method3
throws an Exception
object—this is the throw point. Because the throw
statement at line 49 is not enclosed in a try
block, stack unwinding occurs—method3
terminates at line 49, then returns control to the statement in method2
that invoked method3
(i.e., line 43). Because no try
block encloses line 43, stack unwinding occurs again—method2
terminates at line 43 and returns control to the statement in method1
that invoked method2
(i.e., line 37). Because no try
block encloses line 37, stack unwinding occurs one more time—method1
terminates at line 37 and returns control to the statement in main
that invoked method1
(i.e., line 10). The try
block at lines 8–11 encloses this statement. The exception has not been handled, so the try
block terminates and the first matching catch
block (lines 12–31) catches and processes the exception. If there were no matching catch
blocks, and the exception is not declared in each method that throws it, a compilation error would occur. Remember that this is not always the case—for unchecked exceptions, the application will compile, but it will run with unexpected results.
Recall that exceptions derive from class Throwable
. Class Throwable
offers a printStackTrace method that outputs to the standard error stream the stack trace (discussed in Section H.2). Often, this is helpful in testing and debugging. Class Throwable
also provides a getStackTrace method that retrieves the stack-trace information that might be printed by printStackTrace
. Class Throwable
’s getMessage method returns the descriptive string stored in an exception.
An exception that’s not caught in an application causes Java’s default exception handler to run. This displays the name of the exception, a descriptive message that indicates the problem that occurred and a complete execution stack trace. In an application with a single thread of execution, the application terminates. In an application with multiple threads, the thread that caused the exception terminates.
Throwable
method toString
(inherited by all Throwable
subclasses) returns a String
containing the name of the exception’s class and a descriptive message.
The catch
handler in Fig. H.4 (lines 12–31) demonstrates getMessage
, printStackTrace
and getStackTrace
. If we wanted to output the stack-trace information to streams other than the standard error stream, we could use the information returned from getStackTrace
and output it to another stream or use one of the overloaded versions of method printStackTrace
.
Line 14 invokes the exception’s getMessage
method to get the exception description. Line 15 invokes the exception’s printStackTrace
method to output the stack trace that indicates where the exception occurred. Line 18 invokes the exception’s getStackTrace
method to obtain the stack-trace information as an array of StackTraceElement objects. Lines 24–30 get each StackTraceElement
in the array and invoke its methods getClassName, getFileName, getLineNumber and getMethodName to get the class name, file name, line number and method name, respectively, for that StackTraceElement
. Each StackTraceElement
represents one method call on the method-call stack.
The program’s output shows that the stack-trace information printed by printStackTrace
follows the pattern: className.methodName (fileName:lineNumber), where class-Name, methodName and fileName indicate the names of the class, method and file in which the exception occurred, respectively, and the lineNumber indicates where in the file the exception occurred. You saw this in the output for Fig. H.1. Method getStackTrace
enables custom processing of the exception information. Compare the output of printStackTrace
with the output created from the StackTraceElement
s to see that both contain the same stack-trace information.
Software Engineering Observation H.8
Never provide a catch
handler with an empty body—this effectively ignores the exception. At least use printStackTrace
to output an error message to indicate that a problem exists.
In this appendix, you learned how to use exception handling to deal with errors. You learned that exception handling enables you to remove error-handling code from the “main line” of the program’s execution. We showed how to use try
blocks to enclose code that may throw an exception, and how to use catch
blocks to deal with exceptions that may arise. You learned about the termination model of exception handling, which dictates that after an exception is handled, program control does not return to the throw point. We discussed checked vs. unchecked exceptions, and how to specify with the throws
clause the exceptions that a method might throw. You learned how to use the finally
block to release resources whether or not an exception occurs. You also learned how to throw and rethrow exceptions. We showed how to obtain information about an exception using methods printStackTrace
, getStackTrace
and getMessage
. In the next appendix, we discuss graphical user interface concepts and explain the essentials of event handling.
H.1 List five common examples of exceptions.
H.2 Give several reasons why exception-handling techniques should not be used for conventional program control.
H.3 Why are exceptions particularly appropriate for dealing with errors produced by methods of classes in the Java API?
H.4 What is a “resource leak”?
H.5 If no exceptions are thrown in a try
block, where does control proceed to when the try
block completes execution?
H.6 Give a key advantage of using catch( Exception
exceptionName )
.
H.7 Should a conventional application catch Error
objects? Explain.
H.8 What happens if no catch
handler matches the type of a thrown object?
H.9 What happens if several catch
blocks match the type of the thrown object?
H.10 Why would a programmer specify a superclass type as the type in a catch
block?
H.11 What is the key reason for using finally
blocks?
H.12 What happens when a catch
block throws an Exception
?
H.13 What does the statement throw
exceptionReference do in a catch
block?
H.14 What happens to a local reference in a try
block when that block throws an Exception
?
H.1 Memory exhaustion, array index out of bounds, arithmetic overflow, division by zero, invalid method parameters.
(a) Exception handling is designed to handle infrequently occurring situations that often result in program termination, not situations that arise all the time.
(b) Flow of control with conventional control structures is generally clearer and more efficient than with exceptions.
(c) The additional exceptions can get in the way of genuine error-type exceptions. It becomes more difficult for you to keep track of the larger number of exception cases.
H.3 It’s unlikely that methods of classes in the Java API could perform error processing that would meet the unique needs of all users.
H.4 A “resource leak” occurs when an executing program does not properly release a resource when it’s no longer needed.
H.5 The catch
blocks for that try
statement are skipped, and the program resumes execution after the last catch
block. If there’s a finally
block, it’s executed first; then the program resumes execution after the finally
block.
H.6 The form catch( Exception
exceptionName )
catches any type of exception thrown in a try
block. An advantage is that no thrown Exception
can slip by without being caught. You can then decide to handle the exception or possibly rethrow it.
H.7 Error
s are usually serious problems with the underlying Java system; most programs will not want to catch Error
s because they will not be able to recover from them.
H.8 This causes the search for a match to continue in the next enclosing try
statement. If there’s a finally
block, it will be executed before the exception goes to the next enclosing try
statement. If there are no enclosing try
statements for which there are matching catch
blocks and the exceptions are declared (or unchecked), a stack trace is printed and the current thread terminates early. If the exceptions are checked, but not caught or declared, compilation errors occur.
H.9 The first matching catch
block after the try
block is executed.
H.10 This enables a program to catch related types of exceptions and process them in a uniform manner. However, it’s often useful to process the subclass types individually for more precise exception handling.
H.11 The finally
block is the preferred means for releasing resources to prevent resource leaks.
H.12 First, control passes to the finally
block if there is one. Then the exception will be processed by a catch
block (if one exists) associated with an enclosing try
block (if one exists).
H.13 It rethrows the exception for processing by an exception handler of an enclosing try
statement, after the finally
block of the current try
statement executes.
H.14 The reference goes out of scope. If the referenced object becomes unreachable, the object can be garbage collected.
H.15 (Exceptional Conditions) List the various exceptional conditions that have occurred in programs throughout the appendices so far. List as many additional exceptional conditions as you can. For each of these, describe briefly how a program typically would handle the exception by using the exception-handling techniques discussed in this appendix. Typical exceptions include division by zero and array index out of bounds.
H.16 (Exceptions and Constructor Failure) Until this appendix, we’ve found dealing with errors detected by constructors to be a bit awkward. Explain why exception handling is an effective means for dealing with constructor failure.
H.17 (Catching Exceptions with Superclasses) Use inheritance to create an exception superclass (called ExceptionA
) and exception subclasses ExceptionB
and ExceptionC
, where ExceptionB
inherits from ExceptionA
and ExceptionC
inherits from ExceptionB
. Write a program to demonstrate that the catch
block for type ExceptionA
catches exceptions of types ExceptionB
and ExceptionC
.
H.18 (Catching Exceptions Using Class Exception) Write a program that demonstrates how various exceptions are caught with
catch ( Exception exception )
This time, define classes ExceptionA
(which inherits from class Exception
) and ExceptionB
(which inherits from class ExceptionA
). In your program, create try
blocks that throw exceptions of types ExceptionA
, ExceptionB
, NullPointerException
and IOException
. All exceptions should be caught with catch
blocks specifying type Exception
.
H.19 (Order of catch Blocks) Write a program that shows that the order of catch
blocks is important. If you try to catch a superclass exception type before a subclass type, the compiler should generate errors.
H.20 (Constructor Failure) Write a program that shows a constructor passing information about constructor failure to an exception handler. Define class SomeClass
, which throws an Exception
in the constructor. Your program should try to create an object of type SomeClass
and catch the exception that’s thrown from the constructor.
H.21 (Rethrowing Exceptions) Write a program that illustrates rethrowing an exception. Define methods someMethod
and someMethod2
. Method someMethod2
should initially throw an exception. Method someMethod
should call someMethod2
, catch the exception and rethrow it. Call someMethod
from method main
, and catch the rethrown exception. Print the stack trace of this exception.
H.22 (Catching Exceptions Using Outer Scopes) Write a program showing that a method with its own try
block does not have to catch every possible error generated within the try
. Some exceptions can slip through to, and be handled in, other scopes.