TOC
BACK
FORWARD
HOME

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.

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.