MindView Inc.
[ Viewing Hints ] [ Revision History ] [ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 2nd edition, Revision 12

©2000 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

10: Error Handling
with Exceptions

The basic philosophy of Java is that “badly formed code will not be run.”

The ideal time to catch an error is at compile-time, before you even try to run the program. However, not all errors can be detected at compile-time. The rest of the problems must be handled at run-time, through some formality that allows the originator of the error to pass appropriate information to a recipient who will know how to handle the difficulty properly. [ Add Comment ]

In C and other earlier languages, there could be several of these formalities, and they were generally established by convention and not as part of the programming language. Typically, you returned a special value or set a flag, and the recipient was supposed to look at the value or the flag and determine that something was amiss. However, as the years passed, it was discovered that programmers who use a library tend to think of themselves as invincible—as in, “Yes, errors might happen to others, but not in my code.” So, not too surprisingly, they wouldn’t check for the error conditions (and sometimes the error conditions were too silly to check for[51]). If you were thorough enough to check for an error every time you called a method, your code could turn into an unreadable nightmare. Because programmers could still coax systems out of these languages they were resistant to admitting the truth: This approach to handling errors was a major limitation to creating large, robust, maintainable programs. [ Add Comment ]

The solution is to take the casual nature out of error handling and to enforce formality. This actually has a long history, since implementations of exception handling go back to operating systems in the 1960s, and even to BASIC’s “on error goto.” But C++ exception handling was based on Ada, and Java’s is based primarily on C++ (although it looks even more like Object Pascal). [ Add Comment ]

The word “exception” is meant in the sense of “I take exception to that.” At the point where the problem occurs you might not know what to do with it, but you do know that you can’t just continue on merrily; you must stop and somebody, somewhere, must figure out what to do. But you don’t have enough information in the current context to fix the problem. So you hand the problem out to a higher context where someone is qualified to make the proper decision (much like a chain of command). [ Add Comment ]

The other rather significant benefit of exceptions is that they clean up error handling code. Instead of checking for a particular error and dealing with it at multiple places in your program, you no longer need to check at the point of the method call (since the exception will guarantee that someone catches it). And, you need to handle the problem in only one place, the so-called exception handler. This saves you code, and it separates the code that describes what you want to do from the code that is executed when things go awry. In general, reading, writing, and debugging code becomes much clearer with exceptions than when using the old way of error handling. [ Add Comment ]

Because exception handling is enforced by the Java compiler, there are only so many examples that can be written in this book without learning about exception handling. This chapter introduces you to the code you need to write to properly handle exceptions, and the way you can generate your own exceptions if one of your methods gets into trouble. [ Add Comment ]

Basic exceptions

An exceptional condition is a problem that prevents the continuation of the method or scope that you’re in. It’s important to distinguish an exceptional condition from a normal problem, in which you have enough information in the current context to somehow cope with the difficulty. With an exceptional condition, you cannot continue processing because you don’t have the information necessary to deal with the problem in the current context. All you can do is jump out of the current context and relegate that problem to a higher context. This is what happens when you throw an exception. [ Add Comment ]

A simple example is a divide. If you’re about to divide by zero, it’s worth checking to make sure you don’t go ahead and perform the divide. But what does it mean that the denominator is zero? Maybe you know, in the context of the problem you’re trying to solve in that particular method, how to deal with a zero denominator. But if it’s an unexpected value, you can’t deal with it and so must throw an exception rather than continuing along that path. [ Add Comment ]

When you throw an exception, several things happen. First, the exception object is created in the same way that any Java object is created: on the heap, with new. Then the current path of execution (the one you couldn’t continue) is stopped and the reference for the exception object is ejected from the current context. At this point the exception handling mechanism takes over and begins to look for an appropriate place to continue executing the program. This appropriate place is the exception handler, whose job is to recover from the problem so the program can either try another tack or just continue. [ Add Comment ]

As a simple example of throwing an exception, consider an object reference called t. It’s possible that you might be passed a reference that hasn’t been initialized, so you might want to check before trying to call a method using that object reference. You can send information about the error into a larger context by creating an object representing your information and “throwing” it out of your current context. This is called throwing an exception. Here’s what it looks like:

if(t == null)
  throw new NullPointerException();

This throws the exception, which allows you—in the current context—to abdicate responsibility for thinking about the issue further. It’s just magically handled somewhere else. Precisely where will be shown shortly. [ Add Comment ]

Exception arguments

Like any object in Java, you always create exceptions on the heap using new, which allocates storage and calls a constructor. There are two constructors in all standard exceptions: the first is the default constructor, and the second takes a string argument so you can place pertinent information in the exception:

if(t == null)
  throw new NullPointerException("t = null");

This string can later be extracted using various methods, as you’ll see. [ Add Comment ]

The keyword throw causes a number of relatively magical things to happen. Typically, you’ll first use new to create an object that represents the error condition. You give the resulting reference to throw. The object is, in effect, “returned” from the method, even though that object type isn’t normally what the method is designed to return. A simplistic way to think about exception handling is as an alternate return mechanism, although you get into trouble if you take that analogy too far. You can also exit from ordinary scopes by throwing an exception. But a value is returned, and the method or scope exits. [ Add Comment ]

Any similarity to an ordinary return from a method ends here, because where you return is someplace completely different from where you return for a normal method call. (You end up in an appropriate exception handler that might be miles away—many levels lower on the call stack—from where the exception was thrown.) [ Add Comment ]

In addition, you can throw any type of Throwable object that you want. Typically, you’ll throw a different class of exception for each different type of error. The information about the error is represented both inside the exception object and implicitly in the type of exception object chosen, so someone in the bigger context can figure out what to do with your exception. (Often, the only information is the type of exception object, and nothing meaningful is stored within the exception object.) [ Add Comment ]

Catching an exception

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. [ Add Comment ]

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 which is followed by the code to handle those exceptions. [ Add Comment ]

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 exit the 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. [ Add Comment ]

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. [ Add Comment ]

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. [ Add Comment ]

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

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 that 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. [ Add Comment ]

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. [ Add Comment ]

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 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 nongeneric 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. [ Add Comment ]

Creating your own exceptions

You’re not stuck using the existing Java exceptions. This is important because you’ll often need to create your own exceptions to denote a special error that your library is capable of creating, but which was not foreseen when the Java exception hierarchy was created. [ Add Comment ]

To create your own exception class, you’re forced to inherit from an existing type of exception, preferably one that is close in meaning to your new exception (this is often not possible, however). The most trivial way to create a new type of exception is just to let the compiler create the default constructor for you, so it requires almost no code at all:

//: c10:SimpleExceptionDemo.java
// Inheriting your own exceptions.
class SimpleException extends Exception {} 

public class SimpleExceptionDemo {
  public void f() throws SimpleException {
    System.out.println(
      "Throwing SimpleException from f()");
    throw new SimpleException ();
  }
  public static void main(String[] args) {
    SimpleExceptionDemo sed = 
      new SimpleExceptionDemo();
    try {
      sed.f();
    } catch(SimpleException e) {
      System.err.println("Caught it!");
    }
  }
} ///:~

When the compiler creates the default constructor, it which automatically (and invisibly) calls the base-class default constructor. Of course, in this case you don’t get a SimpleException(String) constructor, but in practice that isn’t used much. As you’ll see, the most important thing about an exception is the class name, so most of the time an exception like the one shown above is satisfactory. [ Add Comment ]

Here, the result is printed to the console standard error stream by writing to System.err. This is usually a better place to send error information than System.out, which may be redirected. If you send output to System.err it will not be redirected along with System.out so the user is more likely to notice it. [ Add Comment ]

Creating an exception class that also has a constructor that takes a String is also quite simple:

//: c10:FullConstructors.java
// Inheriting your own exceptions.

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

public class FullConstructors {
  public static void f() throws MyException {
    System.out.println(
      "Throwing MyException from f()");
    throw new MyException();
  }
  public static void g() throws MyException {
    System.out.println(
      "Throwing MyException from g()");
    throw new MyException("Originated in g()");
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(MyException e) {
      e.printStackTrace(System.err);
    }
    try {
      g();
    } catch(MyException e) {
      e.printStackTrace(System.err);
    }
  }
} ///:~

The added code is small—the addition of two constructors that define the way MyException is created. In the second constructor, the base-class constructor with a String argument is explicitly invoked by using the super keyword. [ Add Comment ]

The stack trace information is sent to System.err so that it’s more likely it will be noticed in the event that System.out has been redirected. [ Add Comment ]

The output of the program is:

Throwing MyException from f()
MyException
        at FullConstructors.f(FullConstructors.java:16)
        at FullConstructors.main(FullConstructors.java:24)
Throwing MyException from g()
MyException: Originated in g()
        at FullConstructors.g(FullConstructors.java:20)
        at FullConstructors.main(FullConstructors.java:29)

You can see the absence of the detail message in the MyException thrown from f( ). [ Add Comment ]

The process of creating your own exceptions can be taken further. You can add extra constructors and members:

//: c10:ExtraFeatures.java
// Further embellishment of exception classes.

class MyException2 extends Exception {
  public MyException2() {}
  public MyException2(String msg) {
    super(msg);
  }
  public MyException2(String msg, int x) {
    super(msg);
    i = x;
  }
  public int val() { return i; }
  private int i;
}

public class ExtraFeatures {
  public static void f() throws MyException2 {
    System.out.println(
      "Throwing MyException2 from f()");
    throw new MyException2();
  }
  public static void g() throws MyException2 {
    System.out.println(
      "Throwing MyException2 from g()");
    throw new MyException2("Originated in g()");
  }
  public static void h() throws MyException2 {
    System.out.println(
      "Throwing MyException2 from h()");
    throw new MyException2(
      "Originated in h()", 47);
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(MyException2 e) {
      e.printStackTrace(System.err);
    }
    try {
      g();
    } catch(MyException2 e) {
      e.printStackTrace(System.err);
    }
    try {
      h();
    } catch(MyException2 e) {
      e.printStackTrace(System.err);
      System.err.println("e.val() = " + e.val());
    }
  }
} ///:~

A data member i has been added, along with a method that reads that value and an additional constructor that sets it. The output is: [ Add Comment ]

Throwing MyException2 from f()
MyException2
        at ExtraFeatures.f(ExtraFeatures.java:22)
        at ExtraFeatures.main(ExtraFeatures.java:34)
Throwing MyException2 from g()
MyException2: Originated in g()
        at ExtraFeatures.g(ExtraFeatures.java:26)
        at ExtraFeatures.main(ExtraFeatures.java:39)
Throwing MyException2 from h()
MyException2: Originated in h()
        at ExtraFeatures.h(ExtraFeatures.java:30)
        at ExtraFeatures.main(ExtraFeatures.java:44)
e.val() = 47

Since an exception is just another kind of object, you can continue this process of embellishing the power of your exception classes. Keep in mind, however, that all this dressing-up might be lost on the client programmers using your packages, since they might simply look for the exception to be thrown and nothing more. (That’s the way most of the Java library exceptions are used.) [ Add Comment ]

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. [ Add Comment ]

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.) [ Add Comment ]

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[52]. [ Add Comment ]

