The basic philosophy of Java is
that “badly-formed code will not be run.”
As with C++, the ideal time to catch the
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.
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[45]). 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.
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).
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).
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.
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 the exceptions, and the way you
can generate your own exceptions if one of your methods gets into
trouble.
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.
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.
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, remember) is stopped and the handle 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 simply
continue.
As a simple example of throwing an
exception, consider an object handle called t. It’s possible that
you might be passed a handle that hasn’t been initialized, so you might
want to check before trying to call a method using that object handle. 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.
Like any object in Java, you always
create exceptions on the heap using new and a constructor gets called.
There are two constructors in all the 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 will be shown later.
The keyword
throw causes a number of relatively magical things to happen. First it
executes the new-expression to create an object that isn’t
there under normal program execution, and of course, the constructor is called
for that object. Then 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.
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.)
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 idea is to store the
information in the exception object and 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.)
If a method throws an exception, it must
assume that exception is caught and dealt with. One of the advantages of Java
exception handling is that it allows you to concentrate on the problem
you’re trying to solve in one place, and then deal with the errors from
that code in another place.
To see how an exception is caught, you
must first understand the concept of a
guarded region, which is
a section of code that might produce exceptions, and is followed by the code to
handle those
exceptions.
If you’re inside a method and you
throw an exception (or another method you call within this method throws an
exception), that method will exit in the process of throwing. If you don’t
want a throw to leave a method, you can set up a special block within
that method to capture the exception. This is called the try
block because you
“try” your various method calls there. The try block is an ordinary
scope, preceded by the keyword try:
try { // Code that might generate exceptions }
If you were checking for errors carefully
in a programming language that didn’t support exception handling,
you’d have to surround every method call with setup and error testing
code, even if you call the same method several times. With exception handling,
you put everything in a try block and capture all the exceptions in one place.
This means your code is a lot easier to write and easier to read because the
goal of the code is not confused with the error
checking.
Of course, the thrown exception must end
up someplace. This “place” is the exception
handler, and there’s
one for every exception type you want to catch. Exception handlers immediately
follow the try block and are denoted by the keyword
catch:
try { // Code that might generate exceptions } catch(Type1 id1) { // Handle exceptions of Type1 } catch(Type2 id2) { // Handle exceptions of Type2 } catch(Type3 id3) { // Handle exceptions of Type3 } // etc...
Each catch clause (exception handler) is
like a little method that takes one and only one argument of a particular type.
The identifier (id1, id2, and so on) can be used inside the
handler, just like a method argument. Sometimes you never use the identifier
because the type of the exception gives you enough information to deal with the
exception, but the identifier must still be there.
The handlers must appear directly after
the try block. If an exception is thrown, the exception-handling mechanism goes
hunting for the first handler with an argument that matches the type of the
exception. Then it enters that catch clause, and the exception is considered
handled. (The search for handlers stops once the catch clause is finished.) Only
the matching catch clause executes; it’s not like a switch
statement in which you need a break after each case to prevent the
remaining ones from executing.
Note that, within the try block, a number
of different method calls might generate the same exception, but you need only
one handler.
There are two basic models in
exception-handling theory. In termination (which is what Java and C++
support), you assume the error is so critical there’s no way to get back
to where the exception occurred. Whoever threw the exception decided that there
was no way to salvage the situation, and they don’t want to come
back.
The alternative is called
resumption. It means that the exception handler is expected to do
something to rectify the situation, and then the faulting method is retried,
presuming success the second time. If you want resumption, it means you still
hope to continue execution after the exception is handled. In this case, your
exception is more like a method call – which is how you should set up
situations in Java in which you want resumption-like behavior. (That is,
don’t throw an exception; call a method that fixes the problem.)
Alternatively, place your try block inside a while loop that keeps
reentering the try block until the result is
satisfactory.
Historically, programmers using operating
systems that supported resumptive exception handling eventually ended up using
termination-like code and skipping resumption. So although resumption sounds
attractive at first, it seems it isn’t quite so useful in practice. The
dominant reason is probably the coupling that
results: your handler must often be aware of where the exception is thrown from
and contain non-generic code specific to the throwing location. This makes the
code difficult to write and maintain, especially for large systems where the
exception can be generated from many
points.
In Java, you’re required to inform
the client programmer, who calls your method, of the exceptions that might be
thrown from your method. This is civilized because the caller can know exactly
what code to write to catch all potential exceptions. Of course, if source code
is available, the client programmer could hunt through and look for throw
statements, but often a library doesn’t come with sources. To prevent this
from being a problem, Java provides syntax (and forces you to use that
syntax) to allow you to politely tell the client programmer what exceptions this
method throws, so the client programmer can handle them. This is the
exception specification and it’s part of the method declaration,
appearing after the argument list.
The exception specification uses an
additional keyword, throws, followed by a list of all the potential
exception types. So your method definition might look like
this:
void f() throws tooBig, tooSmall, divZero { //...
If you say
void f() { // ...
it means that no exceptions are thrown
from the method. (Except for the exceptions of type
RuntimeException, which can reasonably be thrown anywhere – this
will be described later.)
You can’t lie about an exception
specification – if your method causes exceptions and doesn’t handle
them, the compiler will detect this and tell you that you must either handle the
exception or indicate with an exception specification that it may be thrown from
your method. By enforcing exception specifications from top to bottom, Java
guarantees that exception correctness can be ensured at compile
time.[46]
There is one place you can lie: you can
claim to throw an exception that you don’t. The compiler takes your word
for it and forces the users of your method to treat it as if it really does
throw that exception. This has the beneficial effect of being a placeholder for
that exception, so you can actually start throwing the exception later without
requiring changes to existing
code.
It is possible to create a handler that
catches any type of exception. You do this by catching the base-class exception
type Exception (there are other types of base exceptions, but
Exception is the base that’s pertinent to virtually all programming
activities):
catch(Exception e) { System.out.println("caught an exception"); }
This will catch any exception, so if you
use it you’ll want to put it at the end of your list of handlers to
avoid pre-empting any exception handlers that might otherwise follow
it.
Since the Exception class is the
base of all the exception classes that are important to the programmer, you
don’t get much specific information about the exception, but you can call
the methods that come from its base type
Throwable:
String
getMessage( )
Gets the detail message.
String
toString( )
Returns a short description
of the Throwable, including the detail message if there is one.
void
printStackTrace( )
void printStackTrace(PrintStream)
Prints the Throwable and the
Throwable’s call stack trace. The call stack shows the sequence of method
calls that brought you to the point at which the exception was
thrown.
The first version prints to standard
error, the second prints to a stream of your choice. If you’re working
under Windows, you can’t redirect standard error so you might want to use
the second version and send the results to System.out; that way the
output can be redirected any way you want.
In addition, you get some other methods
from Throwable’s base type Object (everybody’s base
type). The one that might come in handy for exceptions is
getClass( ), which
returns an object representing the class of this object. You can in turn query
this Class object for its name with getName( ) or
toString( ). You can also do more sophisticated things with
Class objects that aren’t necessary in exception handling.
Class objects will be studied later in the book.
Here’s an example that shows the
use of the Exception methods:
//: c09: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.out.println("Caught Exception"); System.out.println( "e.getMessage(): " + e.getMessage()); System.out.println("e.toString(): " + e); System.out.println("e.printStackTrace():"); e.printStackTrace(); } } } ///:~
The output for this program
is:
Caught Exception e.getMessage(): Here's my Exception e.toString(): java.lang.Exception: Here's my Exception e.printStackTrace(): java.lang.Exception: Here's my Exception at ExceptionMethods.main
You can see that the methods provide
successively more information – each is effectively a superset of the
previous
one.
Sometimes you’ll want to rethrow
the exception that you just caught, particularly when you use Exception
to catch any exception. Since you already have the handle to the current
exception, you can simply re-throw that handle:
catch(Exception e) { System.out.println("An exception was thrown"); throw e; }
Rethrowing an exception causes the
exception to go to the exception handlers in the next-higher context. Any
further catch clauses for the same try block are still ignored. In
addition, everything about the exception object is preserved, so the handler at
the higher context that catches the specific exception type can extract all the
information from that object.
If you simply re-throw the current
exception, the information that you print about that exception in
printStackTrace( )
will pertain to the exception’s origin, not the place where you
re-throw it. If you want to install new stack trace information, you can do so
by calling
fillInStackTrace( ),
which returns an exception object that it creates by stuffing the current stack
information into the old exception object. Here’s what it looks
like:
//: c09:Rethrowing.java // Demonstrating fillInStackTrace() public class Rethrowing { public static void f() throws Exception { System.out.println( "originating the exception in f()"); throw new Exception("thrown from f()"); } public static void g() throws Throwable { try { f(); } catch(Exception e) { System.out.println( "Inside g(), e.printStackTrace()"); e.printStackTrace(); throw e; // 17 // throw e.fillInStackTrace(); // 18 } } public static void main(String[] args) throws Throwable { try { g(); } catch(Exception e) { System.out.println( "Caught in main, e.printStackTrace()"); e.printStackTrace(); } } } ///:~
The important line numbers are marked
inside of comments. With line 17 un-commented (as shown), the output
is:
originating the exception in f() Inside g(), e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:8) at Rethrowing.g(Rethrowing.java:12) at Rethrowing.main(Rethrowing.java:24) Caught in main, e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:8) at Rethrowing.g(Rethrowing.java:12) at Rethrowing.main(Rethrowing.java:24)
So the exception stack trace always
remembers its true point of origin, no matter how many times it gets
rethrown.
With line 17 commented and line 18
un-commented, fillInStackTrace( ) is used instead, and the result
is:
originating the exception in f() Inside g(), e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:8) at Rethrowing.g(Rethrowing.java:12) at Rethrowing.main(Rethrowing.java:24) Caught in main, e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.g(Rethrowing.java:18) at Rethrowing.main(Rethrowing.java:24)
The class Throwable must appear in
the exception specification for g( ) and main( ) because
fillInStackTrace( ) produces a handle to a Throwable object.
Since Throwable is a base class of
Exception, it’s possible to get an object that’s a
Throwable but not an Exception, so the handler for
Exception in main( ) might miss it. To make sure everything
is in order, the compiler forces an exception specification for
Throwable. For example, the exception in the following program is
not caught in main( ):
//: c09:ThrowOut.java public class ThrowOut { public static void main(String[] args) throws Throwable { try { throw new Throwable(); } catch(Exception e) { System.out.println("Caught in main()"); } } } ///:~
It’s also possible to rethrow a
different exception from the one you caught. If you do this, you get a similar
effect as when you use fillInStackTrace( ): the information about
the original site of the exception is lost, and what you’re left with is
the information pertaining to the new throw:
//: c09:RethrowNew.java // Rethrow a different object from the one that // was caught public class RethrowNew { public static void f() throws Exception { System.out.println( "originating the exception in f()"); throw new Exception("thrown from f()"); } public static void main(String[] args) { try { f(); } catch(Exception e) { System.out.println( "Caught in main, e.printStackTrace()"); e.printStackTrace(); throw new NullPointerException("from main"); } } } ///:~
The output is:
originating the exception in f() Caught in main, e.printStackTrace() java.lang.Exception: thrown from f() at RethrowNew.f(RethrowNew.java:8) at RethrowNew.main(RethrowNew.java:13) java.lang.NullPointerException: from main at RethrowNew.main(RethrowNew.java:18)
The final exception knows only that it
came from main( ), and not from f( ). Note that
Throwable isn’t necessary in any of the exception
specifications.
You never have to worry about cleaning up
the previous exception, or any exceptions for that matter. They’re all
heap-based objects created with new, so the garbage collector
automatically cleans them all
up.
Java contains a class called
Throwable that 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.
The best way to get an overview of the
exceptions is to browse online Java documentation from
http://java.sun.com. (Of course, it’s easier to download it first.)
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.
java.lang.Exception
This is the basic exception class your
program can catch. Other exceptions are derived from this. 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 IO exceptions
are inherited from
java.io.IOException.
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 handle that is passed into a method
(since you can’t know if the caller has passed you a valid handle).
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 handle,
Java will automatically throw a
NullPointerException. So
the above bit of code is always superfluous.
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.
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:
//: c09: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.
The output is:
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.
Keep in mind that it’s possible to
ignore only RuntimeExceptions in your coding, since all other handling is
carefully enforced by the compiler. The reasoning is that a
RuntimeException represents a programming error:
You can see what a tremendous
benefit it is to have exceptions in this case, since they help in the debugging
process.
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.
You’re not stuck using the 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 hierarchy was
created.
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. Inheriting an exception is
quite simple:
//: c09:Inheriting.java // Inheriting your own exceptions class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg); } } public class Inheriting { 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(); } try { g(); } catch(MyException e) { e.printStackTrace(); } } } ///:~
The inheritance occurs in the creation of
the new class:
class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg); } }
The key phrase here is extends
Exception, which says “it’s everything an Exception is
and more.” The added code is small – the addition of two
constructors that define the way MyException is created. Remember that
the compiler automatically calls the base-class default constructor if you
don’t explicitly call a base-class constructor, as in the
MyException( ) default constructor. In the second constructor, the
base-class constructor with a String argument is explicitly invoked by
using the super keyword.
The output of the program
is:
Throwing MyException from f() MyException at Inheriting.f(Inheriting.java:16) at Inheriting.main(Inheriting.java:24) Throwing MyException from g() MyException: Originated in g() at Inheriting.g(Inheriting.java:20) at Inheriting.main(Inheriting.java:29)
You can see the absence of the detail
message in the MyException thrown from f( ).
The process of creating your own
exceptions can be taken further. You can add extra constructors and
members:
//: c09:Inheriting2.java // Inheriting your own exceptions 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 Inheriting2 { 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(); } try { g(); } catch(MyException2 e) { e.printStackTrace(); } try { h(); } catch(MyException2 e) { e.printStackTrace(); System.out.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:
Throwing MyException2 from f() MyException2 at Inheriting2.f(Inheriting2.java:22) at Inheriting2.main(Inheriting2.java:34) Throwing MyException2 from g() MyException2: Originated in g() at Inheriting2.g(Inheriting2.java:26) at Inheriting2.main(Inheriting2.java:39) Throwing MyException2 from h() MyException2: Originated in h() at Inheriting2.h(Inheriting2.java:30) at Inheriting2.main(Inheriting2.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.) If this is the case, it’s possible
to create a new exception type with almost no code at all:
//: c09:SimpleException.java class SimpleException extends Exception { } ///:~
This relies on the compiler to create the
default constructor (which automatically 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.
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.
This example demonstrates the kinds of
restrictions imposed (at compile time) for exceptions:
//: c09: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) { } catch(RainedOut e) { } catch(BaseballException e) {} // 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) { } catch(Foul e) { } catch(RainedOut e) { } catch(BaseballException e) {} } } ///:~
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 you might add in overridden versions of
event( ). The same idea holds for abstract methods, as seen
in atBat( ).
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.
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.
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.
The overridden event( )
method shows that a derived-class version of a method may choose to not 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.
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.[47]
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, 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, and this is quite different from inheriting the methods (that is, 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.
There’s often some piece of code
that you want to execute whether or not an exception occurs in 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[48] at the
end of all the exception handlers. The full picture of an exception-handling
section is thus:
try
{
// The guarded
region:
// Dangerous stuff that might throw A,
B, or C
} catch (A a1)
{
// Handle A
}
catch (B b1) {
// Handle
B
} catch (C c1)
{
// Handle C
}
finally {
// Stuff that happens every
time
}
To demonstrate that the finally
clause always runs, try this program:
//: c09:FinallyWorks.java // The finally clause is always executed 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 Exception(); System.out.println("No exception"); } catch(Exception e) { System.out.println("Exception thrown"); } finally { System.out.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.
The output is:
Exception thrown in finally clause No exception in finally clause
In a language without garbage collection
and without automatic destructor
calls,[49]
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?
finally is necessary when you need
to set something other than memory back to its original state. This is
usually something 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:
//: c09:OnOffSwitch.java // Why use finally? class Switch { boolean state = false; boolean read() { return state; } void on() { state = true; } void off() { state = false; } } public class OnOffSwitch { static Switch sw = new Switch(); public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... sw.off(); } catch(NullPointerException e) { System.out.println("NullPointerException"); sw.off(); } catch(IllegalArgumentException e) { System.out.println("IOException"); 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 closure code from a try block in just one place:
//: c09:WithFinally.java // Finally Guarantees cleanup class Switch2 { boolean state = false; boolean read() { return state; } void on() { state = true; } void off() { state = false; } } public class WithFinally { static Switch2 sw = new Switch2(); public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... } catch(NullPointerException e) { System.out.println("NullPointerException"); } catch(IllegalArgumentException e) { System.out.println("IOException"); } finally { sw.off(); } } } ///:~
Here the sw.off( ) has been
moved to just one place, where it’s guaranteed to run no matter what
happens.
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:
//: c09:AlwaysFinally.java // Finally is always executed class Ex 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 Ex(); } finally { System.out.println( "finally in 2nd try block"); } } catch(Ex e) { System.out.println( "Caught Ex in first try block"); } finally { System.out.println( "finally in 1st try block"); } } } ///:~
The output for this program shows you
what happens:
Entering first try block Entering second try block finally in 2nd try block Caught Ex in first 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.
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:
//: c09: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:
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 the problem. (The above results were produced with Java
1.1.)
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.
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 and 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.
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 IO library that will be discussed in Chapter 10, but which are
simple enough that you probably won’t have any trouble understanding their
basic use:
//: c09: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.out.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.out.println( "in.close() unsuccessful"); } throw e; } finally { // Don't close it here!!! } } String getLine() { String s; try { s = in.readLine(); } catch(IOException e) { System.out.println( "readLine() unsuccessful"); s = "failed"; } return s; } void cleanup() { try { in.close(); } catch(IOException e2) { System.out.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.out.println( "Caught in main, e.printStackTrace()"); e.printStackTrace(); } } } ///:~
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.
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 throws an exception that 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 re-thrown, 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.
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.
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.
The cleanup( ) method must be
called by the user when they are finished using the InputFile object to
release the system resources (such as file handles) that are used by the
BufferedReader and/or FileReader
objects.[50] 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( ).
In Cleanup.java an
InputFile is created to open the same source file that creates the
program, and this 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.
One of the benefits of this example is to
show you why exceptions are introduced at this point in the book. 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.
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.
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:
//: c09: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.out.println("Caught Sneeze"); } catch(Annoyance a) { System.out.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:
try { throw new Sneeze(); } catch(Annoyance a) { System.out.println("Caught Annoyance"); }
The remaining catch clause will still
work because it’s catching the base class of Sneeze. Put another
way, catch(Annoyance e) will catch a Annoyance or any class
derived from it. This is useful because if you decide to add more exceptions
to a method, if they’re all inherited from the same base class then the
client programmer’s code will not need changing, assuming they catch the
base class, at the very least.
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.out.println("Caught Annoyance"); } catch(Sneeze s) { System.out.println("Caught Sneeze"); }
the compiler will give you an error
message, since it sees that the Sneeze catch-clause can never be
reached.
Use exceptions to:
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, and it’s especially important in Java, in
which one of the primary goals is to create program components for others to
use. To create a robust system, each component must be robust.
The goals for exception handling in Java
are to simplify the creation of large, reliable programs using less code than
currently possible, with more confidence that your application doesn’t
have an unhandled error.
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
designer and client
programmer.
[45]
The C programmer can look up the return value of printf( ) for an
example of this.
[46]
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.
[47]
ANSI/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.
[48]
C++ exception handling does not have the finally clause because it relies
on destructors to accomplish this sort of cleanup.
[49]
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).
[50]
In C++, a destructor would handle this for you.