EXCEPTIONS ========== When a run-time error occurs in Java, the JVM "throws an exception," prints an error message, and quits. Oddly, an exception is a Java object (named Exception), and you can prevent the error message from printing and the program from terminating by "catching" the Exception that Java threw. Purpose #1: Coping with Errors ------------------------------- Exceptions are a way of coping with unexpected errors. By catching exceptions, you can recover. For instance, if you try to open a file that doesn't exist or that you aren't allowed to read, Java will throw an exception. You can catch the exception, handle it, and continue, instead of letting the program crash. try { f = new FileInputStream("~cs61b/pj2.solution"); i = f.read(); } catch (FileNotFoundException e1) { System.out.println(e1); // An exception handler. } catch (IOException e2) { f.close(); // Another exception handler. } What does this code do? (a) It executes the code inside the "try" braces. (b) If the "try" code executes normally, we skip over the "catch" clauses. (c) If the "try" code throws an exception, Java does not finish the "try" code. It jumps directly to the first "catch" clause that matches the exception, and executes that "catch" clause. By "matches", I mean that the actual exception object thrown is the same class as, or a subclass of, the static type listed in the "catch" clause. When the "catch" clause finishes executing, Java jumps to the next line of code immediately after all the "catch" clauses. The code within a "catch" clause is called an _exception_handler_. If the FileInputStream constructor fails to find the file, it will throw a FileNotFoundException. The line "i = f.read()" is not executed; execution jumps directly to the first exception handler. FileNotFoundException is a subclass of IOException, so the exception matches both "catch" clauses. However, only one "catch" clause is executed--the first one that matches. The second "catch" clause would execute if the first were not present. If the FileInputStream constructor runs without error, but the read() method throws an exception (for instance, because a disk track is faulty), it typi- cally generates some sort of IOException that isn't a FileNotFoundException. This causes the second "catch" clause to execute and close the file. Exception handlers are often used to recover from errors and clean up loose ends like open files. Note that you don't need a "catch" clause for every exception that can occur. You can catch some exceptions and let others propagate. Purpose #2: Escaping a Sinking Ship ------------------------------------ Believe it or not, you might want to throw your own exception. Exceptions are the easiest way to move program execution out of a method whose purpose has been defeated. For example, suppose you're writing a parser that reads Java code and analyzes its syntactic structure. Parsers are quite complicated, and use many recursive calls and loops. Suppose that your parser is executing a method many methods deep within the program stack within many levels of loop nesting. Suddenly, your parser unexpectedly reaches the end of the file, because a student accidentally erased the last 50 lines of his program. It's quite painful to write code that elegantly retraces its way back up through the method calls and loops when a surprise happens deep within a parser. A better solution? Throw an exception! You can even roll your own. public class ParserException extends Exception { } This class doesn't have any methods except the default constructor. There's no need; the only purpose of a ParserException is to be distinguishable from other types of exceptions. Now we can write some parser methods. public ParseTree parseExpression() throws ParserException { [loops] if (somethingWrong) { throw new ParserException(); } [more code] } } return pt; } The "throw" statement throws a ParserException, thereby immediately getting us out of the routine. How is this different from a "return" statement? First, we don't have to return anything. Second, an exception can propagate several stack frames down the stack, not just one, as we'll see shortly. The method signature has the modifier "throws ParserException". This is necessary; Java won't let you compile the method without it. "throws" clauses help you and the compiler keep track of which exceptions can propagate where. public ParseTree parse() throws ParserException, DumbCodeException { [loops and code] p = parseExpression(); [more code] } } } public void compile() { ParseTree p; try { p = parse(); p.toByteCode(); } catch (ParserException e1) { } catch (DumbCodeException e2) { } } The parse() method above shows how to define a method that can throw two (or more) exceptions. Since every exception is a subclass of Exception, we could have replaced the two exceptions with "Exception", but then the caller would have to catch all types of Exceptions. We don't want (in this case) to catch NullPointerExceptions or otherwise hide our bugs from ourselves. When parseExpression() throws an exception, it propagates right through the calling method parse() and down to compile(), where it is caught. compile() doesn't need a "throws ParserException" clause because it catches any ParserException that can occur. In this code, the "catch" clauses don't do anything except stop the exceptions. If an exception propagates all the way out of main() without being caught, the JVM prints an error message and halts. You've seen this happen many times. Checked and Unchecked Throwables -------------------------------- The top-level class of things you can "throw" and "catch" is called Throwable. Here's part of the Throwable class hierarchy. Throwable / \ / \ / \ / \ / \ Exception Error / \ / \ IOException RunTimeException AssertionError VirtualMachineError / \ \ NullPointerException ClassCastException OutOfMemoryError The "finally" keyword --------------------- A finally clause can also be added to a "try." FileInputStream f = new FileInputStream("filename"); try { statementX; return 1; } catch (IOException e) { e.printStackTrace(); return 2; } finally { f.close(); } If the "try" statement begins to execute, the "finally" clause will be executed at the end, no matter what happens. "finally" clauses are used to do things that need to be done in both normal and exceptional circumstances. In this example, it is used to close a file. If statementX causes no exception, then the "finally" clause is executed, and 1 is returned. If statementX causes a IOException, the exception is caught, the "catch" clause is executed, and then the "finally" clause is executed. After the "finally" clause is done, 2 is returned. If statementX causes some other class of exception, the "finally" clause is executed immediately, then the exception continues to propagate down the stack. In the example above, we've invoked the method "printStackTrace" on the exception we caught. When an exception is constructed, it takes a snapshot of the stack, which can be printed later. It is possible for an exception to occur in a "catch" or "finally" clause. An exception thrown in a "catch" clause will terminate the "catch" clause, but the "finally" clause will still get executed before the exception goes on. An exception thrown in a "finally" clause replaces the old exception, and terminates the "finally" clause and the method immediately. However...you can nest a "try" clause inside a "catch" or "finally" clause, thereby catching those exceptions as well. Exception constructors ---------------------- By convention, most Throwables (including Exceptions) have two constructors. One takes no parameters, and one takes an error message in the form of a String. class MyException extends Exception { public MyException() { super(); } public MyException(String s) { super(s); } } The error message will be printed if it propagates out of main(), and it can be read by the Throwable.getMessage() method. The constructors usually call the superclass constructors, which are defined in Throwable.