There is one place you can lie: you can claim to throw an exception that you really 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. It’s also important for creating abstract base classes and interfaces whose derived classes or implementations may need to throw exceptions. [ Add Comment ]

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.err.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 preempting any exception handlers that might otherwise follow it. [ Add Comment ]

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( )
String getLocalizedMessage( )
Gets the detail message, or a message adjusted for this particular locale. [ Add Comment ]

String toString( )
Returns a short description of the Throwable, including the detail message if there is one. [ Add Comment ]

void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(PrintWriter)
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 and third prints to a stream of your choice (in Chapter 11, you’ll understand why there are two types of streams). [ Add Comment ]

Throwable fillInStackTrace( )
Records information within this Throwable object about the current state of the stack frames. Useful when an application is rethrowing an error or exception (more about this shortly). [ Add Comment ]

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 this book. [ Add Comment ]

Here’s an example that shows the use of the basic Exception methods:

//: c10:ExceptionMethods.java
// Demonstrating the Exception Methods.

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

The output for this program is:

Caught Exception
e.getMessage(): Here's my Exception
e.getLocalizedMessage(): 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(ExceptionMethods.java:7)
java.lang.Exception: 
   Here's my Exception
 at ExceptionMethods.main(ExceptionMethods.java:7)

You can see that the methods provide successively more information—each is effectively a superset of the previous one. [ Add Comment ]

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 reference to the current exception, you can simply rethrow that reference:

