As the programmer, you must know what constitutes a bad result, and what it means. It’s often awkward to work around the limitations of passing error values in the normal path of data flow. An even worse problem is that certain types of errors can legitimately occur almost anywhere, and it’s prohibitive and unreasonable to explicitly test for them at every point in the software.

Java offers an elegant solution to these problems with exception handling. An exception indicates an unusual condition or an error condition. Program control becomes unconditionally transferred or “thrown” to a specially designated section of code where it’s caught and handled. In this way, error handling is somewhat orthogonal to the normal flow of the program. We don’t have to have special return values for all our methods;errors are handled by a separate mechanism. Control can be passed long distance from a deeply nested routine and handled in a single location when that is desirable, or an error can be handled immediately at its source.

An Exception object is created by the code at the point where the error condition arises. It can hold whatever information is necessary to describe the exceptional condition, optionally including a full stack trace for debugging. The Exception object is passed as an argument to the handling block of code, along with the flow of control. This is where the terms “throw” and “catch” come from: the Exception object is thrown from one point in the code and caught by the other, where execution resumes.

Exception Handling

The try/catch guarding statements wrap a block of code and catch designated types of exceptions that occur within it:

try {  
    readFromFile("foo");  
    ...  
}   
catch ( Exception e ) {  
    // Handle error  
    System.out.println( "Exception while reading file: " + e );
    ...  
}

 

In this example, exceptions that occur within the body of the try portion of the statement are directed to the catch clause for possible handling. The catch clause acts like a method; it specifies an argument of the type of exception it wants to handle and, if it’s invoked, it receives the Exception object as an argument. Here we receive the object in the variable e and print it along with a message.

try statement can have multiple catch clauses that specify different types (subclasses) of Exception:

try {  
    readFromFile("foo");  
    ...  
}   
catch ( FileNotFoundException e ) {  
    // Handle file not found  
    ...  
}   
catch ( IOException e ) {  
    // Handle read error  
    ...  
}   
catch ( Exception e ) {  
    // Handle all other errors  
    ...  
}

 

The catch clauses are evaluated in order, and the first possible (assignable) match is taken. At most, one catch clause is executed, which means that the exceptions should be listed from most specific to least. In the previous example, we’ll anticipate that the hypothetical readFromFile( ) can throw two different kinds of exceptions: one that indicates the file is not found; the other indicates a more general read error. Any subclass of Exception is assignable to the parent type Exception, so the third catch clause acts like the default clause in a switch statement and handles any remaining possibilities.

One beauty of the try/catch scheme is that any statement in the try block can assume that all previous statements in the block succeeded. A problem won’t arise suddenly because a programmer forgot to check the return value from some method. If an earlier statement fails, execution jumps immediately to the catch clause; later statements are never executed.

Bubbling Up

What if we hadn’t caught the exception? Where would it have gone? Well, if there is no enclosing try/catch statement, the exception pops to the top of the method in which it appeared and is, in turn, thrown from that method up to its caller. If that point in the calling method is within a try clause, control passes to the corresponding catch clause. Otherwise the exception continues propagating up the call stack. In this way, the exception bubbles up until it’s caught, or until it pops out of the top of the program, terminating it with a runtime error message. There’s a bit more to it than that because, in this case, the compiler would have reminded us to deal with it, but we’ll get back to that in a moment.

Throwing Exceptions

We can throw our own exceptions: either instances of Exception or one of its existing subclasses, or our own specialized exception classes. All we have to do is create an instance of the Exception and throw it with the throw statement:

throw new Exception( );

 

Execution stops and is transferred to the nearest enclosing try/catch statement. An alternative constructor lets us specify a string with an error message:

throw new Exception("Something really bad happened");

 

The finally Clause

What if we have some cleanup to do before we exit our method from one of the catch clauses? To avoid duplicating the code in each catch branch and to make the cleanup more explicit, use the finally clause. A finally clause can be added after a try and any associated catch clauses. Any statements in the body of the finally clause are guaranteed to be executed, no matter why control leaves the try body:

try {  
    // Do something here  
}   
catch ( FileNotFoundException e ) {  
    ...  
}   
catch ( IOException e ) {  
    ...  
}   
catch ( Exception e ) {  
    ...  
}   
finally {  
    // Clean up here  
}

 

In this example, the statements at the cleanup point will be executed eventually, no matter how control leaves the try. If control transfers to one of the catch clauses, the statements in finally are executed after the catch completes. If none of the catch clauses handles the exception, the finally statements are executed before the exception propagates to the next level.

Leave a Reply

Your email address will not be published. Required fields are marked *