百度空间 | 百度首页 
 
查看文章
 
java Exceptions -The finally Block
2009年10月31日 星期六 01:00 A.M.

java Exceptions -The finally Block

http://java.sun.com/docs/books/tutorial/essential/exceptions/putItTogether.html

The finally Block
The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.

Note: If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.

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 vector.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 then 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 block. 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 follows.
try {
    
    out.close();       //Don't do this; it duplicates code. 
    
} catch (FileNotFoundException e) {
    out.close();       //Don't do this; it duplicates code.
    System.err.println("Caught: FileNotFoundException: " 
                      + e.getMessage());
    throw new RuntimeException(e);
    
} catch (IOException e) {
    System.err.println("Caught IOException: " 
                      + e.getMessage());
}
However, this duplicates code, thus making the code difficult to read and error-prone should you modify it later. For example, if you add code that can throw a new type of exception to the try block, you have to remember to close the PrintWriter within the new exception handler.

Important: The finally block is a key tool for preventing resource leaks. When closing a file or otherwise recovering resources, place the code in a finally block to ensure that resource is always recovered.
Putting It All Together
The previous sections described how to construct the try, catch, and finally code blocks for the writeList method in the ListOfNumbers class. Now, let's walk through the code and investigate what can happen.

When all the components are put together, the writeList method looks like the following.

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 + " = " 
                             + vector.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, this method's try block has three different exit possibilities; here are two of them.
  1. Code in the try statement fails and throws an exception. This could be an IOException caused by the new FileWriter statement or an ArrayIndexOutOfBoundsException caused by a wrong index value in the for loop.
  2. Everything succeeds and the try statement exits normally.
Let's look at what happens in the writeList method during these two exit possibilities.

Scenario 1: An Exception Occurs

The statement that creates a FileWriter can fail for a number of reasons. For example, the constructor for the FileWriter throws an IOException if the program cannot create or write to the file indicated.

When FileWriter throws an IOException, the runtime system immediately stops executing the try block; method calls being executed are not completed. 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 — the writeList method — in the method call stack. The writeList method has two exception handlers: one for IOException and one for ArrayIndexOutOfBoundsException.

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. This does not match the type of exception thrown, so the runtime system checks the next exception handler — IOException. This matches the type of exception that was thrown, so the runtime system ends its search for an appropriate exception handler. Now that the runtime has found an appropriate handler, the code in that catch block is executed.

After the exception handler executes, the runtime system passes control to the finally block. Code in the finally block executes regardless of the exception caught above it. In this scenario, the FileWriter was never opened and doesn't need to be closed. After the finally block finishes executing, the program continues with the first statement after the finally block.

Here's the complete output from the ListOfNumbers program that appears 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 
          + " = " + vector.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: 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 finishes 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 sample 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 + " = " 
                         +  vector.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");
          }
    }
}

Unchecked Exceptions — The Controversy

Because the Java programming language does not require methods to catch or to specify unchecked exceptions (RuntimeException, Error, and their subclasses), programmers may be tempted to write code that throws only unchecked exceptions or to make all their exception subclasses inherit from RuntimeException. Both of these shortcuts allow programmers to write code without bothering with compiler errors and without bothering to specify or to catch any exceptions. Although this may seem convenient to the programmer, it sidesteps the intent of the catch or specify requirement and can cause problems for others using your classes.

Why did the designers decide to force a method to specify all uncaught checked exceptions that can be thrown within its scope? Any Exception that can be thrown by a method is part of the method's public programming interface. Those who call a method must know about the exceptions that a method can throw so that they can decide what to do about them. These exceptions are as much a part of that method's programming interface as its parameters and return value.

The next question might be: "If it's so good to document a method's API, including the exceptions it can throw, why not specify runtime exceptions too?" Runtime exceptions represent problems that are the result of a programming problem, and as such, the API client code cannot reasonably be expected to recover from them or to handle them in any way. Such problems include arithmetic exceptions, such as dividing by zero; pointer exceptions, such as trying to access an object through a null reference; and indexing exceptions, such as attempting to access an array element through an index that is too large or too small.

Runtime exceptions can occur anywhere in a program, and in a typical one they can be very numerous. Having to add runtime exceptions in every method declaration would reduce a program's clarity. Thus, the compiler does not require that you catch or specify runtime exceptions (although you can).

One case where it is common practice to throw a RuntimeException is when the user calls a method incorrectly. For example, a method can check if one of its arguments is incorrectly null. If an argument is null, the method might throw a NullPointerException, which is an unchecked exception.

Generally speaking, do not throw a RuntimeException or create a subclass of RuntimeException simply because you don't want to be bothered with specifying the exceptions your methods can throw.

Here's the bottom line guideline: If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.

Advantages of Exceptions
Now that you know what exceptions are and how to use them, it's time to learn the advantages of using exceptions in your programs.

Advantage 1: Separating Error-Handling Code from "Regular" Code

Exceptions provide the means to separate the details of what to do when something out of the ordinary happens from the main logic of a program. In traditional programming, error detection, reporting, and handling often lead to confusing spaghetti code. For example, consider the pseudocode method here that reads an entire file into memory.
readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}
At first glance, this function seems simple enough, but it ignores all the following potential errors.
  • What happens if the file can't be opened?
  • What happens if the length of the file can't be determined?
  • What happens if enough memory can't be allocated?
  • What happens if the read fails?
  • What happens if the file can't be closed?
