Programming Tutorials Browser Tutorials Articles Struts Tutorials Hibernate Tutorials

  Tutorial: Java Tip 134: When catching exceptions, don't cast your net too wide

Java Tip 134: When catching exceptions, don't cast your net too wide

Tutorial Details:

Java Tip 134: When catching exceptions, don't cast your net too wide
Java Tip 134: When catching exceptions, don't cast your net too wide
By: By Dave Schweisguth
Understand how Java compilers check catch clauses at compile time
ava's compile-time checking does a pretty good job of keeping exceptions safely caged?you can't call a method that throws a checked exception without catching the exception or declaring that your own method throws that exception. (For a great discussion on checked and unchecked exceptions and when to use each, see " Designing with Exceptions " ( JavaWorld, 1998).) The compiler will also sometimes stop you from catching an exception that isn't thrown in the try block, but not always, and not when you need it most. This Java Tip discusses this second compile-time check.
Compile-time checking of throws clauses
First, let's distinguish how Java checks the exceptions a method declares it throws from how it checks the exceptions that a catch clause catches. (In this article, when I say exception with a lowercase e, I mean java.lang.Throwable and its subclasses. When I mean a specific class, like java.lang.Exception , I include the package or at least capitalize the class name.) Initially, the approaches seem quite similar: both indicate the exceptions expected to be thrown by the code block with which they're associated. But while Java requires a method to declare the exceptions that it throws, it doesn't require a method to throw every exception it declares for a good reason: Java allows you to design APIs that remain stable as you add functionality.
Consider this initial version of a home-brewed connection pool:
public class ConnectionPool {
public ConnectionPool() throws ConnectionException {
}
public Connection getConnection() throws ConnectionException {
// Allocate a connection (possibly throwing a ConnectionException or a
// subclass) if necessary, then return it
}
}
While the code in getConnection() might throw a ConnectionException , the constructor does nothing, so in this implementation, the method doesn't really need to declare any exceptions. But in the next version, we might rewrite the class to speed up getConnection() :
public class ConnectionPool {
public ConnectionPool() throws ConnectionException {
// Allocate all the connections we think we'll ever need
}
public Connection getConnection() throws ConnectionException {
// Allocate a connection if necessary (not likely), then return it
}
}
Because we made the constructor in the first version declare ConnectionException , code that uses it doesn't have to change to use the second version. Java trades some checking it could do in the throws clause for the sake of long-term stability in all the other classes that call the constructor?not a bad bargain at all.
Compile-time checking of catch clauses
Catch clauses are a different story from throws clauses. The API stability argument doesn't apply: while a method declaration is part of a class's public interface, a try/catch block is an implementation detail hidden from callers. Not only is there no reason for a catch clause to catch an exception that its try block doesn't throw, guaranteeing that it doesn't do so can catch serious coding errors. For these reasons, Java requires your try blocks to actually throw the exceptions that their catch clauses catch. For example, if you were homesick for your old operating system and wrote the following little utility,
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?
Catch clauses catch subclasses too
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.
Errors and RuntimeExceptions can be caught whether or not they're thrown
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, Error s come from the JVM and may occur anywhere, and RuntimeException s 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 .
Put the pieces together
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


 

Read Tutorial at: Click here to view the tutorial

Rate Tutorial:
Java Tip 134: When catching exceptions, don't cast your net too wide

View Tutorial:
Java Tip 134: When catching exceptions, don't cast your net too wide

Related Tutorials:

Reloading Applets
Reloading Applets
 
Exceptions in Java - JavaWorld - July 1998
Exceptions in Java - JavaWorld - July 1998
 
Designing with exceptions - JavaWorld - July 1998
Designing with exceptions - JavaWorld - July 1998
 
Java Tip 39: The trick to using a basic Java 1.1 network and file class loader - JavaWorld -
Java Tip 39: The trick to using a basic Java 1.1 network and file class loader - JavaWorld - October 1997
 
Java Tip 71: Use dynamic messaging in Java - JavaWorld - April 1999
Java Tip 71: Use dynamic messaging in Java - JavaWorld - April 1999
 
Improve the robustness and performance of your ObjectPool - JavaWorld - August 1998
Improve the robustness and performance of your ObjectPool - JavaWorld - August 1998
 
Singletons vs. class (un)loading - JavaWorld - May 1998
Singletons vs. class (un)loading - JavaWorld - May 1998
 
Java Tip 72: Press Escape to close your Swing dialog windows
Java Tip 72: Press Escape to close your Swing dialog windows
 
Enhance your Java application with Java Native Interface (JNI)
Enhance your Java application with Java Native Interface (JNI)
 
Java performance programming, Part 2: The cost of casting - JavaWorld December 1999
Java performance programming, Part 2: The cost of casting - JavaWorld December 1999
 
Java Tip 90: Accelerate your GUIs - JavaWorld
Java Tip 90: Accelerate your GUIs - JavaWorld
 
J2SE 1.4 premieres Java's assertion capabilities, Part 2
J2SE 1.4 premieres Java's assertion capabilities, Part 2
 
Exceptions: Don't get thrown for a loss
Exceptions: Don't get thrown for a loss
 
Rumble in the jungle: J2EE versus .Net, Part 2
Rumble in the jungle: J2EE versus .Net, Part 2
 
Interesting informations
Interesting informations
 
More on typesafe enums
More on typesafe enums
 
roots of constants classes
roots of constants classes
 
Java Tip 134: When catching exceptions, don't cast your net too wide
Java Tip 134: When catching exceptions, don't cast your net too wide
 
Beware the dangers of generic Exceptions
Beware the dangers of generic Exceptions
 
Light-Weight Visual Components Library for different platform: SWT, J2SE, J2ME, .NET
Light-Weight Visual Components Library for different platform: SWT, J2SE, J2ME, .NET
 
Site navigation
 

 

Send your comments, Suggestions or Queries regarding this site at roseindia_net@yahoo.com.

Copyright © 2006. All rights reserved.