Chapter 8: Exceptions......................................................................................................................................
THE EXCEPTION
CLASS.............................................................................................................................................
The Error Class...........................................................................................................................................................
THROWABLES IN
DETAIL.........................................................................................................................................
WHEN TO CATCH
EXCEPTIONS..............................................................................................................................
Finally..........................................................................................................................................................................
PRE-DEFINED
EXCEPTIONS AND ERRORS..........................................................................................................
Catching Multiple Exceptions..............................................................................................................................
Scope and Exceptions.............................................................................................................................................
Catching Interfaces.................................................................................................................................................
WHEN TO THROW
EXCEPTIONS..........................................................................................................................
CATCHING A
DYING THREAD..............................................................................................................................
WHEN TO THROW
OTHER OBJECTS...................................................................................................................
Unblocking Blocked IO...........................................................................................................................................
Reusing Threads.......................................................................................................................................................
NATIVE
EXCEPTIONS...............................................................................................................................................
EXCEPTIONS AND
SCOPE........................................................................................................................................
CONCLUSION...............................................................................................................................................................
EXERCISES...................................................................................................................................................................
I like to think of programming as drawing a circle around a problem. Each bit of code is another little arc in the circle. A bugless, deliverable program draws a complete circle around the problem. Every aspect of the problem is encompassed by the solution.
Unfortunately, there’s no such thing as a bugless program. Parts of the problem always ‘seep’ through our solution. How a program deals with errors often marks the difference between a useable, commercial program, and an interesting, but useless programming exercise.
In Java, the notion of programmatic error is encapsulated in Exceptions. An Exception is a Java class that embodies everything you need to pinpoint where (and often why) an Exception occurred. We’ll talk a little later about what exactly is in an Exception. For right now, let’s think of Exceptions as simple Objects, and we’ll take a look at how exception-handling code is structured.
In order to use exceptions, Java provides the try-catch control structure.
try { set of expressions }
catch ( exception ) { expressions to run if we catch an exception. }
The syntax really describes the function. We ‘try’ the block of code, and ‘catch’ the named exception class if it occurs. The catch clause is easier to understand as a sub-method which takes an argument. In the catch clause, you can name a single Exception type, then follow it with a block of code to execute if that Exception is caught. Here’s an actual Exception handling block:
public void readit( Socket s ) {
int ret;
try {
ret = s.getInputStream().read(); }
catch( IOException ourExc ) {
System.out.println( “Socket error ”+ourExc ); }
}
Listing 8.1: An exception handling block.
In this example, if s.getInputStream().read throws an IOException, the variable, ourExc is initialized with the specifics of the exception. Then, the block attached to the catch clause executes, printing a message to standard output. In order to throw this exception, somewhere in the source for InputStream.read is a statement that reads something like:
throw( new IOException());
If an IOException is NOT thrown by read, the catch block never executes. This is a simple, but illustrative example. Now let’s take a step back and look at a little of the theory of Exceptions.
The main purpose of the throw statement is to break the normal flow of execution. To understand how a thrown Exception affects the flow of execution, we have to hark back to our discussion of threads. Everything that happens within a thread is within the scope of the Thread object’s run method. At any point in the execution of the program, you’ll be executing somewhere within a set of nested scopes with the run method being the outermost scope. When you throw an Exception, Java checks the current scope to see if the object is caught there, that is whether or not the current scope is bracketed by a try-catch block. If the throw occurs within a try-catch block, then the catch clause is checked to see whether the Exception thrown is the same class (or some subclass) as the Exception being caught. If it is, the method continues execution at the start of the catch block. If there is no matching catch clause in this scope, Java exits that scope and runs the same check on the next scope out. This continues until the Exception is caught. By travelling back through the call stack this way, an uncaught Exception effectively terminates the thread. Figure 8-1 shows what happens within the call stack when a method within a Thread object throws an Exception.
Figure 8-1: A thrown object travelling up the call stack.
<ch8_fig1.tif>
Listing 8-2 shows a simple example of a threaded applet generating an exception 2 levels down the call stack.
Listing 8-2: An applet that generates an exception.
package chap8;
import java.awt.Graphics;
import java.awt.*;
import java.applet.Applet;
import java.net.*;
import java.lang.*;
import java.io.*;
import java.util.*;
/** A class for demonstrating a thrown-exceptions effect on the
flow of execution.
*/
public class ch8_fig2 extends Applet implements Runnable {
public Thread myThread;
public void start() {
if( myThread == null ) {
myThread = new Thread( this );
myThread.start();
}
}
/** Override of Thread.run. */
public void run() {
System.out.println( "run invoked" );
try {
while( true ) {
System.out.println( "run-while-1" );
myMethodA();
myMethodB();
System.out.println( "run-while-2" );
}
}
catch( Exception e )
{ System.out.println( "run caught exception "+e ); }
}
/** A method that gets called by run, which calls another
method that generates an exception.
*/
public void myMethodA() throws Exception {
try {
System.out.println( "\tmyMethodA invoked" );
lastMethod();
System.out.println( "\tmyMethodA-2" );
}
catch( Exception e ) {
System.out.println( "\tmyMethodA caught Exception" );
e.printStackTrace( System.out );
throw (Exception)e.fillInStackTrace();
}
}
/** A method, called by myMethodA, that calls another method
that generates an exception.
*/
public void myMethodB() throws MalformedURLException {
try {
System.out.println( "\tmyMethodB invoked" );
lastMethod();
System.out.println( "\tmyMethodB-2" );
}
catch( Exception e ) {
System.out.println( "\tmyMethodB caught Exception" );
throw (MalformedURLException)e.fillInStackTrace();
}
finally { System.out.println( "\tmyMethodB finally" ); }
}
/** A method that generates an MalformedURLException. */
public void lastMethod() throws MalformedURLException {
int ret;
int j = 0;
System.out.println( "\t\tlastMethod invoked" );
URL u = new URL( "JohnHost" );
System.out.println( "\t\tlastMethod-2" );
j++;
}
}
Figure 8-2 shows the output generated by running the applet of Listing 8-2 in appletviewer.
Figure 8-2: The call stack printout from Listing 8-2.
thread applet-chap8/ch8_fig2.class find class chap8.ch8_fig2
Opening stream to: file:/C:/agent/classes/rel/chap8/ch8_fig2.class to get chap8.ch8_fig2
run invoked
run-while-1
myMethodA invoked
lastMethod invoked
myMethodA caught Exception
java.net.MalformedURLException: no protocol: JohnHost
at java.net.URL.<init>(URL.java:157)
at java.net.URL.<init>(URL.java:107)
at chap8.ch8_fig2.lastMethod(ch8_fig2.java:78)
at chap8.ch8_fig2.myMethodA(ch8_fig2.java:47)
at chap8.ch8_fig2.run(ch8_fig2.java:32)
at java.lang.Thread.run(Thread.java:289)
run caught exception java.net.MalformedURLException: no protocol: JohnHost
Here we’ve intentionally passed a bad String URL to the URL constructor in order to make it throw a MalformedURLException. We’ve also placed println’s throughout the code to show where the generated exception breaks the normal flow of execution. As you can see from Figure 8-2, the methods, run, myMethodA and lastMethod get invoked. Taking it from the bottom up:
lastMethod
lastMethod’s first println (“... invoked”) executes, but then the invocation of the URL constructor throws an exception, so lastMethod’s second println never gets called.
myMethodA
The initial println (“... invoked”) gets executed, but then the invocation of lastMethod throws an exception, so the second println (“myMethodA-2”) never gets called. The catch clause matches the exception, so our catch block gets executed, printing out the “caught exception” method, then generating the stack trace (via printStackTrace) that tells us which lines in the source code we were at when the exception blew through. myMethodA re-throws the exception.
run
The initial println (“... invoked”) executes, we fall into the while loop and execute the second println (“run-while-1”). We invoke myMethodA, which throws an exception. The exception bounces us straight out of the while loop without executing either myMethodB or the third println (“run-while-2”), then it matches the catch clause and executes the println there.
Strictly speaking, it is not Exceptions, but objects of class Throwable that get thrown in a Java program. In normal use though, what you throw and catch will almost always be a specialized sub-class of Throwable - Exception. Figure 8.? shows the class hierarchy for the Throwable class and its descendants. All of the intelligence of Exceptions used to reside solely in the Exception class, but the release version split Exception into two classes - the super-class Throwable which contains the toString and StackTrace methods, and a sub-class, Exception that contains only the two constructors. This makes a lot of sense, because, as we will see later, all that is throwable is not neccessarilly an exception.
The compiler enforces the rule that the object of any throw statement, or catch clause must sub-class Throwable. The following statements, which worked in previous versions will not compile:
throw( new String(“Non-exception throw”);
try {
...
} catch( Object o ) { System.out.println( “thrown object is “+o); }
Exception has a mirror image, an evil twin if you will - the Error class. Functionally, Error is identical to Exception. It exists mainly to provide Exception throwers with a way around your Exception handling code. At first glance, this seems outrageous. You go to all the trouble of catching and dealing with Exceptions, and somebody goes and throws an Error just to get around you. In practice, it turns out that there are some problems that you just can’t recover from. A classic example of this is when you run out of memory. Literally anything you do after running out of memory will only make the problem worse, because you can’t instantiate anything. You end up in a spiral of exception handling code, because your exception handler itself causes more exceptions. It’s just not worth it. As the Sun documentation for Error says - only catch Errors if you really know what you’re doing. Error handling, as you might have guessed, is beyond the scope of this book. All you really have to remember about Errors is that the base Error class is identical in function to the Exception class. It exists as a separate entity solely to get around statements like:
catch( Exception e ) { ... }
Anything we say about Exceptions applies equally to Errors, unless otherwise noted.
As you can see in Figure 8.?, the line of inheritance for Errors and Exceptions goes Object-Throwable-Error/Exception. Thus, all the functionality of Exceptions is embodied in the Throwable and Exception classes. Table 8.1 shows the methods and constructors for the Throwable class and its two subclasses, Error and Exception (there are no public variables).
Table 8.1: The Exception and Throwable classes.
Exception
Exception()
Exception( String DetailMessage )
Error
Error()
Error( String DetailMessage )
Throwable
Throwable() Constructs a Throwable with no detail message.
Throwable( String DetailMessage ) Constructs a Throwable with the specified detail message.
String getMessage() Returns the detail message.
String toString() Returns a description of the Throwable.
printStackTrace() Prints the Throwable and its stack trace on standard output.
printStackTrace( PrintStream s ) Prints the Throwable and its stack trace on the specified PrintStream.
Throwable fillInStackTrace() Fills in the stack trace. Must be used whenever you rethrow a Throwable.
Exceptions are designed to accomplish the following tasks:
Break the flow of execution. The throw mechanism accomplishes this.
Preserve and display the call stack at the exception point. The StackTrace methods, fillInStackTrace and printStackTrace deal with this.
Preserve and display a “label” that names the exception and gives a description that would be meaningful to debuggers who don’t have access to the source code. The constructors store the label, while toString and getMessage return it.
As we said, Error and Exception contain only the two constructors - one that sets the detail message, and one that doesn’t. Within the super-class Throwable, getMessage and toString both give you a String that describes the Exception. This String is the detail message that the Exception designer attached to this Exception. getMessage returns only the detail message, while toString prepends the class name to the detailMessage.
Exceptions are like grenades: don’t catch one unless you know what to do with it. Fortunately, we don’t have to catch every Exception, or keep every Exception we catch. The trick is knowing which one to ignore, which to catch and keep, and which ones to pass through.
What often makes Exceptions/Errors confusing to Java novices is that some of them can be ignored, and others can’t. If a method declares itself as “throwing” a particular Exception, then any method that calls that method must either catch, or declare itself as throwing that exception. For example, the following method:
void myRead( InputStream is ) {
int myInt = is.read();
}
will not compile because we haven’t accounted for the IOException that InputStream.read throws. We have only two options for dealing with this. We can catch the Exception ourselves, as in:
void myRead( InputStream is ) {
try {
int myInt = is.read();
} catch( IOException e )
{ System.out.println( “bad read “+e ); }
}
or, we can pass it through, as in:
void myRead( InputStream is ) throws IOException {
int myInt = is.read();
}
This situation arises only because InputStream.read is declared as throwing IOException.
What this leads us to is the realization that Exceptions can spring from two very diferent sources. One source, the one we’ve been talking about so far, is the case where a Java method explicitly states:
throw (new Exception());.
The second source for Exception/Errors is the interpreter itself. In the class hierarchy diagram of Figure 8.?, three huge branches, LinkageError, VirtualMachineError and RuntimeException (as well as a few other Exceptions) fall into this category. These Exceptions/Errors can be thrown by such innocuous statements as
Integer I = new Integer(1);
or
Integer I = (Integer)j;
Java does not expect us to catch, or deal with interpreter-generated Exceptions/Errors. In general, the only Exceptions you should try to catch are those that are declared as being thrown by a method.
That said, efficient and unobtrusive exception handling is a major feature of Java. In C, I often wrote blocks like:
int ret;
ret=myfunction( myargs );
if( ret < 0 )
printf( “Error\n” );
Every pass through that section of code executes the if statement checking for errors. I pay a performance penalty to account for an event that happens very infrequently, if at all. In Java the performance penalty for implementing a try-catch block, is very small, typically only a branch at the end of the try block.
In our socket reading example, it’s pretty clear what the default reaction should be to any SocketExceptions. Kill the socket, and the socket reading thread as well as the socketreadingthread object that contains them. Thus, in the outermost scope of the run method, we bracket socket reading loop with a try-catch for SocketExceptions.
[FILL THIS IN JOHN]
Figure 8-6: Catching Exceptions in socket reading loop.
If you’re only catching a single Exception type, this syntax leads to a lot of code such as:
try { expressions }
catch( exception ) {
Do cleanup;
rethrow the Exception
}
The fact is, most code can’t really do anything with caught Exceptions, but merely needs to do some housekeeping if an Exception should break the method at an inconvenient spot. To deal with this situation, Java allows a short-cut using the try-finally syntax. Our pseudo-code try-catch-rethrow example above then becomes:
try { expressions }
finally {
Do cleanup;
}
The effect is similar, any caught Throwable gets rethrown, but some unneccessary source code is eliminated. Where try-finally really differs from try-catch is that the catch block ONLY GETS EXECUTED IF A THROWN OBJECT IS CAUGHT WHILE THE FINALLY BLOCK IS ALWAYS EXECUTED WHETHER OR NOT A THROW OCCURS. To illustrate, consider the following blocks:
try{
myMethod();
}
catch( Exception e ) { System.out.println( “myMethod is done” ); }
try{
myMethod();
}
finally { System.out.println( “myMethod is done” ); }
The first block prints out “myMethod is done” only if myMethod throws an Exception, while the second block prints out “myMethod is done” whether or not myMethod throws an Exception. Finally blocks execute no matter what happens, whether or not any kind of Object gets thrown.
Pre-Defined Exceptions and Errors
Java, and it’s accompanying packages, define a number of Exceptions and Errors, which are detailed in Tables 8.7 and 8.8.
Table 8-7: A comprehensive list of Java Exceptions.
|
Description |
|
|
ArithmeticException |
Divide by zero/mod by zero |
|
ArrayIndexOutOfBoundsException |
Tried to access past current bounds of array. |
|
ArrayStoreException |
Tried to put the wrong class of Object into an array. byte[] b = new byte[10]; b[2] = new Integer(1); |
|
AWTException |
Exception occurred somewhere in the window toolkit. |
|
ClassCastException |
Tried to cast between classes that are not related |
|
ClassNotFoundException |
ClassLoader failed to load class. See Chapter 9. |
|
CloneNotSupportedException |
Tried to clone an Object that doesn't support it. |
|
EmptyStackException |
Tried to use an empty Stack. |
|
EOFException |
File I/O reached end of file. |
|
Exception |
Base Exception class. Never thrown. |
|
FileNotFoundException |
Tried to create a File object from a file that doesn't exist. |
|
IllegalAccessException |
Tried to invoke a method that couldn't be found. |
|
IllegalArgumentException |
Invoked method detected a bad argument. |
|
IllegalMonitorStateException |
Tried to notify a monitor that you don't own. |
|
IllegalThreadStateException |
Tried to set daemon status on a Thread that was already running. |
|
IndexOutOfBoundsException |
Generic bad index. |
|
InstantiationException |
Problem in new. |
|
InterruptedException |
The receiving thread has been interrupted by another thread. |
|
InterruptedIOException |
A blocking I/O operation has been interrupted. |
|
IOException |
I/O device (socket or file) broke while in use. Often socket connection lost. |
|
MalformedURLException |
String URL passed to URL constructor was nonsense. |
|
NegativeArraySizeException |
Tried to create an array with negative size “int j[] = new int[-5]” |
|
NoSuchElementException |
Tried to access Vector element < 0 or beyond size() of Vector. |
|
NoSuchMethodException |
|
|
NullPointerException |
Tried to use an uninitialized object. |
|
NumberFormatException |
Tried to make a Number from a non-numeric string. Integer I = new Integer("abc"); |
|
ProtocolException |
Problem in protocol handler. |
|
RuntimeException |
Base class for exceptions generated by the interpreter. Never thrown. |
|
SecurityException |
Operation failed security check. |
|
SocketException |
Generic socket use problem. |
|
StringIndexOutOfBoundsException |
Tried to access character at index < 0, or beyond length() of String. |
|
UnknownHostException |
Host name couldn't be resolved to an IP address. |
|
UnknownServiceException |
|
|
UTFDataFormatException |
Malformed string encountered in a DataInputStream. |
Table 8.8: A comprehensive list of Java Errors.
|
AbstractMethodError |
Tried to invoke abstract method. |
|
AWTError |
Unexpected error in window toolkit. |
|
ClassCircularityError |
Circular dependence detected while loading class. |
|
ClassFormatError |
Bad file format detected by implicit class loader. |
|
Error |
Base Error class. Never thrown. |
|
IllegalAccessError |
|
|
IncompatibleClassChangeError |
|
|
InstantiationError |
Tried to instantiate abstract class or interface via new. |
|
InternalError |
Catch-all for interpreter problems. |
|
LinkageError |
Base class for indicating that interdependent classes have changed incompatibly. |
|
NoClassDefFoundError |
Class that was available at compile-time is no longer available. |
|
NoSuchFieldError |
|
|
NoSuchMethodError |
Invoked method that couldn't be found. |
|
OutOfMemoryError |
System is out of memory. |
|
StackOverflowError |
Ran out of stack. Possibly unterminated recursion. See Listing 7.X. |
|
UnknownError |
|
|
UnsatisfiedLinkError |
|
|
VerifyError |
Class bytecode file failed security check during class load operation. |
|
VirtualMachineError |
The Virtual Machine has a problem. |
Many of these, such as OutOfMemory, StackOverflow, NullPointer, and IllegalAccess, should be familiar to C and C++ coders. Others, like ArrayIndexOutOfBounds and NoSuchMethod are designed specifically to implement new features of the Java language. Writing code to try to catch, or recover from most of these run-time Exceptions is usually a waste of time. Using exceptions can often lead to different ways of writing that speed up your code significantly.
In the addAgentDisplay method where a new agent reports his existence, we try to find an unused member of the imageA array which has a hard limit of MAX_ANIMATOR_THREADS members. (we could have used a vector here, but our screen real-estate is limited to YYYxZZZ so it wouldn’t be useful anyway) In the original implementation, Figure X-X, we check the member index and if it overruns the array, we go to plan B. Figure X-X implements the same method using Exceptions. In the normal course of events, we blithely add members to the array. When the XXX index overruns the array, we execute the catch block, which puts up a message giving the user three options: compact the display by getting rid of the no-result agents, change the display to text-only/results-only, or continue as-is. Note the unique construction of this method. For one thing, it’s protected by a variable XXX, so that multiple new agents overrunning the array simultaneously don’t put up multiple dialogs. For another, we spin the dialog off into it’s own thread. If we leave it in the current thread, that thread will block until we get a response from the user, who may have gone to get coffee for all we know.
Another run-time Exception you can use in the same way is NumberFormatException. In the XXX method of the XXX class, we take a String from (DIALOG, MESSAGE????) and turn it into a number representing ????? In C or C++, we’d have scanned the string for illegal characters, returning an error if we found one. To do the same thing in Java, you’d write something like Figure X-X. See the grief we go through to change the String into a byte array that can be scanned, and then checking each member. Figure X-X does it the Java way, simply passing the String to the Number constructor and catching NumberFormatExceptions.
[FILL THIS IN JOHN]
Figure 8-8: Creating a new Number from a dubious String value, the hard way.
[FILL THIS IN JOHN]
Figure 8-9: Creating a new Number from a dubious String value, the Java way.
Each catch clause specifies a single class/interface to catch, but catch clauses can be cascaded as in Figure 8-10. The catch clauses are evaulated in order. When an Object is thrown, it passes through each of the catch clauses and the first catch clause that it happens to match gets executed.
try {
MyClass.doSomeFtpStuff();
}
catch( FtpLoginException ) { System.out.println( “FTP login exception” ); }
catch( FtpProtocolException ) { System.out.println( “FTP protocol exception” ); }
Figure 8-12: Cascaded catch clauses.
Thus you can write a try catch like Figure 8-11 to do one thing for a particular exception, and something else for all the other exceptions.
try {
MyClass.myMethod();
}
catch( ParticularException ) { System.out.println(“particular exception” ); }
catch( Exception ) { System.out.println( “Some other exception” ); }
finally { System.out.println( “The try block has finished.” ); }
Figure 8-13: ParticularException is caught by first clause, while all others fall through to the second clause. Note the finally clause cascaded with the catch clauses.
Each curly-bracketed block defines a level of scope in your program and that applies equally to try-catch blocks as to any other blocks. You have to keep this in mind as you add Exception handling to working code. Consider the example of Figure 8-14 which gets a sub-String from a StringTokenizer. When we bracket the call to nextToken with a try block, s is no longer defined in the top-level scope, and the call to println doesn’t compile.
StringTokenizer st = new StringTokenizer( inputLine );
String s = st.nextToken();
System.out.println( “s = “+s);
Figure 8-14: The original code.
try{ StringTokenizer st = new StringTokenizer( inputLine ); String s = st.nextToken(); }
catch( NoSuchElementException ) { System.out.println( “s = “+s ); }
Figure 8-14a: Adding exception handling. This will not compile.
String s;
try { StringTokenizer st = new StringTokenizer( inputLine ); s = st.nextToken(); }
catch( NoSuchElementException ) { System.out.println( “s = “+s ); }
but this doesn’t
Figure 8-14c: Fixing scope problem. Now exception handling works.
In our catch clause, we can specify either a class or an interface. As stated previously, you catch objects. What the catch clause says then is either “Catch any object that is a class or subclass of X” or “Catch any object that implements the interface X”.
Our Agent interface also implements the AgentException interface. By making this an interface, we can put a very powerful feature into our Agents - self-reporting. Within the AgentServer, we catch all AgentExceptions within each thread’s main loop, then invoke AgentException.selfReport(). selfReport bundles the exception dump into a mail message and mails it back to customer support. Anyone who’s lived with an unresolved bug for months can surely appreciate a system where the buggy program nags customer support all by itself.
[FILL THIS IN JOHN]
Figure 8-15: AgentServer main loop catching AgentException interface objects.
Never, never, never use method return values to pass errors back up the call stack. If you find yourself thinking of implementing a return value for an error condition, this is where you need to throw an Exception.
[IS THIS ONLY FOR PREBETA???]
When throwing Exceptions there are three rules you need to abide by:
1. If a method throws an Exception, it must declare it in the method declaration.
2. If a method overrides a method in a super-class or interface, it can only throw exceptions that the overridden method has declared.
This, in my opinion, is very restrictive, but there is good reason for it. Java is a language for distributed computing, where there are millions of programmers writing large numbers of small-ish objects that interact via interfaces. The percentage of re-used code in the Java environment is some large multiple of that experienced with C and C++. In order to make code re-use a viable option, the published APIs (including thrown exceptions) for public methods must be reliable, and thus must remain immutable. If you were to override a public method, and then go throwing exceptions that users of your method weren’t prepared for (because it wasn’t part of the published API), you’d end up breaking perfectly good code, and the writers of that perfectly good code would have much less confidence in that published API. That is why the compiler enforces this rule. Remember, your applets are travelling over the network and executing within applications you could never have envisioned.
3. If a method overrides a method in a super-class or interface, it must catch any Exception that the overridden method doesn’t throw, if that Exception is thrown by a method that it calls. In other words, the method can’t allow any disallowed Exception from proceeding up the call stack through it. As an example, the run method from the Runnable interface declares no Exceptions, but Thread.sleep throws the InterruptedException, so if we call Thread.sleep, we must catch InterruptedException. HOW DEEP DOES THIS CHECKING GO??? HOW MANY METHODS DEEP??
These three rules help close a large hole in our coding methodology. The fact that interface declarations specify the Exceptions that emanate from implementations will limit the actual number of Exceptions that coders can throw (and define). Forcing coders to catch any disallowed Exceptions limits the propagation of Exceptions through the call stack, and declaring thrown Exceptions in the method declaration guarantees that there will never be ‘undocumented’ Exceptions. The hope is that Java applets will be ‘closed’ systems where problems that arise within the system are handled gracefully within the system.
It is not incumbent on anyone to catch Exceptions, but all public methods are required to declare which Exceptions they throw, and not to throw any Exceptions. Thus, any Java applet documented with javadoc will have its Exceptions clearly visible. Without this rule, Exception throws could be declared willy-nilly and without reading the actual code, noone would ever know they existed. The hope is that the increased attention this focuses on Exceptions will force coders to at least think about them.
There is a large exception to the three rules of Exception throwing stated above. The class known as RuntimeException can be generated anywhere. j++ could potentially throw an ArithmeticException. You can invoke methods and execute statements that throw RuntimeExceptions without worrying about trying to catch them. This is a more than a little inconsistent, in a way that drives language purists crazy, but the tradeoff, a little inconsistency for a lot less brittle code, is probably worth it.
We saw earlier how a thrown object, travelling back up through the call stack, eventually terminates a thread. This is, in fact, how Thread.stop works, by throwing a ThreadDeath object. The ThreadDeath class is defined expressly for this purpose. It has no variables or methods other than the constructor. Java could as easily have defined a ‘ThreadStoppedException’. Two reasons. First, stopping a thread is not an error. Second, and more important, great code will catch, and deal with, all exceptions. Thus, a non-Exception throwable object was needed to deal with this special case.
We catch ThreadDeath in order to implement a bit of debugging code. What we want to do is have a run-time switch that allows us to report the appearance and disappearance of AgentConnectionHandler threads. These threads are very short-lived, and crucial to the correct operation of the system.
The modification to the run method is fairly simple, bracketing the run loop in a try-catch block. Notice, however, that having caught ThreadDeath, we rethrow it. This is not strictly neccessary in this case, since our thread would die when we returned from run anyway. It is, however, recommended practice, and there is no guarantee that Java itself might not catch ThreadDeath for its own purposes, so we toe the line and rethrow it.
[FILL THIS IN JOHN]
Figure 8-16: Catching ThreadDeath in the AgentConnectionHandler.
This catch-rethrow sequence is also useful to us in debugging the effect of Exceptions. In Figure 8-3, we implement generic exception catching/reporting for the XXX section of the XXX class. In the catch block, we simply print out the current ‘state’ of the method we’re in, then rethrow the Exception. Notice in Figure 8-3, I’ve intentionally overrun the XXX array to generate an ArrayIndexOutOfBoundsException. Figure 8-4 shows the weblog output that one run of this thread generates.
Notice that when we rethrow the Exception, we also call fillInStackTrace. This is because WHY IS THIS JOHN??? Figure 8-4 shows the weblog output with and without the call to fillInStackTrace. Notice how blah blah blah.
There are two reasons for throwing an object. The first, throwing an exception within a thread (i.e. throwing up) we’ve already talked about. But throwing also has a side-effect that’s useful all by itself that has nothing to do with error conditions. That is, throwing an object at a thread breaks that thread out of any blocking IO it might be stuck in. In this way, throwing is similar to sending a signal in UNIX (and some Java implementations may in fact use signals to implement throw). If you think about what object-throwing has to accomplish, it’s easy to see why throw must break out of any blocking IO. If a thread continued to block on IO after an exception was thrown, the exception might never actually happen, which would, of course, defeat the purpose.
As we saw with ThreadDeath, the main reason for throwing an object other than an Exception is to avoid being caught by the catch clause - catch(Exception) - which is the accepted way to catch all exceptions. In our connection handler then, we can bracket the blocking IO call with a try-catch of a new class - BlockingIOBreak. And back up in our main loop, we can check all the existing threads to see if they’re hung on a blocking IO call. If a thread has been running for longer than we think is reasonable, we throw a BlockingIOBreak at it.
[FILL THIS IN JOHN]
Figure 8-17: Using thrown object to break out of blocking IO.
We take the same tack with the ChangeImages method in our Animation class. We want the ‘digging’ animation to change to a glittering pile of gold when there are results found, or an empty hole if no results are found. Rather than kill the thread entirely and go through the hassle of starting up a new one just to show a different series of pictures, we let the thread spin on its merry way, and throw a ChangeImage object at it whenever we wish to change the picture. Wherever it’s executing at the moment, the thread will pop back up to the catch(ChangeImage) block in the run loop, then restart the run loop with new parameters. If you use this style of programming you must make sure that you never leave dangerous artifacts anywhere in the code that gets broken out of via the throw.
[FILL THIS IN JOHN]
Figure 8-18: The ChangeImages method, and its invocation from AgentLauncher:reportResults.
Now we could easily have used either Exception, or ThreadDeath to implement the same functionality. We do it this way mainly because we don’t need the stack tracing facility of Exceptions. This method of re-using threads is as valid as the destroy/re-create method and a little more efficient. Whatever it gains in efficiency, it more than gives back in decreased readability.
Exceptions are not part of a method’s signature, and thus, an interface method which has changed the exceptions it throws will still pass version verification.
Generating exceptions within native methods is easy, and good practice. The mechanism you use is signals. In Solaris, for example, you use the SignalError system call to send a signal:
SignalError( int x, char *ExceptionName, int y );
int x; // what is X?
char *ExceptionName; // This is the string name of the Java class of the object you want to throw. It must be a // fully qualified class name.
int y; // What is Y???
Solaris Example:
SignalError( 0, “myPackage/myException”, 0 );
Figure 8-20: Using the Solaris SignalError system call to throw a Java Exception.
In this example, myPackage is an actual Java package (with its attendant directory structure), and myException is a class all its own, defined in a file called myException.class.
Calling SignalError does not actually throw the Exception. What happens is that the Exception is ‘recorded’ when SignalError is called, but doesn’t get thrown until your native method returns to the interpreter. After the native method returns, the native method invoker simply checks whether or not an exception has been ‘recorded’ and throws it. This has one important side-effect. If you throw two exceptions within a single native method (probably bad practice in any case), the second exception will overwrite the first and upon return to the interpreter only the second exception will be thrown. Thus, any native method can only throw one exception per invocation.
Java’s object orientation not only encourages proper scoping of variables, in some not-so-subtle ways it actually enforces it. One of the places where you see this most clearly is in exception handling code development.
The following piece of code attempts to connect to a URL and get the contents of the file there:
public void start() {
URL u = new URL(“http://www.myhouse.com/images/image.gif” );
Object o = u.getContent();
System.out.println( “connected to “+u+” and got object “+o);
}
You compile this, and it bombs with two messages saying you need to catch MalformedURLException and IOException. So you rewrite the code as follows:
public void start() {
try {
URL u = new URL(“http://www.myhouse.com/images/image.gif” );
} catch( MalformedURLException e ){ System.out.println( “url exception “+e ); }
try {
Object o = u.getContent();
} catch( IOException e ) { System.out.println( “io exception “+e ); }
System.out.println( “connected to “+u+” and got object “+o);
}
You recompile and it bombs, complaining that u and o are undefined. You berate yourself for being so stupid and rewrite it this way, taking the declaration of u and o out to the next scope:
public void start() {
URL u;
Object o;
try {
u = new URL(“http://www.myhouse.com/images/image.gif” );
} catch( MalformedURLException e ){ System.out.println( “url exception “+e ); }
try {
o = u.getContent();
} catch( IOException e ) { System.out.println( “io exception “+e ); }
System.out.println( “connected to “+u+” and got object “+o);
}
You compile this one, and Java complains that u and o are uninitialized when they get printed out. At this point you could just set u and o to something, such as null and the applet would compile, and, in the best case, run okay. But all you’ve really accomplished is to work around the compiler’s best efforts to guide you in the right direction. What you really want to end up with, for this example, is the following:
public void start() {
try {
URL u = new URL(“http://www.myhouse.com/images/image.gif” );
try {
Object o = u.getContent();
System.out.println( “connected to “+u+” and got object “+o);
} catch( IOException e ) { System.out.println( “io exception “+e ); }
} catch( MalformedURLException e ){ System.out.println( “url exception “+e ); }
}
As you can see, Java strives mightily to encourage and enforce good coding practices, but our first instincts and old habits of mind will often lead us astray.
We’ve spent a lot of time talking about how to use thrown Objects in general and Exceptions in particular. This is because proper use of Exceptions (and other thrown objects) allows us to write more efficient and readable Java code. Learning to work within the bounds imposed by Java Exception handling will also make our applets better, and more respected, citizens of the global applet society Java has spawned.
1. Don’t be thrown off by the different terminology
of going ‘up’ or ‘down’ the call stack.
For the purposes of this text, the stack grows down as we make nested
calls.
1. In the following code snippet, which of these lines of code actually consume any CPU time if myMethod doesn’t throw an Exception?
1. try{
2. myMethod(); }
3. catch( Exception e ) {
4. System.out.println( “Exception occurred” ); }
A) 2,3
1b. What about this snippet?
1. try{
2. myMethod(); }
3. finally {
4. System.out.println( “cleaning up” ); }
A) 2,3,4
2. Will the following code snippet compile? If so, what prints out if myMethod throws an Exception? A ThreadDeath? A NumberFormatException? What if myMethod doesn’t throw anything?
try { myMethod(); }
catch( Exception e) { System.out.println( “Thrown Exception” ); }
finally { System.out.println( “finally” ); }
catch( NumberFormatException nfe) { System.out.println( “Thrown NumberFormatException” ); }
catch( Object o ) { System.out.println( “Thrown Object” ); }
A) Yes. ThrownException finally. finally Thrown Object. finally Thrown Exception. finally.
3. Fix the following class.
class myClass extends Object implements Runnable {
public void start() { }
public void run() throws myException {
while( true ) {
try { myMethod(); }
finally { myCleanup(); }
}
}
void myMethod() throws myException {
iterations++;
if( iterations > MAX_ITERATIONS )
throw( new myException());
}
void myCleanup() { iterations = 0; }
}
A) public void run() {
while( true ) {
try{ myMethod(); }
catch( myException e ) { System.out.println( “MyException” ); }
finally { myCleanup(); }
}
}
4. Explain the difference between the following two code snippets:
try { ... }
catch( Exception e ) { throw e; }
try{ ... }
catch( Exception e) { throw e.fillInStackTrace(); }
Are both valid?
A) ?????