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 2
Page 2 of 5

Now that you know something about StackTraceElement, that class's methods, and what stack-trace data looks like, you probably want to experiment with that class. But first, you need to understand a few more concepts. After presenting those concepts, I give you a chance to work with StackTraceElement.

Extend the exceptions class hierarchy

The exceptions class hierarchy includes a rich assortment of exception classes. When you need an exception class, you should first search that hierarchy for an appropriate class. If you cannot find an appropriate class or if you need a class resembling an existing class, you extend that hierarchy with a new class. Where does the new class fit within the overall class hierarchy? You must ask yourself if the class describes a checked exception or an unchecked exception. Assuming the answer is checked exception, the class appears within the Exception subhierarchy -- but not within that subhierarchy's RuntimeException portion. However, if the answer is unchecked exception, the class appears within the RuntimeException subhierarchy. Because you shouldn't handle unchecked exceptions that describe JVM failures, ignore the Error subhierarchy. To clarify this discussion, I present the following two examples:

  • You design an email library. That design reveals a pair of static methods that send and receive email messages. The send method uses Simple Mail Transfer Protocol (SMTP) to send an email message by engaging in a simple dialog of text-based commands and responses. Similarly, the receive method uses Post Office Protocol 3 (POP3) to receive an email message, using the same command/response-oriented dialog. Responses indicate success or failure. To describe failure, you create a pair of exception classes, one that describes SMTP failures and one that describes POP3 failures. Because responses -- from the SMTP and POP3 programs that communicate with the send/receive methods -- include response codes with human-readable messages, you store the message in your exception classes' Throwable layer and record the integer-based response code in a private field of each class. You avoid duplicating response code fields by introducing a generic superclass that declares that field and extendes that class with POP3 and SMTP exception subclasses. The following code fragment demonstrates:

    class EmailException extends Exception
    {
       private int responseCode;
       EmailMessage (int responseCode, String message)
       {
          // Call Exception(String message), which calls Throwable(String message).
          super (message);
          this.responseCode = responseCode;
       }
       int getResponseCode ()
       {
          return responseCode;
       }
    }
    class POP3Exception extends EmailException
    {
    }
    class SMTPException extends EmailException
    {
    }
    

    EmailException extends Exception because sometimes SMTP- and POP3-related exceptions arise from reasons other than flawed code -- perhaps the SMTP or POP3 program stops running during a communication session. As a result, POP3Exception and SMTPException identify different checked exceptions. (I'll discuss email, POP3, and SMTP in a future article.)

  • Suppose you design a mathematics library that alerts programs to overflow and underflow exceptions. To represent those exceptions, you introduce Overflow and Underflow classes. Because overflow and underflow exceptions indicate flawed code, they represent unchecked exceptions. That implies Overflow and Underflow subclass RuntimeException, as the following code fragment demonstrates:

    class Overflow extends RuntimeException
    {
    }
    class Underflow extends RuntimeException
    {
    }
    

    Because Overflow and Underflow lack duplicate code, you neither need to introduce a new class that extends RuntimeException nor have Overflow and Underflow subclass that new class.

Throw exception objects

The previous section referred to throwing an exception object. What does that phrase mean? For an answer, consider this: When the JVM detects an unchecked exception, the JVM creates and initializes an exception object. In contrast, when a program or library detects either a checked or unchecked exception, the program/library code creates and initializes an exception object and uses the throw statement to pass that object to the JVM. The throw statement has the following syntax:

'throw' expression ';'

The throw statement begins with keyword throw, continues with an expression that evaluates to an object reference, and terminates with a semicolon character. expression's type must be Throwable or a subclass type. Otherwise, the compiler reports an error. The following code fragment demonstrates creating, initializing, and throwing an exception object of type FileNotFoundException:

throw new FileNotFoundException ("Unable to open file " + filename); // Assume previous String filename; declaration. 

Programs typically use

throw

to throw checked exception objects, as the previous code fragment demonstrates. In contrast, libraries often use

throw

to throw unchecked exception objects. Library methods throw unchecked exception objects because guaranteeing that a calling method will always pass valid argument values to those methods proves impossible. For example, consider the following code fragment:

static void printReport (Employee e)
{
   if (e == null)
       throw new NullPointerException ();
   // ... other code ...
}

The printReport(Employee e) library method checks Employee argument e's contents. If e contains a null reference, printReport(Employee e) creates and throws a NullPointerException unchecked exception object. Execution immediately leaves, and does not return to, the printReport(Employee e) method.

Caution
Do not attempt to throw objects from any class apart from Throwable or a Throwable subclass. Otherwise, the compiler reports an error. The compiler also reports an error when a method attempts to throw a checked exception object but does not list that object's class name in the method's throws clause.

When a method throws a checked exception object, the compiler forces that method to list the object's class or superclass in the method's throws clause. That clause appends to a method's signature and has the following syntax:

'throws' exceptionIdentifier1 ',' exceptionIdentifier2 ',' ...

The throws clause begins with keyword throws and continues with a comma-delimited list of exceptionIdentifier's. Each exceptionIdentifier identifies the class of a checked exception object directly (or indirectly, through a called method) thrown from the method. The following code fragment illustrates a throws clause:

public static void main (String [] args) throws java.net.MalformedURLException
{
   if (args.length < 1) return;
   java.net.URL url = new java.net.URL (args [0]);
}

The above code fragment attempts to create an object from Java's URL class -- found in package java.net. If the contents of the String object, which args [0] references, do not represent a valid URL, the URL (String url) constructor throws a java.net.MalformedURLException object. Because that object represents a checked exception and because the main() method chooses not to receive that object, a throws clause must append to main()'s signature -- to inform the JVM that main() did not receive that object and that the JVM should begin searching for an appropriate exception handler with main()'s caller. (I explore URLs in a future article.)

Tip
To deal with failure that occurs inside a constructor, throw an exception object from that constructor. If the exception object is checked, append a throws clause to the constructor signature. For example: class Employee { Employee () throws IOException { /* ... appropriate code ... */ } }. When a constructor throws an exception object, the JVM does not create an object from the constructor's class. Using the previous example, if Employee() throws an IOException object, the JVM does not create an Employee object.

Throws clauses and method overriding

In "Object-Oriented Language Basics, Part 4," you learned about method overriding. In that article, you learned you must exactly specify a superclass method's name, return type, and parameter list in a subclass when overriding the superclass method. Because a throws clause is part of a method's signature, when overriding a superclass method that has a throws clause, keep in mind two factors:

  • When an exception class name appears in a subclass method's throws clause, either that name must also appear in the superclass method's throws clause or the name of the exception class's superclass must appear in the superclass method's throws clause.
  • An unchecked exception class name that appears in a subclass method's throws clause need not appear in the superclass method's throws clause. The compiler ignores unchecked exception class names when examining throws clauses.

To understand throws clauses and method overriding, consider the following code fragment:

import java.io.*;
class Parent
{
   void foo () throws IOException
   {
   }
}
class Child extends Parent
{
   void foo () throws EOFException, ArrayIndexOutOfBoundsException,
                      ClassNotFoundException
   {
   }
}

According to the code fragment, Child overrides Parent's foo() method. Notice both classes' throws clauses. Parent's throws clause lists one exception class (IOException), whereas Child's clause lists three: java.io.EOFException, ArrayIndexOutOfBoundsException, and java.lang.ClassNotFoundException.

Why won't the code fragment compile? Might the problem involve ArrayIndexOutOfBoundsException? The answer is no: the compiler ignores ArrayIndexOutOfBoundsException and other unchecked exception class names. Might the problem involve EOFException? Once again, the answer is no: class EOFException subclasses IOException, and you can legally list an exception subclass name in a subclass method's throws clause, provided the exception subclass's superclass name appears in the superclass method's throws clause. The problem lies with ClassNotFoundException; that checked exception class name does not appear in Parent's throws clause, and IOException is not ClassNotFoundException's superclass.

Catch exception objects

After the JVM acquires an exception object from either the JVM or a program/library, the JVM searches program/library code for a handler capable of dealing with those exceptions that the exception object's type represents. The search results in either a suitable program/library exception handler or the JVM's default exception handler. The JVM subsequently transfers the exception object and its execution to that exception handler. The acts of locating, calling, and passing the exception object to the exception handler are collectively known as catching an exception object.

The exception handler typically calls various exception object methods to learn more about the exception. Method return values assist the exception handler in its job -- although the default exception handler only displays those values so you can find out what went wrong and fix your code. See the earlier SortIntegerArray1 output for an example of what the default exception handler displays.

To represent an exception handler in source code, Java uses the catch clause. That clause has the following syntax:

'catch' '(' exceptionIdentifier objectIdentifier ')'
'{'
   statement ...
'}'

The catch clause begins with keyword catch and a parameter list consisting of one parameter. objectIdentifier names the parameter, and exceptionIdentifier serves as the parameter's type -- which must be Throwable or a subclass. The catch clause concludes with a block of exception-handling statements that allow a program/library to recover from an exception.

When an exception object is thrown, the JVM searches backwards through the method-call stack for the first catch clause whose exceptionIdentifier either exactly matches or is the supertype of the exception object's type. Upon finding a match, the JVM calls the catch clause and passes the exception object's reference to that clause, as part of the call. At that point, the exception object is caught. The following code fragment demonstrates a catch clause:

catch (FileNotFoundException exc)
{
   System.out.println (exc.getMessage ());
}

The code fragment's catch clause catches FileNotFoundException objects. When the JVM passes execution to catch (FileNotFoundException exc), that clause calls the exception object's getMessage() method to retrieve a String object that describes the exception. That object's contents subsequently print.

A catch clause cannot appear alone in source code. Instead, source code must express a catch clause in association with a try block. A try block has the following syntax:

'try'
'{'
   statement ...
'}'

The try block begins with keyword try and concludes with a statement block. Statements within that block can throw exception objects. The following code fragment throws a FileNotFoundException object from a try block and catches that object in the associated catch clause:

try
{
   throw new FileNotFoundException ();
}
catch (FileNotFoundException exc)
{
   System.out.println (exc.toString ()); // Display a string representation of the exception object.
}

Within the try block, a throw statement creates and throws a FileNotFoundException object to the JVM. In response, the JVM searches backwards through the method-call stack for a catch clause with a parameter of type FileNotFoundException. That search begins with the method that contains the throw statement's surrounding try block. Because an appropriate catch clause is part of the try block, the JVM transfers execution to that catch clause. Once the catch clause executes its last statement, execution continues with the first statement that follows that clause.

1 2 3 4 5 Page 2
Page 2 of 5