To handle such cases, the readFile function must have more code to do error detection, reporting, and handling. Here is an example of what the function might look like.
errorCodeType readFile {
    initialize errorCode = 0;
    
    open the file;
    if (theFileIsOpen) {
        determine the length of the file;
        if (gotTheFileLength) {
            allocate that much memory;
            if (gotEnoughMemory) {
                read the file into memory;
                if (readFailed) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        close the file;
        if (theFileDidntClose && errorCode == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}
There's so much error detection, reporting, and returning here that the original seven lines of code are lost in the clutter. Worse yet, the logical flow of the code has also been lost, thus making it difficult to tell whether the code is doing the right thing: Is the file really being closed if the function fails to allocate enough memory? It's even more difficult to ensure that the code continues to do the right thing when you modify the method three months after writing it. Many programmers solve this problem by simply ignoring it — errors are reported when their programs crash.

Exceptions enable you to write the main flow of your code and to deal with the exceptional cases elsewhere. If the readFile function used exceptions instead of traditional error-management techniques, it would look more like the following.

readFile {
    try {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;
    } catch (fileOpenFailed) {
       doSomething;
    } catch (sizeDeterminationFailed) {
        doSomething;
    } catch (memoryAllocationFailed) {
        doSomething;
    } catch (readFailed) {
        doSomething;
    } catch (fileCloseFailed) {
        doSomething;
    }
}
Note that exceptions don't spare you the effort of doing the work of detecting, reporting, and handling errors, but they do help you organize the work more effectively.

Advantage 2: Propagating Errors Up the Call Stack

A second advantage of exceptions is the ability to propagate error reporting up the call stack of methods. Suppose that the readFile method is the fourth method in a series of nested method calls made by the main program: method1 calls method2, which calls method3, which finally calls readFile.
method1 {
    call method2;
}

method2 {
    call method3;
}

method3 {
    call readFile;
}
Suppose also that method1 is the only method interested in the errors that might occur within readFile. Traditional error-notification techniques force method2 and method3 to propagate the error codes returned by readFile up the call stack until the error codes finally reach method1—the only method that is interested in them.
method1 {
    errorCodeType error;
    error = call method2;
    if (error)
        doErrorProcessing;
    else
        proceed;
}

errorCodeType method2 {
    errorCodeType error;
    error = call method3;
    if (error)
        return error;
    else
        proceed;
}

errorCodeType method3 {
    errorCodeType error;
    error = call readFile;
    if (error)
        return error;
    else
        proceed;
}
Recall that the Java runtime environment searches backward through the call stack to find any methods that are interested in handling a particular exception. A method can duck any exceptions thrown within it, thereby allowing a method farther up the call stack to catch it. Hence, only the methods that care about errors have to worry about detecting errors.
method1 {
    try {
        call method2;
    } catch (exception e) {
        doErrorProcessing;
    }
}

method2 throws exception {
    call method3;
}

method3 throws exception {
    call readFile;
}
However, as the pseudocode shows, ducking an exception requires some effort on the part of the middleman methods. Any checked exceptions that can be thrown within a method must be specified in its throws clause.

Advantage 3: Grouping and Differentiating Error Types

Because all exceptions thrown within a program are objects, the grouping or categorizing of exceptions is a natural outcome of the class hierarchy. An example of a group of related exception classes in the Java platform are those defined in java.ioIOException and its descendants. IOException is the most general and represents any type of error that can occur when performing I/O. Its descendants represent more specific errors. For example, FileNotFoundException means that a file could not be located on disk.

A method can write specific handlers that can handle a very specific exception. The FileNotFoundException class has no descendants, so the following handler can handle only one type of exception.

catch (FileNotFoundException e) {
    ...
}
A method can catch an exception based on its group or general type by specifying any of the exception's superclasses in the catch statement. For example, to catch all I/O exceptions, regardless of their specific type, an exception handler specifies an IOException argument.
catch (IOException e) {
    ...
}
This handler will be able to catch all I/O exceptions, including FileNotFoundException, EOFException, and so on. You can find details about what occurred by querying the argument passed to the exception handler. For example, use the following to print the stack trace.
catch (IOException e) {
    e.printStackTrace();  //Output goes to System.err.
    e.printStackTrace(System.out);  //Send trace to stdout.
}
You could even set up an exception handler that handles any Exception with the handler here.
catch (Exception e) {    //A (too) general exception handler
    ...
}
The Exception class is close to the top of the Throwable class hierarchy. Therefore, this handler will catch many other exceptions in addition to those that the handler is intended to catch. You may want to handle exceptions this way if all you want your program to do, for example, is print out an error message for the user and then exit.

In most situations, however, you want exception handlers to be as specific as possible. The reason is that the first thing a handler must do is determine what type of exception occurred before it can decide on the best recovery strategy. In effect, by not catching specific errors, the handler must accommodate any possibility. Exception handlers that are too general can make code more error-prone by catching and handling exceptions that weren't anticipated by the programmer and for which the handler was not intended.

As noted, you can create groups of exceptions and handle exceptions in a general fashion, or you can use the specific exception type to differentiate exceptions and handle exceptions in an exact fashion.


类别:细节 | 添加到搜藏 | 浏览() | 评论 (1)
 
最近读者:
 
网友评论:
1
2009年10月31日 星期六 01:30 A.M. | 回复
I tried this code but the finally statement with out.close() also requests a IOException handler. In this case the solution would be add a throws IOException to the method or there is a better way to do it?
----------------------
You are right. There is a mistake in the tutorial. The close() method throws an IOException that is a checked exception, therefore it needs to honor the catch or specify requirement. Failure to do this, will result in a compilation error. The solution is to add a try-catch statement in the finally block.
 
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2009 Baidu