class ExceptionDemos { // error public static void main( String[] args ) { int x = 5; x = x / (x - x); System.out.println( "Looks good to me" ); } } // Exception in thread "main" java.lang.ArithmeticException: / by zero // at ExceptionDemos.main(ExceptionDemos.java:4)This is how the defensive programmer can test for and handle exceptions. The normal application code is placed in a try block, and the exception handler code is placed in one of potentially several catch blocks.
Java exceptions are objects - they have private implementation and public interface. They can exercise intelligence, or perform a service, on behalf of their "client". Here, the exception object "e" is interacting intelligently with println().
class ExceptionDemos { // catch everything - println(e) public static void main( String[] args ) { try { int x = 5; x = x / (x - x); } catch (Exception e) { System.out.println( "---" + e + "---" ); } System.out.println( "Looks good to me" ); } } // ---java.lang.ArithmeticException: / by zero--- // Looks good to meThe previous example just passed the exception object to println(). Here are two methods that Exception objects know how to respond to.
class ExceptionDemos { // catch everything - e.method() public static void main( String[] args ) { try { int x = 5; x = x / (x - x); } catch (Exception e) { e.printStackTrace(); System.out.println( "---" + e.getMessage() + "---" ); } System.out.println( "Looks good to me" ); } } // java.lang.ArithmeticException: / by zero // at ExceptionDemos.main(ExceptionDemos.java:34) // ---/ by zero--- // Looks good to meAll Exception objects are defined somewhere in the Java standard library. Here is part of the class hierarchy.
Throwable | +--- Error | | | +--- VirtualMachineError | | | +--- OutOfMemoryError | +--- Exception | +--- IOException | | | +--- EOFException | +--- NoSuchMethodException | +--- RuntimeException | +--- ArithmeticException | +--- ArrayIndexOutOfBoundsExceptionYou can put as many catch blocks after the try as you desire. The order in which they appear should be "most specific" (derived class) to "most general" (base class). This will guarantee that a more specific exception is not "short circuited" by a more general exception that usurps the handling responsibility.
class ExceptionDemos { // be more discriminating public static void main( String[] args ) { try { int x = 5; x = x / (x - x); } catch (ArithmeticException e) { System.out.println( "+++" + e + "+++" ); } catch (Exception e) { System.out.println( "---" + e + "---" ); } catch (Throwable e) { System.out.println( "%%%" + e + "%%%" ); } System.out.println( "Looks good to me" ); } } // +++java.lang.ArithmeticException: / by zero+++ // Looks good to meIf you forget, and order the catch blocks from "more general" to "more specific", the code will not compile.
class ExceptionDemos { // out of order - won't compile public static void main( String[] args ) { try { int x = 5; x = x / (x - x); } catch (Exception e) { System.out.println( "---" + e + "---" ); } catch (ArithmeticException e) { System.out.println( "+++" + e + "+++" ); } } } // ExceptionDemos.java:15: catch not reached. // } catch (ArithmeticException e) { ////////////////////////////// lab - readLine() \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ////////////////////////////// lab - divideByZero \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ////////////////////////////// lab - parseInt() \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\[Source: Peter Haggar, "Effective Exception Handling in Java", Java Report, April 1999, p57]
The finally keyword is probably the best addition to the Java exception handling model over the C++ model. finally enables the execution of code to occur whether an exception occurred or not. It is good for: maintaining the internal state of an object when an exception occurs, and cleaning up non-object resources.
class ExceptionDemos { // "finally" is guaranteed to execute public static void main( String[] args ) { openConnection(); try { performTransaction(); } catch (DomainException e ) ... } finally { closeConnection(); } } }The magic of finally blocks are that they are always executed. However, a few circumstances exist that can prevent execution of a finally block -
class ExceptionDemos { // error, no catch, finally, propogate exception public static void main( String[] args ) { try { int[] array = new int[2]; array[2] = 42; } catch (ArithmeticException e) { System.out.println( "+++" + e + "+++" ); } finally { System.out.println( "finally happened" ); } System.out.println( "after try/catch" ); } } // finally happened // java.lang.ArrayIndexOutOfBoundsException: 2 // at ExceptionDemos.main(ExceptionDemos.java:12)If an exception occurs that matches a catch block, then the finally is executed after the catch, and the exception does not continue to propagate.
class ExceptionDemos { // error, catch, finally, normal flow public static void main( String[] args ) { try { int[] array = new int[2]; array[2] = 42; } catch (ArithmeticException e) { System.out.println( "+++" + e + "+++" ); } catch (ArrayIndexOutOfBoundsException e) { System.out.println( "---" + e + "---" ); } finally { System.out.println( "finally happened" ); } System.out.println( "after try/catch" ); } } // ---java.lang.ArrayIndexOutOfBoundsException: 2--- // finally happened // after try/catchNext, an exception occurs that matches a catch block. That catch block proceeds to throw a brand new exception. But before the new exception is allowed to propagate, the original try block's finally is executed. Notice that the new ArithmeticException is not caught by a "sibling" catch block.
class ExceptionDemos { // error, catch, throw new, finally, propogate public static void main( String[] args ) { try { int[] array = new int[2]; array[2] = 42; } catch (ArrayIndexOutOfBoundsException e) { System.out.println( "+++" + e + "+++" ); throw new ArithmeticException(); } catch (ArithmeticException e) { System.out.println( "+++" + e + "+++" ); } finally { System.out.println( "finally happened" ); } System.out.println( "after try/catch" ); } } // +++java.lang.ArrayIndexOutOfBoundsException: 2+++ // finally happened // Exception in thread "main" java.lang.ArithmeticException // at ExceptionDemos.main(ExceptionDemos.java:136)If no exception occurs, the finally executes after the try.
class ExceptionDemos { // no error, finally, normal flow public static void main( String[] args ) { try { int[] array = new int[2]; array[1] = 42; } catch (ArithmeticException e) { System.out.println( "+++" + e + "+++" ); } catch (ArrayIndexOutOfBoundsException e) { System.out.println( "---" + e + "---" ); } finally { System.out.println( "finally happened" ); } System.out.println( "after try/catch" ); } } // finally happened // after try/catchfinally blocks are always executed. This includes flow of control associated with all the below:
class ExceptionDemos { // finally always executes public static void main( String[] args ) { for (int i=0; ; i++) try { System.out.print( "i is " + i ); if (i == 3) break; if (i == 2) continue; } finally { System.out.println( " loop's finally" ); } System.out.println( "method1 returned " + method1( false ) ); System.out.println( "method1 returned " + method1( true ) ); System.out.println( "method2 returned " + method2( false ) ); System.out.println( "method2 returned " + method2( true ) ); } public static int method1( boolean doThrow ) { try { if (doThrow) throw new Exception(); return 1; } catch (Exception e) { return 11; } } public static int method2( boolean doThrow ) { try { if (doThrow) throw new Exception(); return 2; } finally { return 22; } } } // i is 0 loop's finally // i is 1 loop's finally // i is 2 loop's finally // i is 3 loop's finally // method1 returned 1 // method1 returned 11 // method2 returned 22 // method2 returned 22
class ExceptionDemos { // public static void main( String[] args ) { // HANDLE or declare try { outer(); } catch (java.io.IOException e) { System.out.println( "---" + e + "---" ); } } public static void outer() throws java.io.IOException { // handle or DECLARE inner(); throw new java.io.IOException( "from outer" ); } public static void inner() throws java.io.IOException { // handle or DECLARE throw new java.io.IOException( "from inner" ); } } // ---java.lang.IOException: from inner---"Many view the throws clause in Java as a savior." [Haggar] It is where you can document all the checked exceptions that can propagate from a method. It alerts all callers of your method of the checked exceptions that it can generate. The compiler makes sure you either "handle" them with a catch block, or "declare" them in the throws clause on the enclosing method. This feature is a two-edged sword: it makes the code self-documenting, but it also makes it harder to modify later.
If we wanted to change the previous inner() method to throw a new checked exception (see below), then we not only have to update the signature of inner(), but we also have to go to every method that calls inner() (and potentially every method that calls a method that calls inner()) and change them accordingly. One way around this is to be very "general" (or non-committal) to start with. The following code has done just that. All main() and outer() are coupled to (AND all they are capable of dealing with) are very amorphous objects of type Exception.
class ExceptionDemos { // public static void main( String[] args ) { try { outer(); } catch (Exception e) { System.out.println( "---" + e + "---" ); } } public static void outer() throws Exception { inner(); throw new java.io.IOException( "from outer" ); } public static void inner() throws java.io.IOException,NoSuchMethodException { int i = 2; if (i == 1) throw new java.io.IOException( "from inner" ); else throw new NoSuchMethodException( "from inner" ); } } // ---java.lang.NoSuchMethodException: from inner---
You can place try/catch/finally, try/catch, or try/finally blocks anywhere in your Java code. These blocks can be nested within each other. In doing so, the code be become confusing. But, a more serious side effect is that exceptions can get lost or hidden from the caller.
The example below throws four distinct exceptions (at //4, //6, //9, and //11). As fas as the original caller can tell, only one exception was really thrown (the exception from //11 shows up at //1). The exception thrown at //4 was caught at //5 and not rethrown. The exceptions thrown at //6 and //9 were not caught by any code. What happened to them? In short, they are lost. They are also referred to as hidden. The exception thrown at //9 hid the one thrown at //6. The exception thrown at //11 hid both of them. This could be very bad, because the original problems were not addressed where the problem was known, and, the problems are not knowable in any outer scope.
The best way to deal with this situation is to "wrap" any existing exceptions inside of the new exception you are throwing. This way, the receiver of the new exception will have information about all errors, and critical error information will not be lost.
To review, only one exception can propagate from a try/catch/finally block even though many can be thrown from within it. Only the last exception thrown will be seen by the caller, while others will be hidden or lost.
class ExceptionDemos { // nested exceptions public static void main( String[] args ) { try { System.out.println( "main: calling nestingTest" ); nestingTest(); } catch (Exception e) { //1 System.out.println( "main catch: caught - " + e.getMessage() ); } } public static void nestingTest() throws Exception { try { //2 System.out.println( "outer try" ); try { //3 System.out.println( "inner try" ); throw new Exception( "thrown from inner try" ); //4 } catch (Exception e) { //5 System.out.println( "inner catch: caught - " + e.getMessage() ); throw new Exception( "thrown from inner catch" ); //6 } finally { //7 try { //8 System.out.println( "inner finally's try" ); throw new Exception( "thrown from inner finally's try" ); //9 } finally { //10 System.out.println( "inner finally's finally" ); throw new Exception( "thrown from inner finally's finally"); //11 } } } catch (Exception e) { //12 System.out.println( "outer catch: caught - " + e.getMessage() ); throw e; } finally { //13 System.out.println( "outer finally" ); } } } // main: calling nestingTest // outer try //2 // inner try //3 // inner catch: caught - thrown from inner try //5 // inner finally's try //8 // inner finally's finally //10 // outer catch: caught - thrown from inner finally's finally //12 // outer finally //13 // main catch: caught - thrown from inner finally's finally //1
The Exception class has a constructor that accepts a String argument. The ApplException class has been designed to accept a String object during initialization and pass that object to its base class. Notice how that String object finds its way to standard-out when the exception is subsequently caught and output.
class ApplException extends Exception { // application-defined exception public ApplException( String in ) { super( in ); } public String tootYourHorn() { return "I am a legend in my own mind"; } } class ExceptionDemos { public static void foo() throws ApplException { // handle or DECLARE throw new ApplException( "a string argument" ); } public static void main( String[] args ) { // HANDLE or declare try { foo(); } catch (ApplException e) { System.out.println( "+++" + e + "+++" ); System.out.println( "+++" + e.tootYourHorn() + "+++" ); } catch (Exception e) { System.out.println( "---" + e + "---" ); } } } // +++ApplException: a string argument+++ // +++I am a legend in my own mind+++Because ApplException derives from Exception, the caller of method foo() must follow the "handle or declare" rule. In the example above, the former option is chosen. In the example below, the latter option is chosen. Note that the exception goes unhandled, and a run-time error results.
class ApplException extends Exception { public ApplException( String in ) { super( in ); } } class ExceptionDemos { public static void foo() throws ApplException { // handle or DECLARE throw new ApplException( "a string argument" ); } public static void main( String[] args ) throws ApplException { // DECLARE foo(); } } // ApplException: a string argument // at ExceptionDemos.foo(ExceptionDemos.java:9) // at ExceptionDemos.main(ExceptionDemos.java:12) ////////////////////////////// lab - StackExcept \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ////////////////////////////// lab - nestedExceptions \\\\\\\\\\\\\\\\\\\\\\\\\\To "catch and rethrow" an exception, the syntax is slightly different than C++. Below, foo() throws an exception, main() handles the exception, and then it rethrows the exception to the next enclosing calling context. Since it is main() that is passing on the exception, the JVM is having to ultimately intercede.
class ExceptionDemos { public static void foo() throws java.io.IOException { throw new java.io.IOException( "a string argument" ); } public static void main( String[] args ) throws java.io.IOException { try { foo(); } catch (java.io.IOException e) { System.out.println( "---" + e + "---" ); throw e; } } } // ---java.io.IOException: a string argument--- // java.io.IOException: a string argument // at ExceptionDemos.main(ExceptionDemos.java:12)
2. "Placing try/catch blocks in your Java code can actually slow it down, even when exceptions are not thrown." Consider the following code:
for (int i=0; i < array.length; i++) try { array[i] = i; } catch (Exception e) { // handle exception }Peter Haggar described that for large arrays and a 1.1.4 JVM, this code ran 16% faster when the try/catch block was removed. With JDK 1.2, there was no difference in speed. Placing the for loop inside the try/catch block also fixed the performance penalty. Apparently, even when exceptions are not thrown, there is a measurable amount of overhead associated with the presence of try/catch blocks. "In general, it is better programming practice to place try/catch blocks outside of loops."
3. In a "traditional" Java for loop, two checks are performed each iteration: the i < array.length check, and the ArrayIndexOutOfBoundsException check. Peter Haggar set-up a demo similar to the code below. The first loop is "traditional", the second loop omits the first check. He was able to demonstrate that the second loop is 17% faster with a 1.1.4 JVM, and no difference at all when using JDK 1.2. The numbers documented below also show no significant difference. "I do not recommend using this technique casually. It can be confusing and is not a standard way to program ... Your mileage is entirely a function of the different optimizations used in vendor JVMs."
import java.util.Date; // use exception to terminate a loop class ExceptionDemos { public static void main( String[] args ) { int[] array = new int[ Integer.parseInt(args[0]) ]; Date start = new Date(); for (int i=1; i < array.length; i++) // "traditional" loop array[i] = (int) Math.sqrt( i ); // array[i] = (int) Math.sqrt( array[i-1] ); Date end = new Date(); System.out.println( "normal loop ---- " + (end.getTime() - start.getTime())); start = new Date(); try { for (int i=1; ; i++) // no when-to-exit-loop check array[i] = (int) Math.sqrt( i ); // array[i] = (int) Math.sqrt( array[i-1] ); } catch (Exception e) { } end = new Date(); System.out.println( "exception loop - " + (end.getTime() - start.getTime())); } } // D:> java ExceptionDemos 100000 // normal loop ---- 60 50 110 50 // exception loop - 50 60 50 110 // D:> java ExceptionDemos 1000000 // normal loop ---- 550 540 // exception loop - 550 550 // D:> java ExceptionDemos 10000000 // normal loop ---- 5770 5770 // exception loop - 5660 5600 // array[i] = (int) Math.sqrt( array[i-1] ); // D:> java ExceptionDemos 100000 // normal loop ---- 0 0 50 // exception loop - 0 60 50 // D:> java ExceptionDemos 1000000 // normal loop ---- 330 270 330 // exception loop - 330 280 280 // D:> java ExceptionDemos 10000000 // normal loop ---- 3130 3290 // exception loop - 3070 3130
Exception handling was devised as a robust replacement for traditional error handling techniques. Some believe exception handling should be used for any and all error conditions and thus completely avoiding traditional error return checking. I believe that exception handling can be overused. It should not be used exclusively; or for control flow as a replacement for if/else logic. I believe a more moderate approach is necessary whereby exception handling is used in conjunction with traditional error handling techniques. Here is an example of traditional error handling.
MyInputStream in = new MyInputStream( "filename" ); int data = in.getData(); while (data != 0) { //1 // do something with data data = in.getData(); }At //1 we are checking to see if the data value returned from the getData() call is non-zero. If it is zero, we assume we are at the end of the stream. This makes sense and is intuitive. Now let's change this code by taking a hard line on exception handling. We will not rely on return code values, but use exceptions.
MyInputStream in = new MyInputStream( "filename" ); int data; while (true) { try { //1 // getData() throws NoMoreDataException when data is done data = in.getData(); } catch (NoMoreDataException e) { break; } // do something with data }Notice at //1 we are placing a try/catch block around the call to getData(). That method no longer returns zero when the stream is empty, it throws NoMoreDataException. Even though the new code is a bit uglier than the original, some people believe it represents the way code should be written with exceptions and that returning error codes should never be done. I disagree. I think the original is much more intuitive even though it relies on the older methods of dealing with errors or unexpected results. I think exceptions can be overused, and the revised version is a good example of it.
In some situations, no reasonable end-of-stream marker can be identified or defined. But instead of adding exception handling, "the most reasonable design is to add an explicit eof() method that should be called before any read from the stream." [Gosling, pp159-160]
MyInputStream in = new MyInputStream( "filename" ); int data; while ( ! in.eof()) { //1 data = in.getData(); // do something with data }Exceptions should be used for conditions outside the expected behavior of code. In the example above, we expect to get to the end of the stream, therefore a simple zero return from getData() is appropriate and intuitive. I don't think throwing an exception in this case is wise, as this is not an exceptional condition, but an expected one. We would not, however, expect that the stream is corrupted. That type of condition would warrant an exception being generated. The point is not to use exceptions for all conditions, but to use them where it makes sense - where exceptional condition exist.
"What good is it to throw an exception if you leave an object in an invalid undefined state? After all, the code catching the exception you throw may handle it and recover and call your code again. If your object was left in a bad state, there is a chance that the code will fail. This situation can be more difficult to debug that if you just terminated the program on the first exception instead of trying to recover from it. When throwing exceptions, it is important to consider what state your object is in." What is wrong with the code below?
private int numElements; private TheList myList; public void add( Object o ) throws SomeException { numElements++; //1 if (myList.maxElements() < numElements) { // reallocate myList // copy from old to new // possibly throws SomeException } myList.addToList( o ); // possibly throws SomeException }If an exception is thrown after //1, the object is now in an invalid state. The fix is simple in this case - move the increment of numElements to the end of the method. "This ensures the counter is accurate because we increment it only after we have successfully added the object to the list. This is a simple example, but it exposes a potentially serious class of problems" - object state management. When and where an exception is thrown, you need to be aware of: open files, open sockets, remote object state, open transactions, non-atomic application logic.
"You should review all the places where you generate exceptions to see if you are leaving your objects in a valid state ... Unfortunately, this is extremely difficult to do properly ... The finally clause can and should be used to help preserve object state."
The throws clause is a useful feature, but it can also be painful if you're not careful. "The moral of this story is not to add exception handling at the end of the development cycle. Your error handling strategy should be designed in from the beginning." [Haggar, p58]
A different author suggests the strategy of using an "unchecked exception" instead of a checked exception. The "handle or declare" rule does not apply for exceptions that inherit from either Error or RuntimeException.
class ExceptionDemos { static class Base { public void foo() throws NoSuchMethodException, java.io.IOException { //// public void foo() throws NoSuchMethodException { throw new NoSuchMethodException( "from Base.foo" ); } } static class Derived extends Base { public void foo() throws java.io.IOException { //// public void foo() throws java.io.IOException, NoSuchMethodException { //// ERROR: The method void foo() declared in class Derived cannot //// override the method of the same signature declared in class Base. //// Their throws clauses are incompatible. throw new java.io.IOException( "from Derived.foo" ); } } public static void main( String[] args ) { try { new Derived().foo(); } catch (Exception e) { System.out.println( e ); } } } // java.io.IOException: from Derived.fooAs a matter of design, derived classes should not violate the "100% rule". They should not "cancel out" functionality inherited from the base class. If client code is expecting an instance of the base class, it should not be surprised if it receives an instance of the derived class instead (Liskov Substitution Principle).
There are two Date classes in the Java standard library: java.util.Date, and java.sql.Date. The latter inherits from the former. The former has a method called getHours(). The latter chooses not to support that method by throwing the unchecked exception IllegalArgumentException as its implementation.
"The danger with classes like this is that methods that take arguments of the base type will typically not be written so that they can safely deal with objects of the derived type." If these methods receive an instance of the latter class, the application aborts. The problem in this case is that java.sql.Date "is a" java.util.Date is wrong. "This appears to be a case where the designer used inheritance because it saved having to write half a dozen functions. That convenience comes at a high price, though; it prevents the compiler from detecting that an object of type java.sql.Date is being passed to a function that will call invalid methods on it."
The design should have used aggregation/delegation instead of inheritance. The class java.sql.Date "has a" instance of class java.util.Date. When the client code calls getYear(), the former simply delegates to the latter. When the client code calls getHours() the compiler quits, because getHours() is not defined in class java.sql.Date.
[Source: Pete Becker, "Common Design Mistakes", C/C++ Users Journal, Jan 2000, p73]
When systems are comparatively small (less than 50k LOC), explicitly documenting and mandating the throwing and handling of exceptions at small and many levels of granularily may be quite manageable.
As systems grow in size, the need for centralization increases. Checked exceptions do not support the centralization of error handling at a single point unless every method is complicit in the knowledge of which exceptions may be thrown at the lowest levels. The management of throws clauses can become a maintenance nightmare. And, the design of some participating classes may prohibit them from passing the exceptions along.
"Unchecked exceptions provide a means to throw an exception at any arbitrary point - to be caught higher up - without any intervening method needing to know that it passed it on .... A large C++ system typically centralizes the handling of exceptions at several points, with the most serious conditions being handled at higher points and less severe conditions being handled closed to the source."
[Source: Steve Ball, "Exception Handling in Large Systems", Java Report, July 2000, pp93-94]
Using checked exceptions, I had redundant error handling logic throughout the application - I really did not have much choice. After I switched to unchecked exceptions, I found I could remove the redundancies in the code, fix any inconsistencies, and handle errors much more reliably. If a new exception is added, I could add a single catch block in the one well-defined handler module - without the nasty side effect of declaring thrown exceptions on tons of methods in my system.
After arguing over this "politically incorrect" architecture style with one project lead, he went off and decided to give the coding standard a test on a nontrivial persistence framework. He converted all exceptions from checked to unchecked and did a refactoring pass. As a result, he reduced his source code base by a whooping 30%! He reported the code looked cleaner, read better, was simpler, and finally, he said, he improved the error handling capabilities of the code.
In the best case, checked exceptions introduce redundant error handling in your code. In the worst case, people ignore significant error handling scenarios in their code with a catch-all-and-do-nothing block.
[Source: Todd Lauinger, "To Check or Not to Check", Java Report, January 2001, pp24-26]
Praxis 19: Consider the drawback to the throws clause.
Praxis 20: Be specific and comprehensive with the throws clause.
[Source: Peter Haggar, Practical Java, Addison-Wesley, 2000]