Chapter 10: Exceptions Handling

10.1: Exception

An exception is a normal class with some special characteristics: usually it is a very simple class, with its own type used as the information sent by the exception thrower to the handler. It may also contain a String member to carry more specific information that may distinguish different exception objects of the same class.

Java has defined a list of standard exceptions, or you may create your own exceptions. To do it, simply inherit your own exception class from class java.lang.Exception, which is the common base class of all exceptions. You can add whatever data members or methods, just like normal inheritance:

   
   class MyException extends Exception {
      public MyException() {}
   
      public MyException(String message)
      {   super(message);  }
   }

Exception hierarchy:

   

Throwable

** RuntimeException

** IOException

       ** Other exceptions such as ClassNotFoundException.

Error occurs if something is wrong with the JVM. RuntimeException occurs if the code contains a logic error.

RuntimeExceptions are thrown and dealt with automatically, so you virtually never need to catch them, although sometimes you may decide to throw some of them.

Errors and RuntimeExceptions can be ignored, because they can not be caught, and they should have been found out by the programmer. Other exceptions are enforced by Java compiler to be handled.

If a RuntimeException gets all the way out to main without being caught, printStackTrace() is called to print out its call stack trace (refer to later), then the program exits.

10.2: Two Ways to Throw Exceptions -- Manually & Automatically

Exceptions are thrown in two ways: manually and automatically. For many typical errors, such as dividing-by-zero, passing a null handle to a method, failing to open a file, etc., Java automatically throws a predefined standard exception. If you have any special message you want to convey to outside world, you can also throw a Java standard exception or your self-defined exception manually.

10.3: Where to Handle the Exception

If you know how to handle an exception locally within the method, you can put the code which may throw out exceptions within a try block, and put exception handlers (catch blocks) behind it. This way the exception handling process is encapsulated inside the method and the caller of the method is not noticed of anything wrong.

More often, it is not appropriate to handle the exception locally. An exception acts as a problem notification. If you catch it locally but can not provide proper solution, the caller of your method can not get noticed and provide his more proper solution. In this case you should not try to catch the exception locally. Instead you put the exception specification after the argument list of the method, so that the caller of this method will catch the exception.

The compiler forces you to either handle the exception locally, or declare it in the exception specification.

   
   class MyException extends Exception {
   
      public MyException()
      {  super("Default Exception Message!"); }
   
      public MyException(String message)
      {  super(message);  }
   }
   
   class Thrower {
      public void handleItself()
      {
         try {  throw new MyException("Specific Exception Message!");  }
         catch(MyException e)
         {  System.out.println(e .toString()); }
      }
   
      public void canNotHandle() throws MyException
      {  throw new MyException(); }
   }
   
   public class Test {
      public static void main(String [] s)
      {
         Thrower t = new Thrower();
         t.handleItself();
         try {  t.canNotHandle();  }
         catch(MyException e)
         {  System.out.println(e. getMessage()); }
      }
   }

Output will be:

   
   MyException: Specific Exception Message!
   Defaults Exception Message!

If an exception is not caught in a certain scope, the control will exit to the outer scope, until the main method. Then the program will be terminated.

10.4: Exception Handler: catch Block

In the header of the catch block, the handle of the exception can not be omitted like in C++. Even if the type of the exception is enough and you do not need to access the exception object, you still have to keep the handle there.

The catch block must appear directly after the try block. This is another difference with C++, where you can put the catch block anywhere after the try block.

When an exception is thrown, control will leave the try block to search for following catch blocks. If no match is found, it will exit the present scope to the outer scope, and search for following catch blocks again. This exiting process will repeat until a match is found or the most outer scope -- main is reached, then the program will end.

A complete exception handling structure looks like:

   
   try {
   ...
   }
   catch(Type1 id1)
   {...}
   catch(Type2 id2)
   {...}
   ...
   finally {
   ...
   }

A catch block with a base-class exception will catch all derived-class exceptions. Therefore compiler forbids you to put a base-class exception handler in front of any derived-class one, otherwise the derived-class one will never catch any exception.

10.5: Perform Clean Up with finally

A finally block is put at the end of all catch blocks, to perform important clean-up jobs which should be done whether or not an exception is thrown or caught. It may be related to an opened file, a network connection, something drawn on the screen, or even a real-world switch.

10.6: Return to try Block after Exception handling

Normally there is not much need to return to the try block after exception is handled - this way of handling exception is called resumption. But if you want to do it, you can put the try and catch block inside a while loop. The try block is repeatedly reentered until the result is satisfactory.

Resumption is not good software engineering because of the coupling it results: your handler must often be aware of where the exception is thrown from and contain non-generic code specific to the throwing location. This makes the code difficult to write and maintain, especially for large systems where the same exception might be thrown from many places.

10.7: Exception Specification

In many cases a program is provided to clients without the source code. So it is important that the client must be able to know what kind of exceptions your method will throw, so that they will provide relevant exception handlers for it. This is achieved by putting keyword throws followed by a list of exceptions after the method header. Java compiler enforces that you must either handle the exception locally, or declare it in the exception specification. By this enforcement Java guarantees that exception correctness can be ensured at compiling time not run time. This is a big improvement over C++.

On the other hand, you can claim an exception in the specification, but actually do not throw it. This is to reserve the right to throw it later, so that after you add this feature the client's code need not be changed.

Often a method is expecting a base-class object but you actually pass a derived-class one. So it is forbidden for a derived-class method to have more exceptions than the overridden one in its base-class, so that any code that can handle the base-class exceptions can always handle the derived-class ones. Of course a derived-class method is allowed to throw less exceptions than the base-class one. But if you under-declare the exception in the specification, compiler will find out.

Because a constructor can not be overridden, i.e., you can only call the constructor of a specific type, so a constructor can have more exceptions than its base class. It must declare the base-class exceptions in its specification. A new method which the base class does not have can throw any exceptions.

10.8: Exception's Hierarchy and Methods

Class Exception is a concrete class, and it is the common base class of all exceptions. It inherits from class Throwable, which has the following methods:

   
   String getMessage( )    Return the String message stored in the exception
   String toString( )    Return the name of the exception plus the String message
   void printStackTrace( )
   void printStackTrace(PrintStream)    

Method printStackTrace( ) print the exception name, and its call stack trace. Suppose method a calls b, b calls c, and in c an exception is thrown, then the stack trace will show method c => b => a.

10.9: Rethrow an Exception

   
   try {
   ...
   }
   catch(MyException m)
   {
      ...
      throw e;
   }

Rethrowing an exception in a catch block will cause the control to exit the present scope to the outer scope, ignoring the following catch blocks. When you use printStackTrace( ) in the matching catch block, the origin of the exception will still be the throw point, not the rethrow point. To make the rethrow point the origin, use

   
   throw e. fillInStackTrace();  // instead of
   throw e;