Understanding Exceptions
THE ROLE OF EXCEPTIONS
RETURN CODES VS. EXCEPTIONS
UNDERSTANDING EXCEPTION TYPES
Java has a Throwable superclass for all objects that represent these events.
FIGURE 10.1 Categories of exception
java.lang.Object
^
|
java.lang.Throwable
^ ^
| |
java.lang.Exception java.lang.Error
^
|
java.lang.RuntimeExceptionThrowable is that it’s the parent class of all exceptions, including the Error class.Checked Exceptions
checked exception is an exception that must be declared or handled by the application code where it is thrown.Exception but not RuntimeException.Throwable, but not Error or RuntimeException.class that directly extends Throwable would be a checked exception.Let’s take a look at an example. The following fall() method declares that it might throw an IOException, which is a checked exception:
void fall(int distance) throws IOException {
if(distance > 10) {
throw new IOException();
}
}Now that you know how to declare an exception, how do you instead handle it? The following alternate version of the fall() method handles the exception:
void fall(int distance) {
try {
if(distance > 10) {
throw new IOException();
}
} catch (Exception e) {
e.printStackTrace();
}
}handle or declare
> [!NOTE]
While only checked exceptions must be handled or declared in Java,
unchecked exceptions (which we will present in the next section) may also be handled or declared.
The distinction is that checked exceptions must be handled or declared, while unchecked exceptions can be optionally handled or declared.
Unchecked Exceptions
declared or handled by the application code where it is thrown.RUNTIME VS. AT THE TIME THE PROGRAM IS RUN
void fall(String input) {
System.out.println(input.toLowerCase());
}CHECKED VS. UNCHECKED (RUNTIME) EXCEPTIONS
For the exam, you need to know the rules for how checked versus unchecked exceptions function.
THROWING AN EXCEPTION
On the exam, you will see two types of code that result in an exception.
1. The first is code that’s wrong. Here’s an example:
String[] animals = new String[0]; System.out.println(animals[0]);
This code throws an ArrayIndexOutOfBoundsException since the array has no elements.
That means questions about exceptions can be hidden in questions that appear to be about something else.
calls a method on a null reference or that references an invalid array or List index.
2. The second way for code to result in an exception is to explicitly request Java to throw one. Java lets you write statements like these:
throw new Exception();
throw new Exception("Ow! I fell.");
throw new RuntimeException();
throw new RuntimeException("Ow! I fell.");The throw keyword tells Java you want some other part of the code to deal with the exception.
THROW VS. THROWS
The throw keyword is used as a statement inside a code block to throw a new exception or rethrow an existing exception
throws keyword is used only at the end of a method declaration to indicate what exceptions it supports.
On the exam, you might start reading a long class definition only to realize the entire thing does not compile due to the wrong keyword being used.
Exception e = new RuntimeException(); throw e;
The exception is never instantiated with the new keyword.
throw RuntimeException(); // DOES NOT COMPILE
Since line 4 throws an exception, line 5 can never be reached during runtime. The compiler recognizes this and reports an unreachable code error.
3: try {
4: throw new RuntimeException();
5: throw new ArrayIndexOutOfBoundsException(); // DOES NOT COMPILE
6: } catch (Exception e) {
7: }Types of exceptions and errors
The types of exceptions are important.
Remember that a Throwable is either an Exception or an Error. You should not catch Throwable directly in your code.
Recognizing Exception Classes
You need to recognize three groups of exception classes for the exam:
For the exam, you’ll need to recognize which type of an exception it is and whether it’s thrown by the Java virtual machine (JVM) or a programmer.
For some exceptions, you also need to know which are inherited from one another.
RuntimeException CLASSES
RuntimeException and its subclasses are unchecked exceptions that don’t have to be handled or declared. They can be thrown by the programmer or by the JVM. Common RuntimeException classes include the following:
ArithmeticException ArrayIndexOutOfBoundsException ClassCastExceptionNullPointerExceptionIllegalArgumentException NumberFormatException IllegalArgumentException thrown when an attempt is made to convert a string to a numeric type but the string doesn’t have an appropriate formatArithmeticException
Trying to divide an int by zero gives an undefined result. When this occurs, the JVM will throw an ArithmeticException :
int answer = 11 / 0;
Running this code results in the following output:Exception in thread "main" java.lang.ArithmeticException: / by zero
The thread “main” is telling you the code was called directly or indirectly from a program with a main method. On the exam, this is all the output you will see. Next comes the name of the exception, followed by extra information (if any) that goes with the exception.
ArrayIndexOutOfBoundsException
You know by now that array indexes start with 0 and go up to 1 less than the length of the array—which means this code will throw an ArrayIndexOutOfBoundsException:
int[] countsOfMoose = new int[3]; System.out.println(countsOfMoose[-1]);
This is a problem because there’s no such thing as a negative array index.
Running this code yields the following output:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
At least Java tells us what index was invalid. Can you see what’s wrong with this one?
int total = 0; int[] countsOfMoose = new int[3]; for (int i = 0; i <= countsOfMoose.length; i++) total += countsOfMoose[i];
The problem is that the for loop should have < instead of <=. On the final iteration of the loop, Java tries to call countsOfMoose[3], which is invalid. The array includes only three elements, making 2 the largest possible index. The output looks like this:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
ClassCastException
Java tries to protect you from impossible casts. This code doesn’t compile because Integer is not a subclass of String:
String type = "moose"; Integer number = (Integer) type; // DOES NOT COMPILE
More complicated code thwarts Java’s attempts to protect you. When the cast fails at runtime, Java will throw a ClassCastException:
String type = "moose"; Object obj = type; Integer number = (Integer) obj;
The compiler sees a cast from Object to Integer. This could be okay. The compiler doesn’t realize there’s a String in that Object. When the code runs, it yields the following output:
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.lang.base/java.lang.Integer
Java tells you both types that were involved in the problem, making it apparent what’s wrong.
NullPointerException
Instance variables and methods must be called on a non-null reference. If the reference is null, the JVM will throw a NullPointerException.
It’s usually subtle, such as in the following example, which checks whether you remember instance variable references default to null:
String name;
public void printLength() {
System.out.println(name.length());
}Running this code results in this output:
Exception in thread "main" java.lang.NullPointerException
IllegalArgumentException
IllegalArgumentException is a way for your program to protect itself. You first saw the following setter method in the Swan class in Chapter 7, “Methods and Encapsulation.”
6: public void setNumberEggs(int numberEggs) { // setter
7: if (numberEggs >= 0) // guard condition
8: this.numberEggs = numberEggs;
9: }This code works, but you don’t really want to ignore the caller’s request when they tell you a Swan has –2 eggs. You want to tell the caller that something is wrong—preferably in an obvious way that the caller can’t ignore so that the programmer will fix the problem. Exceptions are an efficient way to do this. Seeing the code end with an exception is a great reminder that something is wrong:
public void setNumberEggs(int numberEggs) {
if (numberEggs < 0)
throw new IllegalArgumentException(
"# eggs must not be negative");
this.numberEggs = numberEggs;
}The program throws an exception when it’s not happy with the parameter values. The output looks like this:
Exception in thread "main" java.lang.IllegalArgumentException: # eggs must not be negative
Clearly this is a problem that must be fixed if the programmer wants the program to do anything useful.
NumberFormatException
Java provides methods to convert strings to numbers. When these are passed an invalid value, they throw a NumberFormatException. The idea is similar to IllegalArgumentException. Since this is a common problem, Java gives it a separate class. In fact, NumberFormatException is a subclass of IllegalArgumentException. Here’s an example of trying to convert something non-numeric into an int:
Integer.parseInt("abc");The output looks like this:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
For the exam, you need to know that NumberFormatException is a subclass of IllegalArgumentException. We’ll cover more about why that is important later in the chapter.
CHECKED EXCEPTION CLASSES
Checked exceptions have Exception in their hierarchy but not RuntimeException. They must be handled or declared. Common checked exceptions include the following:
IOException FileNotFoundException For the exam, you need to know that these are both checked exceptions. You also need to know that FileNotFoundException is a subclass of IOException. You’ll see shortly why that matters.
ERROR CLASSES
Errors are unchecked exceptions that extend the Error class. They are thrown by the JVM and should not be handled or declared. Errors are rare, but you might see these:
1. ExceptionInInitializerError
Thrown when a static initializer throws an exception and doesn’t handle it
2. StackOverflowError
Thrown when a method calls itself too many times (This is called infinite recursion because the method typically calls itself without end.)
3. NoClassDefFoundError
Thrown when a class that the code uses is available at compile time but not runtime
ExceptionInInitializerError
Java runs static initializers the first time a class is used. If one of the static initializers throws an exception, Java can’t start using the class. It declares defeat by throwing an ExceptionInInitializerError. This code throws an ArrayIndexOutOfBounds in a static initializer:
static {
int[] countsOfMoose = new int[3];
nt num = countsOfMoose[-1];
}
public static void main(String... args) { }This code yields information about the error and the underlying exception:
Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.ArrayIndexOutOfBoundsException: -1 out of bounds for length 3
When executed, you get an ExceptionInInitializerError because the error happened in a static initializer. That information alone wouldn’t be particularly useful in fixing the problem. Therefore, Java also tells you the original cause of the problem: the ArrayIndexOutOfBoundsException that you need to fix. The ExceptionInInitializerError is an error because Java failed to load the whole class. This failure prevents Java from continuing.