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:
- 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
- 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
- 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);
}
}