Catching and Handling Exceptions

This section shows you how to use the three components of an exception handlerthe try, catch, and finally blocksto write an exception handler. The last part of this section walks through the example and analyzes what occurs during various scenarios.

The following example defines and implements a class named ListOfNumbers. [1] Upon construction, ListOfNumbers creates a Vector that contains ten Integer elements with sequential values 0 through 9. The ListOfNumbers class also defines a method named writeList that writes the list of numbers into a text file called OutFile.txt. This example uses output classes defined in java.io, which are covered in Chapter 9, I/O: Reading and Writing (page 313).

[1] ListOfNumbers.java is included on the CD and is available online. See Code Samples (page 268).

// Note: This class won't compile by design! import java.io.*; import java.util.Vector; public class ListOfNumbers { private Vector victor; private static final int SIZE = 10; public ListOfNumbers () { victor = new Vector(SIZE); for (int i = 0; i < SIZE; i++) { victor.addElement(new Integer(i)); } } public void writeList() { PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { out.println("Value at: " + i + " = " + victor.elementAt(i)); } out.close(); } }

The first line in boldface is a call to a constructor. The constructor initializes an output stream on a file. If the file cannot be opened, the constructor throws an IOException. The second line in boldface type is a call to the Vector class's elementAt method, which throws an ArrayIndexOutOfBoundsException if the value of its argument is too small (less than zero) or too large (larger than the number of elements currently contained by the Vector).

If you try to compile the ListOfNumbers class, the compiler prints an error message about the exception thrown by the FileWriter constructor. However, it does not display an error message about the exception thrown by elementAt. The reason is that the exception thrown by the constructor, IOException, is a checked exception and the one thrown by the elementAt method, ArrayIndexOutOfBoundsException, is a runtime exception. The Java platform requires only that a program deal with checked exceptions, so you get only one error message.

Now that you're familiar with the ListOfNumbers class and where the exceptions can be thrown within it, you're ready to read about how to write exception handlers to catch and handle those exceptions.

The try Block

The first step in constructing an exception handler is to enclose the statements that might throw an exception within a try block. In general, a try block looks like this.

try { statements }

The segment of code labeled statements contains one or more legal statements that might throw an exception.

To construct an exception handler for the writeList method from the ListOfNumbers class, you need to enclose the exception-throwing statements of the writeList method within a try block. There is more than one way to do this. You can put each statement that might throw an exception within its own try block and provide separate exception handlers for each. Or, you can put all the writeList statements within a single try block and associate multiple handlers with it. The following listing uses one try block for the entire method because the code in question is very short:

PrintWriter out = null; try { System.out.println("Entered try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) { out.println("Value at: " + i + " = " + victor.elementAt(i)); } }

If an exception occurs within the try block, that exception is handled by an exception handler associated with it. To associate an exception handler with a try block, put a catch statement after it. The next section shows you how.

The catch Block(s)

You associate exception handlers with a try block by providing one or more catch blocks directly after the try. No code can be between the end of the try and the beginning of the first catch statement:

try { ... } catch (ExceptionType name) { ... } catch (ExceptionType name) { ... } ...

Each catch block is an exception handler and handles the type of exception indicated by its argument. The argument type, ExceptionType , declares the type of exception that the han-dler can handle and must be the name of a class that inherits from the Throwable [1] class. The handler can refer to the exception with name .

[1] http://java.sun.com/j2se/1.3/docs/api/java/lang/Throwable.html

The catch block contains a series of statements. These statements are executed if and when the exception handler is invoked. The runtime system invokes the exception handler when the handler is the first one in the call stack whose ExceptionType matches the type of the exception thrown. The system considers it a match if the thrown object can legally be assigned to the exception handler's argument.

Here are two exception handlers for writeList methodone for each of the two types of exceptions that can be thrown within the try block:

try { ... } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); }

The handlers shown print an error message. Although simple, this might be the behavior you want. The exception gets caught, the user is notified, and the program continues to execute. However, exception handlers can do more. They can do error recovery, prompt the user to make a decision, or decide to exit the program.

The finally Block

The final step in setting up an exception handler is to clean up before allowing control to be passed to a different part of the program. You do this by enclosing the clean up code within a finally block. The finally block is optional and provides a mechanism to clean up regardless of what happens within the try block. Use the finally block to close files or to release other system resources.

The try block of the writeList method that you've been working with here opens a PrintWriter. The program should close that stream before exiting the writeList method. This poses a somewhat complicated problem because writeList's try block can exit in one of three ways.

  1. The new FileWriter statement fails and throws an IOException.
  2. The victor.elementAt(i) statement fails and throws an ArrayIndexOutOfBoundsException.
  3. Everything succeeds and the try block exits normally.

The runtime system always executes the statements within the finally block regardless of what happens within the try block. So it's the perfect place to perform cleanup.

The following finally block for the writeList method cleans up and closes the PrintWriter:

finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } }

