H. Exception Handling: A Deeper Look


Objectives

In this appendix you’ll:

Image Learn what exceptions are and how they’re handled.

Image Understand when to use exception handling.

Image Use try blocks to delimit code in which exceptions might occur.

Image throw exceptions to indicate a problem.

Image Use catch blocks to specify exception handlers.

Image Use the finally block to release resources.

Image Become familiar with the exception class hierarchy.


H.1 Introduction

An exception is an indication of a problem that occurs during a program’s execution. Exception handling enables you to create applications that can resolve (or handle) exceptions. In many cases, handling an exception allows a program to continute executing as if no problem had been encountered. The features presented in this appendix help you write robust programs that can deal with problems and continue executing or terminate gracefully.

H.2 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 H.1 prompts the user for two integers and passes them to method quotient, which calculates the integer quotient and returns an int result. In this example, you’ll see that exceptions are thrown (i.e., the exception occurs) when a method detects a problem and is unable to handle it.


 1   // Fig. H.1: DivideByZeroNoExceptionHandling.java
 2   // Integer division without exception handling.
 3   import java.util.Scanner;
 4
 5   public class DivideByZeroNoExceptionHandling
 6   {
 7      // demonstrates throwing an exception when a divide-by-zero occurs
 8      public static int quotient( int numerator, int denominator )
 9      {
10         return numerator / denominator; // possible division by zero
11      } // end method quotient
12
13      public static void main( String[] args )
14      {
15         Scanner scanner = new Scanner( System.in ); // scanner for input
16
17         System.out.print( "Please enter an integer numerator: " );
18         int numerator = scanner.nextInt();
19         System.out.print( "Please enter an integer denominator: " );
20         int denominator = scanner.nextInt();
21
22         int result = quotient( numerator, denominator );
23         System.out.printf(
24            " Result: %d / %d = %d ", numerator, denominator, result );
25      } // end main
26   } // end class DivideByZeroNoExceptionHandling


Please enter an integer numerator: 100
Please enter an integer denominator: 7

Result: 100 / 7 = 14



Please enter an integer numerator: 100
Please enter an integer denominator: 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at DivideByZeroNoExceptionHandling.quotient(
           DivideByZeroNoExceptionHandling.java:10)
        at DivideByZeroNoExceptionHandling.main(
           DivideByZeroNoExceptionHandling.java:22)



