CS102: Exception Handling

Copyright © 1999, Kenneth J. Goldman

Intro


Goal: To allow computation to abort in the middle and communicate that fact (and why) to the caller, so the caller can respond to the failure appropriately.


Overview:
  1. Caller attempts a computation by making a method call
  2. One of two things happen, Either:
    1. the call completes succesfully and the return comes back, OR
    2. the call aborts in the middle and throws and exception
  3. If an exception was thrown, the caller catches it and reacts apprpriately



Example 1: Handling exceptions

Suppose you are writing a method that takes in an int value (supposedly) an index into an array in your object. You are supposed to return the Object reference at that location in the array.


You could check the index against the length of the array first, as in

Object elementAt(int i) {
if (i < myArray.length) {
return my Array[i];
}
else {
return null;
}
}

OR... you could assume everything will be ok and handle the exceptional case only when it arises...

Object elementAt(index i) {
try {
return myArray[i];
}
catch (ArrayIndexOutOfBoundsException e) {
return null;
}
}

Of course, you could still get a NullPointerException if myArray is not initialized. We could catch the NullPointerException, but since myArray is an instance variable (presumably private), we should be able to maintain an invariant that myArray is never null (initialize it in the constructor and never set it to null in any method).

Rule of thumb: Use exception handling to take action for errors that can't be prevented by the object itself.

For example, we can't control what index value might be passed in, but we can guarantee that myArray is never null.



Example 2: Cleaning up after handling an exception

Sometimes, you want certain code to execute at the end of a method.

Java supports this with the finally clause. For example, let's do an example similar to the above, but in this case we have an array of integers, and our method is supposed to return the sum of the first n integers in the array.

int sumUpTo(int n) {
int result = 0;
try {
for (int i - 0; i < n; i++)
result += myArray[i];
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("sumUpTo: Warning: array index too high");
}
finally {
return result; // executed regardless of whether we catch an exception or not
}
}

In this example, what would happen if myarray was null? Would the finally clause be executed? Yes. Normally, if you don't catch a NullPointerException, it would get thrown up to the caller (and if not caught there, it would continue out and up the stack, but when there's a finally clause, it gets executed regardless. And in this case, since the finally clause has a control transfer statement (return result), the NullPointerException wouldn't be propagated).

Example 3: Multiple catch clauses

Sometimes, the code inside a try block may result in more than one type of exception. Depending on which exception arises, you may want it handled a different way. Multiple catch clauses can be used for this -- the first one matching the exception is the one executed. For example, consider the following method that returns a FileReader object for a given file name.

FileReader getReaderForInputFile(String filename) throws FileNot {
try {
return new FileReader(filename);
}
catch(FileNotFoundException fnf) {
System.out.println("File " + filename + " not found");
return null;
}
catch(Exception e) {
System.out.println("Error opening file " + filename + ": " + e);
return null;
}
}

Example 4: Throwing your own exceptions

Sometimes, you may want to throw an exception to indicate that an error has occured within your implementation. For example, suppose you have implemented a LinkedList class, and suppose you provide a getNextElement() method that can be used to walk down the list. However, if the list marker is already at the end, you want to throw an exception:

public getNextElement() throws NoSuchElementException {
if (!atEnd()) {
Object result = marker.next.value;
marker = marker.next;
return result;
}
else {
throw new NoSuchElementException("past end of list");
}
}

Note: Any instance of a class that implements the Throwable interface can be thrown. You can use any of the exceptions already defined, subclass them, or define your own.

Another way:

public getNextElement() throws NoSuchElementException {
try {
Object result = marker.next.value;
marker = marker.next
return result;
}
catch (NullPointerException npe) {
throw new NoSuchElementException("past end of list");
}
}