Exception Handling

What Are Exceptions?

Exceptions are used to notify you that something unusual or unexpected has happened. They are often used for error handling, but any kind of unexpected condition can be handled with an exception. Before the advent of exceptions, if you called a function and wanted to know whether that function executed properly, you had to either check the return value of that function, or even worse, verifify several global variables designed to store error codes. The notion of checking the return value is still valid in many cases, but this kind of programming can have several disadvantages:
  1. It can make the business logic of your application hard to grasp because of all the "if some error has occured" statements that are intermixed with the main logical flow of the code
  2. Nothing forces you to check for return values or global error flags, which can be a source of bugs if you forget to do so
  3. If you don't know what the return values or global error flags are, you have to consult the documentation. As we will see, checked exceptions are "self documenting."
Exception, used wisely, can alleviate these problems. Consider the example below:

public class GuessingGame {
public void playGame() {
do {
printPrompt();
} while (getGuess());
}

public boolean processGuess() {
try {
int guess = getGuess();
//do not need to check for errors here
if (guess == mystery) {
System.out.println("you win!");
return false;
}  else {
System.out.println("try again?");
String response = getResponse();
//do not need to check for errors here
if (response.toUppercase().equals("N")) {
System.out.println("thanks for playing!");
return false;
} else {
return true;
}
}
} catch (InvalidInputException e) {
System.out.println(e.getMessage());
}
}
}

Exception Syntax

The basic syntax for handling exception is as follows:
public void readFile() {
try {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
} catch (fileOpenFailed) {
doSomething;
} catch (sizeDeterminationFailed) {
doSomething;
} catch (readFailed) { //<----- this exception must be more general than, or at the same level as, the exception above it
doSomething;
} finally { //<----- the optional finally block make sure that a piece of code executes even if an exception is thrown
try {
close the file;
} catch (Exception e) { /* ignore errors */ }
}
}
If you have a "try" block, then you must have either at least one "catch" block or a "finally" block. You can have a chain of as many "catch" blocks as you wish. Often, there is a "catch Exception (e)" block at the end of such a chain to clean up any un-identified errors.

If in your function you do not wish to handle an exception, you can pass it up to the calling function using the "throws" clause, e.g.
public void readFile() throws fileOpenFailed, sizeDeterminationFailed, readFailed {
try {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
} finally {
try {
close the file;
} catch (Exception e) { /* ignore errors */ }
}
}

Errors, Checked Exceptions, and Runtime Exceptions

There are three kinds of exceptions. Errors are exceptions thrown directly by the Java runtime when a severe problem occurs. An example is an OutOfMemory error. There is generally nothing you can do about errors thrown by the system, but in some rare cases you may want to trap such errors to attempt to perform some last-minute cleanup operations. Checked exceptions are exceptions that are verified at compile-time. What this means is that you have to either handle a checked exception inside of a try-catch block or throw it up to the calling function. A checked exception allows developers of an API to self-document important error/exception conditions and to force calling code to deal with these conditions. The  Stream classes in Java that implement I/O throw a variety of these checked exceptions. Finally, a runtime exception is not verified at compile-time. The notion is that a runtime exception represents a "bug" in the code rather than an exceptional condition. IndexOutOfBounds is a typical example of a runtime exception.

Rolling Your Own Exceptions

When designing your own object-oriented code, you may wish to create your own exceptions. Simply extend Exception or  RuntimeException to do so. When programming a game for example, it might be reasonable to throw a GameOverException. This allows various conditions to cause the termination of the game and for you to handle all of these conditions in one place. Below is a function from a Hangman game which throws an exception if the game is over. This prevents a game from continuing once it's over (until the game is reset that is).
	private void checkGameOver() {
if (gameState == GameState.WON || gameState == GameState.LOST) {
throw new GameOverException();
}
if (errorCount == 7) {
gameState = GameState.LOST;
}
if (isSecretSolved()) {
gameState = GameState.WON;
}
}
The exception might look something like this:

public class GameOverException extends Exception {
public GameOverException() {}
public GameOverException(String message) {
super(message);
}
}

Example of Exception Handling

The following example allows you to play with the exception handling mechanism in order to learn more about it. After running this example, you will be able to see the flow of execution throw the various method calls. Try modifying this example to explore this topic further. Note that changing IWasBadException to extend RuntimeException instead of Exception will prevent any compilation errors from occuring if you fail to handle the exception thrown in method3.

public class ExceptionHandling {
    public void method1() {
        System.out.println("Entering method1");
        try {
            System.out.println("method1: calling method2");
            method2();
            System.out.println("method1: successfully called method2");
        } catch (IWasBadException e) {
            System.out.println("method1: Exception caught!");
        }
    }

    public void method2() throws IWasBadException {
        System.out.println("Entering method2");
        System.out.println("method2: calling method3");
        method3();
        System.out.println("method2: successfully called method3");
    }

    public void method3() throws Exception {
       System.out.println("method3: Generated exception!");
       throw new IWasBadException();
    }

    public static void main(String args) {
        ExceptionHandling handler = new ExceptionHandling();
        handler.method1();
    }
}

class IWasBadException extends Exception {
    public IWasBadException() {}
    public IWasBadException(String msg) {
        super(msg);
    }
}