Catching and Throwing Exceptions

   

When you write programs using Java, sooner or later (and probably much sooner) you're going to run into exceptions. An exception is a special type of object that is created when something goes wrong in a method. The rest of your program is notified about a problem when a method performs an action called throwing an exception. When an exception is thrown, it's up to your program to catch the exception and handle it.

Using try / catch / finally

In some of the previous chapters, you've gotten a quick look at exceptions and how they are handled in a program. An exception is thrown when a method call cannot complete successfully. You can prevent some exceptions by guarding actions that have the potential to cause an error. Examples of this are making sure you don't try to access an invalid array index or divide an integer by zero. Other potential errors are beyond your control, such as the possibility of losing a database connection or encountering an error when reading from an input stream. As an example, many common I/O operations can result in an IOException being thrown:

 try {   int c = System.in.read(); } catch (IOException e) {   System.out.println(e.getMessage()); } 

The System.in.read method throws an IOException if it cannot complete correctly. As you can see in this example, you place any code that might cause an exception inside a try block, and the corresponding exception-handling code inside a catch block. Whenever an exception is thrown, Java ignores the rest of the code inside the current try block, if there is one, and jumps to the corresponding catch block, where the program handles the exception.

When a catch block is entered, it is passed an object of the corresponding exception type, much like a method parameter. Methods can be called on this exception object to obtain a description of the error or a stack trace that can be displayed as an aid to the user or programmer. If a try block executes successfully, its catch block is skipped .

Besides a catch block, a try block can also be followed by a finally block. The code you put inside a finally block will always be executed, no matter what happens inside the try block. Look at a slightly modified version of the example:

 try {   int c = System.in.read(); } catch (IOException e) {   System.out.println(e.getMessage()); } finally {   System.out.println("Will always execute this statement"); } 

When this code is executed, the last println will always be performed, whether an exception is thrown or not. A try block does not have to be followed by a finally block, but every try must be followed by at least one catch or finally block.

The preceding examples focused only on IOException, but Java defines many exception objects that can be thrown by the various methods in the API. How do you know which exceptions you have to handle? In these examples, a compiler error would have resulted if the read method call had not been placed inside a try block that handles IOException (see Figure 4.2). Compiler errors are one way to determine the exception handling required within your programs.

Figure 4.2. The Java compiler gives you an error message if you fail to handle an exception.

graphics/04fig02.gif

Compiler errors will always alert you when you have neglected to handle a specific exception, but you shouldn't rely solely on this passive approach. Earlier in this chapter, you saw that part of each method signature is a list of any exceptions that can be thrown by the method. This information lets you as a programmer know in advance which exceptions, if any, you must handle when you call a particular method. The documentation generated by the javadoc tool for a class also includes the possible exceptions in its description of each method. Figure 4.3 shows an example from the Java API documentation with the exceptions noted for a specific method.

Figure 4.3. Java's API documentation lists the exception objects that can be thrown by methods in a class.

graphics/04fig03.gif

Throwing an Exception

In the IOException example, a try - catch block was used to handle an exception right in the method where it might occur. No other method would ever know that this exception occurred because the code segment fully encapsulated the exception handling. There might be times when this behavior is not desirable. Because a method does not know how it is being used by the rest of your program, it might not have enough knowledge to respond appropriately to an exception. In some situations, it might be acceptable to log an exception and continue. In others, the same exception might be a fatal error that requires a program to exit.

If you don't know what to do with an exception, you might not want to catch it. In that case, Java enables you to pass the buck, so to speak, by throwing the exception up the method chain. The following code fragment shows one way you might do this with an IOException:

 void myMethod() {   try {     doRead();   }   catch (IOException e) {     System.out.println(e.getMessage());   } } void doRead() throws IOException {   int c = System.in.read(); } 

The System.in.read method call in doRead might throw an IOException, so you could have called it within a try - catch block. However, the responsibility can also be passed up the chain by including IOException in the throws clause of doRead 's declaration. The compiler allows you to defer the handling of an exception by including it in a method declaration in this way. This does not, however, relieve you from handling the exception eventually. Notice that in this example, the exception gets handled in myMethod. This method could have also passed the buck and declared the exception in a throws clause instead of handling it.

In short, you can handle an exception in two ways:

  • Write try and catch blocks right where you call a method that might generate an exception.

  • Declare that a method might throw an exception that must be handled somewhere up the chain of method calls.