Please enter an integer numerator: 100
Please enter an integer denominator: hello
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Unknown Source)
        at java.util.Scanner.next(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at java.util.Scanner.nextInt(Unknown Source)
        at DivideByZeroNoExceptionHandling.main(
            DivideByZeroNoExceptionHandling.java:20)


Fig. H.1 | Integer division without exception handling.

The first sample execution in Fig. H.1 shows a successful division. In the second execution, the user enters the value 0 as the denominator. Several lines of information are displayed in response to this invalid input. This information is known as a stack trace, which includes the name of the exception (java.lang.ArithmeticException) in a descriptive message that indicates the problem that occurred and the method-call stack (i.e., the call chain) at the time it occurred. The stack trace includes the path of execution that led to the exception method by method. This helps you debug the program. The first line specifies that an ArithmeticException has occurred. The text after the name of the exception (“/ by zero”) indicates that this exception occurred as a result of an attempt to divide by zero. Java does not allow division by zero in integer arithmetic. When this occurs, Java throws an ArithmeticException. ArithmeticExceptions can arise from a number of different problems in arithmetic, so the extra data (“/ by zero”) provides more specific information. Java does allow division by zero with floating-point values. Such a calculation results in the value positive or negative infinity, which is represented in Java as a floating-point value (but displays as the string Infinity or -Infinity). If 0.0 is divided by 0.0, the result is NaN (not a number), which is also represented in Java as a floating-point value (but displays as NaN).

Starting from the last line of the stack trace, we see that the exception was detected in line 22 of method main. Each line of the stack trace contains the class name and method (DivideByZeroNoExceptionHandling.main) followed by the file name and line number (DivideByZeroNoExceptionHandling.java:22). Moving up the stack trace, we see that the exception occurs in line 10, in method quotient. The top row of the call chain indicates the throw point—the initial point at which the exception occurs. The throw point of this exception is in line 10 of method quotient.

In the third execution, the user enters the string "hello" as the denominator. Notice again that a stack trace is displayed. This informs us that an InputMismatchException has occurred (package java.util). Our prior examples that read numeric values from the user assumed that the user would input a proper integer value. However, users sometimes make mistakes and input noninteger values. An InputMismatchException occurs when Scanner method nextInt receives a string that does not represent a valid integer. Starting from the end of the stack trace, we see that the exception was detected in line 20 of method main. Moving up the stack trace, we see that the exception occurred in method nextInt. Notice that in place of the file name and line number, we’re provided with the text Unknown Source. This means that the so-called debugging symbols that provide the filename and line number information for that method’s class were not available to the JVM—this is typically the case for the classes of the Java API. Many IDEs have access to the Java API source code and will display file names and line numbers in stack traces.

In the sample executions of Fig. H.1 when exceptions occur and stack traces are displayed, the program also exits. This does not always occur in Java—sometimes a program may continue even though an exception has occurred and a stack trace has been printed. In such cases, the application may produce unexpected results. For example, a graphical user interface (GUI) application will often continue executing. The next section demonstrates how to handle these exceptions.

In Fig. H.1 both types of exceptions were detected in method main. In the next example, we’ll see how to handle these exceptions to enable the program to run to normal completion.

H.3 Example: Handling ArithmeticExceptions and InputMismatchExceptions

The application in Fig. H.2, which is based on Fig. H.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 enter the input again.



 1   // Fig. H.2: DivideByZeroWithExceptionHandling.java
 2   // Handling ArithmeticExceptions and InputMismatchExceptions.
 3   import java.util.InputMismatchException;
 4   import java.util.Scanner;
 5
 6   public class DivideByZeroWithExceptionHandling
 7   {
 8      // demonstrates throwing an exception when a divide-by-zero occurs
 9      public static int quotient( int numerator, int denominator )
10         throws ArithmeticException
11      {
12         return numerator / denominator; // possible division by zero
13      } // end method quotient
14
15      public static void main( String[] args )
16      {
17         Scanner scanner = new Scanner( System.in ); // scanner for input
18         boolean continueLoop = true; // determines if more input is needed
19
20         do                                                                  
21         {                                                                   
22            try // read two numbers and calculate quotient                   
23            {                                                                
24               System.out.print( "Please enter an integer numerator: " );    
25               int numerator = scanner.nextInt();                            
26               System.out.print( "Please enter an integer denominator: " );  
27               int denominator = scanner.nextInt();                          
28                                                                             
29               int result = quotient( numerator, denominator );              
30               System.out.printf( " Result: %d / %d = %d ", numerator,     
31                  denominator, result );                                     
32               continueLoop = false; // input successful; end looping        
33            } // end try                                                     
34            catch ( InputMismatchException inputMismatchException )          
35            {                                                                
36               System.err.printf( " Exception: %s ",                       
37                  inputMismatchException );                                  
38               scanner.nextLine(); // discard input so user can try again    
39               System.out.println(                                           
40                  "You must enter integers. Please try again. " );          
41            } // end catch                                                   
42            catch ( ArithmeticException arithmeticException )                
43            {                                                                
44               System.err.printf( " Exception: %s ", arithmeticException );
45               System.out.println(                                           
46                  "Zero is an invalid denominator. Please try again. " );   
47            } // end catch                                                   
48         } while ( continueLoop ); // end do...while                         
49      } // end main
50   } // end class DivideByZeroWithExceptionHandling


Please enter an integer numerator: 100
Please enter an integer denominator: 7

Result: 100 / 7 = 14



Please enter an integer numerator: 100
Please enter an integer denominator: 0

Exception: java.lang.ArithmeticException: / by zero
Zero is an invalid denominator. Please try again.

Please enter an integer numerator: 100
Please enter an integer denominator: 7

Result: 100 / 7 = 14



Please enter an integer numerator: 100
Please enter an integer denominator: hello

Exception: java.util.InputMismatchException
You must enter integers. Please try again.

Please enter an integer numerator: 100
Please enter an integer denominator: 7

Result: 100 / 7 = 14


Fig. H.2 | Handling ArithmeticExceptions and InputMismatchExceptions.

The first sample execution in Fig. H.2 is a successful one that does not encounter any problems. In the second execution the user enters a zero denominator, and an ArithmeticException exception occurs. In the third execution the user enters the string "hello" as the denominator, and an InputMismatchException occurs. For each exception, the user is informed of the mistake and asked to try again, then is prompted for two new integers. In each sample execution, the program runs successfully to completion.

Class InputMismatchException is imported in line 3. Class ArithmeticException does not need to be imported because it’s in package java.lang. Line 18 creates the boolean variable continueLoop, which is true if the user has not yet entered valid input. Lines 20–48 repeatedly ask users for input until a valid input is received.

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 an integer.

The division that can cause an ArithmeticException is not performed in the try block. Rather, the call to method quotient (line 29) invokes the code that attempts the division (line 12); the JVM throws an ArithmeticException object when the denominator is zero.


Image Software Engineering Observation H.1

Exceptions may surface through explicitly mentioned code in a try block, through calls to other methods, through deeply nested method calls initiated by code in a try block or from the Java Virtual Machine as it executes Java bytecodes.


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, whereas the term “catch block” refers to only the block of code following the catch keyword, but not including it. For simplicity, we use the term “catch block” to refer to the block of code following the catch keyword, as well as the keyword itself.]

At least one catch block or a finally block (discussed in Section H.6) must immediately follow the try block. Each catch block specifies in parentheses an exception parameter that identifies the exception type the handler can process. When an exception occurs in a try block, the catch block that executes is the first one whose type matches the type of the exception that occurred (i.e., the type in the catch block matches the thrown exception type exactly or is a superclass of it). The exception parameter’s name enables the catch block to interact with a caught exception object—e.g., to implicitly invoke the caught exception’s toString method (as in lines 37 and 44), which displays basic information about the exception. Notice that we use the System.err (standard error stream) object to output error messages. By default, System.err’s print methods, like those of System.out, display data to the command prompt.

