6.4 Java Exception Handling

 < Day Day Up > 



6.4 Java Exception Handling

This section covers the basic mechanism for handling Java exceptions. Generation and handling of exceptions is shown through the use of try-catch blocks. First, simple try-catch blocks and the actions that can be taken in a catch block are shown, then more complicated behaviors of try-catch blocks, such as exception propagation, finally blocks, and re-throwing of exceptions, are discussed. The basic structure of exceptions handling demonstrated in this section is used in subsequent discussion regarding the uses of exceptions in designing components.

6.4.1 Try-Catch Blocks

The basic mechanism in Java for handling problems that occur at run time is to try to run a section of code. If it completes normally, then the program proceeds to the next statement after all associated catch blocks. However, if a problem is encountered, the program immediately throws the error. The program then looks at each enclosing try block to see if any mechanism to catch the error has been defined. If a try block can catch the exception, the program branches to that catch block, executes it, and then continues executing at the next statement after all the associated catch blocks. If the try block does not have a catch block that can handle the exception, it is sent to the next enclosing try block to check to see if it can be handled. This continues until the exception is caught or the exception has propagated out of the current context (for procedural programs, this is the main method; for threads, it is the run method where the thread started) and has been caught by the Java virtual machine (JVM), which will by default print a stack trace.

Some examples of try-catch blocks are given here. Exhibit 4 (Program6.4a) illustrates a try-catch block that completes normally. The flow of the program can be followed by the messages that are output. In this case, the program executes all the statements in the try block, and because no problem was encountered it skips the catch block and continues executing after the catch block, ending the program.

Exhibit 4: Program6.4a: Try-Catch Block with No Exception Thrown

