Objectives
In this chapter you’ll learn:
• How exception and error handling works.
• To use try
, throw
and catch
to detect, indicate and handle exceptions, respectively.
• To use the finally
block to release resources.
• How stack unwinding enables exceptions not caught in one scope to be caught in another scope.
• How stack traces help in debugging.
• How exceptions are arranged in an exception-class hierarchy.
• To declare new exception classes.
• To create chained exceptions that maintain complete stack-trace information.
It is common sense to take a method and try it. If it fails, admit it frankly and try another. But above all, try something.
—Franklin Delano Roosevelt
O! throw away the worser part of it, And live the purer with the other half.
—William Shakespeare
If they’re running and they don’t look where they’re going I have to come out from somewhere and catch them.
—Jerome David Salinger
O infinite virtue! com’st thou smiling from the world’s great snare uncaught?
—William Shakespeare
Outline
13.1 Introduction
13.2 Exception-Handling Overview
13.3 Example: Divide by Zero without Exception Handling
13.4 Example: Handling ArithmeticException
s and InputMismatchException
s
13.5 When to Use Exception Handling
13.6 Java Exception Hierarchy
13.7 finally
Block
13.8 Stack Unwinding
13.9 printStackTrace
, getStackTrace
and getMessage
13.10 Chained Exceptions
13.11 Declaring New Exception Types
13.12 Preconditions and Postconditions
13.13 Assertions
13.14 Wrap-Up
In this chapter, we introduce exception handling. An exception is an indication of a problem that occurs during a program’s execution. The name “exception” implies that the problem occurs infrequently—if the “rule” is that a statement normally executes correctly, then the “exception to the rule” is that a problem occurs. Exception handling enables you to create applications that can resolve (or handle) exceptions. In many cases, handling an exception allows a program to continue executing as if no problem had been encountered. A more severe problem could prevent a program from continuing normal execution, instead requiring it to notify the user of the problem before terminating in a controlled manner. The features presented in this chapter enable programmers to write robust and fault-tolerant programs (i.e., programs that are able to deal with problems that may arise and continue executing). The style and details of Java exception handling are based in part on Andrew Koenig’s and Bjarne Stroustrup’s paper, “Exception Handling for C++ (revised).”[1]
Error-Prevention Tip 13.1
Exception handling helps improve a program’s fault tolerance.
You have already been briefly introduced to exceptions in earlier chapters. In Chapter 7 you learned that an ArrayIndexOutOfBoundsException
occurs when an attempt is made to access an element past the end of an array. Such a problem may occur if there is an “off-by-one” error in a for
statement that manipulates an array. In Chapter 10, we introduced the ClassCastException
, which occurs when an attempt is made to cast an object that does not have an is-a relationship with the type specified in the cast operator. Chapter 11 briefly mentioned the NullPointerException
, which occurs whenever a null
reference is used where an object is expected (for example, when an attempt is made to attach a GUI component to a Container
, but the GUI component has not yet been created). Throughout this text you have also used class Scanner
—which, as you’ll see in this chapter, also may cause exceptions.
[1]Koenig, A., and B. Stroustrup. “Exception Handling for C++ (revised),” Proceedings of the Usenix C++ Conference, pp. 149–176, San Francisco, April 1990.
We begin with an overview of exception-handling concepts, then demonstrate basic exception-handling techniques. We show these techniques in action by handling an exception that occurs when a method attempts to divide an integer by zero. Next, we introduce several classes at the top of Java’s class hierarchy for exception handling. As you’ll see, only classes that extend Throwable
(package java.lang
) directly or indirectly can be used with exception handling. We then discuss the chained exception feature that allows programmers to wrap information about an exception that occurred in another exception object to provide more detailed information about a problem in a program. Next, we discuss additional exception-handling issues, such as how to handle exceptions that occur in a constructor. We introduce preconditions and postconditions, which must be true when your methods are called and when those methods return, respectively. Finally, we present assertions, which programmers use at development time to help debug their code.
Programs frequently test conditions to determine how program execution should proceed. Consider the following pseudocode:
We begin by performing a task; then we test whether that task executed correctly. If not, we perform error processing; otherwise, we continue with the next task. Although this form of error handling works, intermixing program logic with error-handling logic can make programs difficult to read, modify, maintain and debug—especially in large applications.
Performance Tip 13.1
If the potential problems occur infrequently, intermixing program and error-handling logic can degrade a program’s performance, because the program must perform (potentially frequent) tests to determine whether the task executed correctly and the next task can be performed.
Exception handling enables programmers to remove error-handling code from the “main line” of the program’s execution, improving program clarity and enhancing modifiability. You can decide to handle any exceptions you choose—all exceptions, all exceptions of a certain type or all exceptions of a group of related types (i.e., exception types that are related through an inheritance hierarchy). Such flexibility reduces the likelihood that errors will be overlooked, thus making programs more robust.
With programming languages that do not support exception handling, programmers often delay writing error-processing code or sometimes forget to include it. This results in less robust software products. Java enables programmers to deal with exception handling easily from the inception of a project.
First we demonstrate what happens when errors arise in an application that does not use exception handling. Figure 13.1 prompts the user for two integers and passes them to method quotient
, which calculates the quotient and returns an int
result. In this example, we’ll see that exceptions are thrown (i.e., the exception occurs) when a method detects a problem and is unable to handle it.
Fig. 13.1. Integer division without exception handling.
The first of the three sample executions in Fig. 13.1 shows a successful division. In the second sample execution, the user enters the value 0
as the denominator. Notice that several lines of information are displayed in response to this invalid input. This information is known as the stack trace, which includes the name of the exception (java.lang.ArithmeticException
) in a descriptive message that indicates the problem that occurred and the complete method-call stack (i.e., the call chain) at the time the exception occurred. The stack trace includes the path of execution that led to the exception method by method. This information helps in debugging a 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. [Note: Java does allow division by zero with floating-point values. Such a calculation results in the value infinity, which is represented in Java as a floating-point value (but actually displays as the string Infinity
).] When division by zero in integer arithmetic occurs, Java throws an ArithmeticException
. ArithmeticException
s can arise from a number of different problems in arithmetic, so the extra data (“/ by zero
”) gives us more information about this specific exception.
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 "hello"
as the denominator. This results in a stack trace informing us that an InputMismatchException
(package java.util
) has occurred. 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 occurs in method nextInt
. Notice that in place of the file name and line number, we are provided with the text Unknown Source
. This means that the JVM does not have access to the source code for where the exception occurred.
Notice that in the sample executions of Fig. 13.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. The next section demonstrates how to handle these exceptions and keep the program running successfully. In the next example, we’ll see how to handle these exceptions to enable the program to run to normal completion.
ArithmeticException
s and InputMismatchExceptions
The application in Fig. 13.2, which is based on Fig. 13.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 try to enter the input again.
Fig. 13.2. Handling ArithmeticException
s and InputMismatchException
s.
The first sample execution in Fig. 13.2 shows a successful execution 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 is located in package java.lang
. Method main
(lines 15–49) creates a Scanner
object at line 17. 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.
try
BlockLines 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 a valid 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 13.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, where 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 13.7) 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 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. 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 is 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.
Common Programming Error 13.1
It is a syntax error to place code between a try
block and its corresponding catch
blocks.
Common Programming Error 13.2
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 an exception that occurs for which there are no matching catch
blocks. You saw uncaught exceptions in the second and third outputs of Fig. 13.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. As you’ll learn in Chapter 18, Multithreading, 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.
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. 13.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. 13.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. [Note: Some languages use the resumption model of exception handling, in which, after an exception is handled, control resumes just after the throw point.]
Common Programming Error 13.3
Logic errors can occur if you assume that after an exception is handled, control will return to the first statement after the throw point.
Error-Prevention Tip 13.2
With exception handling, a program can continue executing (rather than terminating) after dealing with a problem. This helps ensure the kind of robust applications that contribute to what is called mission-critical computing or business-critical computing.
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.
Good Programming Practice 13.1
Using an exception parameter name that reflects the parameter’s type promotes clarity by reminding the programmer of the type of exception being handled.
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 13.7). 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 together form a try
statement. Don’t confuse the terms “try
block” and “try
statement”—the term “try
block” refers to the keyword try
followed by a block of code, while “try
statement” 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 are destroyed. 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.
throws
ClauseNow let’s examine method quotient
(Fig. 13.2; lines 9–13). Line 10 incldues a throws
clause, which 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 might throw. 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 indicate to the clients of method quotient
that this method may throw an ArithmeticException
. You’ll learn more about the throws
clause in Section 13.6.
Error-Prevention Tip 13.3
If you know that a method might throw an exception, include appropriate exception-handling code in your program to make it more robust.
Error-Prevention Tip 13.4
Read the online API documentation for a method before using that method in a program. The documentation specifies the exceptions thrown by the method (if any) and indicates reasons why such exceptions may occur. Then provide for handling those exceptions in your program.
Read the online API documentation for an exception class before writing exception-handling code for that type of exception. The documentation for an exception class typically contains potential reasons that such exceptions occur during program execution.
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.
Note that when quotient
throws an ArithmeticException
, quotient
terminates and does not return a value, and quotient
’s local variables go out of scope (variables 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 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 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.
Software Engineering Observation 13.2
Incorporate your exception-handling strategy into your system from the inception of the design process. Including effective exception handling after a system has been implemented can be difficult.
Software Engineering Observation 13.3
Exception handling provides a single, uniform technique for processing problems. This helps programmers working on large projects understand each other’s error-processing code.
Software Engineering Observation 13.4
Avoid using exception handling as an alternate form of flow of control. These “additional” exceptions can “get in the way” of genuine error-type exceptions.
Software Engineering Observation 13.5
Exception handling simplifies combining software components and enables them to work together effectively by enabling predefined components to communicate problems to application-specific components, which can then process the problems in an application-specific manner.
All Java exception classes inherit, either directly or indirectly, from class Exception
, forming an inheritance hierarchy. Programmers can extend this hierarchy to create their own exception classes.
Figure 13.3 shows a small portion of the inheritance hierarchy for class Throwable
(a subclass of Object
), which 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 (e.g., OutOfMemoryError
) represent abnormal situations that could happen in the JVM. Error
s happen infrequently and should not be caught by applications—it is usually not possible for applications to recover from Error
s. [Note: The Java exception hierarchy contains hundreds of classes. Information about Java’s exception classes can be found throughout the Java API. The documentation for class Throwable
can be found at java.sun.com/javase/6/docs/api/java/lang/Throw-able.html. From there, you can look at this class’s subclasses to get more information about Java’s Exception
s and Error
s.]
Fig. 13.3. Portion of class Throwable
’s inheritance hierarchy.
Java distinguishes between two categories of exceptions: 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 the exception is checked or unchecked. All exception types that are direct or indirect subclasses of class RuntimeException
(package java.lang
) are unchecked exceptions. This includes exceptions you have seen already, such as ArrayIndexOutOfBoundsException
s and ArithmeticException
s (shown in Fig. 13.3). All classes that inherit from class Exception
but not class RuntimeException
are considered to be checked exceptions. 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 ensures that the checked exception is caught or is declared in a throws
clause. Recall from Section 13.4 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 programmers to think about the problems that may occur when a method that throws checked exceptions is called. Exception classes are defined to be checked when they are considered important enough to catch or declare.
Software Engineering Observation 13.6
Programmers are forced to deal with checked exceptions. This results in more robust code than would be created if programmers were able to simply ignore the exceptions.
Common Programming Error 13.4
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.
Common Programming Error 13.5
If a subclass method overrides a superclass method, it is 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 13.7
If your method calls other methods that explicitly 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. 13.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 is not required that such exceptions be caught by an application.
Software Engineering Observation 13.8
Although the compiler does not enforce the catch-or-declare requirement for unchecked exceptions, provide appropriate exception-handling code when it is known that such exceptions might occur. For example, a program should process the NumberFormatException
from Integer
method parseInt
, even though NumberFormatException
(a subclass of RuntimeException
) is an unchecked exception type. This makes your programs more robust.
Exception classes can be derived from a common superclass. If a catch
handler catches 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. Catching related exceptions in one catch
block makes sense only if the handling behavior is the same for all subclasses.
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 is 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.
Error-Prevention Tip 13.6
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 for subclasses of that superclass ensures that all subclass exceptions are eventually caught.
Common Programming Error 13.6
Placing a catch
block for a superclass exception type before other catch
blocks that catch subclass exception types prevents those catch
blocks from executing, so a compilation error occurs.
finally
BlockPrograms 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 resource leaks can occur. For example, files, database connections and network connections that are not closed properly might not be available for use in other programs.
Error-Prevention Tip 13.7
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 programmers erroneously keep references to unwanted objects, memory leaks can occur.
The finally
block (which consists of the finally
keyword, followed by code enclosed in curly braces) is optional, and is sometimes referred to as the finally
clause. If it is present, it is placed after the last catch
block, as in Fig. 13.4.
Fig. 13.4. A try
statement with a finally
block.
Java guarantees that a finally
block (if one is present in a try
statement) will execute whether or not an exception is thrown in the corresponding try
block or any of its corresponding catch
blocks. Java also guarantees that a finally
block (if one is present) will execute if a try
block exits by using a return
, break
or continue
statement, or simply by reaching the try
block’s 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, which we demonstrate in the next chapter, 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 does occur in the try
block, the program skips the rest of the try
block. If the program catches the exception in one of the catch
blocks, the program processes the exception, then the finally
block releases the resource, and control proceeds to the first statement after the finally
block.
Performance Tip 13.2
Always release each resource explicitly and at the earliest possible moment at which it is no longer needed. This makes resources immediately available to be reused by your program or other programs, thus improving resource utilization.
Error-Prevention Tip 13.8
Because the finally
block is guaranteed to execute whether or not an exception occurs in the corresponding try
block, this block is an ideal place to release resources acquired in a try
block. This is also an effective way to eliminate resource leaks. For example, the finally
block should close any files opened in the try
block.
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. It is also possible that 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 13.5 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 7–19), throwException
(lines 22–45) and doesNotThrowException
(lines 48–65). Methods throwException
and doesNotThrowException
are declared static
, so main
can call them directly without instantiating a UsingExceptions
object.
Fig. 13.5. try
...catch
...finally
exception-handling mechanism.
Note the use of the System.err
to output data (lines 15, 31–32, 40, 56 and 60–61). By default, System.err.println
, like System.out.println
, displays data to the command prompt.
Both System.out
and System.err
are streams—a sequence of bytes. While System.out
(known as the standard output stream) is used to display a program’s output, System.err
(known as the standard error stream) is used to display a program’s errors. Output from these streams can be redirected (i.e., sent somewhere other than the command prompt, such as to a file). Using two different streams enables the programmer 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 chapter will not redirect output from System.err
, but will display such messages to the command prompt. You’ll learn more about streams in Chapter 14, Files and Streams.
throw
StatementMethod main
(Fig. 13.5) begins executing, enters its try
block and immediately calls method throwException
(line 11). Method throwException
throws an Exception
. The statement at line 27, known as a throw
statement, indicates that an exception has occurred. So far, you’ve only caught exceptions thrown by called methods. You can throw exceptions with the throw
statementto indicate 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 13.9
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 13.10
An object can be thrown without containing information about the problem that occurred. In this case, simple knowledge that an exception of a particular type occurred may provide sufficient information for the handler to process the problem correctly.
Software Engineering Observation 13.11
Exceptions can be thrown from constructors. When an error is detected in a constructor, an exception should be thrown rather than creating an improperly formed object.
Line 33 of Fig. 13.5 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. Note that exceptions cannot be rethrown from a finally
block, as the exception parameter from the catch
block has expired.
When a rethrow occurs, the next enclosing try
block detects the rethrown exception, and that try
block’s catch
blocks attempt to handle the exception. In this case, the next enclosing try
block is found at lines 9–12 in method main
. Before the rethrown exception is handled, however, the finally
block (lines 38–41) executes. Then method main
detects the rethrown exception in the try
block and handles it in the catch
block (lines 13–16).
Next, main
calls method doesNotThrowException
(line 18). No exception is thrown in doesNotThrowException
’s try
block (lines 50–53), so the program skips the catch
block (lines 54–57), but the finally
block (lines 58–62) nevertheless executes. Control proceeds to the statement after the finally
block (line 64). Then control returns to main
and the program terminates.
Common Programming Error 13.7
If an exception has not been caught when control enters a finally
block and the finally
block throws an exception that is 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.
Common Programming Error 13.8
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.
Good Programming Practice 13.2
Java’s exception-handling mechanism 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 are destroyed 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, stack unwinding occurs again. If no catch
block ever catches this exception and the exception is checked (as in the following example), compiling the program will result in an error. The program of Fig. 13.6 demonstrates stack unwinding.
Fig. 13.6. Stack unwinding.
When method main
executes, line 10 in the try
block calls method throwException
(lines 19–35). In the try
block of method throwException
(lines 21–25), line 24 throws an Exception
. This terminates the try
block immediately, and control skips the catch
block at line 26, because the type being caught (RuntimeException
) is not an exact match with the thrown type (Exception
) and is not a superclass of it. Method throwException
terminates (but not until its finally
block executes) and returns control to line 10—the point from which it was called in the program. Line 10 is in an enclosing try
block. The exception has not yet been handled, so the try
block terminates and an attempt is made to catch the exception at line 12. The type being caught (Exception
) does match the thrown type. Consequently, the catch
block processes the exception, and the program terminates at the end of main
. If there were no matching catch
blocks, a compilation error would occur. Remember that this is not always the case—for unchecked exceptions, the application will compile, but will run with unexpected results.
printStackTrace
, getStackTrace
and getMessage
Recall from Section 13.6 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 13.3). Often, this is helpful in testing and debugging. Class Throwable
also provides a getStackTrace
method that retrieves stack-trace information that might be printed by printStackTrace
. Class Throwable
’s getMessage
method returns the descriptive string stored in an exception. The example in this section demonstrates these three methods.
An exception that is 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.
Error-Prevention Tip 13.11
Throwable
method toString
(inherited by all Throwable
subclasses) returns a string containing the name of the exception’s class and a descriptive message.
Figure 13.7 demonstrates getMessage
, printStackTrace
and getStackTrace
. If we wanted to output the stack-trace information to streams other than the standard error stream, we could output the information returned from getStackTrace
to another stream. Sending data to other streams is discussed in Chapter 14, Files and Streams.
Fig. 13.7. Throwable
methods getMessage
, getStackTrace
and printStackTrace
.
In main
, the try
block (lines 8–11) calls method1
(declared at lines 35–38). Next, method1
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.
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 output in Fig. 13.7 shows that the stack-trace information printed by printStackTrace
follows the pattern: className.methodName(fileName:lineNumber), where className, 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. 13.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 13.12
Never ignore an exception you catch. At least use printStackTrace
to output an error message. This will inform users that a problem exists, so that they can take appropriate actions.
Sometimes a catch
block catches one exception type, then throws a new exception of a different type to indicate that a program-specific exception occurred. In earlier Java versions, there was no mechanism to wrap the original exception information with the new exception’s information to provide a complete stack trace showing where the original problem occurred in the program. This made debugging such problems particularly difficult. Chained exceptions enable an exception object to maintain the complete stack-trace information. Figure 13.8 demonstrates chained exceptions.
Fig. 13.8. Chained exceptions.
The program consists of four methods—main
(lines 6–16), method1
(lines 19–29), method2
(lines 32–42) and method3
(lines 45–48). Line 10 in method main
’s try
block calls method1
. Line 23 in method1
’s try
block calls method2
. Line 36 in method2
’s try
block calls method3
. In method3
, line 47 throws a new Exception
. Because this statement is not in a try
block, method3
terminates, and the exception is returned to the calling method (method2
) at line 36. This statement is in a try
block; therefore, the try
block terminates and the exception is caught at lines 38–41. Line 40 in the catch
block throws a new exception. In this case, the Exception
constructor with two arguments is called. The second argument represents the exception that was the original cause of the problem. In this program, that exception occurred at line 47. Because an exception is thrown from the catch
block, method2
terminates and returns the new exception to the calling method (method1
) at line 23. Once again, this statement is in a try
block, so the try
block terminates and the exception is caught at lines 25–28. Line 27 in the catch
block throws a new exception and uses the exception that was caught as the second argument to the Exception
constructor. Because an exception is thrown from the catch
block, method1
terminates and returns the new exception to the calling method (main
) at line 10. The try
block in main
terminates, and the exception is caught at lines 12–15. Line 14 prints a stack trace.
Notice in the program output that the first three lines show the most recent exception that was thrown (i.e., the one from method1
at line 23). The next four lines indicate the exception that was thrown from method2
at line 40. Finally, the last four lines represent the exception that was thrown from method3
at line 47. Also notice that, as you read the output in reverse, it shows how many more chained exceptions remain.
Most Java programmers use existing classes from the Java API, third-party vendors and freely available class libraries (usually downloadable from the Internet) to build Java applications. The methods of those classes typically are declared to throw appropriate exceptions when problems occur. Programmers write code that processes these existing exceptions to make programs more robust.
If you build classes that other programmers will use, you might find it useful to declare your own exception classes that are specific to the problems that can occur when another programmer uses your reusable classes.
Software Engineering Observation 13.13
If possible, indicate exceptions from your methods by using existing exception classes, rather than creating new exception classes. The Java API contains many exception classes that might be suitable for the type of problem your method needs to indicate.
A new exception class must extend an existing exception class to ensure that the class can be used with the exception-handling mechanism. Like any other class, an exception class can contains fields and methods. However, a typical new exception class contains only two constructors—one that takes no arguments and passes a default exception message to the superclass constructor, and one that receives a customized exception message as a string and passes it to the superclass constructor.
Good Programming Practice 13.3
Associating each type of serious execution-time malfunction with an appropriately named Exception
class improves program clarity.
Software Engineering Observation 13.14
When defining your own exception type, study the existing exception classes in the Java API and try to extend a related exception class. For example, if you are creating a new class to represent when a method attempts a division by zero, you might extend class ArithmeticException
because division by zero occurs during arithmetic. If the existing classes are not appropriate superclasses for your new exception class, decide whether your new class should be a checked or an unchecked exception class. The new exception class should be a checked exception (i.e., extend Exception
but not RuntimeException
) if possible clients should be required to handle the exception. The client application should be able to reasonably recover from such an exception. The new exception class should extend RuntimeException
if the client code should be able to ignore the exception (i.e., the exception is an unchecked exception).
Good Programming Practice 13.4
By convention, all exception-class names should end with the word Exception
.
Programmers spend significant portions of their time maintaining and debugging code. To facilitate these tasks and to improve the overall design, they generally specify the expected states before and after a method’s execution. These states are called preconditions and postconditions, respectively.
A precondition must be true when a method is invoked. Preconditions describe constraints on method parameters and any other expectations the method has about the current state of a program. If the preconditions are not met, then the method’s behavior is undefined—it may throw an exception, proceed with an illegal value or attempt to recover from the error. However, you should never rely on or expect consistent behavior if the preconditions are not satisfied.
A postcondition is true after the method successfully returns. Postconditions describe constraints on the return value and any other side effects the method may have. When calling a method, you may assume that a method fulfills all of its postconditions. If you are writing your own method, you should document all postconditions so others know what to expect when they call your method, and you should make certain that your method honors all its postconditions if its preconditions are indeed met.
When their preconditions or postconditions are not met, methods typically throw exceptions. As an example, examine String
method charAt
, which has one int
parameter—an index in the String
. For a precondition, method charAt
assumes that index
is greater than or equal to zero and less than the length of the String
. If the precondition is met, the postcondition states the method will return the character at the position in the String
specified by the parameter index
. Otherwise, the method throws an IndexOutOfBoundsException
. We trust that method charAt
satisfies its postcondition, provided that we meet the precondition. We do not need to be concerned with the details of how the method actually retrieves the character at the index.
Some programmers state the preconditions and postconditions informally as part of the general method specification, while others prefer a more formal approach by explicitly defining them. When designing your own methods, you should state the preconditions and postconditions in a comment before the method declaration in whichever manner you prefer. Stating the preconditions and postconditions before writing a method will also help guide you as you implement the method.
When implementing and debugging a class, it is sometimes useful to state conditions that should be true at a particular point in a method. These conditions, called assertions, help ensure a program’s validity by catching potential bugs and identifying possible logic errors during development. Preconditions and postconditions are two types of assertions. Preconditions are assertions about a program’s state when a method is invoked, and postconditions are assertions about a program’s state after a method finishes.
While assertions can be stated as comments to guide the programmer during development, Java includes two versions of the assert
statement for validating assertions programatically. The assert
statement evaluates a boolean
expression and determines whether it is true or false. The first form of the assert
statement is
assert expression;
This statement evaluates expression and throws an AssertionError
if the expression is false
. The second form is
assert expression1 : expression2;
This statement evaluates expression1 and throws an AssertionError
with expression2 as the error message if expression1 is false
.
You can use assertions to programmatically implement preconditions and postconditions or to verify any other intermediate states that help you ensure your code is working correctly. The example in Fig. 13.9 demonstrates the functionality of the assert
statement. Line 11 prompts the user to enter a number between 0 and 10, then line 12 reads the number from the command line. The assert
statement at line 15 determines whether the user entered a number within the valid range. If the number is out of range, then the program reports an error; otherwise, the program proceeds normally.
Fig. 13.9. Checking with assert
that a value is within range.
Assertions are primarily used by the programmer for debugging and identifying logic errors in an application. By default, assertions are disabled when executing a program because they reduce performance and are unnecessary for the program’s user. To enable assertions at runtime, use the java
command’s -ea
command-line option. To execute the program in Fig. 13.9 with assertions enabled, type
java -ea AssertTest
You should not encounter any AssertionError
s through normal execution of a properly written program. Such errors should only indicate bugs in the implementation. As a result, you should never catch an AssertionError
. Rather, you should allow the program to terminate when the error occurs, so you can see the error message; then you should locate and fix the source of the problem. Since application users can choose not to enable assertions at runtime, you should not use the assert
statement to indicate runtime problems in production code. Rather, you should use the exception mechanism for this purpose.
In this chapter, you learned how to use exception handling to deal with errors in an application. You learned that exception handling enables programmers to remove error-handling code from the “main line” of the program’s execution. You saw exception handling in the context of a divide-by-zero example. You learned 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. You learned the difference between checked and unchecked exceptions, and how to specify with the throws
clause that specific exceptions occurring in a method will be thrown by that method to its caller. 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. You then learned how to obtain information about an exception using methods printStackTrace
, getStackTrace
and getMessage
. The chapter continued with a discussion of chained exceptions, which allow programmers to wrap original exception information with new exception information. Next, we overviewed how to create your own exception classes. We then introduced preconditions and postconditions to help programmers using your methods understand conditions that must be true with the method is called and when it returns. When preconditions and postconditions are not met, methods typically throw exceptions. Finally, we discussed the assert
statement and how it can be used to help you debug your programs. In particular, these can be used to ensure that preconditions and postconditions are met. In the next chapter, you’ll learn about file processing, including how persistent data is stored and how to manipulate it.