Line 38 of the first catch block calls Scanner method nextLine. Because an InputMismatchException occurred, the call to method nextInt never successfully read in the user’s data—so we read that input with a call to method nextLine. We do not do anything with the input at this point, because we know that it’s invalid. Each catch block displays an error message and asks the user to try again. After either catch block terminates, the user is prompted for input. We’ll soon take a deeper look at how this flow of control works in exception handling.


Image Common Programming Error H.1

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


 


Image Common Programming Error H.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 one for which there are no matching catch blocks. You saw uncaught exceptions in the second and third outputs of Fig. H.1. Recall that when exceptions occurred in that example, the application terminated early (after displaying the exception’s stack trace). This does not always occur as a result of uncaught exceptions. Java uses a “multithreaded” model of program execution—each thread is a parallel activity. One program can have many threads. If a program has only one thread, an uncaught exception will cause the program to terminate. If a program has multiple threads, an uncaught exception will terminate only the thread where the exception occurred. In such programs, however, certain threads may rely on others, and if one thread terminates due to an uncaught exception, there may be adverse effects to the rest of the program. Appendix J discusses these issues.

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. H.2), the try block terminates immediately and program control transfers to the first of the following catch blocks in which the exception parameter’s type matches the thrown exception’s type. In Fig. H.2, the first catch block catches 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. Some languages use the resumption model of exception handling, in which, after an exception is handled, control resumes just after the throw point.

Notice that we name our exception parameters (inputMismatchException and arithmeticException) based on their type. Java programmers often simply use the letter e as the name of their exception parameters.

After executing a catch block, this program’s flow of control proceeds to the first statement after the last catch block (line 48 in this case). The condition in the do...while statement is true (variable continueLoop contains its initial value of true), so control returns to the beginning of the loop and the user is once again prompted for input. This control statement will loop until valid input is entered. At that point, program control reaches line 32, which assigns false to variable continueLoop. The try block then terminates. If no exceptions are thrown in the try block, the catch blocks are skipped and control continues with the first statement after the catch blocks (we’ll learn about another possibility when we discuss the finally block in Section H.6). Now the condition for the do...while loop is false, and method main ends.

The try block and its corresponding catch and/or finally blocks form a try statement. Do not confuse the terms “try block” and “try statement”—the latter includes the try block as well as the following catch blocks and/or finally block.

As with any other block of code, when a try block terminates, local variables declared in the block go out of scope and are no longer accessible; thus, the local variables of a try block are not accessible in the corresponding catch blocks. When a catch block terminates, local variables declared within the catch block (including the exception parameter of that catch block) also go out of scope and are destroyed. Any remaining catch blocks in the try statement are ignored, and execution resumes at the first line of code after the try...catch sequence—this will be a finally block, if one is present.

Using the throws Clause

Now let’s examine method quotient (Fig. H.2, lines 9–13). The portion of the method declaration located at line 10 is known as a throws clause. It specifies the exceptions the method throws. This clause appears after the method’s parameter list and before the method’s body. It contains a comma-separated list of the exceptions that the method will throw if various problems occur. Such exceptions may be thrown by statements in the method’s body or by methods called from the body. A method can throw exceptions of the classes listed in its throws clause or of their subclasses. We’ve added the throws clause to this application to indicate to the rest of the program that this method may throw an ArithmeticException. Clients of method quotient are thus informed that the method may throw an ArithmeticException. You’ll learn more about the throws clause in Section H.5.

When line 12 executes, if the denominator is zero, the JVM throws an ArithmeticException object. This object will be caught by the catch block at lines 42–47, which displays basic information about the exception by implicitly invoking the exception’s toString method, then asks the user to try again.

If the denominator is not zero, method quotient performs the division and returns the result to the point of invocation of method quotient in the try block (line 29). Lines 30–31 display the result of the calculation and line 32 sets continueLoop to false. In this case, the try block completes successfully, so the program skips the catch blocks and fails the condition at line 48, and method main completes execution normally.

When quotient throws an ArithmeticException, quotient terminates and does not return a value, and quotient’s local variables go out of scope (and are destroyed). If quotient contained local variables that were references to objects and there were no other references to those objects, the objects would be marked for garbage collection. Also, when an exception occurs, the try block from which quotient was called terminates before lines 30–32 can execute. Here, too, if local variables were created in the try block prior to the exception’s being thrown, these variables would go out of scope.

If an InputMismatchException is generated by lines 25 or 27, the try block terminates and execution continues with the catch block at lines 34–41. In this case, method quotient is not called. Then method main continues after the last catch block (line 48).

H.4 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 (as we’ll see in Appendix J) and unsuccessful memory allocation (due to lack of memory). Exception handling is not designed to process problems associated with asynchronous events (e.g., disk I/O completions, network message arrivals, mouse clicks and keystrokes), which occur in parallel with, and independent of, the program’s flow of control.

H.5 Java Exception Hierarchy

