Catching an exception

Thursday Mar 1st 2001
Share:

If a method throws an exception, it must assume that exception is caught and dealt with. One of the advantages of Java exception handling is that it allows you to concentrate on the problem you’re trying to solve in one place, and then deal with the errors from that code in another place.

Bruce Eckel's Thinking in Java Contents | Prev | Next

If a method throws an exception, it must assume that exception is caught and dealt with. One of the advantages of Java exception handling is that it allows you to concentrate on the problem you’re trying to solve in one place, and then deal with the errors from that code in another place.

To see how an exception is caught, you must first understand the concept of a guarded region , which is a section of code that might produce exceptions, and is followed by the code to handle those exceptions.

The try block

If you’re inside a method and you throw an exception (or another method you call within this method throws an exception), that method will exit in the process of throwing. If you don’t want a throw to leave a method, you can set up a special block within that method to capture the exception. This is called the try block because you “try” your various method calls there. The try block is an ordinary scope, preceded by the keyword try:

try {
  // Code that might generate exceptions
}

If you were checking for errors carefully in a programming language that didn’t support exception handling, you’d have to surround every method call with setup and error testing code, even if you call the same method several times. With exception handling, you put everything in a try block and capture all the exceptions in one place. This means your code is a lot easier to write and easier to read because the goal of the code is not confused with the error checking.

Exception handlers

Of course, the thrown exception must end up someplace. This “place” is the exception handler, and there’s one for every exception type you want to catch. Exception handlers immediately follow the try block and are denoted by the keyword catch:

try {
  // Code that might generate exceptions
} catch(Type1 id1) {
  // Handle exceptions of Type1
} catch(Type2 id2) {
  // Handle exceptions of Type2
} catch(Type3 id3) {
  // Handle exceptions of Type3
}
 
// etc... 

Each catch clause (exception handler) is like a little method that takes one and only one argument of a particular type. The identifier ( id1, id2, and so on) can be used inside the handler, just like a method argument. Sometimes you never use the identifier because the type of the exception gives you enough information to deal with the exception, but the identifier must still be there.

The handlers must appear directly after the try block. If an exception is thrown, the exception-handling mechanism goes hunting for the first handler with an argument that matches the type of the exception. Then it enters that catch clause, and the exception is considered handled. (The search for handlers stops once the catch clause is finished.) Only the matching catch clause executes; it’s not like a switch statement in which you need a break after each case to prevent the remaining ones from executing.

Note that, within the try block, a number of different method calls might generate the same exception, but you need only one handler.

Termination vs. resumption

There are two basic models in exception-handling theory. In termination (which is what Java and C++ support), you assume the error is so critical there’s no way to get back to where the exception occurred. Whoever threw the exception decided that there was no way to salvage the situation, and they don’t want to come back.

The alternative is called resumption. It means that the exception handler is expected to do something to rectify the situation, and then the faulting method is retried, presuming success the second time. If you want resumption, it means you still hope to continue execution after the exception is handled. In this case, your exception is more like a method call – which is how you should set up situations in Java in which you want resumption-like behavior. (That is, don’t throw an exception; call a method that fixes the problem.) Alternatively, place your try block inside a while loop that keeps reentering the try block until the result is satisfactory.

Historically, programmers using operating systems that supported resumptive exception handling eventually ended up using termination-like code and skipping resumption. So although resumption sounds attractive at first, it seems it isn’t quite so useful in practice. The dominant reason is probably the coupling that 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 exception can be generated from many points.

The exception specification

In Java, you’re required to inform the client programmer, who calls your method, of the exceptions that might be thrown from your method. This is civilized because the caller can know exactly what code to write to catch all potential exceptions. Of course, if source code is available, the client programmer could hunt through and look for throw statements, but often a library doesn’t come with sources. To prevent this from being a problem, Java provides syntax (and forces you to use that syntax) to allow you to politely tell the client programmer what exceptions this method throws, so the client programmer can handle them. This is the exception specification and it’s part of the method declaration, appearing after the argument list.

The exception specification uses an additional keyword, throws, followed by a list of all the potential exception types. So your method definition might look like this:

