Chapter 10 Exceptions Notes Flashcards
Exceptions
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.RuntimeException
~~~
- For the exam, the only thing you need to know about
Throwable
is that it’s the parent class of all exceptions, including theError
class. - While you can handle Throwable and Error exceptions, it is not recommended you do so in your application code.
- In this chapter, when we refer to exceptions, we generally mean any class that inherits Throwable, although we are almost always working with the Exception class or subclasses of it.
Checked Exceptions
- A
checked exception
is an exception that must bedeclared
orhandled
by the application code where it is thrown. - In Java, checked exceptions all inherit
Exception
but notRuntimeException
. - Checked exceptions tend to be more anticipated—for example, trying to read a file that doesn’t exist.
-
Checked exceptions also include any class that inherits
Throwable
, but notError
orRuntimeException
. - For example, a
class
that directly extends Throwable would be a checked exception. - For the exam, though, you just need to know about checked exceptions that extend Exception.
- Java has a rule called the handle or declare rule.
- The handle or declare rule means that all checked exceptions that could be thrown within a method are either wrapped in compatible try and catch blocks or declared in the method signature.
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(); } }
- Notice that you’re using two different keywords here.
- The throw keyword tells Java that you want to throw an Exception,
- while the throws keyword simply declares that the method might throw an Exception. It also might not.
- You will see the throws keyword again later in the chapter.
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(); } }
- Notice that the catch statement uses Exception, not IOException.
- Since IOException is a subclass of Exception, the catch block is allowed to catch it.
- We’ll cover try and catch blocks in more detail later in this chapter.
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
- An unchecked exception is any exception that does not need to be
declared
orhandled
by the application code where it is thrown. - Unchecked exceptions are often referred to as runtime exceptions, although in Java, unchecked exceptions include any class that inherits RuntimeException or Error.
- A runtime exception is defined as the RuntimeException class and its subclasses.
- Runtime exceptions tend to be unexpected but not necessarily fatal.
- For example, accessing an invalid array index is unexpected.
- Even though they do inherit the Exception class, they are not checked exceptions.
RUNTIME VS. AT THE TIME THE PROGRAM IS RUN
- A runtime (unchecked) exception is a specific type of exception.
- All exceptions occur at the time that the program is run. (The alternative is compile time, which would be a compiler error.)
- People don’t refer to them as “run time” exceptions because that would be too easy to confuse with runtime! When you see runtime, it means unchecked. (runtime = unchecked)
- An unchecked exception can often occur on nearly any line of code, as it is not required to be handled or declared.
- For example, a NullPointerException can be thrown in the body of the following method if the input reference is null:
void fall(String input) { System.out.println(input.toLowerCase()); }
- We work with objects in Java so frequently, a NullPointerException can happen almost anywhere.
- If you had to declare unchecked exceptions everywhere, every single method would have that clutter!
- The code will compile if you declare an unchecked exception. However, it is redundant.
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.
- Runtime exception,
- Subclass of RuntimeException,
- ok for program to catch
- Not required to handle or declare
- Checked exception
- Subclass of Exception but not subclass of RuntimeException
- ok for program to catch
- Required to handle or declare
- Error
- Subclass of Error
- Not ok for program to catch
- Not Required to handle or declare
Recognizing Exception Classes
You need to recognize three groups of exception classes for the exam:
- RuntimeException,
- checked Exception,
- and Error.
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
Thrown when code attempts to divide by zero -
ArrayIndexOutOfBoundsException
Thrown when code uses an illegal index to access an array -
ClassCastException
Thrown when an attempt is made to cast an object to a class of which it is not an instance -
NullPointerException
Thrown when there is a null reference where an object is required -
IllegalArgumentException
Thrown by the programmer to indicate that a method has been passed an illegal or inappropriate argument -
NumberFormatException
Subclass ofIllegalArgumentException
thrown when an attempt is made to convert a string to a numeric type but the string doesn’t have an appropriate format
ArithmeticException
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
Thrown programmatically when there’s a problem reading or writing a file -
FileNotFoundException
Subclass of IOException thrown programmatically when code tries to reference a file that does not exist
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.
StackOverflowError
When Java calls methods, it puts parameters and local variables on the stack. After doing this a very large number of times, the stack runs out of room and overflows. This is called a StackOverflowError. Most of the time, this error occurs when a method calls itself.
public static void doNotCodeThis(int num) { doNotCodeThis(1); }
The output contains this line:
Exception in thread "main" java.lang.StackOverflowError
Since the method calls itself, it will never end. Eventually, Java runs out of room on the stack and throws the error. This is called infinite recursion. It is better than an infinite loop because at least Java will catch it and throw the error. With an infinite loop, Java just uses all your CPU until you can kill the program.