In the writeList example, you could provide for cleanup without the intervention of a finally statement. For example, you could put the code to close the PrintWriter at the end of the try block and again within the exception handler for ArrayIndexOutOfBoundsException, as shown here:

try { ... out.close(); // don't do this; it duplicates code } catch (ArrayIndexOutOfBoundsException e) { out.close(); // don't do this; it duplicates code System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); }

However, this duplicates code, thus making the code difficult to read and error prone if you later modify it. For example, if you add to the try block code that can throw a new type of exception, you have to remember to close the PrintWriter within the new exception handler.

Putting It All Together

The previous sections describe how to construct the try, catch, and finally code blocks for the writeList example. Next, we walk you through the code and investigate what happens during three scenarios.

When all the components are put together, the writeList method looks like this:

public void writeList() { PrintWriter out = null; try { System.out.println("Entering try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) { out.println("Value at: " + i + " = " + victor.elementAt(i)); } } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }

As mentioned previously, the try block in this method has three exit possibilities.

  1. The new FileWriter statement fails and throws an IOException.
  2. The victor.elementAt(i) statement fails and throws an ArrayIndexOutOfBoundsException.
  3. Everything succeeds and the try statement exits normally.

Let's look at what happens in the writeList method during each of these exit possibilities.

Scenario 1: An IOException Occurs

The statement that creates a FileWriter can fail for a number of reasons. For example, the constructor for FileWriter throws an IOException if the user doesn't have write permission on the file or directory, or the file system is full, or the directory for the file doesn't exist.

When FileWriter throws an IOException, the runtime system immediately stops executing the try block. The runtime system then starts searching at the top of the method call stack for an appropriate exception handler. In this example, when the IOException occurs, the FileWriter constructor is at the top of the call stack. However, the FileWriter constructor doesn't have an appropriate exception handler, so the runtime system checks the next method in the method call stackthe writeList method. The writeList method has two exception handlers: one for ArrayIndexOutOfBoundsException and one for IOException.

The runtime system checks writeList's handlers in the order in which they appear after the try statement. The argument to the first exception handler is ArrayIndexOutOfBoundsException. Since this does not match the type of exception that was thrown, IOException, the runtime system continues its search for an appropriate exception handler.

Next, the runtime system checks writeList's second exception handler. This handler handles IOExceptions (since it takes IOException as an argument). Now that the runtime has found an appropriate handler, the code in that catch clause is executed.

After the exception handler has executed, the runtime system passes control to the finally block. In this scenario, the PrintWriter was never opened and doesn't need to be closed. After the finally block has completed executing, the program continues with the first statement after the finally block.

Here's the complete output that you see from the ListOfNumbers program when an IOException is thrown:

Entering try statement Caught IOException: OutFile.txt PrintWriter not open

The boldface code in the following listing shows the statements that get executed during this scenario:

public void writeList() { PrintWriter out = null; try { System.out.println("Entering try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) { out.println("Value at: " + i + " = " + victor.elementAt(i)); } } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }

Scenario 2: An ArrayIndexOutOfBoundsException Occurs

In this scenario, the argument passed to the Vector's elementAt method is out of bounds. The argument is either less than 0 or is larger than the size of the array. Because of how the code is written, this is impossible, but suppose that a bug is introduced into the code when someone modifies it.

As in scenario 1, when the exception occurs, the runtime system stops executing the try block and attempts to locate an exception handler for an ArrayIndexOutOfBoundsException. The runtime system searches for an appropriate exception handler. It finds the catch statement in the writeList method, which handles exceptions of the type ArrayIndexOutOfBoundsException. Because the type of the thrown exception matches the type of the exception handler, the runtime system executes this exception handler.

After the exception handler has run, the runtime system passes control to the finally block. In this particular scenario, the PrintWriter was open, so the finally statement closes it. After the finally block has completed executing, the program continues with the first statement after the finally block.

Following is the complete output that you see from the ListOfNumbers program when an ArrayIndexOutOfBoundsException is thrown:

Entering try statement Caught ArrayIndexOutOfBoundsException: 10 >= 10 Closing PrintWriter

The boldface code in the following listing shows the statements that get executed during this scenario:

public void writeList() { PrintWriter out = null; try { System.out.println("Entering try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) { out.println("Value at: " + i + " = " + victor.elementAt(i)); } } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }

Scenario 3: The try Block Exits Normally

In this scenario, all the statements within the scope of the try block execute successfully and throw no exceptions. Execution falls off the end of the try block, and the runtime system passes control to the finally block. Because everything was successful, the PrintWriter is open when control reaches the finally block, which closes the PrintWriter. Again, after the finally block has completed executing, the program continues with the first statement after the finally block.

Here is the output from the ListOfNumbers program when no exceptions are thrown:

Entering try statement Closing PrintWriter

The boldface code in the following listing shows the statements that get executed during this scenario:

public void writeList() { PrintWriter out = null; try { System.out.println("Entering try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) { out.println("Value at: " + i + " = " + victor.elementAt(i)); } } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }

Категории