Previous Page TOC Index Next Page

Chapter 16

Exception handling

This chapter covers exceptional conditions in Java. You learn how to handle them, how to create them, and how your code is limited—yet made more robust—by them.

Let’s begin by discussing why new ways of handling exceptions were invented.

Programming languages have long labored to solve the following common problem:

int  status = callSomethingThatAlmostAlwaysWorks();

if (status == FUNNY_RETURN_VALUE) {

    . . .        // something unusual happened, handle it

    switch(someGlobalErrorIndicator) {

        . . .        // handle more specific problems

    }

} else {

    . . .        // all is well, go your merry way

}

Somehow this seems like a lot of work to do to handle a rare case. What’s worse, if the function called returns an int as part of its normal answer, you must distinguish one special integer (FUNNY_RETURN_VALUE) to indicate an error. What if that function really needs all the integers? You must do something even uglier.

Even if you manage to find a distinguished value (such as NULL in C for pointers, -1 for integers, and so forth), what if there are multiple errors that must be produced by the same function? Often, some global variable is used as an error indicator. The function stores a value in it and prays that no one else changes it before the caller gets to handle the error. Multiple errors propagate badly, if at all, and there are numerous problems with generalizing this to large programs, complex errors, and so forth.

Luckily, there is an alternative: using exceptions to help you handle abnormal conditions in your program, making the normal, nonexceptional code cleaner and easier to read.

NOTE
An exception is any object that is an instance of the class Throwable (or any of its subclasses).

Programming in the Large

When you begin to build complex programs in Java, you discover that after designing the classes and interfaces, and their methods’ descriptions, you still have not defined all the behavior of your objects. After all, an interface describes the normal way to use an object and doesn’t include any strange, exceptional cases. In many systems, the documentation takes care of this problem by explicitly listing the distinguished values used in “hacks” like the previous example. Because the system knows nothing about these hacks, it cannot check them for consistency. In fact, the compiler can do nothing at all to help you with these exceptional conditions, in contrast to the helpful warnings and errors it produces if a method is used incorrectly.

More importantly, you have not captured in your design this important aspect of your program. Instead, you are forced to make up a way to describe it in the documentation and hope you have not made any mistakes when you implement it later. What’s worse, everyone else makes up a different way of describing the same thing. Clearly, you need some uniform way of declaring the intentions of classes and methods with respect to these exceptional conditions. Java provides just such a way:

public class  MyFirstExceptionalClass {

    public void  anExceptionalMethod() throws MyFirstException {

        . . .

    }

}

Here, you warn the reader (and the compiler) that the code . . . may throw an exception called MyFirstException.

You can think of a method’s description as a contract between the designer of that method (or class) and you, the caller of the method. Usually, this description tells the types of a method’s arguments, what it returns, and the general semantics of what it normally does. You are now being told, as well, what abnormal things it can do. This is a promise, just like the method promises to return a value of a certain type, and you can count on it when writing your code. These new promises help to tease apart and make explicit all the places where exceptional conditions should be handled in your program, and that makes large-scale design easier.

Because exceptions are instances of classes, they can be put into a hierarchy that can naturally describe the relationships among the different types of exceptions. In fact, if you take amoment to glance in Appendix B, “Class Hierarchy Diagrams,” at the diagrams for java.lang errors and java.lang exceptions, you’ll see that the class Throwable actually has two large hierarchies of classes beneath it. The roots of these two hierarchies are subclasses of Throwable called Exception and Error. These hierarchies embody the rich set of relationships that exist between exceptions and errors in the Java run-time environment.

When you know that a particular kind of error or exception can occur in your method, you are supposed to either handle it yourself or explicitly warn potential callers about the possibility using the throws clause. Not all errors and exceptions must be listed; instances of either class Error or RuntimeException (or any of their subclasses) do not have to be listed in your throws clause. They get special treatment because they can occur anywhere within a Java program and are usually conditions that you, as the programmer, did not directly cause. One good example is the OutOfMemoryError, which can happen anywhere, at any time, and for any number of reasons.

