Runtime errors can be divided into low-level errors that involve violating constraints, such as:
and higher-level, logical errors, such as violations of a method's preconditions:
Logical errors can lead to low-level errors if they are not detected. Often, it is better to detect them (to provide better feedback).
Errors can arise due to:
User error (for example, providing a bad file name or a poorly formatted input file): A good program should be written to anticipate these situations and should deal with them. For example, given a bad file name, an interactive program could print an error message and prompt for a new name.
Programmer error (i.e., a buggy program): These errors should be detected as early as possible to provide good feedback. For some programs it may be desirable to do some recovery after detecting this kind of error, for example, writing out current data.
Note that recovery is often not possible at the point of the error (because the error may occur inside some utility method that doesn't know anything about the overall program or what error recovery should involve). Therefore, it is desirable to "pass the error up" to a level that can deal with it.
There are several possible ways to handle errors:
Write an error message and quit. This doesn't provide any recovery.
Return a special value to indicate that an error occurred. This is the usual approach for C functions (which often return 0 or -1 to signal an error). However:
It doesn't work if the method also returns a value on normal completion and all values are possible (i.e., there is no special value that can be used to signal an error).
It requires that the calling code check for an error. This can reduce the efficiency of the code and is often omitted by programmers out of laziness or carelessness.
It can sometimes make the code more clumsy. For example, if method g might return an error code, one would have to write something like:
ret = g(x); if (ret == ERROR_CODE) { ... } else f(ret);
f(g(x));
Use a reference parameter or a global variable to hold an error code. This solves the first problem of the previous approach, but not the second or third ones.
Use exceptions. This seems to be the method of choice for modern programming languages.
Idea:
Exceptions can be built-in (actually, defined in one of Java's standard libraries) or user-defined. Here are some examples of built-in exceptions with links to their documentation:
Exceptions are caught using try blocks:
try { // statements that might cause exceptions // possibly including method calls } catch ( ExceptionType1 id1 ) { // statements to handle this type of exception } catch ( ExceptionType2 id2 ) { // statements to handle this type of exception . . . } finally { // statements to execute every time this try block executes }
Notes:
Each catch clause specifies the type of one exception and provides a name for it (similar to the way a method header specifies the type and name of a parameter). Java exceptions are objects, so the statements in a catch clause can refer to the thrown exception object using the specified name.
The finally clause is optional; a finally clause is usually included if it is necessary to do some clean-up (e.g., closing opened files).
In general, there can be one or more catch clauses. If there is a finally clause, there can be zero catch clauses.
Here is a program that tries to open a file for reading. The name of the file is given by the first command-line argument .
public class Test { public static void main(String[] args) { Scanner fileIn; File inputFile; try { inputFile = new File(args[0]); fileIn = new Scanner(inputFile); // may throw FileNotFoundException } catch (FileNotFoundException ex) { System.out.println("file " + args[0] + " not found"); } } }
Notes:
java.io.FilenotFoundException: foo at java.io.FileInputStream ... at ... at Test.main ...
Every exception is either a checked exception or an unchecked exception. If a method includes code that could cause a checked exception to be thrown, then:
So, in general, you must always include some code that acknowledges the possibility of a checked exception being thrown. If you don't, you will get an error when you try to compile your code. (The compiler does not check to see if you have included code that acknowledges the possibility of an unchecked exception being thrown.)
How can you determine if an exception is checked or unchecked? The answer lies in the exception hierachy:
+--------+ | Object | +--------+ / | \ \ +-----------+ | Throwable | +-----------+ / \ / \ +-------+ +-----------+ | Error | | Exception | +-------+ +-----------+ / | \ / | \ / | \ +------------------+ +-------------+ | RuntimeException | | IOException | +------------------+ +-------------+ / | \ / | \ / | \ / | \ | +-----------------------+ | | FileNotFoundException | +----------------------+ +-----------------------+ | NullPointerException | +----------------------+
Note that:
If you are writing code that calls a method that might throw an exception, your code can do one of three things:
As mentioned above, if your code might cause a checked exception to be thrown; i.e.,
then your method must include a throws clause listing all such exceptions. For example:
public static void main(String[] args) throws FileNotFoundException, EOFException { // an uncaught FileNotFoundException or EOFException may be thrown here }
Only uncaught checked exceptions need to be listed in a method's throws clause. Unchecked exceptions can be caught in a try block, but if not, they need not be listed in the method's throws clause.
TEST YOURSELF #1
Consider the following program (assume that comments are replaced with actual code that works as specified):
public class TestExceptions { static void e() { // might cause any of the following unchecked exceptions to be thrown: // Ex1, Ex2, Ex3, Ex4 } static void d() { try { e(); } catch (Ex1 ex) { System.out.println("d caught Ex1"); } } static void c() { try { d(); } catch (Ex2 ex) { System.out.println("c caught Ex2"); // now cause exception Ex1 to be thrown } } static void b() { try { c(); } catch (Ex1 ex) { System.out.println("b caught Ex1"); } catch (Ex3 ex) { System.out.println("b caught Ex3"); } } static void a() { try { b(); } catch (Ex4 ex) { System.out.println("a caught Ex4"); // now cause exception Ex1 to be thrown } catch (Ex1 ex) { System.out.println("a caught Ex1"); } } public static void main(String[] args) { a(); } }
Assume that this program is run four times. The first time, method e throws exception Ex1, the second time, it throws exception Ex2, etc. For each of the four runs, say what is printed and whether any uncaught exception is thrown.
Java exceptions are objects. We can define a new kind of exception by defining an instantiable class. This new class must be a subclass of Throwable; as discussed above, they are usually subclasses of Exception (so that they are checked). The simplest way to define a new exception is shown in this example:
public class EmptyStackException extends Exception { }
There is no need to provide any methods or fields; the class can have an empty body as shown above.
Many (if not most) of the exceptions defined in the Java API provide a constructor that takes a string as an argument (in addition to a default constructor). The purpose of the argument is to allow a detailed error message to be given when the exception object is created. You can access this message by calling the getMessage() method on the exception object. Providing this option in the exceptions you write is only slightly more complicated:
public class EmptyStackException extends Exception { public EmptyStackException() { super(); } public EmptyStackException(String message) { super(message); } }
At the point where an error is detected, an exception is thrown using a throw statement:
public class Stack { ... public Object pop() throws EmptyStackException { if (empty()) throw new EmptyStackException(); ... } }
Note that because exceptions are objects, so you cannot simply throw "EmptyStackException" -- you must use "new" to create an exception object. Also, since the pop method might throw the (checked) exception EmptyStackException, that exception must be listed in pop's throws clause.
TEST YOURSELF #2
Question 1: Assume that method f might throw checked exceptions Ex1, Ex2, or Ex3. Complete method g, outlined below, so that:
static void g() throws ... { try { f(); } catch ( ... ) { ... } ... }
Question 2: Consider the following method.
static void f(int k, int[] A, String S) { int j = 1 / k; int len = A.length + 1; char c; try { c = S.charAt(0); if (k == 10) j = A[3]; } catch (ArrayIndexOutOfBoundsException ex) { System.out.println("array error"); throw new InternalError(); } catch (ArithmeticException ex) { System.out.println("arithmetic error"); } catch (NullPointerException ex) { System.out.println("null ptr"); } finally { System.out.println("in finally clause"); } System.out.println("after try block"); }
Part A: Assume that variable X is an array of int that has been initialized to be of length 3. For each of the following calls to method f, say what (if anything) is printed by f and what, if any, uncaught exceptions are thrown by f.
Part B: Why doesn't f need to have a throws clause that lists the uncaught exceptions that it might throw?
try { // statements (including method calls) that might cause exceptions } catch ( ExceptionType1 id1 ) { // code to handle this first type of exception } catch ( ExceptionType2 id2 ) { // code to handle this second type of exception . . . } finally { // code that will execute whenever this try block executes }