void f() throws tooBig, tooSmall, divZero { //...

If you say

void f() { // ...

it means that no exceptions are thrown from the method. ( Except for the exceptions of type RuntimeException, which can reasonably be thrown anywhere – this will be described later.)

You can’t lie about an exception specification – if your method causes exceptions and doesn’t handle them, the compiler will detect this and tell you that you must either handle the exception or indicate with an exception specification that it may be thrown from your method. By enforcing exception specifications from top to bottom, Java guarantees that exception correctness can be ensured at compile time .[42]

There is one place you can lie: you can claim to throw an exception that you don’t. The compiler takes your word for it and forces the users of your method to treat it as if it really does throw that exception. This has the beneficial effect of being a placeholder for that exception, so you can actually start throwing the exception later without requiring changes to existing code.

Catching any exception

It is possible to create a handler that catches any type of exception. You do this by catching the base-class exception type Exception (there are other types of base exceptions, but Exception is the base that’s pertinent to virtually all programming activities):

catch(Exception e) {
  System.out.println("caught an exception");
}

This will catch any exception, so if you use it you’ll want to put it at the end of your list of handlers to avoid pre-empting any exception handlers that might otherwise follow it.

Since the Exception class is the base of all the exception classes that are important to the programmer, you don’t get much specific information about the exception, but you can call the methods that come from its base type Throwable:

String getMessage( )

Gets the detail message.

String toString( )

Returns a short description of the Throwable, including the detail message if there is one.

void printStackTrace( )

void printStackTrace(PrintStream)

Prints the Throwable and the Throwable’s call stack trace. The call stack shows the sequence of method calls that brought you to the point at which the exception was thrown.

The first version prints to standard error, the second prints to a stream of your choice. If you’re working under Windows, you can’t redirect standard error so you might want to use the second version and send the results to System.out; that way the output can be redirected any way you want.

In addition, you get some other methods from Throwable’s base type Object (everybody’s base type). The one that might come in handy for exceptions is getClass( ), which returns an object representing the class of this object. You can in turn query this Class object for its name with getName( ) or toString( ). You can also do more sophisticated things with Class objects that aren’t necessary in exception handling. Class objects will be studied later in the book.

Here’s an example that shows the use of the Exception methods: (See page 97 if you have trouble executing this program.)

//: ExceptionMethods.java
// Demonstrating the Exception Methods
package c09;
 
public class ExceptionMethods {
  public static void main(String[] args) {
    try {
      throw new Exception("Here's my Exception");
    } catch(Exception e) {
      System.out.println("Caught Exception");
      System.out.println(
        "e.getMessage(): " + e.getMessage());
      System.out.println(
        "e.toString(): " + e.toString());
      System.out.println("e.printStackTrace():");
      e.printStackTrace();
    }
  }
} ///:~ 

The output for this program is:

Caught Exception
e.getMessage(): Here's my Exception
e.toString(): java.lang.Exception: Here's my Exception
e.printStackTrace():
java.lang.Exception: Here's my Exception
        at ExceptionMethods.main 

You can see that the methods provide successively more information – each is effectively a superset of the previous one.

Rethrowing an exception

Sometimes you’ll want to rethrow the exception that you just caught, particularly when you use Exception to catch any exception. Since you already have the handle to the current exception, you can simply re-throw that handle:

catch(Exception e) {
  System.out.println("An exception was thrown");
  throw e;
}

Rethrowing an exception causes the exception to go to the exception handlers in the next-higher context. Any further catch clauses for the same try block are still ignored. In addition, everything about the exception object is preserved, so the handler at the higher context that catches the specific exception type can extract all the information from that object.

If you simply re-throw the current exception, the information that you print about that exception in printStackTrace( ) will pertain to the exception’s origin, not the place where you re-throw it. If you want to install new stack trace information, you can do so by calling fillInStackTrace( ), which returns an exception object that it creates by stuffing the current stack information into the old exception object. Here’s what it looks like:

//: Rethrowing.java
// Demonstrating fillInStackTrace()
 
public class Rethrowing {
  public static void f() throws Exception {
    System.out.println(
      "originating the exception in f()");
    throw new Exception("thrown from f()");
  }
  public static void g() throws Throwable {
    try {
      f();
    } catch(Exception e) {
      System.out.println(
        "Inside g(), e.printStackTrace()");
      e.printStackTrace();
      throw e; // 17
      // throw e.fillInStackTrace(); // 18
    }
  }
  public static void
  main(String[] args) throws Throwable {
    try {
      g();
    } catch(Exception e) {
      System.out.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace();
    }
  }
} ///:~ 

The important line numbers are marked inside of comments. With line 17 un-commented (as shown), the output is:

originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)

So the exception stack trace always remembers its true point of origin, no matter how many times it gets rethrown.

With line 17 commented and line 18 un-commented, fillInStackTrace( ) is used instead, and the result is:

originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:8)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:24)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.g(Rethrowing.java:18)
        at Rethrowing.main(Rethrowing.java:24)

Because of fillInStackTrace( ), line 18 becomes the new point of origin of the exception.

The class Throwable must appear in the exception specification for g( ) and main( ) because fillInStackTrace( ) produces a handle to a Throwable object. Since Throwable is a base class of Exception, it’s possible to get an object that’s a Throwable but not an Exception, so the handler for Exception in main( ) might miss it. To make sure everything is in order, the compiler forces an exception specification for Throwable. For example, the exception in the following program is not caught in main( ):

//: ThrowOut.java
public class ThrowOut {
  public static void
  main(String[] args) throws Throwable {
    try {
      throw new Throwable(); 
    } catch(Exception e) {
      System.out.println("Caught in main()");
    }
  }
} ///:~ 

It’s also possible to rethrow a different exception from the one you caught. If you do this, you get a similar effect as when you use fillInStackTrace( ): the information about the original site of the exception is lost, and what you’re left with is the information pertaining to the new throw:

//: RethrowNew.java
// Rethrow a different object from the one that
// was caught
 
public class RethrowNew {
  public static void f() throws Exception {
    System.out.println(
      "originating the exception in f()");
    throw new Exception("thrown from f()");
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(Exception e) {
      System.out.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace();
      throw new NullPointerException("from main");
    }
  }
} ///:~ 

The output is:

originating the exception in f()
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at RethrowNew.f(RethrowNew.java:8)
        at RethrowNew.main(RethrowNew.java:13)
java.lang.NullPointerException: from main
        at RethrowNew.main(RethrowNew.java:18)

The final exception knows only that it came from main( ), and not from f( ). Note that Throwable isn’t necessary in any of the exception specifications.

You never have to worry about cleaning up the previous exception, or any exceptions for that matter. They’re all heap-based objects created with new, so the garbage collector automatically cleans them all up.


[42] This is a significant improvement over C++ exception handling, which doesn’t catch violations of exception specifications until run time, when it’s not very useful.

Contents | Prev | Next
Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved