Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 2 of 3
public class rm {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try {
new File(args[i]).delete();
} catch (IOException e) { // Won't compile!!!
System.err.println("rm: Couldn't delete " + args[i]);
}
}
}
}
Sun Microsystems' Java compiler would tell you that IOException "is never thrown in body of corresponding try statement." That is because File.delete() doesn't throw any exceptions at all. Instead, it returns false if it can't delete the file. If it weren't for this compile-time
exception checking of the catch clause, you might have accidentally written a program that fails silently.
"But wait a minute," you seasoned coders are thinking, "people catch java.lang.Exception in sloppy code all the time, and the code in those try blocks doesn't always throw java.lang.Exception, but it still compiles!" You're right, and here we come to the goal of our expedition: what rule does Java really use when checking the exceptions that a catch clause is allowed to catch?
The answer has two parts: The first is that a catch clause catches exceptions of the type of its parameter and its subclasses. This is a valuable language feature as well: a method that declares it throws, for example, javax.naming.NamingException, can really throw any of NamingException's many subclasses. That method's callers needing to know about a specific subclass can write a catch clause for it; those
that don't can simply catch NamingException. Furthermore, if the method's implementation changes in a later version, it can throw NamingException subclasses, which the original implementation did not, and its callers need not change. This flexibility contributes greatly
to API stability as well. (See "Exceptional Practices, Part 1" (JavaWorld, 2001) for more discussion on how to design an exception hierarchy and throws clauses.)
The fact that catch clauses catch subclasses can also get you into a bit of trouble. For example, many readers might have written something like this utility method:
public class ConnectionUtil {
/** Close the connection silently. Keep going even if there's a problem. */
public static void close(Connection connection) {
try {
connection.close();
} catch (Exception e) {
// Log the message (using the JDK 1.4 logger) and move on
Logger.global.log(Level.WARNING, "Couldn't close connection", e);
}
}
}
Errors in closing a connection probably don't mean much to the application, so we just catch and log them. But remember that
RuntimeException extends Exception. If we catch Exception, as in the example, we'll catch RuntimeException too. A null Connection passed into this method is possibly a much more serious situation—probably indicating a programming error—than failure to
close an otherwise valid Connection. If the attempt to call close() throws NullPointerException, you want the error to propagate up the stack to your serious-error handler, not be mistaken for a warning! The solution?
Rather than catching Exception, catch the specific exception thrown by the method you're calling. If it throws more than one exception, catch each individually,
or catch a common superclass (as long as it's not Exception).
Be careful with those common superclasses too: while catching Exception is the most common case where compile-time checking will let you down, catching any exception with subclasses can put you
into a similar situation. You probably want to handle java.io.FileNotFoundException and java.io.EOFException differently, so don't just catch java.io.IOException and lose the distinction. In general, catch an exception superclass only if it's okay for your code to handle each of its
subclasses the same way.
By itself, the fact that catch clauses catch subclasses doesn't explain why the following code fragment compiles, even though the only line of code that might throw an exception has been commented out:
try {
//doSomething(); // Commented out during development
} catch (Exception e) {
Logger.global.log(Level.SEVERE, "Something failed", e);
}
The second piece of the puzzle is that two exceptions are unchecked, completely exempt from compile-time checking: java.lang.Error and java.lang.RuntimeException. Catch clauses may catch these exceptions whether or not they're actually thrown. Section 11.2 of the Java Language Specification (JLS) explains why these exceptions may be thrown without being caught or declared: briefly, Errors come from the JVM and may occur anywhere, and RuntimeExceptions come from many language constructs (listed in section 15.6 of the JLS) and may occur almost anywhere. Checking for them
would not only prove difficult for compiler writers, but would also force programmers to handle situations they can do nothing
about.
Now, back to checking catch clauses: the JLS doesn't say specifically that Error and RuntimeException may be caught whether or not they're actually thrown, but that's how Sun's compiler behaves—an empty try block or one containing
only statements that throw no exceptions of any kind may have catch clauses that catch Error or RuntimeException.
We still haven't explained why Exception, which is checked, can be caught even when it's apparently not thrown. Put the pieces together: RuntimeException extends Exception, so if RuntimeException can be thrown anywhere, Exception can be thrown anywhere too. Similarly, Error extends Throwable, so although Throwable is checked, it can be caught whether or not it is explicitly thrown.
This logical result of Java's exception-checking rules has an awkward consequence. Everyone first reaches for java.lang.Exception when in a hurry. You know the feeling: you're on deadline, you're struggling with some new toolkit, it has a zillion error
conditions, and you just don't have time to deal with them separately. Even if all the toolkit's exceptions extend some common
subclass, you're not going to look it up right now. To keep moving, you wrap your code in a try block and catch Exception. Oops: you just laid a trap for yourself or the next person who edits your code. Later, when you refactor your prototype,
you'll probably split your big try block. Sometimes, that will result in an exception that was thrown in that try block no
longer being thrown. In fact, as in the example above, you might even end up with no code that throws any exceptions at all—but
since you're catching Exception, you won't find it out from the compiler. That means useless try/catch blocks hanging around, code bloat, and confusion.
So don't catch Exception if you can help it—and if, in a pinch, you do, go back later and tighten up those catch clauses.