Handling Exceptions
This chapter is about what happens when Java encounters an error situation that it can't deal with. Over the years, computer programming languages have devised many different ways to deal with these types of errors. The earliest programming languages dealt with them rudely, by abruptly terminating the program and printing out the entire contents of the computer's memory in hexadecimal. This output was called a dump.
Later programming languages tried various ways to keep the program running when serious errors occurred. In some languages, the statements that could potentially cause an error had extra elements added to them that would provide feedback about errors. For example, a statement that read data from a disk file might return an error code if an I/O error occurred. Still other languages let you create a special error processing section of the program, to which control would be transferred if an error occurred.
Being an object-oriented programming language, Java handles errors by using special exception objects that are created when an error occurs. In addition, Java has a special statement called the try statement that you must use to deal with exception objects. In this chapter, you find all the gory details of working with exception objects and try statements.
Understanding Exceptions
An exception is an object that's created when an error occurs in a Java program and Java can't automatically fix the error. The exception object contains information about the type of error that occurred. However, the most important information-the cause of the error-is indicated by the name of the exception class used to create the exception. You don't usually have to do anything with an exception object other than figure out which one you have.
Each type of exception that can occur is represented by a different exception class. For example, here are some typical exceptions:
- IllegalArgumentException: You passed an incorrect argument to a method.
- InputMismatchException: The console input doesn't match the data type expected by a method of the Scanner class.
- ArithmeticException: You tried an illegal type of arithmetic operation, such as dividing an integer by zero.
- IOException: A method that performs I/O encountered an unrecoverable I/O error.
- ClassNotFoundException: A necessary class couldn't be found.
There are many other types of exceptions besides these. You find out about many of them in later chapters of this book.
You need to know a few other things about exceptions:
- When an error occurs and an exception object is created, Java is said to have thrown an exception. Java has a pretty good throwing arm, so the exception is always thrown right back to the statement that caused it to be created.
- The statement that caused the exception can catch the exception if it wants it. But it doesn't have to catch the exception if it doesn't want it. Instead, it can duck and let someone else catch the exception. That someone else is the statement that called the method that's currently executing.
- If everyone ducks and the exception is never caught by the program, the program ends abruptly and displays a nasty-looking exception message on the console. (More on that in the next section.)
- Two basic types of exceptions in Java are checked exceptions and unchecked exceptions:
- A checked exception is an exception that the compiler requires you to provide for it one way or another. If you don't, your program doesn't compile.
- An unchecked exception is an exception that you can provide for, but you don't have to.
- So far in this book, I've avoided using any Java API methods that throw checked exceptions. However, I have used methods that can throw unchecked exceptions. For example, the nextInt method of the Scanner class throws an unchecked exception if the user enters something other than a valid integer value. For more information, read on.
Witnessing an exception
Submitted for your approval is a tale of a hastily written Java program, quickly put together to illustrate certain Java programming details while ignoring others. Out of sight, out of mind, as they say. Said program played a guessing game with the user, accepting numeric input via a class called Scanner.
Yet this same program ignored the very real possibility that the user may enter strange and unexpected data, data that could hardly be considered numeric, at least not in the conventional sense. The time: Now. The place: Here. This program is about to cross over into … the Exception Zone.
The program I'm talking about here is, of course, the Guessing Game program that's appeared in several forms in recent chapters. (You can find the most recent version at the very end of Book II, Chapter 7.) This program includes a validation routine that prevents the user from making a guess that's not between 1 and 10. However, that validation routine assumes that the user has entered a valid integer number. If the user enters something other than an integer value, the nextInt method of the Scanner class fails badly.
Figure 8-1 shows an example of what the console looks like if the user enters text (such as five) instead of a number. The first line after the user enters the incorrect data says the program has encountered an exception named InputMismatchException. In short, this exception means that the data entered by the user couldn't be properly matched with the type of data that was expected by the Scanner class. That's because the nextInt method expected to find an integer, and instead it found the word five.
Figure 8-1: This program has slipped into the Exception Zone.
Finding the culprit
You can find the exact statement in your program that caused the exception to occur by examining the lines that are displayed right after the line that indicates which exception was encountered. These lines, called the stack trace, list the different methods that the exception passed through before your program was completely aborted. Usually the first method listed is deep in the bowels of the Java API, and the last method listed is your application's main method. Somewhere in the middle, you find the switch from methods in the Java API to a method in your program. That's usually where you find the statement in your program that caused the error.
In Figure 8-1, the stack trace lines look like this:
at java.util.Scanner.throwFor(Scanner.java:819) at java.util.Scanner.next(Scanner.java:1431) at java.util.Scanner.nextInt(Scanner.java:2040) at java.util.Scanner.nextInt(Scanner.java:2000) at GuessingGameMethod3.getGuess(GuessingGameMethod3.java:51) at GuessingGameMethod3.playARound(GuessingGameMethod3.java:31) at GuessingGameMethod3.main(GuessingGameMethod3.java:13)
Each line lists not only a class and method name, but also the name of the source file that contains the class and the line number where the exception occurred. Thus the first line in this stack trace indicates that the exception is handled in the throwFor method of the Scanner class at line 819 of the Scanner.java file. The next three lines also indicate methods in the Scanner class. The first line to mention the GuessingGame class (GuessingGameMethod3) is the fifth line. It shows that the exception happened at line 51 in the GuessingGameMethod3.java file. Sure enough, that's the line that calls the nextInt method of the Scanner class to get input from the user.
Catching Exceptions
Whenever you use a statement that might throw an exception, you should write special code to anticipate and catch the exception. That way, your program won't crash as shown in Figure 8-1 if the exception occurs.
You catch an exception by using a try statement, which has this general form:
try { statements that can throw exceptions } catch (exception-type identifier) { statements executed when exception is thrown }
Here you place the statements that might throw an exception within a try block. Then you catch the exception with a catch block.
Here are a few things to note about try statements:
- You can code more than one catch block. That way, if the statements in the try block might throw more than one type of exception, you can catch each type of exception in a separate catch block.
- For scoping purposes, the try block is its own self-contained block, separate from the catch block. As a result, any variables you declare in the try block are not visible to the catch block. If you want them to be, declare them immediately before the try statement.
- You can also code a special block (called a finally block) after all the catch blocks. For more information about coding finally blocks, see the section "Using a finally Block" later in this chapter.
- The various exception classes in the Java API are defined in different packages. If you use an exception class that isn't defined in the standard java.lang package that's always available, you need to provide an import statement for the package that defines the exception class.
A simple example
To illustrate how to provide for an exception, here's a program that divides two numbers and uses a try/catch statement to catch an exception if the second number turns out to be zero:
public class DivideByZero { public static void main(String[] args) { int a = 5; int b = 0; // you know this wont't work try { int c = a / b; // but you try it anyway } catch (ArithmeticException e) { System.out.println("Oops, you can't + "divide by zero."); } } }
Here the division occurs within a try block, and a catch block handles ArithmeticException. ArithmethicException is defined by java.lang, so an import statement for it isn't necessary.
When you run this program, the following is displayed on the console:
Oops, you can't divide by zero.
There's nothing else to see here. The next section shows a more complicated example, though.
Another example
Listing 8-1 shows a simple example of a program that uses a method to get a valid integer from the user. If the user enters a value that isn't a valid integer, the catch block catches the error and forces the loop to repeat.
Listing 8-1: Getting a Valid Integer
import java.util.*; public class GetInteger { static Scanner sc = new Scanner(System.in); public static void main(String[] args) { System.out.print("Enter an integer: "); int i = GetInteger(); System.out.println("You entered " + i); } public static int GetInteger() { while (true) { try { return sc.nextInt(); } catch (InputMismatchException e) { sc.next(); System.out.print("That's not " + "an integer. Try again: "); } } } }
Here the statement that gets the input from the user and returns it to the methods called is coded within the try block. If the user enters a valid integer, this statement is the only one in this method that gets executed.
However, if the user enters data that can't be converted to an integer, the nextInt method throws an InputMismatchException. Then this exception is intercepted by the catch block-which disposes of the user's incorrect input by calling the next method, as well as by displaying an error message. The while loop then repeats.
Here's what the console might look like for a typical execution of this program:
Enter an integer: three That's 3.001 That's 3 You entered 3
Here are a few other things to note about this program:
- The import statement specifies java.util.* to import all the classes from the java.util package. That way, the InputMismatchException class is imported.
-
TECHNICAL STAUFF The next method must be called in the catch block to dispose of the user's invalid input because the nextInt method leaves the input value in the Scanner 's input stream if an InputMismatchException is thrown. If you omit the statement that calls next, the while loop keeps reading it, throws an exception, and displays an error message in an infinite loop. If you don't believe me, look at Figure 8-2. I found this error out the hard way. (The only way to make it stop is to close the console window.)
Figure 8-2: Why you have to call next to discard the invalid input.
Handling Exceptions with a Pre emptive Strike
The try statement is a useful and necessary tool in any Java programmer's arsenal. However, the best way to handle exceptions is to prevent them from happening in the first place. That's not possible all the time, but in many cases it is. The key is to test your data before performing the operation that can lead to an exception and skipping or bypassing the operation of the data that is problematic. (One thing I really hate is problematic data.)
For example, you can usually avoid the ArithmethicException that results from dividing integer data by zero by checking the data before performing the division:
if (b != 0) c = a / b;
This eliminates the need for enclosing the division in a try block because you know the division by zero won't happen.
You can apply this same technique to input validation using the hasNextInt method of the Scanner class. This method checks the next input value to make sure it's a valid integer. (The Scanner class calls the next input value a token, but that won't be on the test.) You can do this technique in several ways, and I've been encouraging you to ponder the problem since Book II, Chapter 2. Now, behold the long-awaited answer: Listing 8-2 shows a version of the GetInteger method that uses a while loop to avoid the exception.
Listing 8-2: Another Version of the GetInteger Method
import java.util.*; public class GetInteger2 { static Scanner sc = new Scanner(System.in); public static void main(String[] args) { System.out.print("Enter an integer: "); int i = GetInteger(); System.out.println("You entered " + i); } public static int GetInteger() { while (!sc.hasNextInt()) { sc.nextLine(); System.out.print("That's not " + "an integer. Try again: "); } return sc.nextInt(); } }
This is a clever little bit of programming, don't you think? The conditional expression in the while statement calls the hasNextInt method of the Scanner to see if the next value is an integer. The while loop repeats as long as this call returns false, indicating that the next value is not a valid integer. The body of the loop calls nextLine to discard the bad data, and then displays an error message. The loop ends only when you know you have good data in the input stream, so the return statement calls nextInt to parse the data to an integer and return the resulting value.
Catching All Exceptions at Once
Java provides a catch-all exception class called Exception that all other types of exceptions are based on. (Don't worry about the details of what I mean by that. When you read Book III, Chapter 4, it will make more sense.)
If you don't want to be too specific in a catch block, you can specify Exception instead of a more specific exception class. For example:
try { int c = a / b; } catch (Exception e) { System.out.println("Oops, you can't " + "divide by zero."); }
In this example, the catch block specifies Exception rather than ArithmeticException.
If you have some code that might throw several different types of exceptions, and you want to provide specific processing for some but general processing for all the others, code the try statement:
try { // statements that might throw several types of // exceptions } catch (InputMismatchException e) { // statements that process InputMismatchException } catch (IOException e) { // statements that process IOException } catch (Exception e) { // statements that process all other exception types }
In this example, imagine that the code in the try block might throw an InputMismatchException, an IOException, and perhaps some other type of unanticipated exception. Here the three catch blocks provide for each of these possibilities.
Tip |
When you code more than one catch block on a try statement, always list the more specific exceptions first. If you include a catch block to catch Exception, list it last. |
Displaying the Exception Message
In most cases, the catch block of a try statement won't do anything at all with the exception object passed to it. However, you may occasionally want to display an error message; exception objects have a few interesting methods that can come in handy from time to time. These methods are listed in Table 8-1.
Method |
Description |
---|---|
String getMessage() |
A text message that describes the error. |
void printStackTrace() |
Prints the stack trace to the standard error stream. |
String toString() |
Returns a description of the exception. This description includes the name of the exception class followed by a colon and the getMessage message. |
The following example shows how you might print the message for an exception in a catch block:
try { int c = a / b; } catch (Exception e) { System.out.println(e.getMessage()); }
This code displays the text /by zero on the console if b has a value of zero. You can get even more interesting output with this line in the catch clause:
e.printStackTrace(System.out);
Using a finally Block
A finally block is a block that appears after all of the catch blocks for a statement. It's executed whether or not any exceptions are thrown by the try block or caught by any catch blocks. Its purpose is to let you clean up any mess that might be left behind by the exception, such as open files or database connections.
The basic framework for a try statement with a finally block is this:
try { statements that can throw exceptions } catch (exception-type identifier) { statements executed when exception is thrown } finally { statements that are executed whether or not exceptions occur }
Listing 8-3 shows a contrived but helpful example that demonstrates how to use the finally clause. In this example, a method called divideTheseNumbers tries to divide the numbers twice. If the division fails the first time (due to a divide-by-zero exception), it tries the division again. Completely irrational, I know. But persistent, like a teenager.
Listing 8-3: A Program That Uses a finally Clause
public class CrazyWithZeros { public static void main(String[] args) { try { int answer = divideTheseNumbers(5, 0); → 7 } catch (Exception e) → 9 { System.out.println("Tried twice, " + "still didn't work!"); } } public static int divideTheseNumbers(int a, int b) → 16 throws Exception { int c; try { c = a / b; → 22 System.out.println("It worked!"); → 23 } catch (Exception e) { System.out.println("Didn't →27 c = a / b; → 28 System.out.println("It worked the second time!"); → 29 } finally { System.out.println("Better clean up my mess."); → 33 } System.out.println("It worked after all."); → 35 return c; → 36 } }
Here's the console output for the program:
Didn't work the first time. Better clean up my mess. Tried twice, still didn't work!
The following paragraphs explain what's going on, step by step:
→ 7 |
The main method calls the divideTheseNumbers method, passing 5 and 0 as the parameters. You know already this method isn't going to work. |
→ 9 |
The catch clause catches any exceptions thrown by line 7. |
→ 16 |
The divideTheseNumbers method declares that it throws Exception. |
→ 22 |
The first attempt to divide the numbers. |
→ 23 |
If the first attempt succeeds, this line is executed, and the message “It worked!” is printed. Alas, the division throws an exception, so this line never gets executed. |
→ 27 |
Instead, the catch clause catches the exception, and the message “Didn't work the first time.” is displayed. That's the first line in the console output. |
→ 28 |
The divideTheseNumbers method stubbornly tries to divide the same two numbers again. This time, there's no try statement to catch the error. |
→ 29 |
However, because another exception is thrown for the second division, this line is never executed. Thus you don't see the message “It worked the second time!” on the console. (If you do, you're in an episode of The Twilight Zone.) |
→ 33 |
This statement in the finally clause is always executed, no matter what happens. That's where the second line in the console output came from. After the finally clause executes, the ArithmeticException is thrown back up to the calling method, where it is caught by line 9. That's where the last line of the console output came from. |
→ 35 |
If the division did work, this line would be executed after the try block ends, and you'd see the message “It worked after all.” on the console. |
→ 36 |
Then the return statement would return the result of the division. |
Handling Checked Exceptions
Checked exceptions are exceptions that the designers of Java feel your programs absolutely must provide for, one way or another. Whenever you code a statement that might throw a checked exception, your program must do one of two things:
- Catch the exception by placing the statement within a try statement that has a catch block for the exception.
- Specify a throws clause on the method that contains the statement to indicate that your method doesn't want to handle the exception, so it's passing the exception up the line.
This is known as the catch-or-throw rule. In short, any method that includes a statement that might throw a checked exception must acknowledge that it knows the exception might be thrown. The method does this by either handling it directly, or passing the exception up to its caller.
Warning |
To illustrate the use of checked exceptions, I have to use some classes with methods that throw them. Up to now, I've avoided introducing classes that throw checked exceptions. So the following illustrations use some classes you aren't yet familiar with. Don't worry about what those classes do or how they work. The point is to learn how to handle the checked exceptions they throw. |
The catch or throw compiler error
Here's a program that uses a class called FileInputStream. To create an object from this class, you must pass the constructor a string that contains the path and name of a file that exists on your computer. If the file can't be found, the FileInputStream throws a FileNotFoundException that you must either catch or throw. This class is found in the java.io package, so any program that uses it must include an import java.io statement.
Consider the following program:
import java.io.*; public class FileException1 { public static void main(String[] args) { openFile("C: est.txt"); } public static void openFile(String name) { FileInputStream f = new FileInputStream(name); } }
This program won't compile. The compiler issues the following error message:
unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown
This message simply means that you have to deal with the FileNotFoundException.
Catching FileNotFoundException
One way to deal with the FileNotFoundException is to catch it by using an ordinary try statement:
import java.io.*; public class FileException2 { public static void main(String[] args) { openFile("C: est.txt"); } public static void openFile(String name) { try { FileInputStream f = new FileInputStream(name); } catch (FileNotFoundException e) { System.out.println("File not found."); } } }
In this example, the message “File not found.” is displayed if the C: est.txt file doesn't exist.
Throwing the FileNotFoundException
Suppose you don't want to deal with this error condition in the openFile method, but would rather just pass the exception up to the method that calls the openFile method?
To do that, you omit the try statement. Instead, you add a throws clause to the openFile method's declaration. That indicates that the openFile method knows that it contains a statement that might throw a FileNotFoundException, but that it doesn't want to deal with that exception here. Instead, the exception is passed up to the caller.
Here's the openFile method with the throws clause added:
public static void openFile(String name) throws FileNotFoundException { FileInputStream f = new FileInputStream(name); }
As you can see, the throws clause simply lists the exception or exceptions that the method might throw. If more than one exception is on the list, separate them with commas:
public static void readFile(String name) throws FileNotFoundException, IOException
Adding a throws clause to the openFile method means that when the FileNotFoundException occurs, it is simply passed up to the method that called the openFile method. That means the calling method (in this illustration, main) must either catch or throw the exception. To catch the exception, the main method would have to be coded like this:
public static void main(String[] args) { try { openFile("C: est.txt"); } catch (FileNotFoundException e) { System.out.println("File not found."); } }
Then, if the file doesn't exist, the catch block catches the exception, and the error message is displayed.
Throwing an exception from main
If you don't want the program to handle the FileNotFound exception at all, you can add a throws clause to the main method, like this:
public static void main(String[] args) throws FileNotFoundException { openFile("C: est.txt"); }
Then the program abruptly terminates with an exception message and stack trace if the exception occurs.
Swallowing exceptions
What if you don't want to do anything if a checked exception occurs? In other words, you want to simply ignore the exception? You can do that by catching the exception in the catch block of a try statement, but leaving the body of the catch block empty. Here's an example:
public static void openFile(String name) { try { FileInputStream f = new FileInputStream(name); } catch (FileNotFoundException e) { } }
Here the FileNotFoundException is caught and ignored. This is called swallowing the exception.
Warning |
Swallowing an exception is considered to be a bad programming practice. Simply swallowing exceptions that you know you should handle when working on a complicated program is tempting. Because you plan on getting back to that exception handler after you iron out the basic functions of the program, a little exception swallowing doesn't seem like that bad of an idea. The problem is, inevitably, that you'll never get back to the exception handler. |
So your program gets rushed into production with swallowed exceptions.
If you must swallow exceptions, at least write a message to the console indicating that the exception occurred. That way you have a constant reminder that the program has some unfinished details yet to attend to.
Note that not all exception swallowing is bad. For example, suppose you want the openFile method to return a boolean value to indicate whether the file exists, rather than throw an exception. Then you could code the method something like this:
public static boolean openFile(String name) { boolean fileOpened = false; try { FileInputStream f = new FileInputStream(name); fileOpened = true; } catch (FileNotFoundException e) { } return fileOpened; }
Here the exception isn't really swallowed. Instead, its meaning is converted to a boolean result that's returned from the method. As a result, the error condition indicated by the FileNotFoundException isn't lost.
Throwing Your Own Exceptions
Although they're uncommon, you may want to write methods that throw exceptions all on their own. To do that, you use a throw statement.
The throw statement has the following basic format:
throw new exception-class ();
The exception-class can be Exception or a class that's derived from Exception. You find out how to create your own classes-including exception classes-in Book III. For now, I just focus on writing a method that throws a general Exception.
Here's a program that demonstrates the basic structure for a method that throws an exception:
public class MyException { public static void main(String[] args) { try { doSomething(true); } catch (Exception e) { System.out.println("Exception!"); } } public static void doSomething(boolean t) throws Exception { if (t) throw new Exception(); } }
Here the doSomething method accepts a boolean value as a parameter. If this value is true, it throws an exception. Otherwise it doesn't do anything.
Here are the essential points to glean from this admittedly trivial example:
- You throw an exception by executing a throw statement. The throw statement specifies the exception object to be thrown.
- If a method contains a throw statement, it must include a throws clause in its declaration.
- A method that calls a method that throws an exception must either catch or throw the exception.
-
Tip Yup, this example is pretty trivial. But it illustrates the essential points.