NOTE

You can, of course, choose to list these errors and run-time exceptions if you like, but the callers of your methods will not be forced to handle them; only in your throws clause non-run-time exceptions must be handled.

Whenever you see the word “exception” by itself, it almost always means “exception or error” (that is, an instance of Throwable). The previous discussion makes it clear that Exceptions and Errors actually form two separate hierarchies, but except for the throws clause rule, they act exactly the same.

If you examine the diagrams in Appendix B more carefully, you’ll notice that there are only six types of exceptions (in java.lang) that must be listed in a throws clause (remember that all Errors and RuntimeExceptions are exempt):

Each of these names suggests something that is explicitly caused by the programmer, not some behind-the-scenes event such as OutOfMemoryError.

If you look further in Appendix B, near the bottom of the diagrams for java.util and java.io, you’ll see that each package adds some new exceptions. The former is adding two exceptions somewhat akin to ArrayStoreException and IndexOutOfBoundsException, and so decides to place them under RuntimeException. The latter is adding a whole new tree of IOExceptions, which are more explicitly caused by the programmer, and so they are rooted under Exception. Thus, IOExceptions must be described in throws clauses. Finally, package java.awt (in diagram java.awt-components) defines one of each style, implicit and explicit.

The Java class library uses exceptions everywhere, and to good effect. If you examine the detailed API documentation in your Java release, you see that many of the methods in the library have throws clauses, and some of them even document (when they believe it will make something clearer to the reader) when they may throw one of the implicit errors or exceptions. This is just a nicety on the documenter’s part, because you are not required to catch conditions like that. If it wasn’t obvious that such a condition could happen there, and for some reason you really cared about catching it, this would be useful information.

Programming in the Small

Now that you have a feeling for how exceptions can help you design a program and a class library better, how do you actually use exceptions? Let’s try to call anExceptionalMethod() defined in this chapter’s first example:

public void  anotherExceptionalMethod() throws MyFirstException {

    MyFirstExceptionalClass  aMFEC = new MyFirstExceptionalClass();

    aMFEC.anExceptionalMethod();

}

Let’s examine this example more closely. If you assume that MyFirstException is a subclass of Exception, it means that if you don’t handle it in anotherExceptionalMethod()’s code, you must warn your callers about it. Because your code simply calls anExceptionalMethod() without doing anything about the fact that it may throw MyFirstException, you must add that exception to your throws clause. This is perfectly legal, but it does defer to your caller something that perhaps you should be responsible for doing yourself. (It depends on the circumstances, of course.)

Suppose that that you feel responsible today and decide to handle the exception. Because you’re now declaring a method without a throws clause, you must “catch” the expected exception and do something useful with it:

public void  responsibleMethod() {

    MyFirstExceptionalClass  aMFEC = new MyFirstExceptionalClass();

    try {

        aMFEC.anExceptionalMethod();

    } catch (MyFirstException m) {

        . . .    // do something terribly significant and responsible

    }

}

The try statement says basically: “Try running the code inside these braces, and if there are exceptions thrown, I will attach handlers to take care of them.” You can have as many catch clauses at the end of a try as you need. Each allows you to handle any and all exceptions that are instances of the class listed in parentheses, of any of its subclasses, or of a class that implements the interface listed in parentheses. In the catch in this example, exceptions of the class MyFirstException (or any of its subclasses) are being handled.

What if you want to combine both the approaches shown so far? You’d like to handle the exception yourself, but also reflect it up to your caller. This can be done by explicitly rethrowing the exception:

public void  responsibleExceptionalMethod() throws MyFirstException {

    MyFirstExceptionalClass  aMFEC = new MyFirstExceptionalClass();

    try {

        aMFEC.anExceptionalMethod();

    } catch (MyFirstException m) {

        . . .        // do something responsible

        throw m;     // re-throw the exception

    }

}

