The throw statement informs the compiler that a method throws one or more exception.
Show
10: Error Handling with ExceptionsThe basic philosophy of Java is that “badly formed code
will not be run.” The ideal time to catch an error is at compile-time, before you even try to run the program. However, not all errors can be detected at compile-time. The rest of the problems must be handled at run-time, through some formality that allows the originator of the error to pass appropriate information to a recipient who will know how to handle the difficulty properly. Comment In C and other earlier languages, there could be several of these formalities, and they were generally established by convention and not as part of the programming language. Typically, you returned a special value or set a flag, and the recipient was supposed to look at the value or the flag and determine that something was amiss. However, as the years passed, it was discovered that programmers who use
a library tend to think of themselves as invincible—as in, “Yes, errors might happen to others, but not in my code.” So, not too surprisingly, they wouldn’t check for the error conditions (and sometimes the error conditions were too silly to check for[53]). 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. Comment The solution is to take the casual nature out of error handling and to enforce formality. This actually has a long
history, since implementations of exception handling go back to operating systems in the 1960s, and even to BASIC’s “on error goto.” But C++ exception handling was based on Ada, and Java’s is based primarily on C++ (although it looks even more like Object Pascal). Comment The word “exception” is meant in the sense of “I take exception to
that.” At the point where the problem occurs you might not know what to do with it, but you do know that you can’t just continue on merrily; you must stop and somebody, somewhere, must figure out what to do. But you don’t have enough information in the current context to fix the problem. So you hand the problem out to a higher context where someone is qualified to make the proper decision (much like a chain of command).
Comment The other rather significant benefit of exceptions is that they clean up error handling code. Instead of checking for a particular error and dealing with it at multiple places in your program, you no longer need to check at the point of the method call (since the exception will guarantee that someone catches it). And, you need to handle the
problem in only one place, the so-called exception handler. This saves you code, and it separates the code that describes what you want to do from the code that is executed when things go awry. In general, reading, writing, and debugging code becomes much clearer with exceptions than when using the old way of error handling.
Comment Because exception handling is enforced by the Java compiler, there are only so many examples that can be written in this book without learning about exception handling. This chapter introduces you to the code you need to write to properly handle exceptions, and the way you can generate your own exceptions if one of your methods gets into trouble.
Comment Basic exceptionsAn 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.
Comment A simple example is a divide. If you’re about to divide by zero, it’s worth checking to make sure you don’t go ahead and perform the divide. But what does it mean that the denominator is zero? Maybe you know, in the context of the problem you’re trying to solve in that particular method, how to deal with a zero denominator. But if it’s an
unexpected value, you can’t deal with it and so must throw an exception rather than continuing along that path. Comment When you throw an exception, several things happen. First, the exception object is created in the same way that any Java object is created: on the heap, with new. Then the current path of execution (the
one you couldn’t continue) is stopped and the reference for the exception object is ejected from the current context. At this point the exception handling mechanism takes over and begins to look for an appropriate place to continue executing the program. This appropriate place is the exception handler, whose job is to recover from the problem so the program can either try another tack or just continue.
Comment As a simple example of throwing an exception, consider an object reference called t. It’s possible that you might be passed a reference that hasn’t been initialized, so you might want to check before trying to call a method using that object reference. You can send information about the error into a larger context by creating an object
representing your information and “throwing” it out of your current context. This is called throwing an exception. Here’s what it looks like: if(t == null) throw new NullPointerException(); This throws the exception, which allows you—in the current context—to abdicate responsibility for thinking about the issue further. It’s just magically handled somewhere else. Precisely where will be shown shortly.
Comment Exception argumentsLike any object in Java, you always create exceptions on the heap using new, which allocates storage and calls a constructor. There are two constructors in all standard
exceptions: the first is the default constructor, and the second takes a string argument so you can place pertinent information in the exception: if(t == null) throw new NullPointerException("t = null"); This string can later be extracted using various methods, as you’ll see. Comment The keyword throw causes a number
of relatively magical things to happen. Typically, you’ll first use new to createan object that represents the error condition. You give the resulting reference to throw. The object is, in effect, “returned” from the method, even though that object type isn’t normally what the method is designed to return. A simplistic way to think about exception handling is as an alternate return mechanism, although you get into trouble if you take that analogy too far. You can also exit
from ordinary scopes by throwing an exception. But a value is returned, and the method or scope exits. Comment Any similarity to an ordinary return from a method ends here, because where you return is someplace completely different from where you return for a normal method call. (You end up in an appropriate exception handler that might be miles
away—many levels lower on the call stack—from where the exception was thrown.) Comment In addition, you can throw any type of Throwable object that you want. Typically, you’ll throw a different class of exception for each different type of error. The information about the error is represented both inside the exception object and implicitly in the
type of exception object chosen, so someone in the bigger context can figure out what to do with your exception. (Often, the only information is the type of exception object, and nothing meaningful is stored within the exception object.) Comment Catching an exceptionIf 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. Comment To see how an exception is caught, you must first understand the concept of a guarded region, which is a section of code that might produce exceptions, and which is followed by the code to handle those exceptions.
Comment The try blockIf you’re inside a method and you throw an exception (or another method you call within this method throws an exception), that method will exit in the process of throwing. If you don’t want a throw to
exit the method, you can set up a special block within that method to capture the exception. This is called the try block because you “try” your various method calls there. The try block is an ordinary scope, preceded by the keyword try: Comment 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.
Comment Exception handlersOf 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. Comment The handlers must appear directly after the try block. If an exception is thrown, the exception handling mechanism goes hunting for the first handler with an argument that matches the type of the exception. Then it enters that catch clause, and the exception is
considered handled. The search for handlers stops once the catch clause is finished. Only the matching catch clause executes; it’s not like a switch statement in which you need a break after each case to prevent the remaining ones from executing. Comment Note that, within the try block, a number of different method calls might
generate the same exception, but you need only one handler. Comment Termination vs. resumptionThere are two basic models in exception handling theory. In termination (which is what Java and C++ support), you assume the error is so
critical that there’s no way to get back to where the exception occurred. Whoever threw the exception decided that there was no way to salvage the situation, and they don’t want to come back. Comment The alternative is called resumption. It means that the exception handler is expected to do something to rectify the situation, and then the
faulting method is retried, presuming success the second time. If you want resumption, it means you still hope to continue execution after the exception is handled. In this case, your exception is more like a method call—which is how you should set up situations in Java in which you want resumption-like behavior. (That is, don’t throw an exception; call a method that fixes the problem.) Alternatively, place your try block inside a while loop that keeps reentering the try
block until the result is satisfactory. Comment Historically, programmers using operating systems that supported resumptive exception handling eventually ended up using termination-like code and skipping resumption. So although resumption sounds attractive at first, it isn’t quite so useful in practice. The dominant reason is probably the
coupling that results: your handler must often be aware of where the exception is thrown from and contain nongeneric code specific to the throwing location. This makes the code difficult to write and maintain, especially for large systems where the exception can be generated from many points. Comment Creating your own exceptionsYou’re not stuck using the existing Java exceptions. This is important because you’ll often need to create your own exceptions to denote a special error that your library is capable of creating, but which was not foreseen when the Java exception hierarchy was created.
Comment To create your own exception class, you’re forced to inherit from an existing type of exception, preferably one that is close in meaning to your new exception (this is often not possible, however). The most trivial way to create a new type of exception is just to let the compiler create the default constructor for you, so
it requires almost no code at all: //: c10:SimpleExceptionDemo.java // Inheriting your own exceptions. import com.bruceeckel.simpletest.*; class SimpleException extends Exception {} public class SimpleExceptionDemo { static Test monitor = new Test(); public void f() throws SimpleException { System.out.println( "Throwing SimpleException from f()"); throw new SimpleException (); } public static void main(String[] args) { SimpleExceptionDemo sed = new SimpleExceptionDemo(); try { sed.f(); } catch(SimpleException e) { System.err.println("Caught it!"); } monitor.expect(new String[] { "Throwing SimpleException from f()", "Caught it!" }); } } ///:~ When the compiler creates the default constructor, it which automatically (and invisibly) calls the base-class default constructor. Of course, in this case you don’t get a SimpleException(String) constructor, but in practice that isn’t used much. As you’ll see, the most important thing about an exception is the class name, so most of the time an exception like the one shown above is
satisfactory. Comment Here, the result is printed to the console standard error stream by writing to System.err. This is usually a better place to send error information than System.out, which may be redirected. If you send output to System.err it will not be redirected along
with System.out so the user is more likely to notice it. Comment Creating an exception class that also has a constructor that takes a String is also quite simple: //: c10:FullConstructors.java // Inheriting your own exceptions. import com.bruceeckel.simpletest.*; class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg); } } public class FullConstructors { static Test monitor = new Test(); public static void f() throws MyException { System.out.println( "Throwing MyException from f()"); throw new MyException(); } public static void g() throws MyException { System.out.println( "Throwing MyException from g()"); throw new MyException("Originated in g()"); } public static void main(String[] args) { try { f(); } catch(MyException e) { e.printStackTrace(System.err); } try { g(); } catch(MyException e) { e.printStackTrace(System.err); } monitor.expect(new String[] { "Throwing MyException from f()", "MyException", "\tat FullConstructors.f(Unknown Source)", "\tat FullConstructors.main(Unknown Source)", "Throwing MyException from g()", "MyException: Originated in g()", "\tat FullConstructors.g(Unknown Source)", "\tat FullConstructors.main(Unknown Source)" }); } } ///:~ The added code is small—the addition of two constructors that define the way MyException is created. In the
second constructor, the base-class constructor with a String argument is explicitly invoked by using the super keyword. Comment The stack trace information is sent to System.err so that it’s more likely it will be noticed in the event that System.out has been redirected.
Comment The output of the program is: Throwing MyException from f() MyException at FullConstructors.f(FullConstructors.java:16) at FullConstructors.main(FullConstructors.java:24) Throwing MyException from g() MyException: Originated in g() at FullConstructors.g(FullConstructors.java:20) at FullConstructors.main(FullConstructors.java:29) You can see the absence of the detail message in the MyException thrown from f( ). Comment The process of creating your own exceptions can be taken further. You can add extra constructors and members: //: c10:ExtraFeatures.java // Further embellishment of exception classes. import com.bruceeckel.simpletest.*; class MyException2 extends Exception { public MyException2() {} public MyException2(String msg) { super(msg); } public MyException2(String msg, int x) { super(msg); i = x; } public int val() { return i; } private int i; } public class ExtraFeatures { static Test monitor = new Test(); public static void f() throws MyException2 { System.out.println( "Throwing MyException2 from f()"); throw new MyException2(); } public static void g() throws MyException2 { System.out.println( "Throwing MyException2 from g()"); throw new MyException2("Originated in g()"); } public static void h() throws MyException2 { System.out.println( "Throwing MyException2 from h()"); throw new MyException2( "Originated in h()", 47); } public static void main(String[] args) { try { f(); } catch(MyException2 e) { e.printStackTrace(System.err); } try { g(); } catch(MyException2 e) { e.printStackTrace(System.err); } try { h(); } catch(MyException2 e) { e.printStackTrace(System.err); System.err.println("e.val() = " + e.val()); } monitor.expect(new String[] { "Throwing MyException2 from f()", "MyException2", "\tat ExtraFeatures.f(Unknown Source)", "\tat ExtraFeatures.main(Unknown Source)", "Throwing MyException2 from g()", "MyException2: Originated in g()", "\tat ExtraFeatures.g(Unknown Source)", "\tat ExtraFeatures.main(Unknown Source)", "Throwing MyException2 from h()", "MyException2: Originated in h()", "\tat ExtraFeatures.h(Unknown Source)", "\tat ExtraFeatures.main(Unknown Source)", "e.val() = 47" }); } } ///:~ 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: Comment Throwing MyException2 from f() MyException2 at ExtraFeatures.f(ExtraFeatures.java:22) at ExtraFeatures.main(ExtraFeatures.java:34) Throwing MyException2 from g() MyException2: Originated in g() at ExtraFeatures.g(ExtraFeatures.java:26) at ExtraFeatures.main(ExtraFeatures.java:39) Throwing MyException2 from h() MyException2: Originated in h() at ExtraFeatures.h(ExtraFeatures.java:30) at ExtraFeatures.main(ExtraFeatures.java:44) e.val() = 47 Since an exception is just another kind of object, you can continue this process of embellishing the power of your exception classes. Keep in mind, however, that all this dressing-up might be lost on the client programmers using your packages, since they might simply look for the exception to be thrown and nothing more. (That’s the way most of the Java library exceptions are used.)
Comment The exception specificationIn 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. Comment The exception specification uses an additional keyword, throws, followed by a list of all the potential exception types. So your method definition might look like this: void f() throws TooBig, TooSmall, DivZero { //... If you say void f() { // ... it means that no exceptions are thrown from the method. (Except for the exceptions of type RuntimeException, which can reasonably be thrown anywhere—this will be described later.) Comment You can’t lie about an exception specification—if your method
causes exceptions and doesn’t handle them, the compiler will detect this and tell you that you must either handle the exception or indicate with an exception specification that it may be thrown from your method. By enforcing exception specifications from top to bottom, Java guarantees that exception correctness can be ensured at compile-time[54].
Comment There is one place you can lie: you can claim to throw an exception that you really don’t. The compiler takes your word for it, and forces the users of your method to treat it as if it really does throw that exception. This has the beneficial effect of being a placeholder for that exception, so you can actually start throwing the exception later
without requiring changes to existing code. It’s also important for creating abstract base classes and interfaces whose derived classes or implementations may need to throw exceptions. Comment Catching any exceptionIt is possible to create a handler that catches any type of exception. You do this by catching the base-class exception type Exception (there are other types of base exceptions, but Exception is the base that’s pertinent to virtually all programming activities): catch(Exception e) { System.err.println("Caught an exception"); } This will catch any exception, so if you use it you’ll want to put it at the end of your list of handlers
to avoid preempting any exception handlers that might otherwise follow it. Comment Since the Exception class is the base of all the exception classes that are important to the programmer, you don’t get much specific information about the exception, but you can call the methods that come from its base type
Throwable: String getMessage( ) String toString( ) void printStackTrace( ) Throwable fillInStackTrace() In addition, you get some other methods from Throwable’s base type Object (everybody’s base type). The one that might come in handy for exceptions is getClass( ), which returns an object representing the class of this object.
You can in turn query this Class object for its name with getName( ) or toString( ). You can also do more sophisticated things with Class objects that aren’t necessary in exception handling. Class objects will be studied later in this book. Comment Here’s an example that shows the use of the basic
Exception methods: //: c10:ExceptionMethods.java // Demonstrating the Exception Methods. import com.bruceeckel.simpletest.*; public class ExceptionMethods { static Test monitor = new Test(); public static void main(String[] args) { try { throw new Exception("Here's my Exception"); } catch(Exception e) { System.err.println("Caught Exception"); System.err.println( "e.getMessage(): " + e.getMessage()); System.err.println( "e.getLocalizedMessage(): " + e.getLocalizedMessage()); System.err.println("e.toString(): " + e); System.err.println("e.printStackTrace():"); e.printStackTrace(System.err); } monitor.expect(new String[] { "Caught Exception", "e.getMessage(): Here's my Exception", "e.getLocalizedMessage(): Here's my Exception", "e.toString(): java.lang.Exception: Here's my " + "Exception", "e.printStackTrace():", "java.lang.Exception: Here's my Exception", "\tat ExceptionMethods.main(Unknown Source)" }); } } ///:~ The output for this program is: Caught Exception e.getMessage(): Here's my Exception e.getLocalizedMessage(): Here's my Exception e.toString(): java.lang.Exception: Here's my Exception e.printStackTrace(): java.lang.Exception: Here's my Exception at ExceptionMethods.main(ExceptionMethods.java:7) java.lang.Exception: Here's my Exception at ExceptionMethods.main(ExceptionMethods.java:7) You can see that the methods provide successively more information—each is effectively a superset of the previous one. Comment Rethrowing an exceptionSometimes you’ll want to rethrow the exception that you just caught, particularly when you use Exception to catch any exception. Since you already have the reference to the current exception, you can simply rethrow that reference: catch(Exception e) { System.err.println("An exception was thrown"); throw e; } Rethrowing
an exception causes the exception to go to the exception handlers in the next-higher context. Any further catch clauses for the same try block are still ignored. In addition, everything about the exception object is preserved, so the handler at the higher context that catches the specific exception type can extract all the information from that object.
Comment If you simply rethrow the current exception, the information that you print about that exception in printStackTrace( ) will pertain to the exception’s origin, not the place where you rethrow it. If you want to install new stack trace information, you can do so by calling
fillInStackTrace( ), which returns an exception object that it creates by stuffing the current stack information into the old exception object. Here’s what it looks like: Comment //: c10:Rethrowing.java // Demonstrating fillInStackTrace() public class Rethrowing { public static void f() throws Exception { System.out.println( "originating the exception in f()"); throw new Exception("thrown from f()"); } public static void g() throws Throwable { try { f(); } catch(Exception e) { System.err.println( "Inside g(), e.printStackTrace()"); e.printStackTrace(System.err); throw e; // 17 // throw e.fillInStackTrace(); // 18 } } public static void main(String[] args) throws Throwable { try { g(); } catch(Exception e) { System.err.println( "Caught in main, e.printStackTrace()"); e.printStackTrace(System.err); } } } ///:~ The important line numbers are marked as comments. With line 17
uncommented (as shown), the output is: originating the exception in f() Inside g(), e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:8) at Rethrowing.g(Rethrowing.java:12) at Rethrowing.main(Rethrowing.java:24) Caught in main, e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:8) at Rethrowing.g(Rethrowing.java:12) at Rethrowing.main(Rethrowing.java:24) So the exception stack trace always remembers its true point of origin, no matter how many times it gets rethrown. Comment With line 17 commented and line 18 uncommented, fillInStackTrace( ) is used instead, and the result is: originating the exception in f() Inside g(), e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:8) at Rethrowing.g(Rethrowing.java:12) at Rethrowing.main(Rethrowing.java:24) Caught in main, e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.g(Rethrowing.java:18) at Rethrowing.main(Rethrowing.java:24) Because of fillInStackTrace( ), line 18 becomes the new point of origin of the exception. Comment The class Throwable must appear in the exception specification for g( ) and main( ) because fillInStackTrace( ) produces
a reference to a Throwable object. Since Throwable is a base class of Exception, it’s possible to get an object that’s a Throwable but not an Exception, so the handler for Exception in main( ) might miss it. To make sure everything is in order, the compiler forces an exception specification for Throwable. For example, the exception in the following program is not caught in main( ):
Comment //: c10:ThrowOut.java public class ThrowOut { public static void main(String[] args) throws Throwable { try { throw new Throwable(); } catch(Exception e) { System.err.println("Caught in main()"); } } } ///:~ It’s also possible to rethrow a different exception from the one you caught. If you do this, you get a similar effect as when you use fillInStackTrace( )—the information about the original site of the exception is lost, and what you’re left with is the information pertaining to
the new throw: Comment //: c10:RethrowNew.java // Rethrow a different object // from the one that was caught. // {ThrowsException} class OneException extends Exception { public OneException(String s) { super(s); } } class TwoException extends Exception { public TwoException(String s) { super(s); } } public class RethrowNew { public static void f() throws OneException { System.out.println( "originating the exception in f()"); throw new OneException("thrown from f()"); } public static void main(String[] args) throws TwoException { try { f(); } catch(OneException e) { System.err.println( "Caught in main, e.printStackTrace()"); e.printStackTrace(System.err); throw new TwoException("from main()"); } } } ///:~ The output is:
The final exception knows only that it came from main( ), and not from f( ).
Comment You never have to worry about cleaning up the previous exception, or any exceptions for that matter. They’re all heap-based objects created with new, so the garbage collector automatically cleans them all up.
Comment Standard Java exceptionsThe Java class Throwable describes anything that can be thrown as an exception. There are two general types of Throwable objects (“types of” = “inherited from”). Error represents
compile-time and system errors that you don’t worry about catching (except in special cases). Exception is the basic type that can be thrown from any of the standard Java library class methods and from your methods and run-time accidents. So the Java programmer’s base type of interest is Exception. Comment The best
way to get an overview of the exceptions is to browse the HTML Java documentation that you can download from java.sun.com. It’s worth doing this once just to get a feel for the various exceptions, but you’ll soon see that there isn’t anything special between one exception and the next except for the name. Also, the number of exceptions in Java keeps expanding; basically it’s pointless to print them in a book. Any new library you get from a third-party vendor will probably have its own
exceptions as well. The important thing to understand is the concept and what you should do with the exceptions. Comment The basic idea is that the name of the exception represents the problem that occurred, and the exception name is intended to be relatively self-explanatory. The exceptions are not all defined in java.lang; some are created to
support other libraries such as util, net, and io, which you can see from their full class names or what they are inherited from. For example, all I/O exceptions are inherited from java.io.IOException. Comment The special case of RuntimeExceptionThe first example in this chapter was if(t == null) throw new NullPointerException(); It can be a bit horrifying to think that you must check for null on every reference that is passed into a method (since you can’t know if the caller has passed you a valid reference). Fortunately, you don’t—this is part of the standard run-time checking that Java performs for you, and if any call is made to a null reference, Java will automatically
throw a NullPointerException. So the above bit of code is always superfluous. Comment There’s a whole group of exception types that are in this category. They’re always thrown automatically by Java and you don’t need to include them in your exception specifications. Conveniently enough, they’re all
grouped together by putting them under a single base class called RuntimeException, which is a perfect example of inheritance: it establishes a family of types that have some characteristics and behaviors in common. Also, you never need to write an exception specification saying that a method might throw a RuntimeException, since that’s just assumed. Because they indicate bugs, you virtually never catch a RuntimeException—it’s
dealt with automatically. If you were forced to check for RuntimeExceptions your code could get messy. Even though you don’t typically catch RuntimeExceptions,in your own packages you might choose to throw some of the RuntimeExceptions. Comment What happens when you don’t catch such exceptions? Since the compiler doesn’t
enforce exception specifications for these, it’s quite plausible that a RuntimeException could percolate all the way out to your main( ) method without being caught. To see what happens in this case, try the following example: //: c10:NeverCaught.java // Ignoring RuntimeExceptions. // {ThrowsException} 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.
Comment The output is:
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.
Comment Keep in mind that you can only ignore RuntimeExceptions in your coding, since all other handling is carefully enforced by the compiler. The reasoning is that a RuntimeException represents a programming error:
You
can see what a tremendous benefit it is to have exceptions in this case, since they help in the debugging process. Comment It’s interesting to notice that you cannot classify Java exception handling as a single-purpose tool. Yes, it is designed to handle those pesky run-time errors that will occur because of forces outside your code’s control, but it’s
also essential for certain types of programming bugs that the compiler cannot detect. Comment Performing cleanup with finallyThere’s often some piece of code that you want to execute whether or not an exception is thrown within a try block. This usually pertains to some operation
other than memory recovery (since that’s taken care of by the garbage collector). To achieve this effect, you use a finally clause[55] at the end of all the exception handlers. The full picture of an exception handling section is thus: try { // The guarded region: Dangerous activities // that might throw A, B, or C } catch(A a1) { // Handler for situation A } catch(B b1) { // Handler for situation B } catch(C c1) { // Handler for situation C } finally { // Activities that happen every time } To demonstrate that the
finally clause always runs, try this program: Comment //: c10:FinallyWorks.java // The finally clause is always executed. import com.bruceeckel.simpletest.*; class ThreeException extends Exception {} public class FinallyWorks { static Test monitor = new Test(); static int count = 0; public static void main(String[] args) { while(true) { try { // Post-increment is zero first time: if(count++ == 0) throw new ThreeException(); System.out.println("No exception"); } catch(ThreeException e) { System.err.println("ThreeException"); } finally { System.err.println("In finally clause"); if(count == 2) break; // out of "while" } } monitor.expect(new String[] { "ThreeException", "In finally clause", "No exception", "In finally clause" }); } } ///:~ 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. Comment The output is: ThreeException In finally clause No exception In finally clause Whether an exception is thrown or not, the finally clause is always executed. Comment What’s finally for?In a language without garbage collection and without automatic destructor
calls[56], 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? Comment finally is necessary when you need to set something other than memory back to its original state. This is some kind of cleanup like an open file or network connection, something you’ve drawn on the screen, or even a switch in the outside world, as modeled in the following
example: //: c10:OnOffSwitch.java // Why use finally? class Switch { boolean state = false; boolean read() { return state; } void on() { state = true; } void off() { state = false; } } class OnOffException1 extends Exception {} class OnOffException2 extends Exception {} public class OnOffSwitch { static Switch sw = new Switch(); static void f() throws OnOffException1, OnOffException2 {} public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... f(); sw.off(); } catch(OnOffException1 e) { System.err.println("OnOffException1"); sw.off(); } catch(OnOffException2 e) { System.err.println("OnOffException2"); sw.off(); } } } ///:~ The goal here is to make sure that the switch is off when main( ) is completed, so sw.off( ) is placed at the end of the try block and at the end of each exception handler. But it’s possible that an exception could be thrown that isn’t caught here, so sw.off( ) would be missed. However, with finally you can place the cleanup code from a try block in just one place:
Comment //: c10:WithFinally.java // Finally Guarantees cleanup. public class WithFinally { static Switch sw = new Switch(); public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... OnOffSwitch.f(); } catch(OnOffException1 e) { System.err.println("OnOffException1"); } catch(OnOffException2 e) { System.err.println("OnOffException2"); } finally { sw.off(); } } } ///:~ Here the sw.off( ) has been moved to just one place, where it’s guaranteed to run no matter what happens. Comment Even in cases in which
the exception is not caught in the current set of catch clauses, finally will be executed before the exception handling mechanism continues its search for a handler at the next higher level: //: c10:AlwaysFinally.java // Finally is always executed. import com.bruceeckel.simpletest.*; class FourException extends Exception {} public class AlwaysFinally { static Test monitor = new Test(); public static void main(String[] args) { System.out.println( "Entering first try block"); try { System.out.println( "Entering second try block"); try { throw new FourException(); } finally { System.out.println( "finally in 2nd try block"); } } catch(FourException e) { System.err.println( "Caught FourException in 1st try block"); } finally { System.err.println( "finally in 1st try block"); } monitor.expect(new String[] { "Entering first try block", "Entering second try block", "finally in 2nd try block", "Caught FourException in 1st try block", "finally in 1st try block" }); } } ///:~ The output for this program shows you what happens: Comment Entering first try block Entering second try block finally in 2nd try block Caught FourException in 1st try block finally in 1st try block The finally statement will also be executed in situations in which break and continue statements are involved. Note that, along with the labeled break and labeled continue, finally eliminates the need for a goto statement in Java. Comment Pitfall: the lost exceptionIn 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:
Comment //: c10:LostMessage.java // How an exception can be lost. // {ThrowsException} 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:
You can see that there’s no evidence of the VeryImportantException, which is simply replaced by the HoHumException in the finally clause. This is a rather serious pitfall, since it means that an
exception can be completely lost, and in a far more subtle and difficult-to-detect fashion than the example above. In contrast, C++ treats the situation in which a second exception is thrown before the first one is handled as a dire programming error. Perhaps a future version of Java will repair this problem (on the other hand, you will typically wrap any method that throws an exception, such as dispose( ), inside a try-catch clause).
Comment Exception restrictionsWhen 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. Comment This example demonstrates the kinds of restrictions imposed (at compile-time) for exceptions: //: c10:StormyInning.java // Overridden methods may throw only the // exceptions specified in their base-class // versions, or exceptions derived from the // base-class exceptions. class BaseballException extends Exception {} class Foul extends BaseballException {} class Strike extends BaseballException {} abstract class Inning { Inning() throws BaseballException {} void event() throws BaseballException { // Doesn't actually have to throw anything } abstract void atBat() throws Strike, Foul; void walk() {} // Throws nothing } class StormException extends Exception {} class RainedOut extends StormException {} class PopFoul extends Foul {} interface Storm { void event() throws RainedOut; void rainHard() throws RainedOut; } public class StormyInning extends Inning implements Storm { // OK to add new exceptions for // constructors, but you must deal // with the base constructor exceptions: StormyInning() throws RainedOut, BaseballException {} StormyInning(String s) throws Foul, BaseballException {} // Regular methods must conform to base class: //! void walk() throws PopFoul {} //Compile error // Interface CANNOT add exceptions to existing // methods from the base class: //! public void event() throws RainedOut {} // If the method doesn't already exist in the // base class, the exception is OK: public void rainHard() throws RainedOut {} // You can choose to not throw any exceptions, // even if base version does: public void event() {} // Overridden methods can throw // inherited exceptions: void atBat() throws PopFoul {} public static void main(String[] args) { try { StormyInning si = new StormyInning(); si.atBat(); } catch(PopFoul e) { System.err.println("Pop foul"); } catch(RainedOut e) { System.err.println("Rained out"); } catch(BaseballException e) { System.err.println("Generic error"); } // Strike not thrown in derived version. try { // What happens if you upcast? Inning i = new StormyInning(); i.atBat(); // You must catch the exceptions from the // base-class version of the method: } catch(Strike e) { System.err.println("Strike"); } catch(Foul e) { System.err.println("Foul"); } catch(RainedOut e) { System.err.println("Rained out"); } catch(BaseballException e) { System.err.println( "Generic baseball exception"); } } } ///:~ In Inning, you can
see that both the constructor and the event( ) method say they will throw an exception, but they never do. This is legal because it allows you to force the user to catch any exceptions that might be added in overridden versions of event( ). The same idea holds for abstract methods, as seen in atBat( ). Comment The
interface Storm is interesting because it contains one method (event( )) that is defined in Inning, and one method that isn’t. Both methods throw a new type of exception, RainedOut. When StormyInning extends Inning and implements Storm, you’ll see that the event( ) method in Storm cannot change the exception interface of event( ) in Inning. Again, this makes sense because otherwise you’d never know
if you were catching the correct thing when working with the base class. Of course, if a method described in an interface is not in the base class, such as rainHard( ),then there’s no problem if it throws exceptions. Comment The restriction on exceptions does not apply to constructors. You can see in StormyInning that a constructor can throw anything it wants, regardless of what the base-class constructor throws. However, since a base-class constructor must always be called one way or another (here, the default constructor is called automatically), the derived-class constructor must declare any base-class constructor exceptions in its exception specification. Note that a derived-class constructor cannot catch exceptions thrown by its base-class constructor. Comment The reason StormyInning.walk( ) will not compile is that it throws an exception, while Inning.walk( ) does not. If this was allowed, then you could write code that called Inning.walk( ) and that didn’t have to handle any exceptions, but then when you substituted an object of a class derived from Inning,
exceptions would be thrown so your code would break. By forcing the derived-class methods to conform to the exception specifications of the base-class methods, substitutability of objects is maintained. Comment The overridden event( ) method shows that a derived-class version of a method may choose not to throw any exceptions, even if the
base-class version does. Again, this is fine since it doesn’t break any code that is written—assuming the base-class version throws exceptions. Similar logic applies to atBat( ), which throws PopFoul, an exception that is derived from Foul thrown by the base-class version of atBat( ). This way, if someone writes code that works with Inning and calls atBat( ), they must catch the Foul exception. Since PopFoul is derived from
Foul, the exception handler will also catch PopFoul. Comment The last point of interest is in main( ). Here you can see that if you’re dealing with exactly a StormyInning object, the compiler forces you to catch only the exceptions that are specific to that class, but if you upcast to the base type then the compiler
(correctly) forces you to catch the exceptions for the base type. All these constraints produce much more robust exception-handling code[57]. Comment It’s useful to realize that although exception specifications are enforced by the compiler during
inheritance, the exception specifications are not part of the type of a method, which is comprised of only the method name and argument types. Therefore, you cannot overload methods based on exception specifications. In addition, just because an exception specification exists in a base-class version of a method doesn’t mean that it must exist in the derived-class version of the method. This is quite different from inheritance rules, where a method in the base class must also exist in the derived
class. Put another way, the “exception specification interface” for a particular method may narrow during inheritance and overriding, but it may not widen—this is precisely the opposite of the rule for the class interface during inheritance. Comment ConstructorsWhen 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.
Comment Since you’ve just learned about finally, you might think that it is the correct solution. But it’s not quite that simple, because finally performs the cleanup code every time, even in the situations in which you don’t want the cleanup code executed until the cleanup method runs.
Thus, if you do perform cleanup in finally, you must set some kind of flag when the constructor finishes normally so that you don’t do anything in the finally block if the flag is set. Because this isn’t particularly elegant (you are coupling your code from one place to another), it’s best if you try to avoid performing this kind of cleanup in finally unless you are forced to.
Comment In the following example, a class called InputFile is created that opens a file and allows you to read it one line (converted into a String) at a time. It uses the classes FileReader and BufferedReader from the Java standard
I/O library that will be discussed in Chapter 11, but which are simple enough that you probably won’t have any trouble understanding their basic use: //: c10:Cleanup.java // Paying attention to exceptions in constructors. import com.bruceeckel.simpletest.*; import java.io.*; class InputFile { private BufferedReader in; InputFile(String fname) throws Exception { try { in = new BufferedReader( new FileReader(fname)); // Other code that might throw exceptions } catch(FileNotFoundException e) { System.err.println( "Could not open " + fname); // Wasn't open, so don't close it throw e; } catch(Exception e) { // All other exceptions must close it try { in.close(); } catch(IOException e2) { System.err.println( "in.close() unsuccessful"); } throw e; // Rethrow } finally { // Don't close it here!!! } } String getLine() { String s; try { s = in.readLine(); } catch(IOException e) { System.err.println( "readLine() unsuccessful"); s = "failed"; } return s; } void cleanup() { try { in.close(); } catch(IOException e2) { System.err.println( "in.close() unsuccessful"); } } } public class Cleanup { static Test monitor = new Test(); public static void main(String[] args) { try { InputFile in = new InputFile("Cleanup.java"); String s; int i = 1; while((s = in.getLine()) != null) System.out.println(""+ i++ + ": " + s); in.cleanup(); } catch(Exception e) { System.err.println( "Caught in main, e.printStackTrace()"); e.printStackTrace(System.err); } //monitor.expect("Cleanup.out"); } } ///:~ 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. Comment If the FileReader constructor is unsuccessful, it throws a FileNotFoundException, which must be caught separately because that’s the one case
in which you don’t want to close the file since it wasn’t successfully opened. Any other catch clauses must close the file because it was opened by the time those catch clauses are entered. (Of course, this is trickier if more than one method can throw a FileNotFoundException. In that case, you might want to break things into several try blocks.) The close( ) method might throw an exception so it is tried and caught even though it’s within the block of
another catch clause—it’s just another pair of curly braces to the Java compiler. After performing local operations, the exception is rethrown, which is appropriate because this constructor failed, and you wouldn’t want the calling method to assume that the object had been properly created and was valid. Comment In this example, which doesn’t use
the aforementioned flagging technique, the finally clause is definitely not the place to close( ) the file, since that would close it every time the constructor completed. Since we want the file to be open for the useful lifetime of the InputFile object this would not be appropriate. Comment The getLine( )
method returns a String containing the next line in the file. It calls readLine( ), which can throw an exception, but that exception is caught so getLine( ) doesn’t throw any exceptions. One of the design issues with exceptions is whether to handle an exception completely at this level, to handle it partially and pass the same exception (or a different one) on, or whether to simply pass it on. Passing it on, when
appropriate, can certainly simplify coding. The getLine( ) method becomes: String getLine() throws IOException { return in.readLine(); } But of course, the caller is now responsible for handling any IOException that might arise. Comment The cleanup( ) method must be called by the user when
finished using the InputFile object. This will release the system resources (such as file handles) that are used by the BufferedReader and/or FileReader objects[58]. 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( ).
Comment In Cleanup.java an InputFile is created to open the same source file that creates the program, the file is read in a line at a time, and line numbers are added. All exceptions are caught generically in main( ), although you could choose greater granularity.
Comment One of the benefits of this example is to show you why exceptions are introduced at this point in the book—you can’t do basic I/O without using exceptions. Exceptions are so integral to programming in Java, especially because the compiler enforces them, that you can accomplish only so much without knowing how to work with them.
Comment Exception matchingWhen 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. Comment Matching an exception doesn’t require a perfect match between the exception and its handler. A derived-class object will match a handler for the base class, as shown in this example: //: c10:Human.java // Catching exception hierarchies. import com.bruceeckel.simpletest.*; class Annoyance extends Exception {} class Sneeze extends Annoyance {} public class Human { static Test monitor = new Test(); public static void main(String[] args) { try { throw new Sneeze(); } catch(Sneeze s) { System.err.println("Caught Sneeze"); } catch(Annoyance a) { System.err.println("Caught Annoyance"); } monitor.expect(new String[] { "Caught Sneeze" }); } } ///:~ The Sneeze exception will be caught by the first catch clause that it matches—which is the first one, of course. However, if you remove the first catch clause, leaving only: Comment try { throw new Sneeze(); } catch(Annoyance a) { System.err.println("Caught Annoyance"); } The code will still work because it’s catching the base class of Sneeze. Put
another way, catch(Annoyance e) will catch an Annoyance or any class derived from it. This is useful because if you decide to add more derived exceptions to a method, then the client programmer’s code will not need changing as long as the client catches the base class exceptions. Comment If you try to “mask” the derived-class
exceptions by putting the base-class catch clause first, like this: try { throw new Sneeze(); } catch(Annoyance a) { System.err.println("Caught Annoyance"); } catch(Sneeze s) { System.err.println("Caught Sneeze"); } the compiler will give you an error message, since it sees that the Sneeze catch-clause can never be reached. Comment Exception guidelinesUse exceptions to:
SummaryImproved error recovery is one of the most powerful ways that you can increase the robustness of your code. Error recovery is a fundamental concern for every program you write, but it’s especially important in Java, where one of the primary goals
is to create program components for others to use. To create a robust system, each component must be robust. Comment The goals for exception handling in Java are to simplify the creation of large, reliable programs using less code than currently possible, and with more confidence that your application doesn’t have an
unhandled error. Comment Exceptions are not terribly difficult to learn, and are one of those features that provide immediate and significant benefits to your project. Fortunately, Java enforces all aspects of exceptions so it’s guaranteed that they will be used consistently by both library designers and client programmers.
Comment ExercisesSolutions to selected exercises can be found in the electronic document The Thinking in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
[53]
The C programmer can look up the return value of printf( ) for an example of this. [54] 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. [55] C++ exception handling does not have the finally clause because it relies on destructors to accomplish this sort of cleanup. [56] 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). [57] 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. [58] In C++, a destructor would handle this for you. Does the throws clause cause an exception to thrown?The throws clause causes an exception to be thrown. The throw statement informs the compiler that a method throws one or more exception.
When an exception is thrown by a method that is executing?8) When an exception is thrown by a method that is executing under several layers of method calls, a stack trace indicates the method executing when an exception occurred and all of the methods that were called in order to execute that method.
When you write a method that throws a checked exception you must?If a method declares a checked exception (i.e., an exception other than Error or RuntimeException), you must invoke it in a try-catch block or declare to throw the exception in the calling method.
When catching multiple exceptions that are related to one another?When catching multiple exceptions that are related to one another through inheritance, you should handle the more specialized exception classes before the more general exception classes. To serialize an object and write it to the file, use this method of the ObjectOutputStream class.
|