Catching Exceptions

   

Core Java™ 2: Volume I - Fundamentals
By Cay S. Horstmann, Gary Cornell
Table of Contents
Chapter 11.  Exceptions and Debugging


You now know how to throw an exception. It is pretty easy. You throw it and you forget it. Of course, some code has to catch the exception. Catching exceptions requires more planning.

If an exception occurs that is not caught anywhere in a nongraphical application, the program will terminate and print a message to the console giving the type of the exception and a stack trace. A graphics program (both an applet and an application) prints the same error message, but the program goes back to its user interface processing loop. (When you are debugging a graphically-based program, it is a good idea to keep the console available on the screen and not minimized.)

To catch an exception, you set up a try/catch block. The simplest form of the try block is as follows:

 try {    code    more code    more code } catch (ExceptionType e) {    handler for this type } 

If any of the code inside the try block throws an exception of the class specified in the catch clause, then,

  1. The program skips the remainder of the code in the try block;

  2. The program executes the handler code inside the catch clause.

If none of the code inside the try block throws an exception, then the program skips the catch clause.

If any of the code in a method throws an exception of a type other than the one named in the catch clause, this method exits immediately. (Hopefully, one of its callers has already coded a catch clause for that type.)

To show this at work, here is some fairly typical code for reading in text:

 public void read(BufferedReader reader) {    try    {       boolean done = false;       while (!done)       {          String line = reader.readLine();          if (line == null) // end of file             done = true;          else          {             process line;          }       }    }    catch (IOException exception)    {       exception.printStackTrace();    } } 

Notice that most of the code in the try clause is straightforward: it reads and processes lines until we encounter the end of the file. As you can see by looking at the Java API, there is the possibility that the readLine method will throw an IOException. In that case, we skip out of the entire while loop, enter the catch clause and generate a stack trace. For a toy program, that seems like a reasonable way to deal with this exception. What other choice do you have?

Often, the best choice is to do nothing at all. If an error occurs in the readLine method, let the caller of the read method worry about it! If we take that approach, then we have to advertise the fact that the method may throw an IOException.

 public void read(BufferedReader reader) throws IOException {    boolean done = false;    while (!done)    {       String line = reader.readLine();       if (line == null) // end of file          done = true;       else       {          process line;       }    } } 

Remember, the compiler strictly enforces the throws specifiers. If you call a method that throws a checked exception, you must either handle it or pass it on.

Which of the two is better? As a general rule, you should catch those exceptions that you know how to handle, and propagate those that you do not know how to handle. When you propagate an exception, you must add a throws specifier to alert the caller that an exception may be thrown.

Look at the Java API documentation to see what methods throw which exceptions. Then, decide whether you should handle them or add them to the throws list. There is nothing embarrassing about the latter choice. It is better to direct an exception to a competent handler than to squelch it.

Please keep in mind that there is one exception to this rule, as we mentioned earlier. If you are writing a method that overrides a superclass method that throws no exceptions (such as paintComponent in JComponent), then you must catch each checked exception in the method's code. You are not allowed to add more throws specifiers to a subclass method than are present in the superclass method.

graphics/exclamatory_icon.gif

Don't be shy about throwing or propagating exceptions to signal problems that you can't handle properly. On the other hand, your fellow programmers will hate you if you write methods that throw exceptions unnecessarily and that they must handle or pass on. If you can do something intelligent about an exceptional condition, then you should not use the sledgehammer of an exception.

graphics/cplus_icon.gif

Catching exceptions is almost the same in Java and in C++. Strictly speaking, the analog of

 catch (Exception e) // Java 

is

 catch (Exception& e) // C++ 

There is no analog to the C++ catch (...). This is not needed in Java because all exceptions derive from a common superclass.

Catching Multiple Exceptions

You can catch multiple exception types in a try block and handle each type differently. You use a separate catch clause for each type as in the following example:

 try {    code that might    throw exceptions } catch (MalformedURLException e1) {    // emergency action for malformed URLs } catch (UnknownHostException e2) {    // emergency action for unknown hosts } catch (IOException e3) {    // emergency action for all other I/O problems } 

The exception object (e1, e2, e3) may contain information about the nature of the exception. To find out more about the object, try

 e3.getMessage() 

to get the detailed error message (if there is one), or

 e3.getClass().getName() 

to get the actual type of the exception object.

Rethrowing Exceptions

Occasionally, you need to catch an exception without addressing the root cause of it. This need typically occurs when you have to do some local cleanup but can't fully resolve the problem. You then want to take your emergency action and again call throw to send the exception back up the calling chain. You can see a typical example of this in the following code.

 Graphics g = image.getGraphics(); try {    code that might    throw exceptions } catch (MalformedURLException e) {    g.dispose();    throw e; } 

The above code shows one of the most common reasons for having to rethrow an exception that you have caught. If you do not dispose of the graphics context object in the catch clause, it will never be disposed of. (Of course, its finalize method might dispose of it, but that can take a long time.)

On the other hand, the underlying cause, the malformed URL exception, has not disappeared. You still want to report it to the authorities, who presumably know how to deal with such an exception. (See the next section for a more elegant way to achieve the same result.)

You can also throw a different exception than the one you catch.

 try {    acme.util.Widget a = new acme.util.Widget();    a.load(s);    a.paint(g); } catch (RuntimeException e) {    // sheesh another ACME error    throw new Exception("ACME error"); } 
The finally Clause

When your code throws an exception, it stops processing the remaining code in your method and exits the method. This is a problem if the method has acquired some local resource that only it knows about and if that resource must be cleaned up. One solution is to catch and rethrow all exceptions. But this solution is tedious because you need to clean up the resource allocation in two places, in the normal code and in the exception code.

Java has a better solution, the finally clause:

 Graphics g = image.getGraphics(); try {    code that might    throw exceptions } catch (IOException e) {    show error dialog } finally {    g.dispose(); } 

This program executes the code in the finally clause whether or not an exception was caught. This means, in the example code above, the program will dispose of the graphics context under all circumstances.

Let us look at the three possible situations where the program will execute the finally clause.

  1. The code throws no exceptions. In this event, the program first executes all the code in the try block. Then, it executes the code in the finally clause. Afterwards, execution continues with the first line after the try block.

  2. The code throws an exception that is caught in a catch clause, in our case, an IOException. For this, the program executes all code in the try block, up to the point at which the exception was thrown. The remaining code in the try block is skipped. Then, the program executes the code in the matching catch clause, then the code in the finally clause.

    If the catch clause does not throw an exception, then the program executes the first line after the try block. If it does, then the exception is thrown back to the caller of this method.

  3. The code throws an exception that is not caught in any catch clause. For this, the program executes all code in the try block until the exception is thrown. The remaining code in the try block is skipped. Then, the code in the finally clause is executed, and the exception is thrown back to the caller of this method.

You can use the finally clause without a catch clause. For example, consider the following try statement:

 Graphics g = image.getGraphics(); try {    code that might    throw exceptions } finally {    g.dispose(); } 

The g.dispose() command in the finally clause is executed whether or not an exception is encountered in the try block. Of course, if an exception is encountered, it is rethrown and must be caught in another catch clause.

graphics/caution_icon.gif

The finally clause leads to unexpected control flow when you exit the middle of a try block with a return statement. Before the method returns, the contents of the finally block are executed. If it also contains a return statement, then it masks the original return value. Consider this contrived example:

 public static int f(int n) {    try    {       int r = n * n;       return r;    }    finally    {       if (n == 2) return 0;    } } 

If you call f(2), then the try block computes r = 4 and executes the return statement. However, the finally clause is executed before the method actually returns. The finally clause causes the method to return 0, ignoring the original return value of 4.

Sometimes the finally clause gives you grief, namely if the cleanup method can also throw an exception. A typical case is closing a stream. (See Chapter 12 for more information on streams.) Suppose you want to make sure that you close a stream when an exception hits in the stream processing code.

 InputStream in; try {    code that might    throw exceptions } catch (IOException e) {    show error dialog } finally {    in.close(); } 

Now suppose that the code in the try block throws some exception other than an IOException that is of interest to the caller of the code. The finally block executes, and the close method is called. That method can itself throw an IOException! When it does, then the original exception is lost and the IOException is thrown instead. That is very much against the spirit of exception handling.

It is always a good idea unfortunately not one that the designers of the InputStream class chose to follow to throw no exceptions in cleanup operations such as dispose, close, and so on, that you expect users to call in finally blocks.

graphics/cplus_icon.gif

There is one fundamental difference between C++ and Java with regard to exception handling. Java has no destructors; thus, there is no stack unwinding as in C++. This means that the Java programmer must manually place code to reclaim resources in finally blocks. Of course, since Java does garbage collection, there are far fewer resources that require manual deallocation.

Chained Exceptions

It is quite common to turn one exception into another. For example, suppose you call a method that throws a checked exception in an event handler:

 public void actionPerformed(ActionEvent event) {    . . .    out = new FileWriter(fileName);       // may throw IOException    . . . } 

The actionPerformed method cannot throw any checked exception. If you aren't ready to deal with the exception in the handler, then you need to catch it and turn it into an unchecked exception. Prior to SDK 1.4, you had to put the text description of the checked exception object into a new unchecked exception, losing some information. In SDK 1.4, you can simply pass an exception object to the constructor of another, and retrieve it later with the getCause method:

 RuntimeException unchecked = new RuntimeException(checked); . . . Throwable t = e.getCause(); 

A number of exception classes, such as ClassNotFoundException, InvocationTargetException, and RuntimeException, have had their own chaining schemes. These have now been brought into conformance with the general mechanism. You can still retrieve the chained exception in the historical way, or just call getCause.

graphics/notes_icon.gif

Unfortunately, the chained exception most commonly used by application programmers, SQLException, has not been retrofitted.

Stack Frames

Prior to SDK 1.4, you had access to the text description of a stack trace by calling the printStackTrace method of the Throwable class. Now you can call the getStackTrace method to get an array of StackTraceElement objects that you can analyze in your program. For example,

 Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for (int i = 0; i < frames.length; i++)    analyze frames[i] 

The StackTraceElement class has methods to obtain the file name and line number, as well as the class and method name, of the executing line of code. The toString method yields a formatted string containing all of this information.

Program 11-1 prints the stack trace of a recursive factorial function. For example, if you compute factorial(3), the printout is

 factorial(3): StackTraceTest.factorial(StackTraceTest.java:8) StackTraceTest.main(StackTraceTest.java:23) factorial(2): StackTraceTest.factorial(StackTraceTest.java:8) StackTraceTest.factorial(StackTraceTest.java:14) StackTraceTest.main(StackTraceTest.java:23) factorial(1): StackTraceTest.factorial(StackTraceTest.java:8) StackTraceTest.factorial(StackTraceTest.java:14) StackTraceTest.factorial(StackTraceTest.java:14) StackTraceTest.main(StackTraceTest.java:23) return 1 return 2 return 6 
Example 11-1 StackTraceTest.java
  1. import javax.swing.*;  2.  3. /**  4.    A program that displays a trace feature of a recursive  5.    method call.  6. */  7. public class StackTraceTest  8. {  9.    /** 10.       Computes the factorial of a number 11.       @param n a nonnegative integer 12.       @return n! = 1 * 2 * . . . * n 13.    */ 14.    public static int factorial(int n) 15.    { 16.       System.out.println("factorial(" + n + "):"); 17.       Throwable t = new Throwable(); 18.       StackTraceElement[] frames = t.getStackTrace(); 19.       for (int i = 0; i < frames.length; i++) 20.          System.out.println(frames[i]); 21.       int r; 22.       if (n <= 1) r = 1; 23.       else r = n * factorial(n - 1); 24.       System.out.println("return " + r); 25.       return r; 26.    } 27. 28.    public static void main(String[] args) 29.    { 30.       String input = JOptionPane.showInputDialog("Enter n:"); 31.       int n = Integer.parseInt(input); 32.       factorial(n); 33.       System.exit(0); 34.    } 35. } 

java.lang.Throwable 1.0

graphics/api_icon.gif
  • Throwable(Throwable cause) 1.4

  • Throwable(String message, Throwable cause) 1.4

    construct a Throwable with a given cause.

  • Throwable initCause(Throwable cause) 1.4

    sets the cause for this object, or throws an exception if this object already has a cause. Returns this.

  • Throwable getCause() 1.4

    gets the exception object that was set as the cause for this object, or null if no cause was set.

  • StackTraceElement[] getStackTrace() 1.4

    gets the trace of the call stack at the time this object was constructed.

java.lang.Exception 1.0

graphics/api_icon.gif
  • Exception(Throwable cause) 1.4

  • Exception(String message, Throwable cause)

    construct an Exception with a given cause.

java.lang.RuntimeException 1.0

graphics/api_icon.gif
  • RuntimeException(Throwable cause) 1.4

  • RuntimeException(String message, Throwable cause) 1.4

    construct a RuntimeException with a given cause

java.lang.StackTraceElement 1.4

graphics/api_icon.gif
  • String getFileName()

    gets the name of the source file containing the execution point of this element, or null if the information is not available.

  • int getLineNumber()

    gets the line number of the source file containing the execution point of this element, or -1 if the information is not available.

  • String getClassName()

    gets the fully qualified name of the class containing the execution point of this element.

  • String getMethodName()

    gets the name of the method containing the execution point of this element. The name of a constructor is <init>. The name of a static initializer is <clinit>. You can't distinguish between overloaded methods with the same name.

  • boolean isNativeMethod()

    returns true if the execution point of this element is inside a native method.

  • String toString()

    returns a formatted string containing the class and method name and the file name and line number, if available.

A Final Look at Java Error and Exception Handling

Example 11-2 deliberately generates a number of different errors and catches various exceptions (see Figure 11-2).

Figure 11-2. A program that generates exceptions

graphics/11fig02.gif

Try it out. Click on the buttons and see what exceptions are thrown.

As you know, a programmer error such as a bad array index throws a RuntimeException. An attempt to open a nonexistent file triggers an IOException. Perhaps surprisingly, floating-point errors such as dividing by 0.0 or taking the square root of -1 do not generate exceptions. (Integer division by 0 throws an ArithmeticException.)

We trap the exceptions that the actionPerformed methods throw in the fireActionPerformed method of the radio buttons and display them in the text field. However, the actionPerformed method is declared to throw no checked exceptions. Thus, the handler for the "No such file" button must catch the IOException.

If you click on the "Throw unknown" button, an UnknownError object is thrown. This is not a subclass of Exception, so our program does not catch it. Instead, the user interface code prints an error message and a stack trace to the console.

Example 11-2 ExceptTest.java
   1. import java.awt.*;   2. import java.awt.event.*;   3. import javax.swing.*;   4. import java.io.*;   5.   6. public class ExceptTest   7. {   8.    public static void main(String[] args)   9.    {  10.      ExceptTestFrame frame = new ExceptTestFrame();  11.      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  12.      frame.show();  13.   }  14. }  15.  16. /**  17.   A frame with a panel for testing various exceptions  18. */  19. class ExceptTestFrame extends JFrame  20. {  21.    public ExceptTestFrame()  22.    {  23.       setTitle("ExceptTest");  24.       Container contentPane = getContentPane();  25.       ExceptTestPanel panel = new ExceptTestPanel();  26.       contentPane.add(panel);  27.       pack();  28.    }  29. }  30.  31. /**  32.    A panel with radio buttons for running code snippets  33.    and studying their exception behavior  34. */  35. class ExceptTestPanel extends Box  36. {  37.    public ExceptTestPanel()  38.    {  39.       super(BoxLayout.Y_AXIS);  40.       group = new ButtonGroup();  41.  42.       // add radio buttons for code snippets  43.  44.       addRadioButton("Integer divide by zero", new  45.          ActionListener()  46.          {  47.             public void actionPerformed(ActionEvent event)  48.             {  49.                a[1] = 1 / (a.length - a.length);  50.             }  51.          });  52.  53.       addRadioButton("Floating point divide by zero", new  54.          ActionListener()  55.          {  56.             public void actionPerformed(ActionEvent event)  57.             {  58.                a[1] = a[2] / (a[3] - a[3]);  59.             }  60.          });  61.  62.       addRadioButton("Array bounds", new  63.          ActionListener()  64.          {  65.             public void actionPerformed(ActionEvent event)  66.             {  67.                a[1] = a[10];  68.             }  69.          });  70.  71.       addRadioButton("Bad cast", new  72.          ActionListener()  73.          {  74.             public void actionPerformed(ActionEvent event)  75.             {  76.                a = (double[])event.getSource();  77.             }  78.          });  79.  80.       addRadioButton("Null pointer", new  81.          ActionListener()  82.          {  83.             public void actionPerformed(ActionEvent event)  84.             {  85.                event = null;  86.                System.out.println(event.getSource());  87.             }  88.          });  89.  90.       addRadioButton("sqrt(-1)", new  91.          ActionListener()  92.          {  93.             public void actionPerformed(ActionEvent event)  94.             {  95.                a[1] = Math.sqrt(-1);  96.             }  97.          });  98.  99.       addRadioButton("Overflow", new 100.          ActionListener() 101.          { 102.             public void actionPerformed(ActionEvent event) 103.             { 104.                a[1] = 1000 * 1000 * 1000 * 1000; 105.                int n = (int)a[1]; 106.             } 107.          }); 108. 109.       addRadioButton("No such file", new 110.          ActionListener() 111.          { 112.             public void actionPerformed(ActionEvent event) 113.             { 114.                try 115.                { 116.                   FileInputStream is 117.                      = new FileInputStream("No such file"); 118.                } 119.                catch (IOException exception) 120.                { 121.                   textField.setText(exception.toString()); 122.                } 123.             } 124.          }); 125. 126.       addRadioButton("Throw unknown", new 127.          ActionListener() 128.          { 129.             public void actionPerformed(ActionEvent event) 130.             { 131.                throw new UnknownError(); 132.             } 133.          }); 134. 135.       // add the text field for exception display 136.       textField = new JTextField(30); 137.       add(textField); 138.    } 139. 140.    /** 141.       Adds a radio button with a given listener to the 142.       panel. Traps any exceptions in the actionPerformed 143.       method of the listener. 144.       @param s the label of the radio button 145.       @param listener the action listener for the radio button 146.    */ 147.    private void addRadioButton(String s, ActionListener listener) 148.    { 149.       JRadioButton button = new JRadioButton(s, false) 150.          { 151.             // the button calls this method to fire an 152.             // action event. We override it to trap exceptions 153.             protected void fireActionPerformed(ActionEvent event) 154.             { 155.                try 156.                { 157.                   textField.setText("No exception"); 158.                   super.fireActionPerformed(event); 159.                } 160.                catch (Exception exception) 161.                { 162.                   textField.setText(exception.toString()); 163.                } 164.             } 165.          }; 166. 167.       button.addActionListener(listener); 168.       add(button); 169.       group.add(button); 170.    } 171. 172.    private ButtonGroup group; 173.    private JTextField textField; 174.    private double[] a = new double[10]; 175. } 

       
    Top
     



    Core Java 2(c) Volume I - Fundamentals
    Building on Your AIX Investment: Moving Forward with IBM eServer pSeries in an On Demand World (MaxFacts Guidebook series)
    ISBN: 193164408X
    EAN: 2147483647
    Year: 2003
    Pages: 110
    Authors: Jim Hoskins

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