Tuesday 6 March 2012

Exceptions

  • Exceptions
    • Programming in the Large
    • Programming in the Small
    • The Limitations Placed on the Programmer
    • The finally Clause
    • Summary
    • Q&A

Exceptions

by Charles L. Perkins
Today, you'll learn about exceptional conditions in Java:
  • How to declare when you are expecting one
  • How to handle them in your code
  • How to create them
  • How your code is limited, yet made more robust by them
Let's begin by motivating 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 more ugly.
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.

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 a moment to glance in Appendix B 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 via 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 in your throws clause if you like, but the callers of your methods will not be forced to handle them; only non-run-time exception 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):
  • ClassNotFoundException
  • CloneNotSupportedException
  • IllegalAccessException
  • InstantiationException
  • InterrupedException
  • NoSuchMethodException
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 today'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 first saw a try statement on Day 10.) 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. How about fleshing out today'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 allow 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 be expressed 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 (you'll see this tomorrow).
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

Today, you learned about how exceptions aid your program's design, robustness, and multithreading capability (more on this tomorrow).
You also learned about the vast array of exceptions defined and thrown in the Java class library, and how to try methods while catch-ing 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.

Q&A

Q: I'd like to test the last example you gave, but where does getContext() come from?
A: That example wasn't meant to be executable as it stands, but you can make it so as follows. First, remove the clause extends ContextClass from line one. Then, replace getContext() in the third line with
Integer.parseInt(argv[0]). You can now compile, then run, the example via the following:

java MyFinalExceptionClass N

where[]is the mysterious state you want.
Q: I'm still not sure I understand the differences between Exceptions, Errors, and RuntimeExceptions. Is there another way of looking at them?
A: Errors are caused by dynamic linking, or virtual machine problems, and are thus too low-level for most programs to care about (although sophisticated development libraries and environments probably care a great deal about them). RuntimeExceptions are generated by the normal execution of Java code, and though they occasionally reflect a condition you will want to handle explicitly, more often they reflect a coding mistake by the programmer and thus simply need to print an error to help flag that mistake. Exceptions that are not RuntimeExceptions (IOExceptions, for example) are conditions that, because of their nature, should be explicitly handled by any robust and well-thought-out code. The Java class library has been written using only a few of these, but those few are extremely important to using the system safely and correctly. The compiler helps you handle these exceptions properly via its throws clause checks and restrictions.
Q: Is there any way to "get around" the strict restrictions placed on methods by the throws clause?
A: Yes. Suppose you thought long and hard and have decided that you need to circumvent this restriction. This is almost never the case, because the right solution is to go back and redesign your methods to reflect the exceptions that you need to throw. Imagine, however, that for some reason a system class has you in a straitjacket. Your first solution is to subclass RuntimeException to make up a new, exempt exception of your own. Now you can throw it to your heart's content, because the throws clause that was annoying you does not need to include this new exception. If you need a lot of such exceptions, an elegant approach is to mix in some novel exception interfaces to your new Runtime classes. You're free to choose whatever subset of these new interfaces you want to catch (none of the normal Runtime exceptions need be caught), while any leftover (new) Runtime exceptions are (legally) allowed to go through that otherwise annoying standard method in the library.
Q: I'm still a little confused by long chains of catch clauses. Can you label the previous example with which exceptions are handled by each line of code?
A: Certainly, here it is:

try {
someReallyExceptionalMethod();
} catch (NullPointerException n) {
. . . // handles NullPointerExceptions
} catch (RuntimeException r) {
. . . // handles RuntimeExceptions that are not NullPointerExceptions
} catch (IOException i) {
. . . // handles IOExceptions
} catch (MyFirstException m) {
. . . // handles MyFirstExceptions
} catch (Exception e) { // handles Exceptions that are not RuntimeExceptions
. . . // nor IOExceptions nor MyFirstExceptions
} catch (Throwable t) {
. . . // handles Throwables that are not Exceptions (i.e., Errors)
}
Q: Given how annoying it can sometimes be to handle exceptional conditions properly, what's stopping me from surrounding any method as follows:

try { thatAnnoyingMethod(); } catch (Throwable t) { }

and simply ignoring all exceptions?
A: Nothing, other than your own conscience. In some cases, you should do nothing, because it is the correct thing to do for your method's implementation. Otherwise, you should struggle through the annoyance and gain experience. Good style is a struggle even for the best of programmers, but the rewards are rich indeed.

No comments:

Post a Comment