Note

The Java compiler requires you to provide a catch block or throws clause for the exceptions that might occur in a method, but any exceptions that you list must have the potential to occur. If you list an exception in a catch or throws clause that cannot possibly occur in the corresponding try block or method, the compiler will issue an error.


A Combined Approach

There might be situations in your programs where you want to both catch an exception in your code and pass it on to the calling method. Java enables you to construct your code so that different parts of a program can handle an exception as appropriate while still reporting the exception to the caller. To use this combined approach to exception handling, you include both a try - catch block within a method and also a throws clause in its declaration. This is referred to as rethrowing an exception. Listing 4.6 shows an example of this technique.

Listing 4.6 Code That Handles and Rethrows an Exception
 protected void myMethod() throws IOException {   try {     doRead();   }   catch (IOException e) {     System.out.println(e.getMessage();     throw e;   } } 

Listing 4.6 demonstrates the use of the keyword throw. A throw statement allows you to generate your own occurrence of an exception. When you rethrow an exception using throw, you are not limited to passing the same exception object up the chain. You might also create a new exception object that can be of the same or a different exception type. For example, a method might be able to report a more specific exception that provides the caller with a better understanding of the actual problem that occurred. You'll learn how to define your own exception classes for this purpose a little later.

As you've seen in the last few examples, exception objects can do a lot of traveling. They are passed from method to method, up the chain of method calls until they are finally handled. If an exception makes its way up to the Java system, the system handles it in some default manner, usually by generating an error message and exiting.

Types of Exceptions

Java defines many different exception objects. So many in fact that if you were to set up a catch block for every possible exception that a method call could produce, you would find yourself doing little more than writing exception handling code. The truth is you are not required to handle every exception that might occur. Java divides exceptions into two categories, referred to as checked and unchecked exceptions.

A checked exception, such as IOException, is an exception you must always handle or declare in the throws clause of your method when you make a call that can produce it. These exceptions represent problems that might occur in a perfectly correct program. Even though there might be nothing wrong with your code, your program might not be able to perform a certain I/O operation, load a required class file, or execute some other similar task at runtime. Because these errors are beyond your control, the Java compiler requires you to provide a way to handle them if they do occur. Like all exceptions, the checked exceptions are subclasses of java.lang.Exception.

The unchecked exceptions are subclasses of java.lang.RuntimeException, which is itself a subclass of java.lang.Exception. An unchecked exception, such as NullPointerException or ClassCastException, should never occur in a correct program. For this reason, you are not required to handle or declare an unchecked exception in a method's throws clause. You might choose to handle them anyway, but this is not typical.

Table 4.1 and 4.2 list some common exceptions that you should become familiar with as you gain experience programming with exceptions. Table 4.1 contains checked exceptions and Table 4.2 provides some examples of unchecked exceptions.

Table 4.1. Common Java Checked Exceptions
Exception Caused By
AWTException An error in an AWT user interface method
ClassNotFoundException An attempt to load a class file that cannot be located by the class loader
FileNotFoundException An attempt to access a nonexistent file
IOException General I/O failures, such as inability to read from a file
ParseException Inability to parse a string, such as when trying to convert a string into a date object
SQLException A database access error
Table 4.2. Common Java Unchecked Exceptions
Exception Caused By
ArithmeticException Math errors, such as division by zero
ArrayIndexOutOfBoundsException Bad array indexes
ArrayStoreException A program trying to store the wrong type of data in an array
NullPointerException Referencing a null object
NumberFormatException A failed conversion between strings and numbers
SecurityException A security violation detected by the security manager, such as an attempt by an applet to perform an action not allowed by the browser's security setting
StringIndexOutOfBoundsException A program attempting to access a nonexistent character position in a string

The Exception superclass for all Java exceptions is a subclass of java.lang.Throwable. The Throwable class defines three useful methods that you can call to get information about an exception:

  • getMessage() ” Returns a string that describes the exception that occurred.

  • toString() ” Returns a string made up of the specific exception class name and the error message.

  • printStackTrace() ” Displays the sequence of method calls that led to the exception to the standard error stream.

Listing 4.7 shows a catch clause that calls these various methods and Figure 4.4 shows the corresponding output.

Figure 4.4. Here's the output generated by the catch block in Listing 4.7.

graphics/04fig04.gif

Listing 4.7 ExceptionOutput.java ” Calling Methods on a Throwable Subclass
 public class ExceptionOutput {   public static void main( String args[] ) {     try {       int testInt = Integer.parseInt("abc");     }     catch ( NumberFormatException e ) {       System.out.println();       System.out.println("Output of getMessage():");       System.out.println("--------------");       System.out.println( e.getMessage() );       System.out.println();       System.out.println("Output of toString():");       System.out.println("-------------");       System.out.println( e.toString() );       System.out.println();       System.out.println("Here's the stack trace:");       System.out.println("-----------");       e.printStackTrace();     }   } } 

Handling Multiple Exceptions

In the previous examples, each try block was followed by a single catch block that handled a specific exception class. You actually have more flexibility when defining catch blocks. First, you can take advantage of the class hierarchy formed by exception classes to handle exceptions in either very specific or general terms. Remember that all exceptions are subclasses of java.lang.Exception. Beneath this class, there are many other inheritance relationships between exceptions. When you define a catch clause, you can specify the name of a specific exception type or any of its parent classes. As an example, the following two code segments are equivalent:

 try {   int c = System.in.read(); } catch ( java.io.IOException e ) {   e.printStackTrace(); } try {   int c = System.in.read(); } catch ( java.lang.Exception e ) {   e.printStackTrace(); } 

Although, these blocks behave in the same way, the former version that uses IOException provides a clearer picture of what is happening in the code and is normally the preferred approach. The use of a parent exception class such as Exception is useful when multiple exception types can occur in a try block and the response to any of them is the same.

Besides using parent exception classes in catch clauses, you also have the option to define more than one catch block for a single try block. This allows you to handle more than one exception type without using a common parent exception and thus giving up the clarity obtained by identifying the specific exceptions. When you define more than one catch block, you are even allowed to have overlapping exception types (for example, IOException and Exception ). In this case, an exception is handled by the most specific catch that matches it. With this, you have the option to handle a few exceptions in a specific way while providing a catch all response for others. When you do this, the order of the catch blocks does matter. You must always define specific catch blocks before more general ones

Caution

Although handling exceptions is a powerful tool for creating reliable and robust programs, you should only use them in situations in which you have little control over the cause of the exception, such as when dealing with user input or an external system. A try block itself adds little overhead to your code, but the act of throwing an exception is relatively slow. As a general rule, you should never implement a method that generates an exception as a normal part of its execution. Exceptions should not be viewed as a flow control mechanism. There are, however, a few special uses of exceptions related to threads that are discussed later in Chapter 11.


Creating Your Own Exception Classes

Although Java provides exception classes for just about every general error you can imagine, the designers of the language could not possibly know what type of code you are going to write and what kinds of errors that code might experience. You should extend the standard exception classes whenever you need to report and handle a problem not addressed by an existing exception type. For example, you might write a method that sums two numbers within a specific range. If the user enters a value outside the selected range, your program could throw a custom exception called something like NumberRangeException.

To create and throw your own custom exceptions, you must first define a class for the exception. Usually, you derive this class from Java's Exception class. Listing 4.8 shows how you might define the aforementioned NumberRangeException class.

Listing 4.8 NumberRangeException.java ” The NumberRangeException Class
 public class NumberRangeException extends Exception {   public NumberRangeException( String msg ) {      super(msg);   } } 

As you can see, defining a new exception requires little work. You'll learn about the details of class constructors in Chapter 7, but for now, note that a constructor is a special initialization method that has the same name as its class. You can create a new exception by creating a constructor for the class that accepts an error message string as a parameter. This error message should then be passed on to the parent exception class constructor using the super keyword.

After you define your own exception class, whenever you determine that your custom exception condition has occurred, you can create and throw an object of your exception class using a statement like the following:

 throw new NumberRangeException("An out of range value was specified"); 

Exceptions Versus Return Values

If you normally program in languages that do not support the concept of exceptions, you are probably accustomed to using method return values to indicate the occurrence of an error in a method. As you are likely aware, this approach has many drawbacks. Chief among these is that there is no way to force a method caller to check the return value for an error after a call is completed. Such a scheme also leads to an assortment of error code values that must be carefully maintained so that a consistent interpretation of errors is possible. Although some programmers might view exception handling as costly from a performance standpoint, the advantages in maintainability and robustness of code built around solid exception handling cannot be underestimated. The performance impact is minor compared to the benefits of such a framework.

   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net