All Java exception classes inherit directly or indirectly from class Exception, forming an inheritance hierarchy. You can extend this hierarchy with your own exception classes. Class Throwable (a subclass of Object) is the superclass of class Exception. Only Throwable objects can be used with the exception-handling mechanism. Class Throwable has two subclasses: Exception and Error. Class Exception and its subclasses—for instance, RuntimeException (package java.lang) and IOException (package java.io)—represent exceptional situations that can occur in a Java program and that can be caught by the application. Class Error and its subclasses represent abnormal situations that happen in the JVM. Most Errors happen infrequently and should not be caught by applications—it’s usually not possible for applications to recover from Errors.

Checked vs. Unchecked Exceptions

Java distinguishes between checked exceptions and unchecked exceptions. This distinction is important, because the Java compiler enforces a catch-or-declare requirement for checked exceptions. An exception’s type determines whether it’s checked or unchecked. All exception types that are direct or indirect subclasses of class RuntimeException (package java.lang) are unchecked exceptions. These are typically caused by defects in your program’s code. Examples of unchecked exceptions include ArrayIndexOutOfBoundsExceptions (discussed in Appendix E) and ArithmeticExceptions. All classes that inherit from class Exception but not class RuntimeException are considered to be checked exceptions. Such exceptions are typically caused by conditions that are not under the control of the program—for example, in file processing, the program can’t open a file because the file does not exist. Classes that inherit from class Error are considered to be unchecked.

The compiler checks each method call and method declaration to determine whether the method throws checked exceptions. If so, the compiler verifies that the checked exception is caught or is declared in a throws clause. We show how to catch and declare checked exceptions in the next several examples. Recall from Section H.3 that the throws clause specifies the exceptions a method throws. Such exceptions are not caught in the method’s body. To satisfy the catch part of the catch-or-declare requirement, the code that generates the exception must be wrapped in a try block and must provide a catch handler for the checked-exception type (or one of its superclass types). To satisfy the declare part of the catch-or-declare requirement, the method containing the code that generates the exception must provide a throws clause containing the checked-exception type after its parameter list and before its method body. If the catch-or-declare requirement is not satisfied, the compiler will issue an error message indicating that the exception must be caught or declared. This forces you to think about the problems that may occur when a method that throws checked exceptions is called.


Image Software Engineering Observation H.2

You must deal with checked exceptions. This results in more robust code than would be created if you were able to simply ignore the exceptions.


 


Image Common Programming Error H.3

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.


 


Image Common Programming Error H.4

If a subclass method overrides a superclass method, it’s an error for the subclass method to list more exceptions in its throws clause than the overridden superclass method does. However, a subclass’s throws clause can contain a subset of a superclass’s throws list.


 


Image Software Engineering Observation H.3

If your method calls other methods that throw checked exceptions, those exceptions must be caught or declared in your method. If an exception can be handled meaningfully in a method, the method should catch the exception rather than declare it.


Unlike checked exceptions, the Java compiler does not check the code to determine whether an unchecked exception is caught or declared. Unchecked exceptions typically can be prevented by proper coding. For example, the unchecked ArithmeticException thrown by method quotient (lines 9–13) in Fig. H.2 can be avoided if the method ensures that the denominator is not zero before attempting to perform the division. Unchecked exceptions are not required to be listed in a method’s throws clause—even if they are, it’s not required that such exceptions be caught by an application.


Image Software Engineering Observation H.4

Although the compiler does not enforce the catch-or-declare requirement for unchecked exceptions, provide appropriate exception-handling code when it’s known that such exceptions might occur. For example, a program should process the NumberFormatException from Integer method parseInt, even though NumberFormatException (an indirect subclass of RuntimeException) is an unchecked exception type. This makes your programs more robust.


Catching Subclass Exceptions

If a catch handler is written to catch superclass-type exception objects, it can also catch all objects of that class’s subclasses. This enables catch to handle related errors with a concise notation and allows for polymorphic processing of related exceptions. You can certainly catch each subclass type individually if those exceptions require different processing.

Only the First Matching catch Executes

If there are multiple catch blocks that match a particular exception type, only the first matching catch block executes when an exception of that type occurs. It’s a compilation error to catch the exact same type in two different catch blocks associated with a particular try block. However, there may be several catch blocks that match an exception—i.e., several catch blocks whose types are the same as the exception type or a superclass of that type. For instance, we could follow a catch block for type ArithmeticException with a catch block for type Exception—both would match ArithmeticExceptions, but only the first matching catch block would execute.


Image Error-Prevention Tip H.1

Catching subclass types individually is subject to error if you forget to test for one or more of the subclass types explicitly; catching the superclass guarantees that objects of all subclasses will be caught. Positioning a catch block for the superclass type after all other subclass catch blocks ensures that all subclass exceptions are eventually caught.


 


Image Common Programming Error H.5

Placing a catch block for a superclass exception type before other catch blocks that catch subclass exception types would prevent those catch blocks from executing, so a compilation error occurs.


H.6 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 types of resource leaks can occur. For example, files, database connections and network connections that are not closed properly after they’re no longer needed might not be available for use in other programs.


