I. Basic Concepts A. Exception: 1. An unusual event, either erroneous or not 2. Detectable by either hardware or software 3. May require special processing B. Exception handler: A code segment that processes an exception C. Examples of exceptions 1. End of file 2. Division by 0 3. Bad input 4. Subscript out of range 5. Pointer out of range (seg fault in C) D. How exception handling typically occurs in C 1. Function returns a status flag 2. Program unit that calls the function must check the status flag and take appropriate action II. Exception Handling in Java A. Exception Handlers 1. Specified using a try-catch clause try { // Code that is expected to raise the exception } catch(type variable_name) { // A handler body } ... catch(type variable_name) { // A handler body } finally { // An optional block of code to be executed regardless of // whether the try block fails or succeeds } 2. catch clauses can have only a single formal parameter a. the type must be a class whose ultimate superclass is Throwable (more on this in a moment) b. user-defined classes provide a way of passing multiple parameters. c. the formal parameter may be ignored but the variable name must be provided d. the formal parameter's type allows Java to identify the type of exception which has occurred 3. any variable declared within the try block is de-allocated before the catch statement is executed. Hence if you want access to a variable used or assigned a value within the try block, make sure you declare that variable outside the try block. B. Types of Exceptions 1. All Java exceptions are descendants of the Throwable class. The Throwable class provides three very helpful methods: a. printStackTrace(): prints the standard error message for this class plus a record of the method calls leading up to this exception. This method should not be redefined by subclasses. b. getMessage(), toString(): both methods return a string object that contains the standard error message for this class. Typically each subclass redefines this method. 2. Java pre-defines two exception classes that are subclasses of Throwable a. Error: This class and its descendants are related to errors that are thrown by the Java virtual machine, such as running out of heap memory. i. These exceptions are never thrown by user programs and should never be handled by user programs ii. Your program will be terminated when one of these errors occurs b. Exception: Has two descendents i. IOException: Thrown when an input or output operation has an error ii. RuntimeException: All other run-time errors 1. Java provides a number of pre-defined exception classes such as ArrayIndexOutOfBoundsException and NullPointerException 2. User-defined exceptions should subclass RunTimeException C. Raising Exceptions 1. exceptions are raised using the throw command: throw [expression]; Example: throw new BadOperatorException(operatorToken, "bad operator token"); 2. A throw without an operand can only appear in a handler a. Such a throw reraises the exception and propagates it to an outside program unit. If there is another, more general exception handler that could handle the exception in the same catch list as the handler that reraises this throw, the handler will not be called. b. Throws are matched to exception handlers whose formal parameter matches the type of the thrown expression i. A handler with a formal parameter that is a class type T matches any expression with class type T or whose type is a derived class of T. Example: class Exception extends RunTimeException {...} class StackException extends Exception {...} try { throw StackException(); } All of the following catch statements will catch the above exception: a. catch (StackException e) {...} b. catch (Exception e) {...} c. catch (RunTimeException e) {...} c. The throw is matched by examining the catch statements that follow the try statement in sequential order. That means that exception handlers with more restrictive types should be placed before exception handlers with more general types. i. If the throw cannot be matched locally then it is dynamically propagated up the call chain. d. If you want your catch clause to catch any possible exception that occurs within the try block then have it catch an object of type Throwable. Since Throwable is the top-level exception class, every exception object will be a Throwable object and hence will be caught. Also, since a catch clause with a Throwable object will catch everything, it should be the final catch clause in your list. i. When catching a generic Throwable object, you can get the specific name of the class that was thrown by first using Java's getClass() method to get the class object and then the getName() method to get the class's name: catch (Throwable genericObject) { String name = genericObject.getClass().getName(); ... } D. Continuation 1. After a handler completes execution, control flows to the finally statement, if there is one, and then to the first statement following the try construct (the statement immediately after the last catch statement). 2) The finally clause: In Java, the finally clause provides a way to execute a block of code regardless of whether the try exits normally or abnormally. You might need to do this to clean up system state, such as closing a file. a) The finally statement is placed after the last catch statement b) The code in a finally statement is executed even if a return statement is executed within the try or one of the catch clauses c) The code in a finally statement is executed even if no catch clause catches the exception Example: public class temp { public temp() {} public void execute() throws MyException { try { throw new MyException(); } finally { System.out.println("Leaving execute via finally"); } } public static void main(String [] args) { temp foo = new temp(); try { foo.execute(); } catch (MyException e) { System.out.println("caught MyException in main"); } System.out.println("Leaving main"); } } class MyException extends Throwable {} Executing this code generates the following output: Leaving execute via finally caught MyException in main Leaving main E. It is okay for a method to not handle an exception that it throws. If a method throws an exception that it does not handle, then it should declare that it does not do so by using the throws statement at the beginning of the method: a) Syntax: returnType methodName(params) throws exception-list { ... } where exception-list is a comma separated list of exception names Example: char readToken() throws IOException { return (char) System.in.read(); } Notice that you must worry about exceptions thrown by methods that you call. Hence the throws statement is required both for exceptions that a method explicitly throws and for exceptions that are generated but not handled by methods it calls b) Exception to the rule (pardon the pun :)): If the exception is a subclass of Error or RuntimeException, then the method does not need to specify the exception in the throws list c) If you fail to specify an unhandled exception in the throw list, and it is not subject to the above exception, the Java compiler will generate an error message i) In C++ it is not required for a method to declare that it does not handle an exception (although it is optional and can be done using the throws statement). F. Java Example: Here's the Java code that implements an array and does automatic range-checking. Java does range-checking for you automatically but the example is a nice one. class SafeArray { int data []; int size = 10; // We do not have to declare that SafeArray throws an // OutOfMemoryException since an OutOfMemoryException // is a subclass of RuntimeException public SafeArray(int s) throws ArraySizeError, ArrayRangeError { size = s; if (size <= 0) throw new ArraySizeError(size); data = new int[size]; if (data == null) throw new OutOfMemoryException(); for (int i = 0; i < size; i++) data[i] = 0; } public void set(int index, int value) throws ArrayRangeError { rangeCheck(index); data[index] = value; } public int get(int index) throws ArrayRangeError { rangeCheck(index); return data[index]; } void rangeCheck(int index) throws ArrayRangeError { try { if ((index < 0) || (index >= size)) throw new ArrayRangeError(index, size); } catch (ArrayRangeError e) { System.err.println("caught it"); throw e; } } public static void main(String args[]) { SafeArray myArray = null; try { myArray = new SafeArray(100); myArray.set(3, 10); myArray.get(-1); } catch (OutOfMemoryException e) { System.err.println("Out of memory error occurred while trying to allocate memory for myArray\n"); } catch (ArrayRangeError e) { System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size); } catch (ArrayError e) { // catches the ArraySizeError exception // thrown by SafeArray e.response(); System.exit(1); } for (int i = -2; i <= 100; i++) { try { myArray.set(i, i); if (i == 50) new SafeArray(-10); } catch (ArrayRangeError e) { System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size); } catch (ArrayError e) { e.response(); } } } } abstract class ArrayError extends Throwable { abstract public void response(); } class OutOfMemoryException extends RuntimeException {} class ArraySizeError extends ArrayError { public int size; public ArraySizeError(int s) { size = s; } public void response() { System.err.println("Error: The array size of " + size + " is negative"); } } class ArrayRangeError extends ArrayError { public int index; public int size; public ArrayRangeError(int i, int s) { index = i; size = s; } public void response() {} })