start example

 public class TryCatch1 {   public static void main(String args[]) {     int i = 3, j = 7, k;     try {       //This division results in a zero divide       //and immediately branches to the catch block.       k = j/i;       System.out.println("Statement is executed normally");      } catch(ArithmeticException e) {       System.out.println("Zero divide occurred - This does not         happen.");      }      //The exception was handled, so we continue.      System.out.println("This is after the exception is        handled.");   } } 

end example

In Exhibit 5 (Program6.4b) the program enters the block and attempts to do a division when the denominator is 0. This causes an ArithmeticException (specifically, a divide by zero) to be thrown which is caught by a catch for the enclosing try block. The catch block handles the error and prints a message and then continues executing the program at the end of all catch blocks for this try block, printing out the answer. Note that any lines of code after the exception is thrown in the try block are not executed.

Exhibit 5: Program6.4b: Catch Block Handling the ArithmeticException

start example

 public class TryCatch2 {   public static void main(String args[]) {     int i = 0, j = 7, k;     try {       //This division results in a zero divide       //and immediately branches to the catch block.       k = j/i;       System.out.println("Statement is not executed.");      } catch(ArithmeticException e) {       System.out.println("Zero divide occurred.");      }      //The exception was handled, so we continue.      System.out.println("This is after the exception is        handled.");   } } 

end example

Exhibit 6 (Program6.4c) shows what happens if the exception is not handled in the program. In this case, a ClassCastException is caught, not a ArithmeticException, so the zero divide is not caught. The program will now start to look to other enclosing catch blocks to see if one catches the exception. None does, so the Java VM finally catches the exception, printing out an error and a stack trace.

Exhibit 6: Program6.4c: Unhandled ArithmeticException

start example

 public class TryCatch3 {   public static void main(String args[]) {     int i = 0, j = 7, k;     try {       //This division results in a zero divide       //and immediately branches to the catch block.       k = j/i;       System.out.println("Statement is not executed.");      } catch(ClassCastException e) {       System.out.println("This is not an Arithmetic Exception,         so this is not executed.");      }      //The exception was handled, so we continue.      System.out.println("This statement is never reached.");   } } 

end example

Finally, a try block can catch more than one exception, as shown in Exhibit 7 (Program6.4d). When this happens, the catch blocks are evaluated in the order in which they appear in the program until one is found that catches the exception. That catch block is executed, the exception is reset, and the program continues executing after the last catch associated with the try block where the exception was raised.

Exhibit 7: Program6.4d: Multiple Catch Statements

start example

 public class MultiCatch {   public static void main(String args[]) {     int i = 0, j = 7, k;     try {       //This division results in a zero divide       //and immediately branches to the catch block.       k = j/i;       System.out.println("Statement is not executed.");      } catch(ArithmeticException e) {       System.out.println("Arithmetic Exception caught.");      } catch(ArrayIndexOutOfBoundsException e) {       System.out.println("Array Index Out Of Bounds Exception         caught.");      } catch(Exception e) {       System.out.println("Any other Exception was caught.");      }   } } 

end example

Once an exception is caught, what you do with it depends on the overall exception handling strategy for the program. Some examples of the things that can be done are shown in Exhibit 8 (Program6.4e). If the program is a simple student program, often the best strategy is to call the printStackTrace method to print out the reason for the exception, the line of code that failed, and the program stack at the time of the call. This can be very useful in debugging the program; however, if this is meant to be a production program, a stack trace will be less than useful. Because it will most likely confuse the person using the system and because it will provide no suggestions for fixing the problem, it is likely to lead to worry and frustration. Further, if the user is running a GUI interface, the stack trace might not even be accessible to the user. In production programs, a catch block should provide the user with a message on which the user can act, even if it is only to call a program support number. The message should follow any conventions that have been established for the project in regard to how to report the error. If the programmer would like to see a stack trace for debugging purposes, it should be written to a file that the user could later send to the programmer.

Exhibit 8: Program6.4e: Actions That Can Be Taken on Exceptions

start example

 import java.io.*; public class ExceptionActions {   public static void main(String args[]) {     int i = 0, j = 7, k;     try {       k = j/i;     } catch(Exception e) {       try {         System.out.println("e.toString = "+ e.toString());         System.out.println("e.getMessage = "+ e.getMessage());         e.printStackTrace();         PrintStream ps = new PrintStream(           new FileOutputStream("temp.dbg"));         e.printStackTrace(ps);        } catch (IOException e1) {         System.out.println("File write failed.");        }     }   } } 

end example

In a production program, even if the stack trace is written to a file, the user must be informed as to what has happened. It might be enough to know the type of exception, in which case the catch clause that is executed could provide enough information to give the user an error message. However, sometimes the catch clause could be used for a number of different exceptions; for example, an ArithmeticException catches both "zero divide" and "underflow" exceptions. In these cases, more information about the message can be obtained by calling the toString or getMessage methods on the exception. The information from these methods can be used to give the user a more specific error message. These methods are defined in the Throwable object [1] and thus are available for every exception and error. Exception handling is relatively easy as long as only a single method and try-catch block are used; however, it becomes more complicated for multiple method calls and try-catch blocks, as will be shown in the next section.

6.4.2 Exception Propagation: Unwinding the Stack

In the preceding example, the error was handled immediately in the block in which it occurred, but what happens when an error occurs in a block that is contained inside of a try block? With Java exception handling, when an exception occurs in a block enclosed in another block, the exception is passed up the blocks until a catch is found that can handle this error. This is called propagating an exception. How this works is important, but the details can be somewhat confusing.

To understand exception propagation, remember that anything between a set of "{ }" is a block in Java. Thus if a method is called from within a try block, that method is an enclosed block in the try block. A method called from a method in the try block is still in the try block. For example, in Exhibit 9 (Program6.5), the statement "k = j/i;" in the try block in f2 is also in the block for method f2, which is in the try block in method f1, which is in the block for the method f1, etc.

Exhibit 9: Program6.5: Exception Propagation

start example

 public class ErrorPropagation {   public void f1() {     try {       f2();     } catch (ArithmeticException e) {       System.out.println("Error is handled here.");     }     System.out.println("Error has been handled in f1.");   }   public void f2() {     int i = 0, j = 7, k;     try {       //This division results in a zero divide       //and immediately branches to the catch block.       k = j/i;       System.out.println("Statement is not executed.");      } catch(ClassCastException e) {       System.out.println("This does not catch the zero divide.");     }     System.out.println("Error was not handled, so we skip       this.");   }   public static void main(String args[]) {     ErrorPropagation ep = new ErrorPropagation();     try {       ep.f1();     } catch (ArithmeticException e) {       System.out.println("Exception was already handled");       System.out.println("so we skip this statement.");     }     //The exception was handled, so we continue.     System.out.println("This is after the exception is       handled.");   } } 

end example

The rule for try blocks is that when an exception occurs the program immediately branches to the end of each enclosing block until it reaches a try-catch block that can handle that exception. So, when an exception occurs in a try block, the program immediately goes to the end of that try block to find a catch block that can handle this exception. If the program does not find an applicable catch block, then it immediately goes to the end of the block enclosing the try block. It continues to do this until either the exception can be handled in a catch block or the main method is exited. Because some of these blocks can be methods, it appears that an immediate return from the method is executed when the exception occurs and that the program is returning from the methods. Thus, this behavior is referred to as unwinding the stack.

How this works is illustrated in Exhibit 9 (Program6.5), where the exception occurs in a method call two levels deeper than the try block that catches it. In this program the main method calls method f1, which in turn calls method f2. In method f2, an ArithmeticException (zero divide) exception occurs. When this happens, the first enclosing try block, the one in f2, is checked to see if it can handle the exception. Because it does not handle the ArithmeticException, the exception is propagated to the try block in method f1. This try block handles the exception and then resets the exception and continues executing at the next statement in the method after the catch block. The catch for ArithmeticException is not executed in the main method, as it was handled and reset in method f1.

If an exception is not caught in any try block in the program, it is propagated to the JVM, which has an implicit try-catch block. If an exception propagates to the JVM, the default behavior is to print the exception and a program trace and then to end the program.

6.4.3 Finally Blocks

Often, some code needs to be executed regardless of whether or not an exception occurs or, if one does occur, whether or not it is caught. For example, a program using a socket for network communication might want to shut the socket down cleanly so that the process on the remote computer knows that it is going away. The program will always want to do this clean shut down, regardless of any exceptions that might occur. The code to handle this shutdown cannot be at the end of the try block, because if an exception occurs then this code will not be executed. It cannot be after the try block, because if the exception is not caught in this try block then the code will again be skipped. It cannot be in the catch block, because if the exception does not occur then the code will never be executed. This situation is not easily handled simply by using try-catch blocks; therefore, an additional block, a finally block, is used.

A try-catch-finally block works by making the finally block always execute. Exhibit 10 (Program6.6) shows how this is done. If no exception occurs, the code in the finally block is executed. If an exception occurs and is caught, the code in the finally block is executed. If an exception occurs but is not caught, the code in the finally block is executed. Even if an attempt is made to get around the finally block by using a return statement, the finally block is executed. The finally block is always executed.

Exhibit 10: Program6.6: Finally Block

start example

 public class FinallyExample {   public void f1() {     try {       f2();     } catch (ArithmeticException e) {       System.out.println("Error is handled here.");       return;       //Note that even though there is a return       //statement here, the finally block is executed.      } finally {        System.out.println("Error is handled and finally block          executed.");      }   }   public void f2() {     int i = 0, j = 7, k;     try {      //This division results in a zero divide      //and immediately branches to the catch block.      k = j/i;      System.out.println("Statement is not executed.");     } catch(ClassCastException e) {      System.out.println("This does not catch the zero divide.");     } finally {       System.out.println("Error is not handled but finally         is executed.");     }   }   public static void main(String args[]) {     FinallyExample ep = new FinallyExample();     try {       ep.f1();     } catch (ArithmeticException e) {      System.out.println("Exception was already handled");      System.out.println("so we skip this statement.");     } finally {      System.out.println("No error, but finally still        executed.");     }   } } 

end example

[1]JDK 1.4 has extended the Throwable to include a cause and to allow the programmer access to parts of the stack trace. The reader should read the Java API for the latest extensions to the Throwable class.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net