The special case of RuntimeException
The first example in this chapter was
if(t == null)
throw new NullPointerException();
It can be a bit horrifying to think that you must check for null on every 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.
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 (or any type inherited from RuntimeException), because they are unchecked exceptions. Because they indicate bugs, you don’t usually catch a RuntimeException—it’s dealt with automatically. If you were forced to check for RuntimeExceptions, your code could get too messy. Even though you don’t typically catch RuntimeExceptions, in your own packages you might choose to throw some of the RuntimeExceptions.
What happens when you don’t catch such exceptions? Since the compiler doesn’t enforce exception specifications for these, it’s quite plausible that a RuntimeException could percolate all the way out to your main( ) method without being caught. To see what happens in this case, try the following example:
//: c09:NeverCaught.java
// Ignoring RuntimeExceptions.
// {ThrowsException}
import com.bruceeckel.simpletest.*;
public class NeverCaught {
private static Test monitor = new Test();
static void f() {
throw new RuntimeException("From f()");
}
static void g() {
f();
}
public static void main(String[] args) {
g();
monitor.expect(new String[] {
"Exception in thread \"main\" " +
"java.lang.RuntimeException: From f()",
" at NeverCaught.f(NeverCaught.java:7)",
" at NeverCaught.g(NeverCaught.java:10)",
" at NeverCaught.main(NeverCaught.java:13)"
});
}
} ///:~
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.
So the answer is: If a RuntimeException gets all the way out to main( ) without being caught, printStackTrace( ) is called for that exception as the program exits.
Keep in mind that you can only ignore exceptions of type RuntimeException (and subclasses) in your coding, since all other handling is carefully enforced by the compiler. The reasoning is that a RuntimeException represents a programming error:
- An error you cannot anticipate. For example, a null reference that is
outside of your control.
- An error that you, as a programmer, should have checked for in your code
(such as ArrayIndexOutOfBoundsException where you should have paid
attention to the size of the array). An exception that happens from point #1
often becomes an issue for point #2.
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.