Java ScreenShot
     

Screenshot Core Java 2: Volume I - Fundamentals

Table of Contents
 11.  Exceptions and Debugging


Catching Exceptions

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 app, 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 app) 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.

Java 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.

Java 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.

Java 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 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.

Java 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.

Java graphics notes_icon.gif

Unfortunately, the chained exception most commonly used by app 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

Java 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

Java 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

Java 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

Java 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 Screenshot-2).

Screenshot-2. A program that generates exceptions

Java 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. }


Screenshot

Java ScreenShot
     
Top
 

Comments