Image Error-Prevention Tip H.2

A subtle issue is that Java does not entirely eliminate memory leaks. Java will not garbage-collect an object until there are no remaining references to it. Thus, if you erroneously keep references to unwanted objects, memory leaks can occur. To help avoid this problem, set reference-type variables to null when they’re no longer needed.


The finally block (which consists of the finally keyword, followed by code enclosed in curly braces), sometimes referred to as the finally clause, is optional. If it’s present, it’s placed after the last catch block. If there are no catch blocks, the finally block immediately follows the try block.

The finally block will execute whether or not an exception is thrown in the corresponding try block. The finally block also will execute if a try block exits by using a return, break or continue statement or simply by reaching its closing right brace. The finally block will not execute if the application exits early from a try block by calling method System.exit. This method immediately terminates an application.

Because a finally block almost always executes, it typically contains resource-release code. Suppose a resource is allocated in a try block. If no exception occurs, the catch blocks are skipped and control proceeds to the finally block, which frees the resource. Control then proceeds to the first statement after the finally block. If an exception occurs in the try block, the try block terminates. If the program catches the exception in one of the corresponding catch blocks, it processes the exception, then the finally block releases the resource and control proceeds to the first statement after the finally block. If the program doesn’t catch the exception, the finally block still releases the resource and an attempt is made to catch the exception in a calling method.


Image Error-Prevention Tip H.3

The finally block is an ideal place to release resources acquired in a try block (such as opened files), which helps eliminate resource leaks.


 


Image Performance Tip H.1

Always release a resource explicitly and at the earliest possible moment at which it’s no longer needed. This makes resources available for reuse as early as possible, thus improving resource utilization.


If an exception that occurs in a try block cannot be caught by one of that try block’s catch handlers, the program skips the rest of the try block and control proceeds to the finally block. Then the program passes the exception to the next outer try block—normally in the calling method—where an associated catch block might catch it. This process can occur through many levels of try blocks. Also, the exception could go uncaught.

If a catch block throws an exception, the finally block still executes. Then the exception is passed to the next outer try block—again, normally in the calling method.

Figure H.3 demonstrates that the finally block executes even if an exception is not thrown in the corresponding try block. The program contains static methods main (lines 6–18), throwException (lines 21–44) and doesNotThrowException (lines 47–64). Methods throwException and doesNotThrowException are declared static, so main can call them directly without instantiating a UsingExceptions object.


 1   // Fig. H.3: UsingExceptions.java
 2   // try...catch...finally exception handling mechanism.
 3
 4   public class UsingExceptions
 5   {
 6      public static void main( String[] args )
 7      {
 8         try
 9         {
10            throwException(); // call method throwException
11         } // end try
12         catch ( Exception exception ) // exception thrown by throwException
13         {
14            System.err.println( "Exception handled in main" );
15         } // end catch
16
17         doesNotThrowException();
18      } // end main
19
20      // demonstrate try...catch...finally
21      public static void throwException() throws Exception
22      {
23         try // throw an exception and immediately catch it
24         {
25            System.out.println( "Method throwException" );
26            throw new Exception(); // generate exception
27         } // end try
28         catch ( Exception exception ) // catch exception thrown in try
29         {
30            System.err.println(
31               "Exception handled in method throwException" );
32            throw exception; // rethrow for further processing
33
34            // code here would not be reached; would cause compilation errors
35
36         } // end catch
37         finally // executes regardless of what occurs in try...catch  
38         {                                                             
39            System.err.println( "Finally executed in throwException" );
40         } // end finally                                              
41
42         // code here would not be reached; would cause compilation errors
43
44      } // end method throwException
45
46      // demonstrate finally when no exception occurs
47      public static void doesNotThrowException()
48      {
49         try // try block does not throw an exception
50         {
51            System.out.println( "Method doesNotThrowException" );
52         } // end try
53         catch ( Exception exception ) // does not execute
54         {
55            System.err.println( exception );
56         } // end catch
57         finally // executes regardless of what occurs in try...catch
58         {                                                           
59            System.err.println(                                      
60               "Finally executed in doesNotThrowException" );        
61         } // end finally                                            
62
63         System.out.println( "End of method doesNotThrowException" );
64      } // end method doesNotThrowException
65   } // end class UsingExceptions


Method throwException
Exception handled in method throwException
Finally executed in throwException
Exception handled in main
Method doesNotThrowException
Finally executed in doesNotThrowException
End of method doesNotThrowException


Fig. H.3 | try...catch...finally exception-handling mechanism.

System.out and System.err are streams—sequences of bytes. While System.out (known as the standard output stream) displays a program’s output, System.err (known as the standard error stream) displays a program’s errors. Output from these streams can be redirected (i.e., sent to somewhere other than the command prompt, such as to a file). Using two different streams enables you to easily separate error messages from other output. For instance, data output from System.err could be sent to a log file, while data output from System.out can be displayed on the screen. For simplicity, this appendix will not redirect output from System.err, but will display such messages to the command prompt. You’ll learn more about streams in Appendix J.

