All Categories :
Java
Chapter 10
Exception Handling
by David R. Chung
CONTENTS
Errors are a normal part of programming. Some of these errors
are flaws in a program's basic design or implementation-these
are called bugs. Other types of errors are not really bugs;
rather, they are the result of situations like low memory or invalid
filenames.
The way you handle the second type of error determines whether
they become bugs. Unfortunately, if your goal is to produce robust
applications, you probably find yourself spending more time handling
errors than actually writing the core of your application.
Java's exception handling mechanism lets you handle errors without
forcing you to spend most of your energy worrying about them.
As the name implies, an exception is an exceptional condition.
An exception is something that is out of the ordinary. Most often,
exceptions are used as a way to report error conditions. Exceptions
can be used as a means of indicating other situations as well.
This chapter concentrates primarily on exceptions as an error
handling mechanism.
Exceptions provide notification of errors and a way to handle
them. This new control structure allows you to specify exactly
where to handle specific types of errors.
Note |
Other languages such as C++ and Ada provide exception handling. Java's exception handling is similar to the one used by C++.
|
Tennyson Understood the Problem
In his poem, Charge of the Light Brigade, Alfred, Lord
Tennyson describes an actual battle. In this battle, a cavalry
brigade is ordered to attack a gun emplacement. It turns out that
the valley the troops attack is a trap. There are big guns on
three sides and the brave soldiers on horseback with their sabers
are massacred. The poem describes an actual battle from the Crimean
War.
The battle as Tennyson describes it highlights a classic problem.
Someone (probably far from the front) had given the order to attack.
The men who led the charge very quickly became aware that an error
had been made. Unfortunately, they did not have the authority
to do anything about it. In Tennyson's immortal words, "Theirs
not to reason why, theirs but to do and die: into the valley of
Death rode the 600."
Using exceptions in Java allows you to determine exactly who handles
an error. In fact, low-level functions can detect errors while
higher-level functions decide what to do about them. Exceptions
provide a means of communicating information about errors up through
the chain of methods until one of them can handle it.
Most procedural languages like C and Pascal do not use exception
handling. In these languages, a variety of techniques are used
to determine whether an error has occurred. The most common means
of error checking is the function's return value.
Consider the problem of calculating the retail cost of an item
and displaying it. For this example, the retail cost is twice
the wholesale cost:
int retailCost( int wholesale ) {
if ( wholesale <= 0 ) {
return 0 ;
}
return (wholesale * 2 ) ;
}
The retailCost() method takes
the wholesale price of an item and doubles it. If the wholesale
price is negative or zero, the function returns zero to indicate
that an error has occurred. This method can be used in an application
as follows:
int wholesalePrice = 30 ;
int retailPrice = 0 ;
retailPrice = retailCost( wholesalePrice ) ;
System.out.println( "Wholesale price = " + wholesalePrice ) ;
System.out.println( "Retail price = " + retailPrice ) ;
In this example, the retailCost()
method calculates the correct retail cost and prints it. The problem
is that the code segment never checks whether the wholesalePrice
variable is negative. Even though the method checks the value
of wholesalePrice and reports
an error-there is nothing that forces the calling method to deal
with the error. If this method is called with a negative wholesalePrice,
the function blindly prints invalid data. Therefore, no matter
how diligent you are in ensuring that your methods return error
values, the callers of your methods are free to ignore them.
You can prevent bad values from being printed by putting the whole
operation in a method. The showRetail()
method takes the wholesale price, doubles it, and prints it. If
the wholesale price is negative or zero, the method does not print
anything and returns the boolean
value false:
boolean showRetail( int wholesale ) {
if ( wholesale <= 0 ) {
return false ;
}
int retailPrice ;
retailPrice = wholesalePrice * 2 ;
System.out.println( "Wholesale price = " + wholesale ) ;
System.out.println( "Retail price = " + retailPrice ) ;
return true ;
}
Using this new and improved method guarantees that bad
values are never printed. However, once again, the caller does
not have to check to see whether the method returned true.
The fact that the caller can choose to ignore return values is
not the only problem with using return values to report errors.
What happens if your method returns a boolean
and both true and false
are valid return values? How does this method report an error?
Consider a method to determine whether a student passes a test.
The pass() method takes the
number of correct answers and the number of questions. The method
calculates the percentage; if it is greater than 70 percent, the
student passes. Consider the passingGrade()
method:
boolean passingGrade( int correct, int total ) {
boolean returnCode = false ;
if ( (float)correct / (float)total > 0.70 ) {
returnCode = true ;
}
return returnCode ;
}
In this example, everything works fine as long as the method arguments
are well behaved. What happens if the number correct is greater
than the total? Or worse, if the total is zero (because this causes
a division by zero in the method)? By relying on return values
in this case, there is no way to report an error in this
function.
Exceptions prevent you from making your return values do double
duty. Exceptions allow you to use return values to return only
useful information from your methods. Exceptions also force
the caller to deal with errors-because exceptions cannot be ignored.
Exception handling can be viewed as a nonlocal control structure.
When a method throws an exception, its caller must determine
whether it can catch the exception. If the calling method
can catch the exception, it takes over and execution continues
in the caller. If the calling method cannot catch the exception,
the exception is passed on to its caller. This process continues
until either the exception is caught or the top (or bottom, depending
on how you look at it) of the call stack is reached and the application
terminates because the exception has not been caught.
Java exceptions are class objects subclassed from java.lang.Throwable.
Because exceptions are class objects, they can contain both data
and methods. In fact, the base class Throwable
implements a method that returns a String
describing the error that caused the exception. This is useful
for debugging and for giving users a meaningful error message.
The passingGrade() method
presented in the preceding section was unable to report an error
condition because all its possible return values were valid. Adding
exception handling to the method makes it possible to uncouple
the reporting of results from the reporting of errors.
The first step is to modify the passingGrade()
method definition to include the throws
clause. The throws clause
lists the types of exceptions that can be thrown by the method.
In the following revised code, the method throws only an exception
of type Exception:
static boolean passingGrade( int correct, int total )
throws Exception {
boolean returnCode = false ;
The rest of the method remains largely unchanged. This time, the
method checks to see whether its arguments make sense. Because
this method determines passing grades, it would be un-reasonable
to have more correct responses than total responses. Therefore,
if there are more correct responses than total responses, the
method throws an exception.
The method instantiates an object of type Exception.
The Exception constructor
takes a String parameter.
The String contains a message
that can be retrieved when the exception is caught. The throw
statement terminates the method and gives its caller the opportunity
to catch it:
if( correct > total ) {
throw new Exception( "Invalid values" ) ;
}
if ( (float)correct / (float)total > 0.70 ) {
returnCode = true ;
}
return returnCode ;
}
To respond to an exception, the call to the method that produces
it must be placed within a try
block. A try block is a block
of code beginning with the try
keyword followed by a left and a right curly brace. Every try
block is associated with one or more catch
blocks. Here is a try block:
try
{
// method calls go here
}
If a method is to catch exceptions thrown by the methods it calls,
the calls must be placed within a try
block. If an exception is thrown, it is handled in a catch
block. Different catch blocks
handle different types of exceptions. This is a try
block and a catch block set
up to handle exceptions of type Exception:
try
{
// method calls go here
}
catch( Exception e )
{
// handle exceptons here
}
When any method in the try
block throws any type of exception, execution of the try
block ceases. Program control passes immediately to the associated
catch block. If the catch
block can handle the given exception type, it takes over. If it
cannot handle the exception, the exception is passed to the method's
caller. In an application, this process goes on until a catch
block catches the exception or the exception reaches the main()
method uncaught and causes the application to terminate.
An Exceptional Example
Because all Java methods are class members, the passingGrade()
method is incorporated in the gradeTest
application class. Because main()
calls passingGrade(), main()
must be able to catch any exceptions passingGrade()
might throw. To do this, main()
places the call to passingGrade()
in a try block. Because the
throws clause lists type
Exception, the catch
block catches the Exception
class. Listing 10.1 shows the entire gradeTest
application.
Listing 10.1. The gradeTest
application.
import java.io.* ;
import java.lang.Exception ;
public class gradeTest {
public static void main( String[] args ) {
try
{
// the second call to passingGrade throws
// an excption so the third call never
// gets executed
System.out.println( passingGrade( 60, 80 ) ) ;
System.out.println( passingGrade( 75, 0 ) ) ;
System.out.println( passingGrade( 90, 100 ) ) ;
}
catch( Exception e )
{
System.out.println( "Caught exception --" +
e.getMessage() ) ;
}
}
static boolean passingGrade( int correct, int total )
throws Exception {
boolean returnCode = false ;
if( correct > total ) {
throw new Exception( "Invalid values" ) ;
}
if ( (float)correct / (float)total > 0.70 ) {
returnCode = true ;
}
return returnCode ;
}
}
The second call to passingGrade()
fails in this case, because the method checks to see whether the
number of correct responses is less than the total responses.
When passingGrade() throws
the exception, control passes to the main()
method. In this example, the catch
block in main() catches the
exception and prints Caught exception
-- Invalid values.
Multiple catch
Blocks
In some cases, a method may have to catch different types of exceptions.
Java supports multiple catch
blocks. Each catch block
must specify a different type of exception:
try
{
// method calls go here
}
catch( SomeExceptionClass e )
{
// handle SomeExceptionClass exceptions here
}
catch( SomeOtherExceptionClass e )
{
// handle SomeOtherExceptionClass exceptions here
}
When an exception is thrown in the try
block, it is caught by the first catch
block of the appropriate type. Only one catch
block in a given set will be executed. Notice that the catch
block looks a lot like a method declaration. The exception caught
in a catch block is a local
reference to the actual exception object. You can use this exception
object to help determine what caused the exception to be thrown
in the first place.
Does Every Method Have to Catch Every Exception?
What happens if a method calls another method that throws an exception
but chooses not to catch it? In the example in Listing 10.2, main()
calls foo(), which in turn
calls bar(). bar()
lists Exception in its throws
clause; because foo() is
not going to catch the exception, it must also have Exception
in its throws clause. The
application in Listing 10.2 shows a method, foo(),
that ignores exceptions thrown by the called method.
Listing 10.2. A method that ignores exceptions thrown by the
method it calls.
import java.io.* ;
import java.lang.Exception ;
public class MultiThrow {
public static void main( String[] args ) {
try
{
foo() ;
}
catch( Exception e )
{
System.out.println( "Caught exception " +
e.getMessage() ) ;
}
}
static void foo() throws Exception {
bar() ;
}
static void bar() throws Exception {
throw new Exception( "Who cares" ) ;
}
}
In the example in Listing 10.3, main()
calls foo() which calls bar().
Because bar() throws an exception
and doesn't catch it, foo()
has the opportunity to catch it. The foo()
method has no catch block,
so it cannot catch the exception. In this case, the exception
propagates up the call stack to foo()'s
caller, main().
Listing 10.3. A method that catches and rethrows an exception.
import java.io.* ;
import java.lang.Exception ;
public class MultiThrow {
public static void main( String[] args ) {
try
{
foo() ;
}
catch( Exception e )
{
System.out.println( "Caught exception " +
e.getMessage() ) ;
}
}
static void foo() throws Exception {
try
{
bar() ;
}
catch( Exception e )
{
System.out.println( "Re throw exception -- " +
e.getMessage() ) ;
throw e ;
} }
static void bar() throws Exception {
throw new Exception( "Who cares" ) ;
}
}
The foo() method calls bar().
The bar() method throws an
exception and foo() catches
it. In this example, foo()
simply rethrows the exception, which is ultimately caught
in the application's main()
method. In a real application, foo()
could do some processing and then rethrow the exception. This
arrangement allows both foo()
and main() to handle the
exception.
The finally
Clause
Java introduces a new concept in exception handling: the finally
clause. The finally clause
sets apart a block of code that is always executed. Here's an
example of a finally clause:
import java.io.* ;
import java.lang.Exception ;
public class MultiThrow {
public static void main( String[] args ) {
try
{
alpha() ;
}
catch( Exception e }
{
System.out.println( "Caught exception " ) ;
}
finally()
{
System.out.println( "Finally. " ) ;
}
}
}
In normal execution (that is, when no exceptions are thrown),
the finally block is executed
immediately after the try
block. When an exception is thrown, the finally
block is executed before control passes to the caller.
If alpha() throws an exception,
it is caught in the catch
block and then the finally
block is executed. If alpha()
does not throw an exception, the finally
block is executed after the try
block. If any code in a try
block is executed, the finally
block is executed as well.
All exceptions in Java are subclassed from the class Throwable.
If you want to create your own exception classes, you must subclass
Throwable. Most Java programs
do not have to subclass their own exception classes.
Following is the public portion
of the class definition of Throwable:
public class Throwable {
public Throwable() ;
public Throwable(String message) ;
public String getMessage()
public String toString() ;
public void printStackTrace() ;
public void printStackTrace(java.io.PrintStream s) ;
private native void printStackTrace0(java.io.PrintStream s);
public native Throwable fillInStackTrace();
}
The constructor takes a string that describes the exception. Later,
when an exception is thrown, you can call the getMessage()
method to get the error string that was reported.
The methods of the Java API and the language itself also throw
exceptions. These exceptions can be broken into two classes: Exception
and Error.
Both the Exception and Error
classes are derived from Throwable.
Exception and its subclasses
are used to indicate conditions that may be recoverable. Error
and its subclasses indicate conditions that are generally not
recoverable and should cause your applet to terminate.
The various packages included in the Java Developers Kit throw
different kinds of Exception
and Error exceptions, as
described in the following sections.
java.lang
Exceptions
The java.lang package contains
much of the core Java language. The exceptions subclassed from
RuntimeException do not have
to be declared in a method's throws
clause. These exceptions are considered normal and nearly
any method can throw them. Figure 10.1 and Table 10.1 show the
recoverable exceptions from the java.lang
package. Figure 10.2 and Table 10.2 show the nonrecoverable errors
in the java.lang package.
Figure 10.1: The Java.lang exception hierarchy.
Figure 10.2: The Java.lang error hierarchy.
Table 10.1. The java.lang
exceptions.
Exception | Cause
|
ArithmeticException
| Arithmetic error condition (for example, divide by zero).
|
ArrayIndexOutOfBoundsException
| Array index is less than zero or greater than the actual size of the array.
|
ArrayStoreException
| Object type mismatch between the array and the object to be stored in the array.
|
ClassCastException
| Cast of object to inappropriate type. |
ClassNotFoundException
| Unable to load the requested class. |
CloneNotSupportedException
| Object does not implement the cloneable interface.
|
Exception
| Root class of the exception hierarchy. |
IllegalAccessException
| Class is not accessible. |
IllegalArgumentException
| Method receives an illegal argument. |
IllegalMonitorStateException
| Improper monitor state (thread synchronization).
|
IllegalThreadStateException
| The thread is in an improper state for the requested operation.
|
IndexOutOfBoundsException
| Index is out of bounds. |
InstantiationException
| Attempt to create an instance of the abstract class.
|
InterruptedException
| Thread interrupted. |
NegativeArraySizeException
| Array size is less than zero. |
NoSuchMethodException
| Unable to resolve method. |
NullPointerException
| Attempt to access a null object member.
|
NumberFormatException
| Unable to convert the string to a number. |
RuntimeException
| Base class for many java.lang exceptions.
|
SecurityException
| Security settings do not allow the operation.
|
StringIndexOutOfBoundsException
| Index is negative or greater than the size of the string.
|
Table 10.2. The java.lang
errors.
Error | Cause
|
AbstractMethodError
| Attempt to call an abstract method. |
ClassCircularityError
| This error is no longer used. |
ClassFormatError
| Invalid binary class format. |
Error
| Root class of the error hierarchy. |
IllegalAccessError
| Attempt to access an inaccessible object. |
IncompatibleClassChangeError
| Improper use of a class. |
InstantiationError
| Attempt to instantiate an abstract class. |
InternalError
| Error in the interpreter. |
LinkageError
| Error in class dependencies. |
NoClassDefFoundError
| Unable to find the class definition. |
NoSuchFieldError
| Unable to find the requested field. |
NoSuchMethodError
| Unable to find the requested method. |
OutOfMemoryError
| Out of memory. |
StackOverflowError
| Stack overflow. |
ThreadDeath
| Indicates that the thread will terminate. May be caught to perform cleanup. (If caught, must be rethrown.)
|
UnknownError
| Unknown virtual machine error. |
UnsatisfiedLinkError
| Unresolved links in the loaded class. |
VerifyError
| Unable to verify bytecode. |
VirtualMachineError
| Root class for virtual machine errors. |
java.io
Exceptions
The classes in java.io throw
a variety of exceptions, as shown in Table 10.3 and Figure 10.3.
Any classes that work with I/O are good candidates to throw recoverable
exceptions. For example, activities such as opening files or writing
to files are likely to fail from time to time. The classes of
the java.io package do not
throw errors at all.
Figure 10.3: The Java.io exception hierarchy.
Table 10.3. The java.io
exceptions.
Exception | Cause
|
IOException
| Root class for I/O exceptions. |
EOFException
| End of file. |
FileNotFoundException
| Unable to locate the file. |
InterruptedIOException
| I/O operation was interrupted. Contains a bytesTransferred member that indicates how many bytes were transferred before the operation was interrupted.
|
UTFDataFormatException
| Malformed UTF-8 string. |
java.net
Exceptions
The java.net package handles
network communications. Its classes most often throw exceptions
to indicate connect failures and the like. Table 10.4 and Figure
10.4 show the recoverable exceptions from the java.net
package. The classes of the java.net
package do not throw errors at all.
Figure 10.4: The Java.net exception hirearchy.
Table 10.4. The java.net
exceptions.
Exception | Cause
|
MalformedURLException
| Unable to interpret URL. |
ProtocolException
| Socket class protocol error. |
SocketException
| Socket class exception. |
UnknownHostException
| Unable to resolve the host name. |
UnknownServiceException
| Connection does not support the service. |
java.awt
Exceptions
The AWT classes have members that throw one error and one exception:
- AWTException (exception
in AWT)
- AWTError (error in AWT)
java.util
Exceptions
The classes of java.util
throw the following exceptions:
- EmptyStackException
(no objects on stack)
- NoSuchElementException
(no more objects in collection)
In the example in Listing 10.4, you see how the automatic
exceptions in Java work. This application creates a method and
forces it to divide by zero. The method does not have to explicitly
throw an exception because the division operator throws an exception
when required.
Listing 10.4. An example of a built-in exception.
import java.io.* ;
import java.lang.Exception ;
public class DivideBy0 {
public static void main( String[] args ) {
int a = 2 ;
int b = 3 ;
int c = 5 ;
int d = 0 ;
int e = 1 ;
int f = 3 ;
try
{
System.out.println( a+"/"+b+" = "+div( a, b ) ) ;
System.out.println( c+"/"+d+" = "+div( c, d ) ) ;
System.out.println( e+"/"+f+" = "+div( e, f ) ) ;
}
catch( Exception except )
{
System.out.println( "Caught exception " +
except.getMessage() ) ;
}
}
static int div( int a, int b ) {
return (a/b) ;
}
}
The output of this application is shown here:
2/3 = 0
Caught exception / by zero
The first call to div() works
fine. The second call fails because of the divide-by-zero error.
Even though the application did not specify it, an exception was
thrown-and caught. So you can use arithmetic in your code without
writing code that explicitly checks bounds.
The exception handling mechanism in Java allows your methods to
report errors in a manner that cannot be ignored. Every exception
that is thrown must be caught or the application terminates. Exceptions
are actually class objects derived from the Throwable
class. Therefore, exceptions combine data and methods; an exception
object generally contains a string explaining what the error is.
Exception handling helps you combine error processing in one place.
It uncouples the reporting of results and the reporting of errors.
If you use exception handling, you can create much more powerful
and robust code.
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.