Chapter 13
In This Chapter
Recovering from bad input and other nasty situations
Making your code (more or less) crash proof
Defining your own exception class
September 9, 1945: A moth flies into one of the relays of the Harvard Mark II computer and gums up the works. This becomes the first recorded case of a real computer bug.
April 19, 1957: Herbert Bright, manager of the data processing center at Westinghouse in Pittsburgh, receives an unmarked deck of computer punch cards in the mail (which is like getting an unlabeled CD-ROM in the mail today). Mr. Bright guesses that this deck comes from the development team for FORTRAN — the first computer programming language. He’s been waiting a few years for this software. (No web downloads were available at the time.)
Armed with nothing but this good guess, Bright writes a small FORTRAN program and tries to compile it on his IBM 704. (The IBM 704 lives in its own specially built, 2,000-square-foot room. With vacuum tubes instead of transistors, the machine has a whopping 32K of RAM. The operating system has to be loaded from tape before the running of each program, and a typical program takes between two and four hours to run.) After the usual waiting time, Bright’s attempt to compile a FORTRAN program comes back with a single error — a missing comma in one of the statements. Bright corrects the error, and the program runs like a charm.
July 22, 1962: Mariner I, the first U.S. spacecraft aimed at another planet, is destroyed when it behaves badly four minutes after launch. The bad behavior is attributed to a missing bar (like a hyphen) in the formula for the rocket’s velocity.
Around the same time, orbit computation software at NASA is found to contain the incorrect statement DO 10 I=1.10 (instead of the correct DO 10 I=1,10). In modern notation, this is like writing do10i = 1.10 in place of for (int i=1; i<=10; i++). The change from a comma to a period turns a loop into an assignment statement.
January 1, 2000: The Year 2000 Problem wreaks havoc on the modern world.
Any historically accurate facts in these notes were borrowed from the following sources: the Computer Folklore newsgroup (https://groups.google.com/d/forum/alt.folklore.computers), the Free On-line Dictionary of Computing (http://foldoc.org), the “Looking Back” column in Computer Now (http://www.computer.org/portal/web/computingnow/computer), and the web pages of the IEEE (www.computer.org).
You’re taking inventory. This means counting item after item, box after box, and marking the numbers of such things on log sheets, in little handheld gizmos, and into forms on computer keyboards. A particular part of the project involves entering the number of boxes that you find on the Big Dusty Boxes That Haven’t Been Opened Since Year One shelf. Rather than break the company’s decades-old habit, you decide not to open any of these boxes. You arbitrarily assign the value $3.25 to each box.
Listing 13-1 shows the software to handle this bit of inventory. The software has a flaw, which is revealed in Figure 13-1. When the user enters a whole number value, things are okay. But when the user enters something else (like the number 3.5), the program comes crashing to the ground. Surely something can be done about this. Computers are stupid, but they’re not so stupid that they should fail royally when a user enters an improper value.
Listing 13-1: Counting Boxes
import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryA {
public static void main(String args[]) {
final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency =
NumberFormat.getCurrencyInstance();
out.print("How many boxes do we have? ");
String numBoxesIn = keyboard.next();
int numBoxes = Integer.parseInt(numBoxesIn);
out.print("The value is ");
out.println(currency.format(numBoxes * boxPrice));
keyboard.close();
}
}
The key to fixing a program bug is examining the message that appears when the program crashes. The inventory program’s message says java.lang.NumberFormatException. That means a class named NumberFormatException is in the java.lang API package. Somehow, the call to Integer.parseInt brought this NumberFormatException class out of hiding.
Well, here’s what’s going on. The Java programming language has a mechanism called exception handling. With exception handling, a program can detect that things are about to go wrong and respond by creating a brand-new object. In the official terminology, the program is said to be throwing an exception. That new object, an instance of the Exception class, is passed like a hot potato from one piece of code to another until some piece of code decides to catch the exception. When the exception is caught, the program executes some recovery code, buries the exception, and moves on to the next normal statement as if nothing had ever happened. The process is illustrated in Figure 13-2.
The whole thing is done with the aid of several Java keywords. These keywords are as follows:
So, the truth is out. Through some chain of events like the one shown in Figure 13-2, the method Integer.parseInt can throw a NumberFormat Exception. When you call Integer.parseInt, this NumberFormat Exception is passed on to you.
If you call yourself a hero, you’d better catch the exception so that all the other code can get on with its regular business. Listing 13-2 shows the catching of an exception.
Listing 13-2: A Hero Counts Boxes
import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryB {
public static void main(String args[]) {
final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency =
NumberFormat.getCurrencyInstance();
out.print("How many boxes do we have? ");
String numBoxesIn = keyboard.next();
try {
int numBoxes = Integer.parseInt(numBoxesIn);
out.print("The value is ");
out.println(
currency.format(numBoxes * boxPrice));
} catch (NumberFormatException e) {
out.println("That's not a number.");
}
keyboard.close();
}
}
Figure 13-3 shows three runs of the code from Listing 13-2. When a misguided user types three instead of 3, the program maintains its cool by displaying That's not a number. The trick is to enclose the call to Integer.parse Int inside a try clause. If you do this, the computer watches for exceptions when any statement inside the try clause is executed. If an exception is thrown, the computer jumps from inside the try clause to a catch clause below it. In Listing 13-2, the computer jumps directly to the catch (Number FormatException e) clause. The computer executes the println statement inside the clause and then marches on with normal processing. (If there were statements in Listing 13-2 after the end of the catch clause, the computer would go on and execute them.)
Take a look at the catch clause in Listing 13-2 and pay particular attention to the words (NumberFormatException e). This looks a lot like a method’s parameter list, doesn’t it? In fact, every catch clause is like a little mini-method with its own parameter list. The parameter list always has an exception type name and then a parameter.
In Listing 13-2, I don’t do anything with the catch clause’s e parameter, but I certainly could if I wanted to. Remember, the exception that’s thrown is an object — an instance of the NumberFormatException class. When an exception is caught, the computer makes the catch clause’s parameter refer to that exception object. In other words, the name e stores a bunch of information about the exception. To take advantage of this, you can call some of the exception object’s methods.
} catch (NumberFormatException e) {
out.println("Message: ***" + e.getMessage() + "***");
e.printStackTrace();
}
With this new catch clause, a run of the inventory program may look like the run shown in Figure 13-4. When you call getMessage, you fetch some detail about the exception. (In Figure 13-4, the detail is Message: ***For input string: "three"***.) When you call printStackTrace, you get some additional information; namely, a display showing the methods that were running at the moment when the exception was thrown. (In Figure 13-4, the display includes Integer.parseInt and the main method.) Both getMessage and printStackTrace present information to help you find the source of the program’s difficulties.
So what else can go wrong today? Are there other kinds of exceptions — things that don’t come from the NumberFormatException class? Sure, plenty of different exception types are out there. You can even create one of your own. You wanna try? If so, look at Listings 2-13 and 3-4.
Listing 13-3: Making Your Own Kind of Exception
@SuppressWarnings("serial")
class OutOfRangeException extends Exception {
}
Listing 13-4: Using Your Custom Made Exception
import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryC {
public static void main(String args[]) {
final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency =
NumberFormat.getCurrencyInstance();
out.print("How many boxes do we have? ");
String numBoxesIn = keyboard.next();
try {
int numBoxes = Integer.parseInt(numBoxesIn);
if (numBoxes < 0) {
throw new OutOfRangeException();
}
out.print("The value is ");
out.println(
currency.format(numBoxes * boxPrice));
} catch (NumberFormatException e) {
out.println("That's not a number.");
} catch (OutOfRangeException e) {
out.print(numBoxesIn);
out.println("? That's impossible!");
}
keyboard.close();
}
}
Listings 13-3 and 13-4 remedy a problem that cropped up in Figure 13-3. Look at the last of the three runs in Figure 13-3. The user reports that the shelves have –25 boxes, and the computer takes this value without blinking an eye. The truth is that you would need a black hole (or some other exotic space-time warping phenomenon) to have a negative number of boxes on any shelf in your warehouse. So the program should get upset if the user enters a negative number of boxes, which is what the code in Listing 13-4 does. To see the upset code, look at Figure 13-5.
The code in Listing 13-3 declares a new kind of exception class — OutOf RangeException. In many situations, typing a negative number would be just fine, so OutOfRangeException isn’t built in to the Java API. However, in the inventory program, a negative number should be flagged as an anomaly.
The OutOfRangeException class in Listing 13-3 wins the award for the shortest self-contained piece of code in the book. The class’s code is just a declaration line and an empty pair of braces. The code’s operative phrase is extends Exception. Being a subclass of the Java API Exception class allows any instance of the OutOfRangeException class to be thrown.
Back in Listing 13-4, a new OutOfRangeException instance is thrown. When this happens, the catch clause (OutOfRangeException e) catches the instance. The clause echoes the user’s input and displays the message That's impossible!
Take one more look at Listing 13-4. Notice that more than one catch clause can accompany a single try clause. When an exception is thrown inside a try clause, the computer starts going down the accompanying list of catch clauses. The computer starts at whatever catch clause comes immediately after the try clause and works its way down the program’s text.
For each catch clause, the computer asks itself, “Is the exception that was just thrown an instance of the class in this clause’s parameter list?”
For some concrete examples, see Listings 2-13 and 5-4.
Listing 13-5: Yet Another Exception
@SuppressWarnings("serial")
class NumberTooLargeException
extends OutOfRangeException {
}
Listing 13-6: Where Does the Buck Stop?
import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryD {
public static void main(String args[]) {
final double boxPrice = 3.25;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency =
NumberFormat.getCurrencyInstance();
out.print("How many boxes do we have? ");
String numBoxesIn = keyboard.next();
try {
int numBoxes = Integer.parseInt(numBoxesIn);
if (numBoxes < 0) {
throw new OutOfRangeException();
}
if (numBoxes > 1000) {
throw new NumberTooLargeException();
}
out.print("The value is ");
out.println(
currency.format(numBoxes * boxPrice));
}
catch (NumberFormatException e) {
out.println("That's not a number.");
}
catch (OutOfRangeException e) {
out.print(numBoxesIn);
out.println("? That's impossible!");
}
catch (Exception e) {
out.print("Something went wrong, ");
out.print("but I'm clueless about what ");
out.println("it actually was.");
}
out.println("That's that.");
keyboard.close();
}
}
To run the code in Listings 2-13 and 5-4, you need one additional Java program file. You need the OutOfRangeException class in Listing 13-3.
Listing 13-6 addresses the scenario in which you have limited shelf space. You don’t have room for more than 1,000 boxes, but once in a while the program asks how many boxes you have, and somebody enters the number 100000 by accident. In cases like this, Listing 13-6 does a quick reality check. Any number of boxes over 1,000 is tossed out as being unrealistic.
Listing 13-6 watches for a NumberTooLargeException, but to make life more interesting, Listing 13-6 doesn’t have a catch clause for the Number TooLargeException. In spite of this, everything still works out just fine. It’s fine because NumberTooLargeException is declared to be a subclass of OutOfRangeException, and Listing 13-6 has a catch clause for the OutOfRangeException.
You see, because NumberTooLargeException is a subclass of OutOfRange Exception, any instance of NumberTooLargeException is just a special kind of OutOfRangeException. So in Listing 13-6, the computer may start looking for a clause to catch a NumberTooLargeException. When the computer stumbles upon the OutOfRangeExceptioncatch clause, the computer says, “Okay, I’ve found a match. I’ll execute the statements in this catch clause.”
To keep from having to write this whole story over and over again, I introduce some new terminology. I say that the catch clause with parameter OutOfRangeException matches the NumberTooLargeException that’s been thrown. I call this catch clause a matching catch clause.
The following bullets describe different things that the user may do and how the computer responds. As you read through the bullets, you can follow along by looking at the runs shown in Figure 13-6.
But, according to the code in Listing 13-5, NumberTooLargeException is a subclass of OutOfRangeException. When the computer reaches the second catch clause, the computer says, “Hmm! A NumberToo LargeException is a kind of OutOfRangeException. I’ll execute the statements in this catch clause — the clause with parameter of type OutOfRangeException.” In other words, it’s a match.
So, the computer executes the statements inside the second catch clause. Then the computer skips the third catch clause and executes the code that comes immediately after all the catch clauses. (See Figure 13-10.)
When the code threw an IOException, the computer skipped past the remaining statements in the try clause. Then the computer skipped past the statements in the first and second catch clauses. When the computer reached the third catch clause, I could hear the computer say, “Hmm! An IOException is a kind of Exception. I’ve found a matching catch clause — a clause with a parameter of type Exception. I’ll execute the statements in this catch clause.”
So, the computer executed the statements inside the third catch clause. Then the computer executed the code that comes immediately after all the catch clauses. (See Figure 13-11.)
When the computer looks for a matching catch clause, the computer latches on to the topmost clause that fits one of the following descriptions:
If a better match appears farther down the list of catch clauses, that’s just too bad. For instance, imagine that you added a catch clause with a parameter of type NumberTooLargeException to the code in Listing 13-6. Imagine, also, that you put this new catch clause after the catch clause with parameter of type OutOfRangeException. Then, because NumberTooLargeException is a subclass of the OutOfRangeException class, the code in your new NumberTooLargeException clause would never be executed. That’s just the way the cookie crumbles.
Starting with Java 7, you can catch more than one kind of exception in a single catch clause. For example, in a particular inventory program, you might not want to distinguish between the throwing of a NumberFormatException and your own OutOfRangeException. In that case, you can rewrite part of Listing 13-6 as follows:
try {
int numBoxes = Integer.parseInt(numBoxesIn);
if (numBoxes < 0) {
throw new OutOfRangeException();
}
if (numBoxes > 1000) {
throw new NumberTooLargeException();
}
out.print("The value is ");
out.println(
currency.format(numBoxes * boxPrice));
}
catch (NumberFormatException | OutOfRangeException e) {
out.print(numBoxesIn);
out.println("? That's impossible!");
}
catch (Exception e) {
out.print("Something went wrong, ");
out.print("but I'm clueless about what ");
out.println("it actually was.");
}
The pipe symbol, |, tells Java to catch either a NumberFormatException or an OutOfRangeException. If you throw an exception of either type, the program displays the value of numBoxesIn followed by the text ? That's impossible! If you throw an exception that is neither a NumberFormatException nor an OutOfRangeException, the program jumps to the last catch clause and displays Something went wrong, but I'm clueless...
Are you one of those obsessive-compulsive types? Do you like to catch every possible exception before the exception can possibly crash your program? Well, watch out. Java doesn’t let you become paranoid. You can’t catch an exception if the exception has no chance of being thrown.
Consider the following code. The code has a very innocent i++ statement inside a try clause. That’s fair enough. But then the code’s catch clause is pretending to catch an IOException.
// Bad code!
try {
i++;
} catch (IOException e) {
e.printStackTrace();
}
Who is this catch clause trying to impress? A statement like i++ doesn’t do any input or output. The code inside the try clause can’t possibly throw an IOException. So the compiler comes back and says, “Hey, catch clause. Get real. Get off your high horse.” Well, to be a bit more precise, the compiler’s reprimand reads as follows:
exception java.io.IOException is never thrown in body of corresponding try statement
So far, each example in this chapter catches an exception, prints a “bad input” message, and then closes up shop. Wouldn’t it be nice to see a program that actually carries on after an exception has been caught? Well, it’s time for something nice. Listing 13-7 has a try-catch statement inside a loop. The loop keeps running until the user types something sensible.
Listing 13-7: Keep Pluggin’ Along
import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryLoop {
public static void main(String args[]) {
final double boxPrice = 3.25;
boolean gotGoodInput = false;
Scanner keyboard = new Scanner(System.in);
NumberFormat currency =
NumberFormat.getCurrencyInstance();
do {
out.print("How many boxes do we have? ");
String numBoxesIn = keyboard.next();
try {
int numBoxes = Integer.parseInt(numBoxesIn);
out.print("The value is ");
out.println
(currency.format(numBoxes * boxPrice));
gotGoodInput = true;
} catch (NumberFormatException e) {
out.println();
out.println("That's not a number.");
}
} while (!gotGoodInput);
out.println("That's that.");
keyboard.close();
}
}
Figure 13-12 shows a run of the code from Listing 13-7. In the first three attempts, the user types just about everything except a valid whole number. At last, the fourth attempt is a success. The user types 3, and the computer leaves the loop.
A rumor is going around that Java exceptions always come from unwanted, erroneous situations. Although there’s some truth to this rumor, the rumor isn’t entirely accurate. Occasionally, an exception arises from a normal, expected occurrence. Take, for instance, the detection of the end of a file. The following code makes a copy of a file:
try {
while (true) {
dataOut.writeByte(dataIn.readByte());
}
} catch (EOFException e) {
numFilesCopied = 1;
}
To copy bytes from dataIn to dataOut, you just go into a while loop. With its true condition, the while loop is seemingly endless. But eventually, you reach the end of the dataIn file. When this happens, the readByte method throws an EOFException (an end-of-file exception). The throwing of this exception sends the computer out of the try clause and out of the while loop. From there, you do whatever you want to do in the catch clause and then proceed with normal processing.
So you’re getting to know Java, hey? What? You say you’re all the way up to Chapter 13? I’m impressed. You must be a hard worker. But remember, all work and no play… .
So, how about taking a break? A little nap could do you a world of good. Is ten seconds okay? Or is that too long? Better make it five seconds.
Listing 13-8 has a program that’s supposed to pause its execution for five seconds. The problem is that the program in Listing 13-8 is incorrect. Take a look at Listing 13-8 for a minute, and then I’ll tell you what’s wrong with it.
Listing 13-8: An Incorrect Program
/*
* This code does not compile.
*/
import static java.lang.System.out;
public class NoSleepForTheWeary {
public static void main(String args[]) {
out.print("Excuse me while I nap ");
out.println("for just five seconds...");
takeANap();
out.println("Ah, that was refreshing.");
}
static void takeANap() {
Thread.sleep(5000);
}
}
The strategy in Listing 13-8 isn’t bad. The idea is to call the sleep method, which is defined in the Java API. This sleep method belongs to the API Thread class. When you call the sleep method, the number that you feed it is a number of milliseconds. So, Thread.sleep(5000) means pause for five seconds.
The problem is that the code inside the sleep method can throw an exception. This kind of exception is an instance of the InterruptedException class. When you try to compile the code in Listing 13-8, you get a message such as
unreported exception java.lang.InterruptedException;
must be caught or declared to be thrown
Maybe the message reads
Unhandled exception type InterruptedException
One way or another, the message is unwelcome.
Now, the Java programming language has two kinds of exceptions. They’re called checked and unchecked exceptions:
An InterruptedException is one of Java’s checked exception types. When you call a method that has the potential to throw an InterruptedException, you need to acknowledge that exception in the code.
Now, when I say that an exception is acknowledged in the code, what do I really mean?
// The author wishes to thank that InterruptedException,
// without which this code could not have been written.
No, that’s not what it means to be acknowledged in the code. Acknowledging an exception in the code means one of two things:
If you’re confused by the wording of these two bullets, don’t worry. The next two listings illustrate the points made in the bullets.
In Listing 13-9, the method call that can throw an InterruptedException is inside a try clause. That try clause has a catch clause with exception type InterruptedException.
Listing 13-9: Acknowledging with a try-catch Statement
import static java.lang.System.out;
public class GoodNightsSleepA {
public static void main(String args[]) {
out.print("Excuse me while I nap ");
out.println("for just five seconds...");
takeANap();
out.println("Ah, that was refreshing.");
}
static void takeANap() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
out.println("Hey, who woke me up?");
}
}
}
It’s my custom, at this point in a section, to remind you that a run of Listing Such-and-Such is shown in Figure So-and-So. But the problem here is that Figure 13-13 doesn’t do justice to the code in Listing 13-9. When you run the program in Listing 13-9, the computer displays Excuse me while I nap for just five seconds, pauses for five seconds, and then displays Ah, that was refreshing. The code works because the call to the sleep method, which can throw an InterruptedException, is inside a try clause. That try clause has a catch clause whose exception is of type InterruptedException.
So much for acknowledging an exception with a try-catch statement. You can acknowledge an exception another way, shown in Listing 13-10.
Listing 13-10: Acknowledging with throws
import static java.lang.System.out;
public class GoodNightsSleepB {
public static void main(String args[]) {
out.print("Excuse me while I nap ");
out.println("for just five seconds...");
try {
takeANap();
} catch (InterruptedException e) {
out.println("Hey, who woke me up?");
}
out.println("Ah, that was refreshing.");
}
static void takeANap() throws InterruptedException {
Thread.sleep(5000);
}
}
To see a run of the code in Listing 13-10, refer to Figure 13-13. Once again, Figure 13-13 fails to capture the true essence of the run, but that’s okay. Just remember that in Figure 13-13, the computer pauses for five seconds before it displays Ah, that was refreshing.
The important part of Listing 13-10 is in the takeANap method’s header. That header ends with throws InterruptedException. By announcing that it throws an InterruptedException, method takeANap passes the buck. What this throws clause really says is, “I realize that a statement inside this method has the potential to throw an InterruptedException, but I’m not acknowledging the exception in a try-catch statement. Java compiler, please don’t bug me about this. Instead of having a try-catch statement, I’m passing the responsibility for acknowledging the exception to the main method (the method that called the takeANap method).”
Indeed, in the main method, the call to takeANap is inside a try clause. That try clause has a catch clause with a parameter of type Interrupted Exception. So everything is okay. Method takeANap passes the responsibility to the main method, and the main method accepts the responsibility with an appropriate try-catch statement. Everybody’s happy. Even the Java compiler is happy.
To better understand the throws clause, imagine a volleyball game in which the volleyball is an exception. When a player on the other team serves, that player is throwing the exception. The ball crosses the net and comes right to you. If you pound the ball back across the net, you’re catching the exception. But if you pass the ball to another player, you’re using the throws clause. In essence, you’re saying, “Here, other player. You deal with this exception.”
throws InterruptedException, IOException,
ArithmeticException
The Java API has hundreds of exception types. Several of them are subclasses of the RuntimeException class. Anything that’s a subclass of RuntimeException (or a sub-subclass, sub-sub-subclass, and so on) is unchecked. Any exception that’s not a descendent of RuntimeException is checked. The unchecked exceptions include things that would be hard for the computer to predict. Such things include the NumberFormatException (of Listings 13-2, 13-4, and others), the ArithmeticException, the Index OutOfBoundsException, the infamous NullPointerException, and many others. When you write Java code, much of your code is susceptible to these exceptions, but enclosing the code in try clauses (or passing the buck with throws clauses) is completely optional.
The Java API also has its share of checked exceptions. The computer can readily detect exceptions of this kind. So Java insists that, for an exception of this kind, any potential exception-throwing statement is acknowledged with either a try statement or a throws clause. Java’s checked exceptions include the InterruptedException (Listings 2-13 and 9-4), the IOException, the SQLException, and a gang of other interesting exceptions.
Once upon a time, I was a young fellow, living with my parents in Philadelphia, just starting to drive a car. I was heading toward a friend’s house and thinking about who knows what when another car came from nowhere and bashed my car’s passenger door. This kind of thing is called a RunARedLightException.
Anyway, both cars were still drivable, and we were right in the middle of a busy intersection. To avoid causing a traffic jam, we both pulled over to the nearest curb. I fumbled for my driver’s license (which had a very young picture of me on it) and opened the door to get out of my car.
And that’s when the second accident happened. As I was getting out of my car, a city bus was coming by. The bus hit me and rolled me against my car a few times. This kind of thing is called a DealWithLawyersException.
The truth is that everything came out just fine. I was bruised but not battered. My parents paid for the damage to the car, so I never suffered any financial consequences. (I managed to pass on the financial burden by putting the Run ARedLightException into my throws clause.)
This incident helps to explain why I think the way I do about exception handling. In particular, I wonder, “What happens if, while the computer is recovering from one exception, a second exception is thrown?” After all, the statements inside a catch clause aren’t immune to calamities.
Well, the answer to this question is anything but simple. For starters, you can put a try statement inside a catch clause. This protects you against unexpected, potentially embarrassing incidents that can crop up during the execution of the catch clause. But when you start worrying about cascading exceptions, you open up a very slimy can of worms. The number of scenarios is large, and things can become complicated very quickly.
One not-too-complicated thing that you can do is to create a finally clause. Like a catch clause, a finally clause comes after a try clause. The big difference is that the statements in a finally clause are executed whether or not an exception is thrown. The idea is, “No matter what happens, good or bad, execute the statements inside this finally clause.” Listing 13-11 has an example.
Listing 13-11: Jumping Around
import static java.lang.System.out;
public class DemoFinally {
public static void main(String args[]) {
try {
doSomething();
} catch (Exception e) {
out.println("Exception caught in main.");
}
}
static void doSomething() {
try {
out.println(0 / 0);
} catch (Exception e) {
out.println(
"Exception caught in doSomething.");
out.println(0 / 0);
} finally {
out.println("I'll get printed.");
}
out.println("I won't get printed.");
}
}
Normally, when I think about a try statement, I think about the computer recovering from an unpleasant situation. The recovery takes place inside a catch clause, and then the computer marches on to whatever statements come after the try statement. Well, if something goes wrong during execution of a catch clause, this picture can start looking different.
Listing 13-11 gets a workout in Figure 13-14. First, the main method calls do Something. Then, the stupid doSomething method goes out of its way to cause trouble. The doSomething method divides 0 by 0, which is illegal and undoable in anyone’s programming language. This foolish action by the doSomething method throws an ArithmeticException, which is caught by the try statement’s one and only catch clause.
Inside the catch clause, that lowlife doSomething method divides 0 by 0 again. This time, the statement that does the division isn’t inside a protective try clause. That’s okay, because an ArithmeticException isn’t checked. (It’s one of those RuntimeException subclasses. It’s an exception that doesn’t have to be acknowledged in a try or a throws clause. For details, see the previous section.)
Well, checked or not, the throwing of another ArithmeticException causes control to jump out of the doSomething method. But, before leaving the doSomething method, the computer executes the try statement’s last will and testament — namely, the statements inside the finally clause. That’s why in Figure 13-14 you see the words I'll get printed.
Interestingly enough, you don’t see the words I won't get printed in Figure 13-14. Because the catch clause’s execution throws its own uncaught exception, the computer never makes it down past the try-catch-finally statement.
So, the computer goes back to where it left off in the main method. Back in the main method, word of the doSomething method’s ArithmeticException mishaps causes execution to jump into a catch clause. The computer prints Exception caught in main, and then this terrible nightmare of a run is finished.
Imagine a program that gets input from two different files or from a Scanner and a disk file. To make sure that you clean up properly, you put close method calls in a finally clause. (See Listing 13-12.)
Listing 13-12: Using Two Files
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class Main {
public static void main(String args[]) {
Scanner scan1 = null;
Scanner scan2 = null;
try {
scan1 = new Scanner(new File("File1.txt"));
scan2 = new Scanner(new File("File2.txt"));
// Do useful stuff
} catch (IOException e) {
// Oops!
} finally {
scan1.close();
scan2.close();
System.out.println("Done!");
}
}
}
In theory, the computer always executes scan1.close() and scan2.close() no matter what goes wrong during execution of the try clause. But that’s theory. In reality, another programmer (not you, of course) might modify the code by closing scan1 in the middle of the try clause:
try {
scan1 = new Scanner(new File("File1.txt"));
scan2 = new Scanner(new File("File2.txt"));
// Do useful stuff but also ...
scan1.close();
scan1 = null;
} catch (IOException e) {
// Oops!
} finally {
scan1.close();
scan2.close();
System.out.println("Done!");
}
Now you have a real predicament. Inside the finally clause, the value of scan1 is null. The call to scan1.close() fails, so the program throws a NullPointerException and stops running before reaching the call to scan2.close(). In the worst of circumstances, scan2 isn't closed, and your program has File2.txt locked up so that no other program can use the file.
When a program uses several resources (many files, a database and a file, or whatever) the buildup of try statements becomes very complicated. You can make try statements within catch clauses and all kinds of crazy combinations. But Java has a better way to solve the problem. In Java 7 (and later versions of Java), you can create a try-with-resources statement. Listing 13-13 shows you how.
Listing 13-13: Making Sure to Close Resources
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class NewMain {
public static void main(String args[]) {
try (Scanner scan1 =
new Scanner(new File("File1.txt"));
Scanner scan2 =
new Scanner(new File("File2.txt"))) {
// Do useful stuff
} catch (IOException e) {
// Oops!
}
System.out.println("Done!");
}
}
In Listing 13-13, the declarations of scan1 and scan2 are in parentheses after the word try. The parenthesized declarations tell Java to close scan1 and scan2 automatically after execution of the statements in the try clause. You can declare several resources inside one try statement’s parentheses. When you do, Java closes all the resources automatically after execution of the try clause’s statements. You can add catch clauses and a finally clause if you want. You can access all kinds of resources (files, databases, connections to servers, and others) and have peace of mind knowing that Java will sever the connections automatically.
Life is good.
3.142.249.42