Throwing Exceptions Using the throw Statement

Method main (Fig. H.3) begins executing, enters its try block and immediately calls method throwException (line 10). Method throwException throws an Exception. The statement at line 26 is known as a throw statement—it’s executed to indicate that an exception has occurred. So far, you’ve only caught exceptions thrown by called methods. You can throw exceptions yourself by using the throw statement. Just as with exceptions thrown by the Java API’s methods, this indicates to client applications that an error has occurred. A throw statement specifies an object to be thrown. The operand of a throw can be of any class derived from class Throwable.


Image Software Engineering Observation H.5

When toString is invoked on any Throwable object, its resulting string includes the descriptive string that was supplied to the constructor, or simply the class name if no string was supplied.


 


Image Software Engineering Observation H.6

An object can be thrown without containing information about the problem that occurred. In this case, simply knowing that an exception of a particular type occurred may provide sufficient information for the handler to process the problem correctly.


 


Image Software Engineering Observation H.7

Exceptions can be thrown from constructors. When an error is detected in a constructor, an exception should be thrown to avoid creating an improperly formed object.


Rethrowing Exceptions

Line 32 of Fig. H.3 rethrows the exception. Exceptions are rethrown when a catch block, upon receiving an exception, decides either that it cannot process that exception or that it can only partially process it. Rethrowing an exception defers the exception handling (or perhaps a portion of it) to another catch block associated with an outer try statement. An exception is rethrown by using the throw keyword, followed by a reference to the exception object that was just caught. Exceptions cannot be rethrown from a finally block, as the exception parameter (a local variable) from the catch block no longer exists.

When a rethrow occurs, the next enclosing try block detects the rethrown exception, and that try block’s catch blocks attempt to handle it. In this case, the next enclosing try block is found at lines 8–11 in method main. Before the rethrown exception is handled, however, the finally block (lines 37–40) executes. Then method main detects the rethrown exception in the try block and handles it in the catch block (lines 12–15).

Next, main calls method doesNotThrowException (line 17). No exception is thrown in doesNotThrowException’s try block (lines 49–52), so the program skips the catch block (lines 53–56), but the finally block (lines 57–61) nevertheless executes. Control proceeds to the statement after the finally block (line 63). Then control returns to main and the program terminates.


Image Common Programming Error H.6

If an exception has not been caught when control enters a finally block and the finally block throws an exception that’s not caught in the finally block, the first exception will be lost and the exception from the finally block will be returned to the calling method.


 


Image Error-Prevention Tip H.4

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.


 


Image Common Programming Error H.7

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.


 


Image Good Programming Practice H.1

Exception handling is intended to remove error-processing code from the main line of a program’s code to improve program clarity. Do not place try...catch... finally around every statement that may throw an exception. This makes programs difficult to read. Rather, place one try block around a significant portion of your code, follow that try block with catch blocks that handle each possible exception and follow the catch blocks with a single finally block (if one is required).


H.7 Stack Unwinding and Obtaining Information from an Exception Object

When an exception is thrown but not caught in a particular scope, the method-call stack is “unwound,” and an attempt is made to catch the exception in the next outer try block. This process is called stack unwinding. Unwinding the method-call stack means that the method in which the exception was not caught terminates, all local variables in that method go out of scope and control returns to the statement that originally invoked that method. If a try block encloses that statement, an attempt is made to catch the exception. If a try block does not enclose that statement or if the exception is not caught, stack unwinding occurs again. Figure H.4 demonstrates stack unwinding, and the exception handler in main shows how to access the data in an exception object.


 1   // Fig. H.4: UsingExceptions.java
 2   // Stack unwinding and obtaining data from an exception object.
 3
 4   public class UsingExceptions
 5   {
 6      public static void main( String[] args )
 7      {
 8         try
 9         {
10            method1(); // call method1
11         } // end try
12         catch ( Exception exception ) // catch exception thrown in method1
13         {
14            System.err.printf( "%s ", exception.getMessage() );
15            exception.printStackTrace(); // print exception stack trace
16
17            // obtain the stack-trace information
18            StackTraceElement[] traceElements = exception.getStackTrace();
19
20            System.out.println( " Stack trace from getStackTrace:" );
21            System.out.println( "Class File Line Method" );
22
23            // loop through traceElements to get exception description
24            for ( StackTraceElement element : traceElements )
25            {
26               System.out.printf( "%s ", element.getClassName() );
27               System.out.printf( "%s ", element.getFileName() );
28               System.out.printf( "%s ", element.getLineNumber() );
29               System.out.printf( "%s ", element.getMethodName() );
30            } // end for
31         } // end catch
32      } // end main
33
34      // call method2; throw exceptions back to main
35      public static void method1() throws Exception
36      {
37         method2();
38      } // end method method1
39
40      // call method3; throw exceptions back to method1
41      public static void method2() throws Exception
42      {
43         method3();
44      } // end method method2
45
46      // throw Exception back to method2
47      public static void method3() throws Exception
48      {
49         throw new Exception( "Exception thrown in method3" );
50      } // end method method3
51   } // end class UsingExceptions


