Java Exceptions by Example Christoph Gächter, Akadia AG,
Information Technology, CH-3604 Thun |
|
Overview Java has a powerful concept for exception and error handling. An exception is an error that occurs at runtime. It is either generated by the Java Virtual Machine (VM) in response to an unexpected condition or it is generated by your code as a result of executing a throw statement. Understanding Exceptions Exceptions generated from runtime are called unchecked exceptions, since it is not possible for the compiler to determine that your code will handle the exception. Exception classes that descend from RuntimeException and Error classes are unchecked exceptions. Examples for RuntimeException are illegal cast operation, inappropriate use of a null pointer, referencing an out of bounds array element. Error exception classes signal critical problems that typically cannot be handled by your application. Examples are out of memory error, stack overflow, failure of the Java VM. Thrown exceptions are referred to as checked exceptions. The compiler will confirm at compile time that the method includes code that might throw an exception. Moreover the compiler requires the code that calls such a method to include this call within a try block, and provide an appropriate catch block to catch the exception. java.lang.Object When an (either checked or unchecked) exception is thrown, execution will attempt to immediately branch to the first catch block whose associated exception class matches the class or a superclass of the thrown exception. If the exception does not occur within a try block or the thrown exception is not caught in a matching catch block, execution of the method immediately terminates and control returns to the invoker of the method, where this process is repeated. The result is that the exception chain is escalated until a matching catch block is found. If not, the thread containing the thrown exception is terminated. First Example The following Demo1 class demonstrates the behaviour of exceptions and applications. import java.io.*; class Demo1 { public static FileInputStream f1(String fileName) public static FileInputStream f2(String fileName) public static void main(String args[]) System.out.println( "main: Starting " +
Demo1.class.getName() // get file input stream
1 // get file input stream
2 System.out.println("main: " + Demo1.class.getName() +
" ended"); Compile and run the Demo1 class will generate output as follows (assuming that the file foo.bar does not exist):
The example program tries to create two file input streams. Therefore two methods f1 and f2 are implemented. First, the main program calls f1 method. Because the constructor of the FileInputStream throws a FileNotFoundException the method f1 must be defined with the throws FileNotFoundException in the method definition. The thrown exception is not handled in the method but forwarded to the invoker. Note, that the system output before the return statement is never executed. So the invoker, in our example the main program, must catch this exception. The method f1 is called in a try block. The following catch blocks catch either a FileNotFoundException or a general Exception. In our example, the exception is caught in the first catch block and the system output is generated. Since the exception in f1 is caught and handled, the execution of the program is not terminated. Second, the example program creates another FileInputStream invoking the f2 method. This method catches the FileNotFoundException, so this exception must not be forwarded to the invoker. The try ... catch statement is followed by a finally block. This block is always executed, regardless whether or not an exception occurs within the try block. This generates the system output in our example. Again, the exception is caught and the program executes, this generates the system output in f2 and main. This was a straight forward example with caught exceptions. What happens with uncaught exceptions? Change the fileName assignment in the main method: Comment out the first assignment and activate the second String fileName = null; then compile and execute Demo1 again. The generated output is:
This time, we try to create two FileInputStream objects using null instead of a file name. Invoking f1 will generate a NullPointerException which is caught in the main program. Next f2 is called which could handle only the FileNotFoundException but not a NullPointerException. Nevertheless the finally block is executed and then the control returns to the main program. Because there is no try ... catch statement around the call to f2 and no matching catch block is found, the thread is terminated. Note, that f2 and main can not execute the system output statements any more. Second Example The second example will show some special behaviour in catch and finally blocks: import java.io.*; class Demo2 { public static FileInputStream f1(String fileName) public static FileInputStream f2(String fileName) // Compiler error: statement
not reacheable public static void main(String args[]) System.out.println( "main: Starting " +
Demo2.class.getName() // get file input stream
1 // get file input stream
2 System.out.println("main: " + Demo2.class.getName() +
" ended"); Compile and run Demo2 will generate the following output:
Again f1 is called to get a new FileInputStream with a given file name. The thrown FileNotFoundException in f1 is caught in the following catch block. But this time an Error is thrown so that the method is terminated immediately. Because Error is not a subclass of Exception the first catch block does not match. But the second catch block matches all throwable objects. Calling f2 method will generate a FileNotFoundException too. This exception is caught in f2 and the method returns directly from the catch block. Remember, that the finally block is executed regardless whether an exception is caught or not. In both methods f1 and f2, the FileNotFoundException is caught and handled, so the program can terminate normally. Change again the fileName assignment in the main method and compile and run Demo2 again:
The NullPointerException thrown in f1 is not caught, so the exception is forwarded to the invoker. The main program catches Exception, which is a superclass of the thrown exception. The call to f2 method seems to work fine even the thrown NullPointerException is not caught directly. Note, that the finally block is executed regardless whether an exception is caught or not. Since the finally block terminates with a return statement, the program executes without failure. So be careful returning within finally blocks, this breaks the exception chaining to the invoker and it simulates error free program execution. Summary
|