Session 16: Exceptions

What is an exception? (Section 8.1)
Catching exceptions
    Getting exception information
    Checked and unchecked exceptions
Throwing exceptions
    The throw statement
    The throws clause

What is an exception?

Textbook: Section 8.1

Sometimes a program will generate an exception while it is running, indicating that something unusual has occurred that constitutes a major problem. Generally, the program aborts with an error message describing the error. This process of generating an exception is called raising or throwing an exception.

Consider the following program that works with arrays.

import csbsju.cs160.*;

public class Example {
    public static void main(String[] args) {
        int[] arr = new int[2];
        for(int i = 0; i < 2; i++) arr[i] = IO.readInt();
        for(int i = 2; i >= 0; i--) IO.println(arr[i]);
    }
}
This program compiles fine. But when you run it, it would show something like the following.
1
2
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
        at Example.main(Example.java:7)
What we see here is an error message indicating that an exception has been thrown.

This error message includes a lot of useful information. First, it says that there was an ArrayIndexOutOfBounds exception, indicating that the program attempted to accessed an array beyond its last index. Then it lists exactly where the computer was in the program at the time the exception was encountered: It was in the main() method of the Example class, specifically at line 7 in the file ``Example.java.'' All of this information is listed in the error message.

To get a better handle on what happened, we could go back to ``Example.java'' and find what's at line 7.

        for(int i = 2; i >= 0; i--) IO.println(arr[i]);
We know we accessed an array index off the end of some array. After inspecting this for a short while, we'll notice that this code accesses array index 2 of arr, which only has two elements in it (and hence its only valid indices are 0 and 1). This is what caused the exception.

Exceptions arise in many other circumstances.

Catching exceptions

Java has a way of writing a program that can catch an exception, via what's called a try block. A try block looks vaguely like an if...else statement.

import csbsju.cs160.*;

public class Example {
    public static void main(String[] args) {
        IO.print("Number of scores? ");
        int num_scores = IO.readInt();
        IO.println("Type 'em.");
        int total = 0;
        for(int i = 0; i < num_scores; i++) {
            total += IO.readInt();
        }
        try {
            IO.println("Average: " + (total / num_scores));
        } catch(ArithmeticException e) {
            IO.println("Error: Can't average over zero scores");
        }
        IO.println("Good-bye");
    }
}
Here, we've told the JVM to print the quotient of total and num_scores. We've placed it in a try block of braces, and followed it up with a catch expression. Inside the parentheses after catch, we've indicated that we specifically want to catch an ArithmeticException, and between the braces, we've told the computer what to do in the case that, in the process of completing the code within the try block, an ArithmeticException is raised.

When the computer reaches a try block, it executes all the code within the block, in sequence. If no exception is raised while performing the code, the computer skips over the catch block and does the code afterward.

Number of scores? 2
Type 'em.
3
5
Average: 4
Good-bye

If, however, an exception is raised during the try block, the computer immediately skips to the catch block and see whether the exception matches the exception named in the parentheses. If so, it performs the code given in the following braces and then performs the code afterward.

Number of scores? 0
Type 'em.
Error: Can't average over zero scores
Good-bye
In this example, the computer never executes the for loop body, since i immediately reaches its destination of num_scores. It gets into the try block and tries to divide total by num_scores, which results in division by 0. The JVM raises an ArithmeticException, and so the computer immediately skips over the rest of the try block to check out the catch clause. In this case, the raised exception matches the exception named in the clause, so the computer does the code within the catch clause and finally does whatever follows the clause.

This example is a bit contrived. A better (i.e., more readable) technique would be to avoid the exception in the first place, rather than try to catch it after it occurs.

if(num_scores != 0) {
    IO.println("Average: " + (total / num_scores));
} else {
    IO.println("Error: Can't average over zero scores");
}

Getting exception information

The catch clause actually has a parameter variable, named within the parentheses, with which one can access information about the raised exception. Above, I called this parameter variable e. This is an object like any other, and it will have a few methods. (But I can actually call it anything I want.)

For example, all exception objects contain a method called getMessage(), which returns a String holding additional information about the exception.

try {
    IO.println("Average: " + (total / num_scores));
} catch(ArithmeticException except) {
    IO.println("Error in averaging: " + except.getMessage());
}

Exception objects could conceivably carry additional data about the offending information. Generally, they only carry a string describing the problem, however.

Checked and unchecked exceptions

Sometimes, the Java compiler will insist that you catch a particular type of exception. These are called unchecked exceptions because Java will not insist that they be checked. The exceptions we've mentioned so far have all been unchecked.

One example of a method that throws a checked exception is Thread.sleep(), which will halt for a number of milliseconds specified as a parameter.

Thread.sleep(20); // won't compile: exception not caught
This method can throw an InterruptedException, indicating that the method wasn't able to sleep as long as requested because of some other signal it received. Java will refuse to compile any code that calls Thread.sleep() but doesn't specifically handle this exception.

So what you must do is place the code that uses Thread.sleep() within a try clause, followed by a catch clause to handle an InterruptedException.

try {
    Thread.sleep(20);
} catch(InterruptedException e) { }
Notice that here, we've told Java to do nothing when the exception occurs - essentially, we just want to ignore the exception. For this particular exception, this is frequently the best thing you can do.

(In fact, we used Clock.sleep() in Labs 2 and 4, defined in the csbsju.cs160 package. We did this instead of using the Thread class in the java.lang package because doing that instead would force us to worry about this checked exception, and we hadn't studied exceptions yet.)

Throwing exceptions

The throw statement

You can have your own code initiate an exception itself, using the throw statement.

throw new Exception("Hi there");
The throw statement is structured similarly to a return statement - you have the word throw, followed by an expression, followed by a semicolon.

In this case, we've created a new Exception object, using the constructor that takes a string as its parameter. This sets of the message returned by the exception's getMessage() method.

The throws clause

Throwing an exception from a method is useful when you want to indicate that the exception failed. Until now, we've been doing this via return values, but this is problematic: It reduces the number of possible return values. (For example, IO.readInt() will return Integer.MIN_VALUE if the user types a non-number. But this effectively disallows the user from typing the integer value Integer.MIN_VALUE also.) And it allows people to write programs where they might unintentially ignore a potential problem. (You've probably been ignoring this case with IO.readInt().)

When you write a method that might throw an exception, you must include a throws clause to make it clear to the compiler that you didn't just fail to catch the exception.

public class IntArray {
    private int[] arr;
    private int arr_elts;

    // other method definitions

    public int remove() throws Exception {
        if(arr_elts == 0) throw new Exception("array is already empty");
        --arr_elts;
        return arr[arr_elts];
    }
}

Actually, it's pretty rare that people write programs that throw Exception objects around. It's more frequent that they throw instances of other, more specific exceptions. The following alternative throws a NoSuchElementException object (defined in the java.util package)

import java.util.*;

public class IntArray {
    private int[] arr;
    private int arr_elts;

    // other method definitions

    public int remove() throws NoSuchElementException {
        if(arr_elts == 0) throw new NoSuchElementException("array is already empty");
        --arr_elts;
        return arr[arr_elts];
    }
}
Given this, then somebody who wants to catch the exception would catch a NoSuchElementException instead.
public static void run() {
    IntArray array = new IntArray();
    // : (omitting code that uses IntArray methods to add to array)

    try {
        array.remove();
    } catch(NoSuchElementException e) {
        System.err.println("could not remove: " + e.getMessage());
    }
}
(In fact, NoSuchElementException happens to be an unchecked exception, so catching it is optional.)