by David R. Chung
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++. |
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.
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.
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.
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.
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.
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.
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. |
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. |
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.
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. |
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.
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. |
The AWT classes have members that throw one error and one exception:
The classes of java.util throw the following exceptions:
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.