Java 101: Exceptions to the programming rules, Part 2

Incorporate Java's throw-object/catch-object exception-handling technique into your Java programs

1 2 3 4 5 Page 4
Page 4 of 5

Access stack-trace-element data from a catch clause

Earlier in this article, I promised you an example of the StackTraceElement class and its methods. I now fulfill that promise by introducing a program that uses StackTraceElement methods to access stack-trace-element data from a catch clause, and produce output similar to the previous TranslatedExceptionDemo2 output:

Listing 5. TranslatedExceptionDemo3.java

// TranslatedExceptionDemo3.java
class TranslatedExceptionDemo3
{
   public static void main (String [] args)
   {
      try
      {
         Library.methodA (); // Line 9.
      }
      catch (Library.ExternalException e)
      {
         System.out.println (e.getMessage ());
         System.out.println ("\nUppermost stack trace");
         StackTraceElement [] stearray = e.getStackTrace ();
         
         for (int i = 0; i < stearray.length; i++)
         {
              System.out.println ("\nStack Trace element: " + i);
              System.out.println ("File name = " +
                                  stearray [i].getFileName ());
              System.out.println ("Class name = " +
                                  stearray [i].getClassName ());
              System.out.println ("Method name = " +
                                  stearray [i].getMethodName ());
              System.out.println ("Line number = " +
                                  stearray [i].getLineNumber ());
         }
         System.out.println ("\nBottommost stack trace");
         stearray = e.getCause ().getStackTrace ();
         
         for (int i = 0; i < stearray.length; i++)
         {
              System.out.println ("\nStack Trace element: " + i);
              System.out.println ("File name = " +
                                  stearray [i].getFileName ());
              System.out.println ("Class name = " +
                                  stearray [i].getClassName ());
              System.out.println ("Method name = " +
                                  stearray [i].getMethodName ());
              System.out.println ("Line number = " +
                                  stearray [i].getLineNumber ());
         }
      }
   }
}
class Library
{
   static void methodA () throws ExternalException
   {
      try
      {
         methodB (); // Line 29.
      }
      catch (InternalException e)
      {
         System.out.println (e.getMessage ());
         ExternalException ee = new ExternalException (); // Line 35.
         ee.initCause (e);
         throw ee;
      }
   }
   static class ExternalException extends Exception
   {
      ExternalException ()
      {
         super ("External Exception");
      }
   }
   private static void methodB () throws InternalException
   {
      throw new InternalException (); // Line 51.
   }
   private static class InternalException extends Exception
   {
      InternalException ()
      {
         super ("Internal Exception");
      }
   }
}

When run, TranslatedExceptionDemo2 produces the following output:

Internal Exception
External Exception
Uppermost stack trace
Stack Trace element: 0
File name = TranslatedExceptionDemo3.java
Class name = Library
Method name = methodA
Line number = 72
Stack Trace element: 1
File name = TranslatedExceptionDemo3.java
Class name = TranslatedExceptionDemo3
Method name = main
Line number = 9
Bottommost stack trace
Stack Trace element: 0
File name = TranslatedExceptionDemo3.java
Class name = Library
Method name = methodB
Line number = 88
Stack Trace element: 1
File name = TranslatedExceptionDemo3.java
Class name = Library
Method name = methodA
Line number = 66
Stack Trace element: 2
File name = TranslatedExceptionDemo3.java
Class name = TranslatedExceptionDemo3
Method name = main
Line number = 9

Clean up