Exception thrown in method3

java.lang.Exception: Exception thrown in method3
        at UsingExceptions.method3(UsingExceptions.java:49)
        at UsingExceptions.method2(UsingExceptions.java:43)
        at UsingExceptions.method1(UsingExceptions.java:37)
        at UsingExceptions.main(UsingExceptions.java:10)

Stack trace from getStackTrace:
Class           File                    Line    Method
UsingExceptions UsingExceptions.java    49      method3
UsingExceptions UsingExceptions.java    43      method2
UsingExceptions UsingExceptions.java    37      method1
UsingExceptions UsingExceptions.java    10      main


Fig. H.4 | Stack unwinding and obtaining data from an exception object.

Stack Unwinding

In main, the try block (lines 8–11) calls method1 (declared at lines 35–38), which in turn calls method2 (declared at lines 41–44), which in turn calls method3 (declared at lines 47–50). Line 49 of method3 throws an Exception object—this is the throw point. Because the throw statement at line 49 is not enclosed in a try block, stack unwinding occurs—method3 terminates at line 49, then returns control to the statement in method2 that invoked method3 (i.e., line 43). Because no try block encloses line 43, stack unwinding occurs again—method2 terminates at line 43 and returns control to the statement in method1 that invoked method2 (i.e., line 37). Because no try block encloses line 37, stack unwinding occurs one more time—method1 terminates at line 37 and returns control to the statement in main that invoked method1 (i.e., line 10). The try block at lines 8–11 encloses this statement. The exception has not been handled, so the try block terminates and the first matching catch block (lines 12–31) catches and processes the exception. If there were no matching catch blocks, and the exception is not declared in each method that throws it, a compilation error would occur. Remember that this is not always the case—for unchecked exceptions, the application will compile, but it will run with unexpected results.

Obtaining Data from an Exception Object

Recall that exceptions derive from class Throwable. Class Throwable offers a printStackTrace method that outputs to the standard error stream the stack trace (discussed in Section H.2). Often, this is helpful in testing and debugging. Class Throwable also provides a getStackTrace method that retrieves the stack-trace information that might be printed by printStackTrace. Class Throwable’s getMessage method returns the descriptive string stored in an exception.


Image Error-Prevention Tip H.5

An exception that’s not caught in an application causes Java’s default exception handler to run. This displays the name of the exception, a descriptive message that indicates the problem that occurred and a complete execution stack trace. In an application with a single thread of execution, the application terminates. In an application with multiple threads, the thread that caused the exception terminates.


 


Image Error-Prevention Tip H.6

Throwable method toString (inherited by all Throwable subclasses) returns a String containing the name of the exception’s class and a descriptive message.


The catch handler in Fig. H.4 (lines 12–31) demonstrates getMessage, printStackTrace and getStackTrace. If we wanted to output the stack-trace information to streams other than the standard error stream, we could use the information returned from getStackTrace and output it to another stream or use one of the overloaded versions of method printStackTrace.

Line 14 invokes the exception’s getMessage method to get the exception description. Line 15 invokes the exception’s printStackTrace method to output the stack trace that indicates where the exception occurred. Line 18 invokes the exception’s getStackTrace method to obtain the stack-trace information as an array of StackTraceElement objects. Lines 24–30 get each StackTraceElement in the array and invoke its methods getClassName, getFileName, getLineNumber and getMethodName to get the class name, file name, line number and method name, respectively, for that StackTraceElement. Each StackTraceElement represents one method call on the method-call stack.

The program’s output shows that the stack-trace information printed by printStackTrace follows the pattern: className.methodName (fileName:lineNumber), where class-Name, methodName and fileName indicate the names of the class, method and file in which the exception occurred, respectively, and the lineNumber indicates where in the file the exception occurred. You saw this in the output for Fig. H.1. Method getStackTrace enables custom processing of the exception information. Compare the output of printStackTrace with the output created from the StackTraceElements to see that both contain the same stack-trace information.


Image Software Engineering Observation H.8

Never provide a catch handler with an empty body—this effectively ignores the exception. At least use printStackTrace to output an error message to indicate that a problem exists.


H.8 Wrap-Up

In this appendix, you learned how to use exception handling to deal with errors. You learned that exception handling enables you to remove error-handling code from the “main line” of the program’s execution. We showed how to use try blocks to enclose code that may throw an exception, and how to use catch blocks to deal with exceptions that may arise. You learned about the termination model of exception handling, which dictates that after an exception is handled, program control does not return to the throw point. We discussed checked vs. unchecked exceptions, and how to specify with the throws clause the exceptions that a method might throw. You learned how to use the finally block to release resources whether or not an exception occurs. You also learned how to throw and rethrow exceptions. We showed how to obtain information about an exception using methods printStackTrace, getStackTrace and getMessage. In the next appendix, we discuss graphical user interface concepts and explain the essentials of event handling.

Self-Review Exercises

H.1 List five common examples of exceptions.

H.2 Give several reasons why exception-handling techniques should not be used for conventional program control.

H.3 Why are exceptions particularly appropriate for dealing with errors produced by methods of classes in the Java API?