This works because exception handlers can be nested. You handle the exception by doing something responsible with it, but decide that it is too important to not give an exception handler that might be in your caller a chance to handle it as well. Exceptions float all the way up the chain of method callers this way (usually not being handled by most of them) until at last, the system itself handles any uncaught ones by aborting your program and printing an error message. In a stand-alone program, this is not such a bad idea; but in an applet, it can cause the browser to crash. Most browsers protect themselves from this disaster by catching all exceptions themselves whenever they run an applet, but you can never tell. If it’s possible for you to catch an exception and do something intelligent with it, you should.

Let’s see what throwing a new exception looks like. Let’s flesh out this chapter’s first example:

public class  MyFirstExceptionalClass {

    public void  anExceptionalMethod() throws MyFirstException {

        . . .

        if (someThingUnusualHasHappened()) {

            throw new MyFirstException();

            // execution never reaches here

        }

    }

}
NOTE
throw is a little like a break statement—nothing “beyond it” is executed.

This is the fundamental way that all exceptions are generated; someone, somewhere, has to create an exception object and throw it. In fact, the whole hierarchy under the class Throwable would be worth much less if there were not throw statements scattered throughout the code in the Java library at just the right places. Because exceptions propagate up from any depth down inside methods, any method call you make might generate a plethora of possible errors and exceptions. Luckily, only the ones listed in the throws clause of that method need be thought about; the rest travel silently past on their way to becoming an error message (or being caught and handled “higher up” in the system).

Here’s an unusual demonstration of this, where the throw, and the handler that catches it, are very close together:

System.out.print(“Now “);

try {

    System.out.print(“is “);

    throw new MyFirstException();

    System.out.print(“a “);

} catch (MyFirstException m) {

    System.out.print(“the “);

}

System.out.print(“time.\n”);

It prints out Now is the time.

Exceptions are really a quite powerful way of partitioning the space of all possible error conditions into manageable pieces. Because the first catch clause that matches is executed, you can build chains such as the following:

try {

    someReallyExceptionalMethod();

} catch (NullPointerException n) {  // a subclass of RuntimeException

    . . .

} catch (RuntimeException r) {      // a subclass of Exception

    . . .

} catch (IOException i) {           // a subclass of Exception

    . . .

} catch (MyFirstException m) {      // our subclass of Exception

    . . .

} catch (Exception e) {             // a subclass of Throwable

    . . .

} catch (Throwable t) {

    . . .  // Errors, plus anything not caught above are caught here

}

By listing subclasses before their parent classes, the parent catches anything it would normally catch that’s also not one of the subclasses above it. By juggling chains like these, you can express almost any combination of tests. If there’s some really obscure case you can’t handle, perhaps you can use an interface to catch it instead. That would enable you to design your (peculiar) exceptions hierarchy using multiple inheritance. Catching an interface rather than a class can also be used to test for a property that many exceptions share but that cannot beexpressed in the single-inheritance tree alone.

Suppose, for example, that a scattered set of your exception classes require a reboot after being thrown. You create an interface called NeedsReboot, and all these classes implement the interface. (None of them needs to have a common parent exception class.) Then, the highest level of exception handler simply catches classes that implement NeedsReboot and performs a reboot:

public interface  NeedsReboot { }   // needs no contents at all

try {

    someMethodThatGeneratesExceptionsThatImplementNeedsReboot();

} catch (NeedsReboot n) {    // catch an interface

    . . .                    // cleanup

    SystemClass.reboot();    // reboot using a made-up system class

}

By the way, if you need really unusual behavior during an exception, you can place the behavior into the exception class itself! Remember that an exception is also a normal class, so it can contain instance variables and methods. Although using them is a little unusual, it might be valuable on a few occasions. Here’s what this might look like:

try {

    someExceptionallyStrangeMethod();

} catch (ComplexException e) {

    switch (e.internalState()) {    // probably returns an instance variable value

        case e.COMPLEX_CASE:        // a class variable of the exception’s class

            e.performComplexBehavior(myState, theContext, etc);

            break;

        . . .

    }

}

The Limitations Placed on the Programmer

As powerful as all this sounds, isn’t it a little limiting, too? For example, suppose you want to override one of the standard methods of the Object class, toString(), to be smarter about how you print yourself:

