Java 1.1 Unleashed
- 7-
Exception Handling
by David R. Chung
IN THIS CHAPTER
- What Is an Exception?
- If Exceptions Are the Answer, What Is the Question?
- Some Terminology
- Don't Throw Up Your Hands-- throw an Exception
- The Throwable Class
- Types of Exceptions
- Built-In Exceptions
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.
What Is an Exception?
As the name implies, an exception is an exceptional condition. An exception is
something 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 control
structure allows you to specify exactly where to handle specific types of errors.
NOTE: Other languages like 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, Andrew 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 they 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 became
aware very quickly 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 way to communicate information about
errors up through the chain of methods until one of them can handle it.
If Exceptions Are the Answer, What Is the Question?
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
value, 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.
Some Terminology
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
if you want to give users a meaningful error message.
Don't Throw Up Your Hands-- throw an Exception
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 ;
}
throw, try, and catch Blocks
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 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 7.1 shows the entire gradeTest application.
Listing 7.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 an 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 7.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 7.2 shows a method, foo(), that ignores exceptions thrown by
the called method.
Listing 7.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 7.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 7.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.
The Throwable Class
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.
Types of Exceptions
The methods of the Java API and the language itself also throw exceptions. These
exceptions can be divided 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 that should cause your applet to terminate.
The various packages included in the Java Development Kit throw different kinds
of Exception and Error exceptions, as described in the following
sections.
java.awt Exceptions
The AWT classes have members that throw one error and two exceptions:
- AWTException (exception in AWT)
- llegalComponentStateException (a component is not in the proper state
for a requested operation)
- AWTErr (error in AWT)
java.awt.datatransfer Exception
Classes of the AWT data transfer package may throw this exception:
- UnsupportedFlavorException (data in improper format)
NOTE: In the JDK 1.1 data transfer model,
Java uses the word Flavor in place of the more common term Format.
java.beans Exceptions
The classes of the java.beans package throw the following exceptions:
- IntrospectionException (unable to resolve object during introspection)
- PropertyVetoException (illegal property change)
java.io Exceptions
The classes in the java.io package throw a variety of exceptions, as
shown in Table 7.1 and Figure 7.1. 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.
Table 7.1. The java.io exceptions.
Exception |
Cause |
CharConversionException |
Root class for character conversion exceptions |
IOException |
Root class for I/O exceptions |
EOFException |
End of file |
FileNotFoundException |
Unable to locate file |
InterruptedIOException |
I/O operation was interrupted; contains a member bytesTransferred that indicates
how many bytes were transferred before the operation was interrupted |
InvalidClassException |
Class is not valid for serialization |
InvalidObjectException |
Class explicitly forbids serialization |
NotActiveException |
Serialization not active |
NotSerializableException |
Class may not be serialized |
ObjectStreamException |
Root class for object stream exceptions |
OptionalDataException |
Contains data members to indicate end of file or optional data to read |
StreamCorruptedException |
Stream fails internal consistency test |
SyncFailedException |
Synchronization failed |
UTFDataFormatException |
Malformed UTF-8 string |
UnsupportedEncodingException |
Character-encoding mechanism not supported |
WriteAbortException |
Exception in stream |
Figure 7.1.
The java.io exception hierarchy.
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 because nearly any
method can throw them. Table 7.2 and Figure 7.2 show the recoverable exceptions from
the java.lang package. Table 7.3 and Figure 7.3 show the nonrecoverable
errors in the java.lang package.
Table 7.2. The java.lang exceptions.
Exception |
Cause |
ArithmeticException |
Arithmetic error condition (for example, divide by zero) |
ArrayIndexOutOfBoundsException |
Array index less than zero or greater than actual size of array |
ArrayStoreException |
Object type mismatch between array and object to be stored in array |
ClassCastException |
Cast of object to inappropriate type |
ClassNotFoundException |
Unable to load requested class |
CloneNotSupportedException |
Object does not implement cloneable interface |
Exception |
Root class of exception hierarchy |
IllegalAccessException |
Class is not accessible |
IllegalArgumentException |
Method received illegal argument |
IllegalMonitorStateException |
Improper monitor state (thread synchronization) |
IllegalStateException |
Method invoked at improper time |
IllegalThreadStateException |
Thread is in improper state for requested operation |
IndexOutOfBoundsException |
Index is out of bounds |
InstantiationException |
Attempt to create instance of abstract class |
InterruptedException |
Thread interrupted |
NegativeArraySizeException |
Array size less than zero |
NoSuchFieldException |
Attempt to access invalid field |
NoSuchMethodException |
Unable to resolve method |
NullPointerException |
Attempt to access null object member |
NumberFormatException |
Unable to convert string to number |
RuntimeException |
Base class for many java.lang exceptions |
SecurityException |
Security settings do not allow operation |
StringIndexOutOfBoundsException |
Index is negative or greater than size of string |
Table 7.3. The java.lang errors.
Error |
Cause |
AbstractMethodError |
Attempt to call abstract method |
ClassCircularityError |
This error is no longer used |
ClassFormatError |
Invalid binary class format |
Error |
Root class of error hierarchy |
ExceptionInInitializerError |
Unexpected exception in initializer |
IllegalAccessError |
Attempt to access inaccessible object |
IncompatibleClassChangeError |
Improper use of class |
InstantiationError |
Attempt to instantiate abstract class |
InternalError |
Error in interpreter |
LinkageError |
Error in class dependencies |
NoClassDefFoundError |
Unable to find class definition |
NoSuchFieldError |
Unable to find requested field |
NoSuchMethodError |
Unable to find requested method |
OutOfMemoryError |
Out of memory |
StackOverflowError |
Stack overflow |
ThreadDeath |
Indicates that thread will terminate; can be caught to perform clean up (if caught,
must be rethrown) |
UnknownError |
Unknown virtual machine error |
UnsatisfiedLinkError |
Unresolved links in loaded class |
VerifyError |
Unable to verify bytecode |
VirtualMachineError |
Root class for virtual machine errors |
Figure 7.2.
The java.lang exception hierarchy.
java.lang.reflect Exception
The classes of java.lang.reflect throw the following exception:
- InvocationTargetException (invoked method has thrown an exception)
Figure 7.3.
The java.lang error hierarchy.
java.net Exceptions
The java.net package handles network communications. Its classes most
often throw exceptions to indicate connect failures and the like. Table 7.4 and Figure
7.4 show the recoverable exceptions from the java.net package. The classes
of the java.net package do not throw errors at all.
Table 7.4. The java.net exceptions.
Exception |
Cause |
BindException |
Unable to bind socket--port in use |
ConnectException |
Remote socket refused connection--no listening socket |
MalformedURLException |
Unable to interpret URL |
NoRouteToHostException |
Unable to reach host--firewall in the way |
ProtocolException |
Socket class protocol error |
SocketException |
Socket class exception |
UnknownHostException |
Unable to resolve host name |
UnknownServiceException |
Connection does not support service |
Figure 7.4.
The java.net exception hierarchy.
java.rmi Error
The Java Remote Method Invocation classes allow Java objects to exist on remote
machines. These classes throw the following error:
- ServerError (remote server indicates error)
java.rmi Exceptions
Java objects whose methods are invoked remotely through RMI may throw exceptions.
Table 7.5 and Figure 7.5 show the exceptions thrown from the java.rmi package.
Table 7.5. The java.rmi exceptions.
Exception |
Cause |
AccessException |
Operation not allowed |
AlreadyBoundException |
Name is already bound |
ConnectException |
Host refused connection |
ConnectIOException |
I/O exception during connection |
MarshalException |
Error during marshaling |
NoSuchObjectException |
Object no longer exists |
NotBoundException |
Name is not bound |
RMISecurityException |
RMISecurityManager throws exception |
RemoteException |
Invalid remote method |
ServerException |
Remote server throws exception |
ServerRuntimeException |
Remote server throws runtime exception |
StubNotFoundException |
Remote object not exported |
UnexpectedException |
Unknown error |
UnknownHostException |
Exception not in method signature |
UnmarshalException |
Error in unmarshaling; possible stream corruption |
java.rmi.server Exceptions
RMI servers throw exceptions. Table 7.6 shows these java.rmi.server exceptions.
Figure 7.5.
The java.rmi exception hierarchy.
Table 7.6. The java.rmi.server exceptions.
Exception |
Cause |
ExportException |
Port in use |
ServerCloneException |
Unable to clone |
ServerNotActiveException |
Server not executing remote method |
SkeletonMismatchException |
Stub and skeleton do not match |
SkeletonNotFoundException |
Skeleton not found or invalid |
SocketSecurityException |
Attempt to use invalid port |
java.security Exceptions
The Java security API allows users to implement security features in Java. The
API includes support for digital signatures, data encryption, key management, and
access control. Table 7.7 and Figure 7.6 show the exceptions thrown from the java.security
package.
Table 7.7. The java.security exceptions.
Exception |
Cause |
DigestException |
Generic digest error |
InvalidKeyException |
Key invalid |
InvalidParameterException |
Actual method parameter invalid |
KeyException |
Generic key error |
KeyManagementException |
Key management system error |
NoSuchAlgorithmException |
Algorithm does not exist |
NoSuchProviderException |
Security provider is not available |
ProviderException |
Security provider exception |
SignatureException |
Generic signature error |
Figure 7.6.
The java.security exception hierarchy.
java.security.acl Exceptions
The Java security access control list API allows Java developers to control access
to specific users. The classes of java.security.acl throw the following
exceptions:
- ACLNotFoundException (unable to find access control list)
LastOwnerExcepti (attempt to delete last owner of ACL)
NotOwnerExcepti (only the owner may modify)
java.sql Exceptions
The Java SQL API throws the following exceptions:
- DataTruncation (unexpected data truncation)
- SQLException (SQL error--contains detailed SQL information)
- SQLWarning (SQL warning)
java.text Exception
The Java text API throws the following exception:
- FormatException (format or parsing error)
java.util Exceptions
The classes of the java.util package throw the following exceptions:
- EmptyStackException (no objects on stack)
- MissingResourceException (resource missing)
- NoSuchElementException (no more objects in collection)
- TooManyListenersException (thrown by unicast event listeners)
NOTE: Unicast is Java terminology for
a singleton server object. Singletons are objects that can be instantiated only once.
java.utils.zip Exceptions
The Java utilities zip API throws the following exceptions:
- DataFormatException (format error)
- ZipException (Zip error)
Built-In Exceptions
In the example in Listing 7.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 7.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.
Summary
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.
|