13. Exception Handling

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 ArithmeticExceptions and InputMismatchExceptions

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

13.1 Introduction

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

Image

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.

13.2 Exception-Handling Overview

Programs frequently test conditions to determine how program execution should proceed. Consider the following pseudocode:

Image

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

Image

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.

13.3 Example: Divide by Zero without Exception Handling

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.

Image

Image

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

13.4 Example: Handling ArithmeticExceptions and InputMismatchExceptions

The application in Fig. 13.2, which is based on Fig. 13.1, uses exception handling to process any ArithmeticExceptions and InputMistmatchExceptions 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 ArithmeticExceptions and InputMismatchExceptions.

Image

Image

Image

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.

Enclosing Code in a try Block

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

Image

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.

Catching Exceptions

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

Image

It is a syntax error to place code between a try block and its corresponding catch blocks.

Common Programming Error 13.2

Image

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.

Termination Model of Exception Handling

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 InputMismatchExceptions (which occur if invalid input is entered) and the second catch block catches ArithmeticExceptions (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

Image

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

Image

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

Image

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.

Using the throws Clause

Now 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

Image

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

Image

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.

Error-Prevention Tip 13.5

Image

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

13.5 When to Use Exception Handling

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

Image

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

Image

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

Image

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

Image

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.

13.6 Java Exception Hierarchy

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. Errors happen infrequently and should not be caught by applications—it is usually not possible for applications to recover from Errors. [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 Exceptions and Errors.]

Fig. 13.3. Portion of class Throwable’s inheritance hierarchy.

Image

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 ArrayIndexOutOfBoundsExceptions and ArithmeticExceptions (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

Image

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

Image

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

Image

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

Image

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

Image

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 ArithmeticExceptions, but only the first matching catch block would execute.

Error-Prevention Tip 13.6

Image

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

Image

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.

13.7 finally Block

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

Image

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.

Image

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

Image

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

Image

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.

Image

Image

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.

Throwing Exceptions Using the throw Statement

Method 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

Image

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

Image

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

Image

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.

Rethrowing Exceptions

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

Image

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.

Error-Prevention Tip 13.9

Image

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

Image

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

Image

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

13.8 Stack Unwinding

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.

Image

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.

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

Error-Prevention Tip 13.10

Image

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

Image

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.

Image

Image

Image

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 StackTraceElements to see that both contain the same stack-trace information.

Software Engineering Observation 13.12

Image

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.

13.10 Chained Exceptions

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.

Image

Image

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.

13.11 Declaring New Exception Types

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

Image

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

Image

Associating each type of serious execution-time malfunction with an appropriately named Exception class improves program clarity.

Software Engineering Observation 13.14

Image

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

Image

By convention, all exception-class names should end with the word Exception.

13.12 Preconditions and Postconditions

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.

13.13 Assertions

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.

Image

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

13.14 Wrap-Up

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.

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

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