Lesson 7.4 Using try-catch-finally
In the last section, you learned about the different kinds of Throwable objects that are generated when an exception occurs. In this section, you'll learn to trap those Throwable objects and put them to work. You'll learn:
- How to use try-catch to respond to specific errors.
- How to use the throws clause when you want to allow the Java runtime system to handle an exception for you.
- How to use try-catch-finally to make sure that necessary portions of your program are executed whether or not an exception occurs.
Using try-catch
If you are a long-time programmer, Java's try-catch mechanism, shown here, looks kind of strange the first time you see it.
Most programming structures--loops, selection statements, method definitions--have the same basic "shape" in every programming language, even though the details might differ.
That's not true with try-catch, which seems to combine elements of loops and methods together in a package that just doesn't "look right." Once you understand how try-catch works, though, the strangeness goes away, and it starts to look a little bit more natural.
The try Block
The first portion of a try-catch statement is the try block. This is the easiest part to understand. A try block consists of the keyword try followed by a brace-delimited block. The braces are part of the try-catch syntax, and are required. This is different than a loop body or the body of an if statement, where the braces are optional.
Inside the braces you can put any number of statements that may throw an exception.
|
A try Block
|
int a = 3, b = 0;
int n = 0, x = 0;
try
{
n = System.in.read();
Thread.sleep(100);
x = a / b;
} |
If you use a method that throws a checked exception--that is, an exception that is not a subclass of the RuntimeException class--then you must put the statement inside a try block. Common methods that throw checked exceptions include the System.in.read() method, [which throws IOException], the Thread.sleep() method, and the MediaTracker waitFor() methods which you'll encounter when we start using Image objects.
The statements inside the try block are not restricted to those that may throw checked exceptions; you can place any statements you like inside the try block. If you call a method or perform an operation that generates an unchecked exception--say a NullPointerException or a NumberFormatException--then you can handle the exception by adding an appropriate catch block, which you'll meet shortly.
You are not required to provide a catch block for the unchecked exceptions which may be generated inside your try block, but you are required to provide a catch block for each checked exception that might be thrown.
The catch Blocks
Immediately following your try block you must supply one or more catch blocks. The syntax for the catch blocks looks like this:
catch (<Exception1-Class> <var1>)
{
// Handle exception1 here
}
catch (<Exception2-Class> <var2>)
{
// Handle exception 2 here
} |
This is the part that gets a little tricky. Each catch block starts with the keyword catch, which is followed by parentheses. Inside the parentheses you declare an exception variable which is used to hold the exception object if that particular exception is thrown inside the try block.
You must provide a name for each exception object, and, although this looks like a method header, the scoping rules are somewhat different. Each exception variable used in one try-catch situation must have a unique name.
Following the exception variable declaration is a brace-delimited block where you add the actions you want to take when that particular exception is thrown. As with the try block, the braces around the catch block body are required. [Early versions of javac did not enforce this restriction, so you may still see some code that has no braces surrounding the body of the catch block].
You must provide a catch block for every checked exception that may be thrown inside your try block. Let's continue the example we started earlier. Because you know that System.in.read() throws an IOException, and that IOException is a checked exception, you can add a catch block like this:
|
A catch Block
|
int a = 3, b = 0;
int n = 0, x = 0;
try
{
n = System.in.read();
Thread.sleep(100);
x = a / b;
}
catch (IOException ioe)
{
System.out.println("No read()");
} |
To compile this, you must import the java.io package, where IOException is defined. Once you do that, you can recompile. When you do, here's what you'll see:
C:\JavaOnline\unit7>javac TryCatch.java TryCatch.java:15: Exception java.lang.InterruptedException must be caught, or it must be declared in the throws clause of this method. Thread.sleep(100); ^ 1 error |
As you can see, the Thread.sleep() method throws an InterruptedException. This is a checked exception, so you must catch it as well. Let's add a catch block for InterruptedException and see what happens.
|
A Second catch Block
|
int a = 3, b = 0;
int n = 0, x = 0;
try
{
n = System.in.read();
Thread.sleep(100);
x = a / b;
}
catch (IOException ioe)
{
System.out.println("No read()");
}
catch (InterruptedException ie)
{
System.out.println("No sleep()");
} |
Because InterruptedException is part of the automatically-included java.lang package, you don't have to add any new import statements. You must make sure that your new exception variable has a different name than the first, however, or Java will complain that a variable with that name already exists. The new variable is named ie instead of ioe like the first.
Unchecked Exceptions
Once you do that, the program compiles without error. If you put this code into a console-mode application and run it, however, this is what you'll see:
As you can see, an ArtithmeticException is being thrown because we are dividing by zero. ArithmeticException is a sublcass of RuntimeException, so it is unchecked. The Java compiler does not require you to catch it, nor do you have to place the code that causes the exception inside a try block. If you move the line
x = a / b;
outside of the try-catch, the program runs just the same.
Although you don't have to use try-catch with unchecked exceptions, you certainly can. If you add a catch block for ArithmeticException to your program, you can catch and handle the divide-by-zero exception in your own code. Generally, you don't want to do this, because runtime exceptions usually indicate a programming error that should be fixed. For certain tasks, however, such as checking to see if a String is properly formatted as a number by using NumberFormatException, catching an unchecked exception is the smart way to go.
Exception Matching
Because the various exception classes are arranged in a hierarchy, it is possible to catch several exceptions using a single catch block. If, for instance, you add a catch block for Exception, the superclass for all exceptions, then your catch block will intercept every exception that can be generated. [This is not a good idea, by the way.]
You can use this hierarchy of exceptions to handle certain exceptions and to defer handling others.
When an exception occurs, the catch blocks are searched in order, looking for a match. That means, you should place your most specific catch blocks first, and the more general ones later.
If you place your general [superclass] catch blocks first, then the more specific [subclass] catch blocks will never be searched.
An Alternative to try-catch
One of the beauties of Java's exception mechanism is its flexibility. If you decide that you don't want to handle a particular exception, then you don't have to: you can pass it, like a hot-potato, back to the Java runtime system, and let Java handle it. Here's how that works.
Suppose you want to write a method for a console-mode application that reads integers from the keyboard. You want the method to return an int if it succeeds, but, if the user types in "cat" or "100", you don't want to be bothered correcting them. You're happy to let Java do that.
As you can see from the illustration [click to enlarge], there are two possible things that can go wrong inside your method. The System.in.read() method, used to read from the keyboard, can throw an IOException. Also, the Integer.parseInt() method can throw a NumberFormatException.
|
Correcting Figure 8-14
|
| This is taken from Figure 8-14 in your book which has two errors. The method readInt() should return an int, not a String, and the Integer.parseInt() method throws a NumberFormatException, not the non-existent NumericFormatException. |
Because NumberFormatException is an unchecked exception, you don't have to do anything to pass it on to the Java runtime. IOException is a different matter. If you try to use System.in.read() in your code without placing the statement inside a try-catch block, then Java will not compile your code.
You don’t have to use try-catch for IOException, however. Instead, you can defer to the caller of your method by adding “throws IOException” to the method definition. This means that any method which calls readInt() must place the call in a try-catch block, or throw the exception as well.
If you wanted to add a Thread.sleep() to the readInt() method, you would have to add the InterruptedException to your throws clause as well, like this:
public int readInt()
throws IOException, InterruptedException
{
// Code here
} |
If you don't want to be bothered with handling any exceptions at all, however, you can simply use throws Exception, and be done with it.
Exception Strategies
When should you use try-catch and when should you rely on the throws clause? Here are some simple guidelines that might help you:
- If you know how to handle a situation, use try-catch.
- If you don’t know how to handle an error, or, if you think there may be situations where users of your code will want to handle an error differently, then use throws.
In any event, you should make sure you use exceptions for exceptional situations only. Don't let exception handling make up for sloppy programming. You should never, for instance, rely on exception handling to tell you when you have exceeded the bounds of an array.
The finally Clause
As you've just seen, when an exception is thrown in a try block, program control jumps to the catch block that handles that particular exception.
Sometimes, however, this can lead to problems because code necessary for managing resources may be skipped. For instance, every time you open a file, you should close the file. Files are an operating system resource that must be manually returned to the O/S by closing the file. But, where should you put the code to close the file? Here's an example:
|
A Problem with try-catch
|
try
{
openFile();
readData(); // Exception thrown
closeFile(); // This is skipped
}
catch (IOException ioe)
{
// closeFile() not executed if no exception
}
// closeFile() possibly executed |
As you can see, there are three possible places where the call to closeFile() can go:
- In the try block, following readData()
- In the catch block
- After the try-catch block
Each of these locations has a problem.
- If you put the code in the try block, then it will not be executed if an exception is thrown in the readData() method.
- If you put the code only in the catch block, it won't be called unless an error occurs.
- If you put the code after the try-catch block, and the catch block terminates the program, you'll have failed to close the file as well.
The first alternative is to put the code in multiple places; a better solution is to place the code, once, in a finally block.
To fix this problem, try-catch has an optional finally clause, which will always be called.
You can use the finally clause to guard against resource depletion of items like file handles and graphic context handles [which you'll meet in upcoming units].
Something to Talk About
Here's a simple exercise. Download, compile and run the console-mode application TryCatch.java. Then, add the following features:
- Catch the divide by zero error and print an error message.
- Add a finally clause and print a message there as well.
Was there any difficulty in catching divide-by-zero?
Please continue to the next section of this lesson.
|