The try-block/catch-clause exception-handling arrangement features a problem. Suppose a statement within the try block opens a file. At some point, that file must close -- especially if the try-block/catch-clause executes in a loop. (If the file doesn't close, most likely, the program will eventually stop opening files because of limitations to the number of simultaneously open files.) To ensure each open file closes, a file-closing statement must appear within the try block and each catch clause associated with that block, as the following code fragment demonstrates:

try
{
    // open file.
    // file-manipulation statements.
    // close file. This executes when no exceptions are thrown.
}
catch (IOException e)
{
    // handle exception.
    // close file. This executes when file-related exceptions are thrown.
}

That duplicate file-closing code hearkens back to one of the problems with C's error code-testing approach to dealing with exceptions: redundancy. Ideally, the file-closing code should appear in only one place. Java supports that idea by providing a finally clause. That clause's syntax is:

'finally'
'{'
   // cleanup statements
'}'

A finally clause begins with the finally keyword, and a block of cleanup statements follows. If there are no catch clauses, the finally clause immediately follows a try block; otherwise it immediately follows the final catch clause. After the last statement executes, in either the try block (if that block did not throw any exception objects) or a catch clause (if an exception object was thrown), the finally clause executes. The following code fragment shows how finally eliminates redundancy:

try
{
    // Open file.
    // File-manipulation statements.
}
catch (IOException e)
{
    // Handle exception.
}
finally
{
    // Close file, whether or not an exception was thrown.
}

In the preceding code fragment, finally's file-closing code executes after the last statement executes in either the try block or the catch clause. That behavior implies the following: if there is no catch clause and an exception object is thrown from the try block, the finally clause executes, and then the JVM searches for an appropriate catch clause to catch that object.

Caution
The finally keyword must appear immediately after the closing brace character of a preceding try block or catch clause. The compiler reports an error upon encountering anything else ahead of finally.

A problem with finally clauses

The finally clause also features a problem -- lost exception objects. Suppose a method contains a try block with a finally clause, and the method signature includes a throws clause identifying the class name of the checked exception object that try throws. Now suppose code within the try block throws an exception object. Before the JVM can look for an appropriate exception handler, the finally clause must execute. Suppose code within the finally clause creates an exception object and throws that object. The first exception object disappears, and the JVM searches the method-call stack for a method with a catch clause that handles the new exception object. Listing 6 demonstrates that scenario:

Listing 6. MissingException.java

// MissingException.java
class ExceptionA extends Exception
{
   ExceptionA ()
   {
      super ("Exception A");
   }
}
class ExceptionB extends Exception
{
   ExceptionB ()
   {
      super ("Exception B");
   }
}
class MissingException
{
   public static void main (String [] args)
   {
      try
      {
         methodA ();
      }
      catch (ExceptionA e)
      {
         System.out.println (e.getMessage ());
      }
      catch (ExceptionB e)
      {
         System.out.println (e.getMessage ());
      }
   }
   static void methodA () throws ExceptionA, ExceptionB
   {
      try
      {
         methodB ();
      }
      finally
      {
         throw new ExceptionA ();
      }
   }
   static void methodB () throws ExceptionB
   {
      throw new ExceptionB ();
   }
}

When run, MissingException produces the following output:

Exception A

MissingException works as follows: main() invokes methodA(), which invokes methodB(). methodB() throws an exception object of type ExceptionB(). Before the JVM can locate a catch clause (in main()) that catches ExceptionB objects, the JVM must execute methodA's finally clause. However, that clause throws an ExceptionA object, and the JVM searches for an ExceptionA catch clause -- which the JVM finds in main(). The previous output proves that the ExceptionB object is missing.

Caution
Do not throw exception objects from finally clauses; you risk losing other exception objects.

Review

Java's throw-object/catch-object exception-handling technique consists of an exceptions class hierarchy, statements/clauses, and various rules. At the top of the exceptions class hierarchy, class Throwable describes an exception object's internal architecture: a message and a cause. Below Throwable, classes Exception and Error introduce subhierarchies that identify different kinds of program- and JVM-related exceptions, respectively. Apart from Exception's RuntimeException subclass hierarchy, all Exception classes identify checked exceptions. In contrast, RuntimeException and its subclasses (and Error and its subclasses) identify unchecked exceptions.

A method uses a throw statement to throw an exception object to the JVM. The calling method completes one of two tasks: It declares its interest in catching that object by wrapping the method call in a try block and specifying an associated catch clause. Or, it incorporates a throws clause as part of its signature to indicate that the calling method wants the throw statement's calling method to catch the exception object. If no method catches the exception object, the JVM passes that object to a default exception handler, which calls various Throwable methods to display exception object details and a stack trace. Whether or not a method chooses to catch an exception object, that method can incorporate a finally clause as part of a try block or as part of a try block and one or more catch clauses. Following the execution of a try block or a catch clause, the JVM passes execution to the finally clause. Code within finally performs various cleanup tasks.

To be successful with Java's exception-handling architecture, you must understand and follow the various rules I've outlined in this article.

Now turn to the study guide and do some homework. That homework will help solidify what you have just read.

I encourage you to email me with any questions you might have involving either this or any previous article's material. (Please keep such questions relevant to material discussed in this column's articles.) Your questions and my answers will appear in the relevant study guides.

In next month's article, you'll discover Java's thread support.

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners -- Java 2 By Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932) -- and helped write Special Edition Using Java 2 Platform (Que Publishing, 2001; ISBN: 0789724685). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.
1 2 3 4 5 Page 4
Page 4 of 5