Understanding Exceptions
Understanding Exception Types
An exception is an event that alters program flow. There’s a Throwable
superclass for all objects that represent these events.
The class Error
is for when something goes so horribly wrong that your program should not attempt to recover from it. From example, the disk drive has disappeared or the program runs out of memory.
Throwable
is the parent class of all exceptions, including Error
.
Checked Exceotions
A checked exception is an exception that must be declared or handled by the application code where it’s thrown. They all inherit from Exception
, but not from Error
or RuntimeException
.
They tend to be more anticipated - from example, trying to read a file that doesn’t exist.
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.
void fall(int distance) throws IOException {
if(distance > 10) {
throw new IOException();
}
}
The throw
keyword tells Java that you want to throw an Exception.
The throws
keyword simply declares that the method might throw an Exception
. It also might not.
To handle the exception
void fall(int distance) {
try {
if(distance > 10) {
throw new IOException();
}
} catch(Exception e) {
e.printStackTrace();
}
}
Unchecked Exceptions
An unchecked exception is any exception that does not need to be declared or handled by the application code where it’s thrown. They’re often referred to as runtime exceptions. They include any class that inherits from RuntimeException
or Error
.
They tend to be unexpected, but not necessarily fatal. For example, NullPointerException
.
void fall(String input) {
sout(input.toLowerCase());
}
void(null);
Throwing an Exception
On the exam, you will see two types of code that result in an exception.
The first is code that’s wrong
// throws ArrayIndexOutOfBoundsException
String[] animals = new String[0];
sout(animals[0]);
(!) Pay special attention to code that calls a method on a null reference or that references an invalid array or List index. (!)
The second way is to explicitly request to throw the Exception.
// all these are valid
throw new Exception();
throw new Exception("Ow! I fell. ");
throw new RuntimeException();
throw new RuntimeException("Ow! I fell. ");
(!) Anytime you see throw
or throws
, make sure the correct one is being used. (!)
throw
is used to throw a new exception
throws
is used only at the end of a method declaration.
An Exception
is an Object
. You can store it in a variable.
Exception e = new RuntimeException();
throw e;
They need to be instantiated
throw RuntimeException(); // DOES NOT COMPILE
Be careful with unreacheable code.
try {
throw new RuntimeException();
throw new ArrayIndexOutOfBoundsException(); // DOES NOT COMPILE!
} catch(Exception ex) {
}
Recognizing Exception Classes
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.
- ArithmeticException - Thrown when code attempts to divide by zero
- ArrayIndexOutOfBoundsException - code uses an illegal index to access an array
- ClassCastException - an attempt is made to cast an object to a class of which is not an instance
- NullPointerException - there’s a null reference where an object is required
- IllegalArgumentException - thrown by the programmer to indicate that a method has been passed an illegal or inappropiate argument
- NumberFormatException - Subclass of
IllegalArgumentException
. When an attempt is made to convert a String to a numeric type, but it doesn’t have an appropiate format
Checked Exception Classes
They have Exception
in their hierarchy but not RuntimeException
. They must be handled or declared.
- IOException - thrown programmatically when there’s a problem reading or writing a file.
- FileNotFoundException - subclass of
IOException
. thrown when code tries to reference a file that does not exist.
Error Classes
Errors are unchecked exceptions that extend from Error
. They’re thrown by the JVM and should not be handled or declared. They’re rare.
- ExceptionInInitializerError - Thrown when a static initializer throws an exception and doesn’t handle it.
static {
int[] countsOfMoose = new int[3];
int num = countsOfMoose[-1];
}
- StackOverflowError - a method calls itself too many times (infinite recursion).
- NoClassDefFoundError - a class that the code uses is available at compile time but not at runtime.
Handling Exceptions
Using try and catch Statements
Java uses a try statement to separate the logic that might throw an exception, from the logic to handle that exception.
The curly braces are required for try and catch blocks.
try {
} catch(Exception ex) {
}
Some invalid try statements
try // DOES NOT COMPILE
fall();
catch(Exception ex)
sout();
try { // DOES NOT COMPILE
fall();
}
Chaining catch Blocks
First, you must be able to recognize if the exception is a checked or an unchecked exception.
Second, you need to determine whether any of the exceptions are subclasses of the others.
// unchecked exceptions
class AnimalsOutForAWalk extends RuntimeException { }
class ExhibitClosed extends RuntimeException { }
class ExhibitClosedForLunch extends ExhibitClosed { }
public void visitPorcupine() {
try {
seeAnimal();
} catch (AnimalsOutForAWalk e) { // first catch block
System.out.print("try back later");
} catch (ExhibitClosed e) { // second catch block
System.out.print("not today");
}
}
A rule exists for the order of the catch blocks. Java looks at them in the order they appear. If it’s impossible for one of the catch blocks to be executed, a compiler error about unreachable code occurs.
(!) This happens when a superclass catch block appears before a subclass catch block. Pay attention to any subclass exceptions. (!)
public void visitMonkeys() {
try {
seeAnimal();
} catch(ExhibitClosedForLunch e) { // subclass
sout("try back later");
} catch(ExhibitClosed e) { // superclass
sout("not today");
}
}
If the order was reversed, they wouldn’t compile as the second one couldn’t ever be reached.
public void visitMonkeys() {
try {
seeAnimal();
} catch(ExhibitClosed e) {
sout("try back later");
} catch(ExhibitClosedForLunch e) { // DOES NOT COMPILE
sout("not today");
}
}
Applying a Multi-catch Block
If we want to apply the same result to multiple exceptions, we may re-use code.
public static void main(String args[]) {
try {
soutln(Integer.parseInt(args[1]));
} catch(ArrayIndexOutOfBoundsException | NumberFormatException ex) {
sout("Missing or invalid input");
}
}
(!) The exam might try to trick you with invalid syntax (!)
catch(Exception1 e | Exception2 e) // DOES NOT COMPILE
catch(Exception1 e1 | Exception2 e2) // DOES NOT COMPILE
catch(Exception1 | Exception2 e) // COMPILES
Multi-cath is intented to be used for exceptions that aren’t related, and it prevents you from specifying redundant types in a multi-catch.
try {
throw new IOException();
} catch(FileNotFoundException | IOException p) {} // DOES NOT COMPILE
This fails, because FileNotFoundException
is already caught by the alternative IOException
.
The Exceptions’ order doesn’t matter inside a single block.
You can’t list the same exception type more than once in the same try
statement.
The more general superclasses must be caught after their subclasses.
Adding a finally Block
The try
statement also lets you run code at the end with a finally
clause, regardless of whether an exception is thrown.
try {
seeAnimals();
fall();
} catch(Exception e) {
getHug();
} finally {
seeMoreAnimals();
}
If an exception is thrown, the finally
block is run after the catch block. If no exception is thrown, the finally
block is run after the try
block completes.
The exam will try to trick you with missing clasuses, or clasuse in the wrong order.
try { // DOES NOT COMPILE
fall();
} finally {
sout("all better");
} catch(Exception e) {
sout("get up");
}
try { // DOES NOT COMPILE
fall();
}
try { // COMPILES
fall();
} finally {
sout("all better");
}
The catch
block is not required if finally
is present.
StringBuilder sb = new StringBuilder();
try {
sb.append("t");
} catch(Exception e) {
sb.append("c");
} finally {
sb.append("f");
}
sb.append("a");
sout(sb.toString());
This will print tfa
.
If there’s a finally
block, this will always be executed.
int goHome() {
try {
sout("1");
return -1;
} catch(Exception e) {
sout("2");
return -2;
} finally {
sout("3");
return -3;
}
}
This will print 13
and it will always return -3
.
There’s one exception to this: System.exit(0)
.
try {
System.exit(0);
} finally {
sout("Never going to get here"); // not printed.
}
Finally Closing Resources
It avoids resource leaks by closing resources.
public void readFile(String file) {
try (FileInputStream is = new FileInputStream("myfile.txt")) {
// do something
} catch(IOException e) {
e.printStackTrace();
}
}
As soon as a connection passes out of scope, Java will attempt to close it.
Behind the scenes, the compiler replaces this try-with-resources block for a try with a finally block. You can still create a programmer defined finally block, just be aware that the implicit one will be called first.
Basics of Try-with-Resources
One or more resources can be opened in the try clause. When there’re multiple resources opened, they’re closed in the reverse order from which they were created.
try (FileInputStream in = new FileInputStream("data.txt");
FileOutputStream out = new FileOutputStream("output.txt")) {
}
The catch block is optional in try-with-resources!
You can’t put any random class inside the try-with-resources statement. Java requires a class which implements AutoCloseable
interface, which includes a void close()
method.
Declaring Resources
While try-with-resources does support declaring multiple variables, each variable must be declared in a separate statement.
try (MyFileClass is = new MyFileClass(1), // DOES NOT COMPILE
os = new MyFileClass(2)) {
}
try (MyFileClass ab = new MyFileClass(1),
MyFileClass cd = new MyFileClass(2)) { // DOES NOT COMPILE
}
You can declare a resource using var
as the data type.
try (var f = new FileInputStream("it.txt")) {
// process file
}
Scope of Try-with-Resources
The resources created are in scope only within the try block.
try (Scanner s = new Scanner(System.in)) {
s.nextLine();
} catch(Exception e) {
s.nextLine(); // DOES NOT COMPILE
} finally {
s.nextLine(); // DOES NOT COMPILE
}
The problem is that Scanner
has gone out of scope. You can’t accidentally use an object that has been closed.
Following Order of Operation
- Resources are closed after the try clause ends and before any catch/finally clauses.
- Resources are closed in the reverse order from which they were created.
public class MyFileClass implements AutoCloseable {
private final int num;
public MyFileClass(int num) { this.num = num; }
public void close() {
sout("Closing: " + num);
}
}
public static void main(String... xyz) {
try(MyFileClass a1 = new MyFileClass(1);
MyFileClass a2 = new MyFileClass(2)) {
throw new RuntimeException();
} catch (Exception e) {
sout("ex");
} finally {
sout("finally");
}
}
Since the resources are closed in reverse order, this prints
Closing: 2
Closing: 1
ex
finally
Throwing Additional Exceptions
There’re situations where we need to catch an exception inside of a catch or finally block.
public static void main(String[] a) {
FileReader reader = null;
try {
reader = read();
} catch(IOException ex) {
try {
if(reader != null)
reader.close();
} catch (IOException inner) {
//
}
}
}
private static FileReader read() throws IOException {
//
}
The following example shows that only the last exception to be thrown matter.
try {
throw new RuntimeException();
} catch(RuntimeException ex) {
throw new RuntimeException();
} finally {
throw new Exception();
}
Another example
public String exceptions() {
StringBuilder result = new StringBuilder();
String v = null;
try {
try {
result.append("before_");
v.length();
result.append("after_");
} catch(NullPointerException e) {
result.append("catch_");
throw new RuntimeException();
} finally {
result.append("finally_");
throw new Exception();
}
} catch(Exception e) {
result.append("done");
}
return result.toString();
}
This prints before_catch_finally_done
Calling Methods That Throw Exceptions
When you’re calling a method that throws an exception, the rules are the same as within a method.
class NoMoreCarrotsException extends Exception {}
public class Bunny {
private static void eatCarrot() throws NoMoreCarrotsException {
}
public static void main(String[] args) {
eatCarrot(); // DOES NOT COMPILE
}
}
Checked exceptions must be handled or declared. The solution would be either of this.
public static void main(String[] args)
throws NoMoreCarrotsException {
eatCarrot();
}
public static void main(String[] args) {
try {
eatCarrot();
} catch(NoMoreCarrotsException ex) {
//
}
}
The reverse is also true, we cannot try to catch an exception when a method doesn’t include it in its declaration.
private void eatCarrot() {}
public void bad() {
try {
eatCarrot();
} catch(NoMoreCarrotsException ex) { // DOES NOT COMPILE
// ex
}
}
(!) When you see a checked exception declared inside a catch block on the exam, check and make sure the code in the associated try block is capable of throwing the exception or a subclass of the exception. If it’s not capable, the code is unreachable and does not compile.
Remember that this rules does not extend to unchecked exceptions or exceptions declared in a method signature. (!)
Declaring and Overriding Methods with Exceptions
When a class overrides a method from a superclass or implements a method from an interface, it’s not allowed to add new checked exceptions to the method signature.
class CanNotHopException extends Exception { }
class Hopper {
public void hop() {}
}
class Bunny extends Hopper {
public void hop() throws CanNotHopException {} // DOES NOT COMPILE
}
An overriden method in a subclass is allowed to declare fewer exceptions than the superclass or interface. This is legal because callers are already handling them.
class Hopper {
public void hop() throws CanNotHopException { }
}
class Bunny extends Hopper {
public void hop() { }
}
Similarly, a class is allowed to declare a subclass of an exception type.
class Hopper {
public void hop() throws Exception {}
}
class Bunny extends Hopper {
public void hop() throws CanNotHopException {}
}
The following code is legal because it has an unchecked exception in the subclass.
class Hopper {
public void hop() {}
}
class Bunny extends Hopper {
public void hop() throws IllegalStateException {}
}
The declaration is redundant. Methods are free to throw any unchecked exceptions they want without mentioning them in the method declaration.
Printing an Exception
There’re three ways to print an exception.
- You can let Java print it out
- Print just the message
- Print where the stack trace comes from
private static void hop() {
throw new RuntimeException("cannot hop");
}
public static void main(String[] args) {
try {
hop();
} catch(Exception ex) {
sout(ex);
sout(ex.getMessage());
ex.printStackTrace();
}
}
They result in the following output
java.lang.RuntimeException: cannot hop
cannot hop
java.lang.RuntimeException: cannot hop
at Handling.hop(Handling.java:15)
at Handling.main(Handling.java:7)
Summary
An exception indicates something unexpected happened. A method can handle an exception by catching it or declaring it for the caller to deal with. Many exceptions are thrown by Java libraries. You can throw your own exceptions with code such as throw new Exception().
All exceptions inherit Throwable. Subclasses of Error are exceptions that a programmer should not attempt to handle. Classes that inherit RuntimeException and Error are runtime (unchecked) exceptions. Classes that inherit Exception, but not RuntimeException, are checked exceptions. Java requires checked exceptions to be handled with a catch block or declared with the throws keyword.
A try statement must include at least one catch block or a finally block. A multicatch block is one that catches multiple unrelated exceptions in a single catch block. If a try statement has multiple catch blocks chained together, at most one catch block can run. Java looks for an exception that can be caught by each catch block in the order they appear, and the first match is run. Then execution continues after the try statement. If both catch and finally throw an exception, the one from finally gets thrown.
A try-with-resources block is used to ensure a resource like a database or a file is closed properly after it is created. A try-with-resources statement does not require a catch or finally block but may optionally include them. The implicit finally block is executed before any programmer-defined catch or finally blocks.
RuntimeException classes you should know for the exam include the following:
- ArithmeticException
- ArrayIndexOutOfBoundsException
- ClassCastException
- IllegalArgumentException
- NullPointerException
- NumberFormatException
IllegalArgumentException is typically thrown by the programmer, whereas the others are typically thrown by the standard Java library.
Checked Exception classes you should know for the exam include the following:
- IOException
- FileNotFoundException
Error classes you should know for the exam include the following:
- ExceptionInInitializerError
- StackOverflowError
- NoClassDefFoundError
For the exam, remember that NumberFormatException is a subclass of IllegalArgumentException, and FileNotFoundException is a subclass of IOException. When a method overrides a method in a superclass or interface, it is not allowed to add checked exceptions. It is allowed to declare fewer exceptions or declare a subclass of a declared exception. Methods declare exceptions with the keyword throws.
Exam Essentials
Understand the various types of exceptions. All exceptions are subclasses of java.lang.Throwable. Subclasses of java.lang.Error should never be caught. Only subclasses of java.lang.Exception should be handled in application code.
Differentiate between checked and unchecked exceptions. Unchecked exceptions do not need to be caught or handled and are subclasses of java.lang.RuntimeException and java.lang.Error. All other subclasses of java.lang.Exception are checked exceptions and must be handled or declared.
Understand the flow of a try statement. A try statement must have a catch or a finally block. Multiple catch blocks can be chained together, provided no superclass exception type appears in an earlier catch block than its subclass. A multi-catch expression may be used to handle multiple exceptions in the same catch block, provided one exception is not a subclass of another. The finally block runs last regardless of whether an exception is thrown.
Be able to follow the order of a try-with-resources statement. A try-with-resources statement is a special type of try block in which one or more resources are declared and automatically closed in the reverse order of which they are declared. It can be used with or without a catch or finally block, with the implicit finally block always executed first.
Identify whether an exception is thrown by the programmer or the JVM. IllegalArgumentException and NumberFormatException are commonly thrown by the programmer. Most of the other unchecked exceptions are typically thrown by the JVM or built-in Java libraries.
Write methods that declare exceptions. The throws keyword is used in a method declaration to indicate an exception might be thrown. When overriding a method, the method is allowed to throw fewer or narrower checked exceptions than the original version.
Recognize when to use throw versus throws. The throw keyword is used when you actually want to throw an exception—for example, throw new RuntimeException(). The throws keyword is used in a method declaration.