public class  MyIllegalClass {

    public String  toString() {

        someReallyExceptionalMethod();

        . . .        // returns some String

    }

}

Because the superclass (Object) defined the method declaration for toString() without a throws clause, any implementation of it in any subclass must obey this restriction. In particular, you cannot just call someReallyExceptionalMethod(), as you did previously, because it will generate a host of errors and exceptions, some of which are not exempt from being listed in a throws clause (such as IOException and MyFirstException). If all the exceptions thrown were exempt, you would have no problem, but because some are not, you have to catch at least those few exceptions for this to be legal Java:

public class  MyLegalClass {

    public String  toString() {

        try {

            someReallyExceptionalMethod();

        } catch (IOException e) {

        } catch (MyFirstException m) {

        }

        . . .        // returns some String

    }

}

In both cases, you elect to catch the exceptions and do absolutely nothing with them. Although this is legal, it is not always the right thing to do. You may need to think for a while to come up with the best, nontrivial behavior for any particular catch clause. This extra thought and care makes your program more robust, better able to handle unusual input, and more likely to work correctly when used by multiple threads.

MyIllegalClass’s toString() method produces a compiler error to remind you to reflect on these issues. This extra care will richly reward you as you reuse your classes in later projects and in larger and larger programs. Of course, the Java class library has been written with exactly this degree of care, and that’s one of the reasons it’s robust enough to be used in constructing all your Java projects.

The finally Clause

Finally, for finally. Suppose there is some action that you absolutely must do, no matter what happens. Usually, this is to free some external resource after acquiring it, to close a file after opening it, or something similar. To be sure that “no matter what” includes exceptions as well, you use a clause of the try statement designed for exactly this sort of thing, finally:

SomeFileClass  f = new SomeFileClass();

if (f.open(“/a/file/name/path”)) {

    try {

        someReallyExceptionalMethod();

    } finally {

        f.close();

    }

}

This use of finally behaves very much like the following

SomeFileClass  f = new SomeFileClass();

if (f.open(“/a/file/name/path”)) {

    try {

        someReallyExceptionalMethod();

    } catch (Throwable t) {

        f.close();

        throw t;

    }

}

except that finally can also be used to clean up not only after exceptions but after return, break, and continue statements as well. Here’s a complex demonstration:

public class  MyFinalExceptionalClass extends ContextClass {

    public static void  main(String argv[]) {

        int  mysteriousState = getContext();

        while (true) {

            System.out.print(“Who “);

            try {

                System.out.print(“is “);

                if (mysteriousState == 1)

                    return;

                System.out.print(“that “);

                if (mysteriousState == 2)

                    break;

                System.out.print(“strange “);

                if (mysteriousState == 3)

                    continue;

                System.out.print(“but kindly “);

                if (mysteriousState == 4)

                    throw new UncaughtException();

                System.out.print(“not at all “);

            } finally {

                System.out.print(“amusing man?\n”);

            }

            System.out.print(“I’d like to meet the man “);

        }

        System.out.print(“Please tell me.\n”);

    }

}

Here is the output produced depending on the value of mysteriousState:

1     Who is amusing man?

2     Who is that amusing man?

      Please tell me

3     Who is that strange amusing man?

      ...

4     Who is that strange but kindly amusing man?

5     Who is that strange but kindly not at all amusing man?

      I’d like to meet the man Who is that strange...?

      ...
NOTE
In cases 3 and 5, the output never ends until you press Ctrl+C. In 4, an error message generated by the UncaughtException is also printed.

Summary

This chapter discussed how exceptions aid your program’s design, robustness, and multithreading capability.

You also learned about the vast array of exceptions defined and thrown in the Java class library, and how to try methods while catching any of a hierarchically ordered set of possible exceptions and errors. Java’s reliance on strict exception handling does place some restrictions on the programmer, but you learned that these restrictions are light compared to the rewards.

Finally, the finally clause was discussed, which provides a fool-proof way to be certain that something is accomplished, no matter what.


Previous Page TOC Index Next Page