Hour 18. Handling Errors in A Program
Hour 18. Handling Errors in A Program
No subject in this book is less popular than the one you're about to tackle: errors.
Errors—the various bugs, blunders, typos, and other problems that stop a program from running
successfully—are a natural part of the software development process. "Natural" is probably the
kindest word that has ever been used to describe them—in my own Java programming, when I can't
find the cause of an elusive error that keeps my program from working, I use words that would make
a gangsta rapper blush.
Some errors are flagged by the compiler and prevent you from creating a class. Others are noted by
the interpreter in response to a problem that keeps it from running successfully.
Exceptions— Events that signal an unusual circumstance has taken place as a program runs
Errors— Events that signal the interpreter is having problems that may be unrelated to your
program
During this hour, we'll explore exceptions as the following topics are discussed:
How to create methods that ignore an exception, leaving it for another class to handle
You'll also learn about a new feature of version 1.4 that helps keep errors out of your programs:
assertions.
Exceptions
Although you are just learning about them now, you have probably become well-acquainted with
exceptions during the previous hours of this book. These errors turn up when you write a Java
program that compiles successfully but encounters a problem when it runs.
For example, a common programming mistake is to refer to an element of an array that doesn't exist,
as in the following statements:
In this example, the String array has three elements. Because the first element in an array is numbered
0 rather than 1, the first element is greek[0], the second is greek[1] and the third is greek[2]. The
statement to display the contents of greek[3] is erroneous, because that element does not exist.
The above statements will compile successfully, but when you run the program, the Java interpreter
will halt with a message such as the following:
This message indicates that the application has generated an exception, which the interpreter made
note of by displaying an error message and stopping the program.
When a Java class encounters an exception, it alerts users of the class to the error. In this example, the
user of the class is the Java interpreter.
Note
Two terms that are used to describe this process are throw and catch. Classes and methods can throw
exceptions to alert others that they have occurred. These exceptions can be caught by other classes,
methods, or the Java interpreter.
All exceptions are subclasses of Exception, another class in the java.lang package. The
ArrayIndexOutOfBoundsException does what you would expect—it lets you know you have used an
array element that isn't within the array's boundaries.
There are hundreds of exceptions in Java. Many of them indicate a problem that can be fixed with a
change in your program, such as the array exception. These are comparable to compiler errors—once
you correct the situation, you don't have to concern yourself with the exception any longer.
Other exceptions must be dealt with every time a program runs by using five new statements: try,
catch, finally, throw, and throws.
Up to this point, you have dealt with exceptions by fixing the problem that caused them. There are
times you either can't deal with an exception like this or want to handle them within a Java class.
As an introduction to why this is useful, enter the short Java application in Listing 18.1 and save it as
SumNumbers.java.
1: class SumNumbers {
2: public static void main(String[] arguments) {
3: float sum = 0;
4: for (int i = 0; i < arguments.length; i++)
5: sum = sum + Float.parseFloat(arguments[i]);
6: System.out.println("Those numbers add up to " + sum);
7: }
8: }
You can compile this application successfully. The SumNumbers application takes one or more
numbers as command-line arguments, adds all of them up, and displays the total.
Because all command-line arguments are represented by strings in a Java application, the program
must convert them into floating-point numbers before adding them together. The Float.parseFloat()
class method in Line 5 takes care of this, adding the converted number to a variable named sum.
Run the application with the following command (or use another Java tool where you can specify the
same seven numeric arguments):
java SumNumbers 8 6 7 5 3 0 9
Run the program several times with different numbers as arguments. It should handle all of them
successfully, which might make you wonder what this has to do with exceptions.
To see the relevance, run the SumNumbers application with the following command:
java SumNumbers 1 3 5x
The third argument in this example contains a typo—there shouldn't be an x after the number 5. The
SumNumbers application has no way to know this is a mistake, so it tries to add 5x to the other
numbers, causing the following exception to be displayed:
This message is informative to the programmer developing the application, but it's not something you
would want a user to see. Java programs can take care of their own exceptions by using a try-catch
block.
try {
// statements that might cause the exception
} catch (Exception e) {
// what to do when the exception occurs
}
A try-catch block should be used with any exception that you want a program to handle. The
Exception object that appears in the catch statement should be one of two things:
The try section of the try-catch block should contain the statement or statements that might throw an
exception. In the SumNumbers application, the call to the Float.parseFloat() method in Line 5 will
throw a NumberFormatException whenever it is used to convert a non-numeric string to a floating-
point value.
To improve the SumNumbers application so that it never stops running with this kind of error, you
can use a try-catch block that deals with this exception.
Open your text editor and create an application called NewSumNumbers.java from Listing 18.2.
1: class NewSumNumbers {
2: public static void main(String[] arguments) {
3: float sum = 0;
4: for (int i = 0; i < arguments.length; i++) {
5: try {
6: sum = sum + Float.parseFloat(arguments[i]);
7: } catch (NumberFormatException e) {
8: System.out.println(arguments[i] + " is not a number.");
9: }
10: }
11: System.out.println("Those numbers add up to " + sum);
12: }
13: }
After you save and compile the application, run it with a non-numeric command-line argument along
with a few numbers, such as the following:
java NewSumNumbers 1 3 5x
5x is not a number
Those numbers add up to 4.0
The try-catch block in Lines 5–9 deals with NumberFormatException errors that are thrown by the
Float.parseFloat() method. These exceptions are caught within the NewSumNumbers class, which
displays an error message for any argument that is not a number.
Because the exception is handled within the class, the Java interpreter does not display an error
message when something like 5x is used as a command-line argument.
You can often deal with problems related to user input and other unexpected data by using try-catch
blocks.
Note
You can also use try and catch to deal with errors that could be fixed by making some changes to your
program, such as the ArrayIndexOutOfBoundsException problem that was described earlier this hour.
This isn't recommended—the easiest solution is to change your program so the error never occurs.
try-catch blocks can be used to handle several different kinds of exceptions, even if they are thrown
by different statements.
Listing 18.3 contains an application called DivideNumbers that takes two integer arguments from the
command-line and uses them in a division expression.
This application must be able to deal with two potential problems in user input:
Non-numeric arguments
Division by zero
1: class DivideNumbers {
2: public static void main(String[] arguments) {
3: if (arguments.length == 2) {
4: int result = 0;
5: try {
6: result = Integer.parseInt(arguments[0]) /
7: Integer.parseInt(arguments[1]);
8: System.out.println(arguments[0] + " divided by " +
9: arguments[1] + " equals " + result);
10: } catch (NumberFormatException e) {
11: System.out.println("Both arguments must be numbers.");
12: } catch (ArithmeticException e) {
13: System.out.println("You cannot divide by zero.");
14: }
15: }
16: }
17: }
Compile the application and try it out with some integers, floating-point numbers, and non-numeric
arguments.
The if statement in Line 3 checks to make sure that two arguments were sent to the application. If not,
the program exits without displaying anything.
The DivideNumbers application performs integer division, so the result will be an integer. This isn't
going to be as accurate as division using floating-point numbers—in integer division, 5 divided by 2
equals 2, not 2.5.
When you are dealing with multiple exceptions by using try and catch, there are times when you want
the program to do something at the end of the block whether an exception occurred or not.
You can handle this by using a try-catch-finally block, which takes the following form:
try {
// statements that might cause the exception
} catch (Exception e) {
// what to do when the exception occurs
} finally {
// statements to execute no matter what
}
The statement or statements within the finally section of the block will be executed after everything
else in the block, even if an exception occurs.
One place this is useful is in a program that reads data from a file on disk, which you will do in Hour
20, "Reading and Writing Files." There are several ways an exception can occur when you are
accessing data—the file might not exist, a disk error could occur, and so on. If the statements to read
the disk are in a try section and errors are handled in a catch section, you can close the file in the
finally section. This makes sure that the file will be closed whether or not an exception was thrown as
it was read.
Throwing Exceptions
When you call a method of another class, that class can control how the method is used by throwing
exceptions.
As you make use of the classes in the Java class library, the compiler will often display a message
such as the following:
Whenever you see an error stating that an exception "must be caught or declared to be thrown," it
indicates that the method you are trying to use throws an exception.
Any class that calls these methods, such as an application that you write, must do one of the following
things:
Handle the exception with a try-catch block and then throw it.
Up to this point in the hour, you have seen how to handle exceptions. If you would like to throw an
exception after handling it, you can use a throw statement followed by the exception object to throw.
The following statements handle a NumberFormatException error in a catch block and then throw the
exception:
try {
principal = Float.parseFloat(loanText) * 1.1F;
} catch (NumberFormatException e) {
System.out.println(arguments[i] + " is not a number.");
throw e;
}
When you throw an exception in this manner, it generally means that you have not done everything
that needs to be done to take care of the exception.
3. Ask the computer if the customer has enough credit to make the purchase.
As the CheckDatabase class is doing its job, what happens if the credit card lender's computer doesn't
answer the phone at all? This kind of error is exactly the kind of thing that the try-catch block was
designed for, and it is used within CheckDatabase to handle connection errors.
If the CheckDatabase class handles this error by itself, the CreditCardChecker application won't know
that the exception took place at all. This isn't a good idea—the application should know when a
connection cannot be made so it can report this to the person using the application.
One way to notify the CreditCardChecker application is for CheckDatabase to catch the exception in a
catch block, then throw it again with a throw statement. The exception will be thrown in
CheckDatabase, which must then deal with it like any other exception.
Exception handling is a way that classes can communicate with each other in the event of an error or
other unusual circumstance.
Ignoring Exceptions
The last technique that will be covered today is the laziest: how to ignore an exception completely.
A method in a class can ignore exceptions by using a throws clause as part of the method definition.
The following method throws a MalformedURLException, an error that can occur when you are
working with World Wide Web addresses in a Java program:
The second statement in this example, URL page = new URL(address); creates a URL object, which
represents an address on the Web. The constructor method of the URL class throws a
MalformedURLException to indicate that an invalid address was used, so no object can be
constructed. The following statement would cause one of these exceptions to be thrown:
The string http:www.java24hours.com is not a valid URL. It's missing some punctuation—two slash
characters (//).
Because the loadURL() method has been declared to throw MalformedURLException errors, it does
not have to deal with them inside the method. The responsibility for handling this exception falls to
any class that calls the loadURL() method.
Assertions
Sun Microsystems did something in Java 2 version 1.4 that hasn't happened often in the history of the
language: It added a new keyword, assert.
The assert keyword enables Java programmers to use assertions, a technique that's supposed to
improve the reliability of software. An assertion is a Boolean true-or-false expression that represents
something that should be true at that spot in the program. Here's an example:
This statement asserts that the speed variable has a value greater than 55. It's another way for a
programmer to say, "I expect speed to be greater than 55 at this position, and if it isn't, my program
would be completely FUBAR."
FUBAR is an acronym popular among computer programmers that means "fouled up beyond all
recognition" or "fouled up beyond all repair." A saltier version that uses a different F word is even
more popular.
The assert keyword must be followed by something that produces a Boolean value: an expression, a
boolean, or a method that returns a boolean. Three examples:
assert pointX = 0;
assert endOfFileReached;
assert network.noLongerConnected();
If the expression used with the assert keyword is false, an AssertionError exception will be thrown.
You can make these errors more meaningful by specifying an error message in an assert statement.
Add a colon and text at the end, as in this example:
If you're using the SDK interpreter, here's how it responds to an AssertionError exception:
Assertions are offered in Java 1.4, but are turned off by default in the SDK (and presumably other
tools as well).
SDK users must use command-line arguments to turn on assertion support. To compile a class that
contains assert statements, use the -source 1.4 option, as in the following example:
You must also enable assertions when running a Java class with the SDK interpreter. The easiest way
to do this is to use the -ea argument, which enables assertions for all classes (except those that are part
of the Java class library):
To enable assertions only in one class, follow -ea with a colon and the name of the class, as in this
example:
To enable assertions only in one package, follow -ea with a colon and the name of the package:
Summary
Now that you have put Java's exception handling techniques to use, I hope the subject of errors is a
little more popular than it was at the beginning of the hour.
Ignore an exception, leaving it for another class or the Java interpreter to take care of
Managing exceptions in your Java programs makes them more reliable, more versatile, and easier to
use, because you don't display any cryptic error messages to people who are running your software.
Once you're comfortable handling errors, you can even use assertions to create more of them—as a
safeguard against the times when your assumptions about a program are not correct.