H.4 What is a “resource leak”?

H.5 If no exceptions are thrown in a try block, where does control proceed to when the try block completes execution?

H.6 Give a key advantage of using catch( Exception exceptionName ).

H.7 Should a conventional application catch Error objects? Explain.

H.8 What happens if no catch handler matches the type of a thrown object?

H.9 What happens if several catch blocks match the type of the thrown object?

H.10 Why would a programmer specify a superclass type as the type in a catch block?

H.11 What is the key reason for using finally blocks?

H.12 What happens when a catch block throws an Exception?

H.13 What does the statement throw exceptionReference do in a catch block?

H.14 What happens to a local reference in a try block when that block throws an Exception?

Answers to Self-Review Exercises

H.1 Memory exhaustion, array index out of bounds, arithmetic overflow, division by zero, invalid method parameters.

H.2

(a) Exception handling is designed to handle infrequently occurring situations that often result in program termination, not situations that arise all the time.

(b) Flow of control with conventional control structures is generally clearer and more efficient than with exceptions.

(c) The additional exceptions can get in the way of genuine error-type exceptions. It becomes more difficult for you to keep track of the larger number of exception cases.

H.3 It’s unlikely that methods of classes in the Java API could perform error processing that would meet the unique needs of all users.

H.4 A “resource leak” occurs when an executing program does not properly release a resource when it’s no longer needed.

H.5 The catch blocks for that try statement are skipped, and the program resumes execution after the last catch block. If there’s a finally block, it’s executed first; then the program resumes execution after the finally block.

H.6 The form catch( Exception exceptionName ) catches any type of exception thrown in a try block. An advantage is that no thrown Exception can slip by without being caught. You can then decide to handle the exception or possibly rethrow it.

H.7 Errors are usually serious problems with the underlying Java system; most programs will not want to catch Errors because they will not be able to recover from them.

H.8 This causes the search for a match to continue in the next enclosing try statement. If there’s a finally block, it will be executed before the exception goes to the next enclosing try statement. If there are no enclosing try statements for which there are matching catch blocks and the exceptions are declared (or unchecked), a stack trace is printed and the current thread terminates early. If the exceptions are checked, but not caught or declared, compilation errors occur.

H.9 The first matching catch block after the try block is executed.

H.10 This enables a program to catch related types of exceptions and process them in a uniform manner. However, it’s often useful to process the subclass types individually for more precise exception handling.

H.11 The finally block is the preferred means for releasing resources to prevent resource leaks.

H.12 First, control passes to the finally block if there is one. Then the exception will be processed by a catch block (if one exists) associated with an enclosing try block (if one exists).

H.13 It rethrows the exception for processing by an exception handler of an enclosing try statement, after the finally block of the current try statement executes.

H.14 The reference goes out of scope. If the referenced object becomes unreachable, the object can be garbage collected.

Exercises

H.15 (Exceptional Conditions) List the various exceptional conditions that have occurred in programs throughout the appendices so far. List as many additional exceptional conditions as you can. For each of these, describe briefly how a program typically would handle the exception by using the exception-handling techniques discussed in this appendix. Typical exceptions include division by zero and array index out of bounds.

H.16 (Exceptions and Constructor Failure) Until this appendix, we’ve found dealing with errors detected by constructors to be a bit awkward. Explain why exception handling is an effective means for dealing with constructor failure.

H.17 (Catching Exceptions with Superclasses) Use inheritance to create an exception superclass (called ExceptionA) and exception subclasses ExceptionB and ExceptionC, where ExceptionB inherits from ExceptionA and ExceptionC inherits from ExceptionB. Write a program to demonstrate that the catch block for type ExceptionA catches exceptions of types ExceptionB and ExceptionC.

H.18 (Catching Exceptions Using Class Exception) Write a program that demonstrates how various exceptions are caught with

catch ( Exception exception )

This time, define classes ExceptionA (which inherits from class Exception) and ExceptionB (which inherits from class ExceptionA). In your program, create try blocks that throw exceptions of types ExceptionA, ExceptionB, NullPointerException and IOException. All exceptions should be caught with catch blocks specifying type Exception.

H.19 (Order of catch Blocks) Write a program that shows that the order of catch blocks is important. If you try to catch a superclass exception type before a subclass type, the compiler should generate errors.

H.20 (Constructor Failure) Write a program that shows a constructor passing information about constructor failure to an exception handler. Define class SomeClass, which throws an Exception in the constructor. Your program should try to create an object of type SomeClass and catch the exception that’s thrown from the constructor.

H.21 (Rethrowing Exceptions) Write a program that illustrates rethrowing an exception. Define methods someMethod and someMethod2. Method someMethod2 should initially throw an exception. Method someMethod should call someMethod2, catch the exception and rethrow it. Call someMethod from method main, and catch the rethrown exception. Print the stack trace of this exception.

H.22 (Catching Exceptions Using Outer Scopes) Write a program showing that a method with its own try block does not have to catch every possible error generated within the try. Some exceptions can slip through to, and be handled in, other scopes.

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

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