catch(Exception e) {
  System.err.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. [ Add Comment ]

If you simply rethrow 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 rethrow 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:

//: c10: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.err.println(
        "Inside g(), e.printStackTrace()");
      e.printStackTrace(System.err);
      throw e; // 17
      // throw e.fillInStackTrace(); // 18
    }
  }
  public static void
  main(String[] args) throws Throwable {
    try {
      g();
    } catch(Exception e) {
      System.err.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace(System.err);
    }
  }
} ///:~

The important line numbers are marked as comments. With line 17 uncommented (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. [ Add Comment ]

With line 17 commented and line 18 uncommented, 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. [ Add Comment ]

The class Throwable must appear in the exception specification for g( ) and main( ) because fillInStackTrace( ) produces a reference 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( ): [ Add Comment ]

//: c10:ThrowOut.java
public class ThrowOut {
  public static void
  main(String[] args) throws Throwable {
    try {
      throw new Throwable(); 
    } catch(Exception e) {
      System.err.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: [ Add Comment ]

//: c10:RethrowNew.java
// Rethrow a different object 
// from the one that was caught.

class OneException extends Exception {
  public OneException(String s) { super(s); }
}

class TwoException extends Exception {
  public TwoException(String s) { super(s); }
}

public class RethrowNew {
  public static void f() throws OneException {
    System.out.println(
      "originating the exception in f()");
    throw new OneException("thrown from f()");
  }
  public static void main(String[] args) 
  throws TwoException {
    try {
      f();
    } catch(OneException e) {
      System.err.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace(System.err);
      throw new TwoException("from main()");
    }
  }
} ///:~

The output is:

originating the exception in f()
Caught in main, e.printStackTrace()
OneException: thrown from f()
        at RethrowNew.f(RethrowNew.java:17)
        at RethrowNew.main(RethrowNew.java:22)
Exception in thread "main" TwoException: from main()
        at RethrowNew.main(RethrowNew.java:27)

The final exception knows only that it came from main( ), and not from f( ). [ Add Comment ]

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. [ Add Comment ]

Standard Java exceptions

The Java class Throwable describes anything that can be thrown as an exception. There are two general types of Throwable objects (“types of” = “inherited from”). Error represents compile-time and system errors that you don’t worry about catching (except in special cases). Exception is the basic type that can be thrown from any of the standard Java library class methods and from your methods and run-time accidents. So the Java programmer’s base type of interest is Exception. [ Add Comment ]

The best way to get an overview of the exceptions is to browse the HTML Java documentation that you can download from java.sun.com. It’s worth doing this once just to get a feel for the various exceptions, but you’ll soon see that there isn’t anything special between one exception and the next except for the name. Also, the number of exceptions in Java keeps expanding; basically it’s pointless to print them in a book. Any new library you get from a third-party vendor will probably have its own exceptions as well. The important thing to understand is the concept and what you should do with the exceptions. [ Add Comment ]

The basic idea is that the name of the exception represents the problem that occurred, and the exception name is intended to be relatively self-explanatory. The exceptions are not all defined in java.lang; some are created to support other libraries such as util, net, and io, which you can see from their full class names or what they are inherited from. For example, all I/O exceptions are inherited from java.io.IOException. [ Add Comment ]

The special case of RuntimeException

The first example in this chapter was

if(t == null)
  throw new NullPointerException();

It can be a bit horrifying to think that you must check for null on every reference that is passed into a method (since you can’t know if the caller has passed you a valid reference). Fortunately, you don’t—this is part of the standard run-time checking that Java performs for you, and if any call is made to a null reference, Java will automatically throw a NullPointerException. So the above bit of code is always superfluous. [ Add Comment ]

There’s a whole group of exception types that are in this category. They’re always thrown automatically by Java and you don’t need to include them in your exception specifications. Conveniently enough, they’re all grouped together by putting them under a single base class called RuntimeException, which is a perfect example of inheritance: it establishes a family of types that have some characteristics and behaviors in common. Also, you never need to write an exception specification saying that a method might throw a RuntimeException, since that’s just assumed. Because they indicate bugs, you virtually never catch a RuntimeException—it’s dealt with automatically. If you were forced to check for RuntimeExceptions your code could get messy. Even though you don’t typically catch RuntimeExceptions, in your own packages you might choose to throw some of the RuntimeExceptions. [ Add Comment ]

What happens when you don’t catch such exceptions? Since the compiler doesn’t enforce exception specifications for these, it’s quite plausible that a RuntimeException could percolate all the way out to your main( ) method without being caught. To see what happens in this case, try the following example:

//: c10:NeverCaught.java
// Ignoring RuntimeExceptions.

public class NeverCaught {
  static void f() {
    throw new RuntimeException("From f()");
  }
  static void g() {
    f();
  }
  public static void main(String[] args) {
    g();
  }
} ///:~

You can already see that a RuntimeException (or anything inherited from it) is a special case, since the compiler doesn’t require an exception specification for these types. [ Add Comment ]

The output is:

Exception in thread "main"
java.lang.RuntimeException: From f()
        at NeverCaught.f(NeverCaught.java:9)
        at NeverCaught.g(NeverCaught.java:12)
        at NeverCaught.main(NeverCaught.java:15)

So the answer is: If a RuntimeException gets all the way out to main( ) without being caught, printStackTrace( ) is called for that exception as the program exits. [ Add Comment ]

Keep in mind that you can only ignore RuntimeExceptions in your coding, since all other handling is carefully enforced by the compiler. The reasoning is that a RuntimeException represents a programming error:

  1. An error you cannot catch (receiving a null reference handed to your method by a client programmer, for example) . [ Add Comment ]
  2. An error that you, as a programmer, should have checked for in your code (such as ArrayIndexOutOfBoundsException where you should have paid attention to the size of the array). [ Add Comment ]

You can see what a tremendous benefit it is to have exceptions in this case, since they help in the debugging process. [ Add Comment ]

It’s interesting to notice that you cannot classify Java exception handling as a single-purpose tool. Yes, it is designed to handle those pesky run-time errors that will occur because of forces outside your code’s control, but it’s also essential for certain types of programming bugs that the compiler cannot detect. [ Add Comment ]

Performing cleanup
with finally

There’s often some piece of code that you want to execute whether or not an exception is thrown within a try block. This usually pertains to some operation other than memory recovery (since that’s taken care of by the garbage collector). To achieve this effect, you use a finally clause[53] at the end of all the exception handlers. The full picture of an exception handling section is thus:

try {
  // The guarded region: Dangerous activities
  // that might throw A, B, or C 
} catch(A a1) {
  // Handler for situation A
} catch(B b1) {
  // Handler for situation B
} catch(C c1) {
  // Handler for situation C
} finally {
  // Activities that happen every time
}

To demonstrate that the finally clause always runs, try this program: [ Add Comment ]

//: c10:FinallyWorks.java
// The finally clause is always executed.

class ThreeException extends Exception {}

public class FinallyWorks {
  static int count = 0;
  public static void main(String[] args) {
    while(true) {
      try {
        // Post-increment is zero first time:
        if(count++ == 0)
          throw new ThreeException();
        System.out.println("No exception");
      } catch(ThreeException e) {
        System.err.println("ThreeException");
      } finally {
        System.err.println("In finally clause");
        if(count == 2) break; // out of "while"
      }
    }
  }
} ///:~

This program also gives a hint for how you can deal with the fact that exceptions in Java (like exceptions in C++) do not allow you to resume back to where the exception was thrown, as discussed earlier. If you place your try block in a loop, you can establish a condition that must be met before you continue the program. You can also add a static counter or some other device to allow the loop to try several different approaches before giving up. This way you can build a greater level of robustness into your programs. [ Add Comment ]

The output is:

ThreeException
In finally clause
No exception
In finally clause

Whether an exception is thrown or not, the finally clause is always executed. [ Add Comment ]

What’s finally for?

In a language without garbage collection and without automatic destructor calls[54], finally is important because it allows the programmer to guarantee the release of memory regardless of what happens in the try block. But Java has garbage collection, so releasing memory is virtually never a problem. Also, it has no destructors to call. So when do you need to use finally in Java? [ Add Comment ]

finally is necessary when you need to set something other than memory back to its original state. This is some kind of cleanup like an open file or network connection, something you’ve drawn on the screen, or even a switch in the outside world, as modeled in the following example:

//: c10:OnOffSwitch.java 
// Why use finally?

class Switch {
  boolean state = false;
  boolean read() { return state; }
  void on() { state = true; }
  void off() { state = false; }
} 
class OnOffException1 extends Exception {}
class OnOffException2 extends Exception {}

public class OnOffSwitch {
  static Switch sw = new Switch();
  static void f() throws 
    OnOffException1, OnOffException2 {}
  public static void main(String[] args) {
    try {
      sw.on();
      // Code that can throw exceptions...
      f();
      sw.off();
    } catch(OnOffException1 e) {
      System.err.println("OnOffException1");
      sw.off();
    } catch(OnOffException2 e) {
      System.err.println("OnOffException2");
      sw.off();
    }
  }
} ///:~

The goal here is to make sure that the switch is off when main( ) is completed, so sw.off( ) is placed at the end of the try block and at the end of each exception handler. But it’s possible that an exception could be thrown that isn’t caught here, so sw.off( ) would be missed. However, with finally you can place the cleanup code from a try block in just one place: [ Add Comment ]

//: c10:WithFinally.java
// Finally Guarantees cleanup.

public class WithFinally {
  static Switch sw = new Switch();
  public static void main(String[] args) {
    try {
      sw.on();
      // Code that can throw exceptions...
      OnOffSwitch.f();
    } catch(OnOffException1 e) {
      System.err.println("OnOffException1");
    } catch(OnOffException2 e) {
      System.err.println("OnOffException2");
    } finally {
      sw.off();
    }
  }
} ///:~

Here the sw.off( ) has been moved to just one place, where it’s guaranteed to run no matter what happens. [ Add Comment ]

Even in cases in which the exception is not caught in the current set of catch clauses, finally will be executed before the exception handling mechanism continues its search for a handler at the next higher level:

//: c10:AlwaysFinally.java
// Finally is always executed.

class FourException extends Exception {}

public class AlwaysFinally {
  public static void main(String[] args) {
    System.out.println(
      "Entering first try block");
    try {
      System.out.println(
        "Entering second try block");
      try {
        throw new FourException();
      } finally {
        System.out.println(
          "finally in 2nd try block");
      }
    } catch(FourException e) {
      System.err.println(
        "Caught FourException in 1st try block");
    } finally {
      System.err.println(
        "finally in 1st try block");
    }
  }
} ///:~

The output for this program shows you what happens: [ Add Comment ]

Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block

The finally statement will also be executed in situations in which break and continue statements are involved. Note that, along with the labeled break and labeled continue, finally eliminates the need for a goto statement in Java. [ Add Comment ]

Pitfall: the lost exception

In general, Java’s exception implementation is quite outstanding, but unfortunately there’s a flaw. Although exceptions are an indication of a crisis in your program and should never be ignored, it’s possible for an exception to simply be lost. This happens with a particular configuration using a finally clause:

//: c10:LostMessage.java
// How an exception can be lost.

class VeryImportantException extends Exception {
  public String toString() {
    return "A very important exception!";
  }
}

class HoHumException extends Exception {
  public String toString() {
    return "A trivial exception";
  }
}

public class LostMessage {
  void f() throws VeryImportantException {
    throw new VeryImportantException();
  }
  void dispose() throws HoHumException {
    throw new HoHumException();
  }
  public static void main(String[] args) 
      throws Exception {
    LostMessage lm = new LostMessage();
    try {
      lm.f();
    } finally {
      lm.dispose();
    }
  }
} ///:~

The output is:

Exception in thread "main" A trivial exception
    at LostMessage.dispose(LostMessage.java:21)
    at LostMessage.main(LostMessage.java:29)

You can see that there’s no evidence of the VeryImportantException, which is simply replaced by the HoHumException in the finally clause. This is a rather serious pitfall, since it means that an exception can be completely lost, and in a far more subtle and difficult-to-detect fashion than the example above. In contrast, C++ treats the situation in which a second exception is thrown before the first one is handled as a dire programming error. Perhaps a future version of Java will repair this problem (on the other hand, you will typically wrap any method that throws an exception, such as dispose( ), inside a try-catch clause). [ Add Comment ]

Exception restrictions

When you override a method, you can throw only the exceptions that have been specified in the base-class version of the method. This is a useful restriction, since it means that code that works with the base class will automatically work with any object derived from the base class (a fundamental OOP concept, of course), including exceptions. [ Add Comment ]

This example demonstrates the kinds of restrictions imposed (at compile-time) for exceptions:

//: c10:StormyInning.java
// Overridden methods may throw only the 
// exceptions specified in their base-class 
// versions, or exceptions derived from the 
// base-class exceptions.

class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}

abstract class Inning {
  Inning() throws BaseballException {}
  void event () throws BaseballException {
   // Doesn't actually have to throw anything
  }
  abstract void atBat() throws Strike, Foul;
  void walk() {} // Throws nothing
}

class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}

interface Storm {
  void event() throws RainedOut;
  void rainHard() throws RainedOut;
}

public class StormyInning extends Inning 
    implements Storm {
  // OK to add new exceptions for 
  // constructors, but you must deal 
  // with the base constructor exceptions:
  StormyInning() throws RainedOut, 
    BaseballException {}
  StormyInning(String s) throws Foul, 
    BaseballException {}
  // Regular methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
  // Interface CANNOT add exceptions to existing
  // methods from the base class:
//! public void event() throws RainedOut {}
  // If the method doesn't already exist in the
  // base class, the exception is OK:
  public void rainHard() throws RainedOut {}
  // You can choose to not throw any exceptions,
  // even if base version does:
  public void event() {}
  // Overridden methods can throw 
  // inherited exceptions:
  void atBat() throws PopFoul {}
  public static void main(String[] args) {
    try {
      StormyInning si = new StormyInning();
      si.atBat();
    } catch(PopFoul e) {
      System.err.println("Pop foul");
    } catch(RainedOut e) {
      System.err.println("Rained out");
    } catch(BaseballException e) {
      System.err.println("Generic error");
    }
    // Strike not thrown in derived version.
    try {
      // What happens if you upcast?
      Inning i = new StormyInning();
      i.atBat();
      // You must catch the exceptions from the
      // base-class version of the method:
    } catch(Strike e) {
      System.err.println("Strike");
    } catch(Foul e) {
      System.err.println("Foul");
    } catch(RainedOut e) {
      System.err.println("Rained out");
    } catch(BaseballException e) {
      System.err.println(
        "Generic baseball exception");
    }
  }
} ///:~

In Inning, you can see that both the constructor and the event( ) method say they will throw an exception, but they never do. This is legal because it allows you to force the user to catch any exceptions that might be added in overridden versions of event( ). The same idea holds for abstract methods, as seen in atBat( ). [ Add Comment ]

The interface Storm is interesting because it contains one method (event( )) that is defined in Inning, and one method that isn’t. Both methods throw a new type of exception, RainedOut. When StormyInning extends Inning and implements Storm, you’ll see that the event( ) method in Storm cannot change the exception interface of event( ) in Inning. Again, this makes sense because otherwise you’d never know if you were catching the correct thing when working with the base class. Of course, if a method described in an interface is not in the base class, such as rainHard( ), then there’s no problem if it throws exceptions. [ Add Comment ]

The restriction on exceptions does not apply to constructors. You can see in StormyInning that a constructor can throw anything it wants, regardless of what the base-class constructor throws. However, since a base-class constructor must always be called one way or another (here, the default constructor is called automatically), the derived-class constructor must declare any base-class constructor exceptions in its exception specification. Note that a derived-class constructor cannot catch exceptions thrown by its base-class constructor. [ Add Comment ]

The reason StormyInning.walk( ) will not compile is that it throws an exception, while Inning.walk( ) does not. If this was allowed, then you could write code that called Inning.walk( ) and that didn’t have to handle any exceptions, but then when you substituted an object of a class derived from Inning, exceptions would be thrown so your code would break. By forcing the derived-class methods to conform to the exception specifications of the base-class methods, substitutability of objects is maintained. [ Add Comment ]

The overridden event( ) method shows that a derived-class version of a method may choose not to throw any exceptions, even if the base-class version does. Again, this is fine since it doesn’t break any code that is written—assuming the base-class version throws exceptions. Similar logic applies to atBat( ), which throws PopFoul, an exception that is derived from Foul thrown by the base-class version of atBat( ). This way, if someone writes code that works with Inning and calls atBat( ), they must catch the Foul exception. Since PopFoul is derived from Foul, the exception handler will also catch PopFoul. [ Add Comment ]

The last point of interest is in main( ). Here you can see that if you’re dealing with exactly a StormyInning object, the compiler forces you to catch only the exceptions that are specific to that class, but if you upcast to the base type then the compiler (correctly) forces you to catch the exceptions for the base type. All these constraints produce much more robust exception-handling code[55]. [ Add Comment ]

It’s useful to realize that although exception specifications are enforced by the compiler during inheritance, the exception specifications are not part of the type of a method, which is comprised of only the method name and argument types. Therefore, you cannot overload methods based on exception specifications. In addition, just because an exception specification exists in a base-class version of a method doesn’t mean that it must exist in the derived-class version of the method. This is quite different from inheritance rules, where a method in the base class must also exist in the derived class. Put another way, the “exception specification interface” for a particular method may narrow during inheritance and overriding, but it may not widen—this is precisely the opposite of the rule for the class interface during inheritance. [ Add Comment ]

Constructors

When writing code with exceptions, it’s particularly important that you always ask, “If an exception occurs, will this be properly cleaned up?” Most of the time you’re fairly safe, but in constructors there’s a problem. The constructor puts the object into a safe starting state, but it might perform some operation—such as opening a file—that doesn’t get cleaned up until the user is finished with the object and calls a special cleanup method. If you throw an exception from inside a constructor, these cleanup behaviors might not occur properly. This means that you must be especially diligent while you write your constructor. [ Add Comment ]

Since you’ve just learned about finally, you might think that it is the correct solution. But it’s not quite that simple, because finally performs the cleanup code every time, even in the situations in which you don’t want the cleanup code executed until the cleanup method runs. Thus, if you do perform cleanup in finally, you must set some kind of flag when the constructor finishes normally so that you don’t do anything in the finally block if the flag is set. Because this isn’t particularly elegant (you are coupling your code from one place to another), it’s best if you try to avoid performing this kind of cleanup in finally unless you are forced to. [ Add Comment ]

In the following example, a class called InputFile is created that opens a file and allows you to read it one line (converted into a String) at a time. It uses the classes FileReader and BufferedReader from the Java standard I/O library that will be discussed in Chapter 11, but which are simple enough that you probably won’t have any trouble understanding their basic use:

//: c10:Cleanup.java
// Paying attention to exceptions
// in constructors.
import java.io.*;

class InputFile {
  private BufferedReader in;
  InputFile(String fname) throws Exception {
    try {
      in = 
        new BufferedReader(
          new FileReader(fname));
      // Other code that might throw exceptions
    } catch(FileNotFoundException e) {
      System.err.println(
        "Could not open " + fname);
      // Wasn't open, so don't close it
      throw e;
    } catch(Exception e) {
      // All other exceptions must close it
      try {
        in.close();
      } catch(IOException e2) {
        System.err.println(
          "in.close() unsuccessful");
      }
      throw e; // Rethrow
    } finally {
      // Don't close it here!!!
    }
  }
  String getLine() {
    String s;
    try {
      s = in.readLine();
    } catch(IOException e) {
      System.err.println(
        "readLine() unsuccessful");
      s = "failed";
    }
    return s;
  }
  void cleanup() {
    try {
      in.close();
    } catch(IOException e2) {
      System.err.println(
        "in.close() unsuccessful");
    }
  }
}

public class Cleanup {
  public static void main(String[] args) {
    try {
      InputFile in = 
        new InputFile("Cleanup.java");
      String s;
      int i = 1;
      while((s = in.getLine()) != null)
        System.out.println(""+ i++ + ": " + s);
      in.cleanup();
    } catch(Exception e) {
      System.err.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace(System.err);
    }
  }
} ///:~

The constructor for InputFile takes a String argument, which is the name of the file you want to open. Inside a try block, it creates a FileReader using the file name. A FileReader isn’t particularly useful until you turn around and use it to create a BufferedReader that you can actually talk to—notice that one of the benefits of InputFile is that it combines these two actions. [ Add Comment ]

If the FileReader constructor is unsuccessful, it throws a FileNotFoundException, which must be caught separately because that’s the one case in which you don’t want to close the file since it wasn’t successfully opened. Any other catch clauses must close the file because it was opened by the time those catch clauses are entered. (Of course, this is trickier if more than one method can throw a FileNotFoundException. In that case, you might want to break things into several try blocks.) The close( ) method might throw an exception so it is tried and caught even though it’s within the block of another catch clause—it’s just another pair of curly braces to the Java compiler. After performing local operations, the exception is rethrown, which is appropriate because this constructor failed, and you wouldn’t want the calling method to assume that the object had been properly created and was valid. [ Add Comment ]

In this example, which doesn’t use the aforementioned flagging technique, the finally clause is definitely not the place to close( ) the file, since that would close it every time the constructor completed. Since we want the file to be open for the useful lifetime of the InputFile object this would not be appropriate. [ Add Comment ]

The getLine( ) method returns a String containing the next line in the file. It calls readLine( ), which can throw an exception, but that exception is caught so getLine( ) doesn’t throw any exceptions. One of the design issues with exceptions is whether to handle an exception completely at this level, to handle it partially and pass the same exception (or a different one) on, or whether to simply pass it on. Passing it on, when appropriate, can certainly simplify coding. The getLine( ) method becomes:

String getLine() throws IOException {
  return in.readLine();
}

But of course, the caller is now responsible for handling any IOException that might arise. [ Add Comment ]

The cleanup( ) method must be called by the user when finished using the InputFile object. This will release the system resources (such as file handles) that are used by the BufferedReader and/or FileReader objects[56]. You don’t want to do this until you’re finished with the InputFile object, at the point you’re going to let it go. You might think of putting such functionality into a finalize( ) method, but as mentioned in Chapter 4 you can’t always be sure that finalize( ) will be called (even if you can be sure that it will be called, you don’t know when). This is one of the downsides to Java: all cleanup—other than memory cleanup—doesn’t happen automatically, so you must inform the client programmer that they are responsible, and possibly guarantee that cleanup occurs using finalize( ). [ Add Comment ]

In Cleanup.java an InputFile is created to open the same source file that creates the program, the file is read in a line at a time, and line numbers are added. All exceptions are caught generically in main( ), although you could choose greater granularity. [ Add Comment ]

One of the benefits of this example is to show you why exceptions are introduced at this point in the book—you can’t do basic I/O without using exceptions. Exceptions are so integral to programming in Java, especially because the compiler enforces them, that you can accomplish only so much without knowing how to work with them. [ Add Comment ]

Exception matching

When an exception is thrown, the exception handling system looks through the “nearest” handlers in the order they are written. When it finds a match, the exception is considered handled, and no further searching occurs. [ Add Comment ]

Matching an exception doesn’t require a perfect match between the exception and its handler. A derived-class object will match a handler for the base class, as shown in this example:

//: c10:Human.java
// Catching exception hierarchies.

class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

public class Human {
  public static void main(String[] args) {
    try {
      throw new Sneeze();
    } catch(Sneeze s) {
      System.err.println("Caught Sneeze");
    } catch(Annoyance a) {
      System.err.println("Caught Annoyance");
    }
  }
} ///:~

The Sneeze exception will be caught by the first catch clause that it matches—which is the first one, of course. However, if you remove the first catch clause, leaving only: [ Add Comment ]

    try {
      throw new Sneeze();
    } catch(Annoyance a) {
      System.err.println("Caught Annoyance");
    }

The code will still work because it’s catching the base class of Sneeze. Put another way, catch(Annoyance e) will catch an Annoyance or any class derived from it. This is useful because if you decide to add more derived exceptions to a method, then the client programmer’s code will not need changing as long as the client catches the base class exceptions. [ Add Comment ]

If you try to “mask” the derived-class exceptions by putting the base-class catch clause first, like this:

    try {
      throw new Sneeze();
    } catch(Annoyance a) {
      System.err.println("Caught Annoyance");
    } catch(Sneeze s) {
      System.err.println("Caught Sneeze");
    }

the compiler will give you an error message, since it sees that the Sneeze catch-clause can never be reached. [ Add Comment ]

Exception guidelines

Use exceptions to:

  1. Fix the problem and call the method that caused the exception again.
  2. Patch things up and continue without retrying the method.
  3. Calculate some alternative result instead of what the method was supposed to produce.
  4. Do whatever you can in the current context and rethrow the same exception to a higher context.
  5. Do whatever you can in the current context and throw a different exception to a higher context.
  6. Terminate the program.
  7. Simplify. (If your exception scheme makes things more complicated, then it is painful and annoying to use.)
  8. Make your library and program safer. (This is a short-term investment for debugging, and a long-term investment (for application robustness.) [ Add Comment ]

Summary

Improved error recovery is one of the most powerful ways that you can increase the robustness of your code. Error recovery is a fundamental concern for every program you write, but it’s especially important in Java, where one of the primary goals is to create program components for others to use. To create a robust system, each component must be robust.Add Comment ]

The goals for exception handling in Java are to simplify the creation of large, reliable programs using less code than currently possible, and with more confidence that your application doesn’t have an unhandled error. [ Add Comment ]

Exceptions are not terribly difficult to learn, and are one of those features that provide immediate and significant benefits to your project. Fortunately, Java enforces all aspects of exceptions so it’s guaranteed that they will be used consistently by both library designers and client programmers. 

[ Add Comment ]


"_Toc375545380">"_Toc481064733">

Exercises

Solutions to selected exercises can be found in the electronic document The Thinking in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.

  1. Create a class with a main( ) that throws an object of class Exception inside a try block. Give the constructor for Exception a String argument. Catch the exception inside a catch clause and print the String argument. Add a finally clause and print a message to prove you were there. [ Add Comment ]
  2. Create your own exception class using the extends keyword. Write a constructor for this class that takes a String argument and stores it inside the object with a String reference. Write a method that prints out the stored String. Create a try-catch clause to exercise your new exception. [ Add Comment ]
  3. Write a class with a method that throws an exception of the type created in Exercise 2. Try compiling it without an exception specification to see what the compiler says. Add the appropriate exception specification. Try out your class and its exception inside a try-catch clause. [ Add Comment ]
  4. Define an object reference and initialize it to null. Try to call a method through this reference. Now wrap the code in a try-catch clause to catch the exception. [ Add Comment ]
  5. Create a class with two methods, f( ) and g( ). In g( ), throw an exception of a new type that you define. In f( ), call g( ), catch its exception and, in the catch clause, throw a different exception (of a second type that you define). Test your code in main( ). [ Add Comment ]
  6. Create three new types of exceptions. Write a class with a method that throws all three. In main( ), call the method but only use a single catch clause that will catch all three types of exceptions. [ Add Comment ]
  7. Write code to generate and catch an ArrayIndexOutOfBoundsException. [ Add Comment ]
  8. Create your own resumption-like behavior using a while loop that repeats until an exception is no longer thrown. [ Add Comment ]
  9. Create a three-level hierarchy of exceptions. Now create a base-class A with a method that throws an exception at the base of your hierarchy. Inherit B from A and override the method so it throws an exception at level two of your hierarchy. Repeat by inheriting class C from B. In main( ), create a C and upcast it to A, then call the method. [ Add Comment ]
  10. Demonstrate that a derived-class constructor cannot catch exceptions thrown by its base-class constructor. [ Add Comment ]
  11. Show that OnOffSwitch.java can fail by throwing a RuntimeException inside the try block. [ Add Comment ]
  12. Show that WithFinally.java doesn’t fail by throwing a RuntimeException inside the try block. [ Add Comment ]
  13. Modify Exercise 6 by adding a finally clause. Verify your finally clause is executed, even if a NullPointerException is thrown. [ Add Comment ]
  14. Create an example where you use a flag to control whether cleanup code is called, as described in the second paragraph after the heading “Constructors.” [ Add Comment ]
  15. Modify StormyInning.java by adding an UmpireArgument exception type, and methods that throw this exception. Test the modified hierarchy. [ Add Comment ]
  16. Remove the first catch clause in Human.java and verify that the code still compiles and runs properly. [ Add Comment ]
  17. Add a second level of exception loss to LostMessage.java so that the HoHumException is itself replaced by a third exception. [ Add Comment ]
  18. In Chapter 5, find the two programs called Assert.java and modify these to throw their own type of exception instead of printing to System.err. This exception should be an inner class that extends RuntimeException. [ Add Comment ]
  19. Add an appropriate set of exceptions to c08:GreenhouseControls.java. [ Add Comment ]

[51] The C programmer can look up the return value of printf( ) for an example of this.

[52] 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.

[53] C++ exception handling does not have the finally clause because it relies on destructors to accomplish this sort of cleanup.

[54] A destructor is a function that’s always called when an object becomes unused. You always know exactly where and when the destructor gets called. C++ has automatic destructor calls, but Delphi’s Object Pascal versions 1 and 2 do not (which changes the meaning and use of the concept of a destructor for that language).

[55] ISO C++ added similar constraints that require derived-method exceptions to be the same as, or derived from, the exceptions thrown by the base-class method. This is one case in which C++ is actually able to check exception specifications at compile-time.

[56] In C++, a destructor would handle this for you.

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:05/21/2001