Lecture 20


An exception signals an exceptional occurrence:

  1. A serious bug
  2. A recoverable error (such as "file not found" when trying to open a nonexistent file)

Exceptions are thrown when the execution cannot proceed.  In Java, there are many kinds of exceptions, and they can arise for many reasons:

ArrayIndexOutOfBoundsException: thrown when an array index is negative, or is greater than or equal to the length of the array.  Example:

int[] a = new int [3];
a[3] = 42; // ArrayIndexOutOfBoundsException is thrown

ClassCastException: thrown when a downcast fails.  Example:

ArrayList list = new ArrayList();
list.add(new Integer(3));
String s = (String) list.get(0); // ClassCastException is thrown

The problem in the code above is that the downcast is trying to convert an Object reference into a String reference, but the object the reference refers to is really an Integer.

FileNotFoundException: thrown when opening a nonexistent file for input, or trying to access a file in a directory that does not exist.  Example:

FileInputStream in = new FileInputStream("myfile.txt");
// throws FileNotFoundException if "myfile.txt" does not exist

Consequences of An Exception

When an exception is thrown, one of two possibilities occurs:

  1. The exception is handled.  This means that the program explicitly anticipates the possibility of the exception, and contains code to recover from the exception.  Handling an exception involves a try/catch block.

  2. The exception is not handled, and terminates the program.

Exception Objects and Classes

Exceptions are objects, and like all objects in Java, they are instances of a class.  Java has a number of standard exception classes: some of the important ones are shown in the following diagram:

All exceptions are subclasses of the Throwable class.

Error, RuntimeException, and their subclasses are unchecked exceptions.  These generally represent bugs in the program (such as ArrayIndexOutOfBoundsException, which indicates an invalid array index was used), or unrecoverable failures (such as OutOfMemoryError, which occurs when the program needs to create a new object but has exhausted available memory).  They are unchecked because they may be thrown out of a method without being declared.  We will see what that means in a moment.

Throwable, Exception, and their subclasses (that are not subclasses of Error or RuntimeException) are checked exceptions.  A checked exception is generally a recoverable exception, in the sense that the program should be able to meaningfully handle the exception and then continue executing.  Checked exceptions must be declared if they are thrown out of a method.

Declaring an Exception

If it is possible for a checked exception to be thrown in a method, and the method does not handle the exception, then the method must declare the exception.

As an example, let's say we are writing the saveToFile method of a Photo class.  Most methods that work with files and input/output in Java can throw IOException:

// in the Photo class...

public void saveToFile(String fileName) {
  FileOutputStream out = new FileOutputStream(fileName);
  byte[] data = getPhotoData();

All of the code marked in red can result in an IOException being thrown.  The reason is that each of these methods (the FileOutputStream class's constructor, write method, and close method) are all declared to throw IOException.  Because IOException is a checked exception, the compiler will issue an error when we try to compile the class.  The problem is that IOException is neither handled in the method nor declared by the method.

The easiest way to fix the problem is to declare that the method throws IOException:

public void saveToFile(String fileName) throws IOException {
  FileOutputStream out = new FileOutputStream(fileName);
  byte[] data = getPhotoData();

By declaring the exception, saveToFile shifts the responsibility for a possible IOException onto its caller.

Handling an Exception

Handling an exception requires a try/catch construct:

The try block contains code that may throw an exception, and the catch block contains code that handles the exception.

The purple arrows indicate the non-exception path through the try/catch: starting at point S, execution enters the try block and the statements inside it are executed.  When the end of the try block is reached, control jumps to point E, which is just after the closing brace of the catch block.

The red arrows indicate an exception path through the try/catch.  Execution enters the try block from point S as usual.  However, at some point inside the try block, an exception occurs, as marked by the red X.  Exception jumps immediately to the beginning of the catch block, and the statements inside the catch block (which handle the exception) are executed.  Finally, the end of the catch block is reached and control reaches the end point E.

Note that the catch block is declared to catch a particular exception type.  That means that the catch block only handles exceptions that are instances of that particular exception type or a subtype.  The identifier associated with the catch block is a variable name that allows you to refer to the exception object within the catch block.

Here is a simple example of handling an IOException, using our earlier example of the Photo class:

Photo p = ...
String fileName = ...

try {
} catch (IOException e) {
  System.err.println("Could not save photo: " + e.toString());

In this example, the IOException that may be thrown by the saveToFile method is handled by displaying an error message to the user.  Note that the toString method is invoked on the IOException object: this is a useful way to get information about why the exception occurred.

When To Handle Exceptions?

In general, try/catch should be used sparingly, and only in places in the program where there is a reasonable way to recover from the error.  Therefore, you should declare methods as throwing an exception more often than you handle exceptions with a method.