|
![]() |
![]() |
![]() |
![]() |
Well, you now understand how to put try blocks together with catch blocks and finally blocks in your methods. You may be thinking, at this point, that it seems a lot of trouble to go to, just to display a message when an exception is thrown. You may be right, but whether you can do very much more depends on the nature and context of the problem. In many situations a message may be the best you can do, although you can produce messages that are a bit more informative than those we've used so far in our examples. For one thing we have totally ignored the exception object that is passed to the catch block.
The exception object that is passed to a catch block can provide additional information about the nature of the problem that originated it. To understand more about this, let's first look at the members of the base class for exceptions Throwable, because these will be inherited by all exception classes and are therefore contained in every exception object that is thrown.
The class Throwable is the class from which all Java exception classes are derived - that is, every exception object will contain the methods defined in this class. The class Throwable has two constructors, a default constructor, and a constructor that accepts an argument of type String. The String object that is passed to the constructor is used to provide a description of the nature of the problem causing the exception. Both constructors are public.
Objects of type Throwable contain two items of information about an exception:
A message, that we have just referred to as being initialized by a constructor
A record of the execution stack at the time the object was created
The execution stack keeps track of all the methods that are in execution at any given instant. It provides the means whereby executing a return gets back to the calling point for a method. The record of the execution stack that is stored in the exception object will consist of the line number in the source code where the exception originated followed by a trace of the method calls that immediately preceded the point at which the exception occurred. This is made up of the fully qualified name for each of the methods called, plus the line number in the source file where each method call occurred. The method calls are in sequence with the most recent method call appearing first. This will help you to understand how this point in the program was reached.
The Throwable class has the following public methods that enable you to access the message and the stack trace:
There's another method, fillInStackTrace(), which will update the stack trace to the point at which this method is called. For example, if you put a call to this method in the catch block:
e.fillInStackTrace();
The line number recorded in the stack record for the method in which the exception occurred will be the line where fillInStackTrace() is called. The main use of this is when you want to rethrow an exception (so it will be caught by the calling method) and record the point at which it is rethrown. For example:
e.fillInStackTrace(); // Record the throw point throw e; // Rethrow the exception
In practice, it's often more useful to throw an exception of your own. We'll see how to define your own exceptions in the next section, but first, let's exercise some of the methods defined in the Throwable class, and see the results.
The easiest way to try out some of the methods we've just discussed is to make some judicious additions to the catch blocks in the divide() method we have in the TryBlockTest class example:
public static int divide(int[] array, int index) { try { System.out.println("\nFirst try block in divide() entered"); array[index + 2] = array[index]/array[index + 1]; System.out.println("Code at end of first try block in divide()"); return array[index + 2]; } catch(ArithmeticException e) { System.err.println("Arithmetic exception caught in divide()\n" + "\nMessage in exception object:\n\t" + e.getMessage()); System.err.println("\nStack trace output:\n"); e.printStackTrace(); System.err.println("\nEnd of stack trace output\n"); } catch(ArrayIndexOutOfBoundsException e) { System.err.println("Index-out-of-bounds exception caught in divide()\n" + "\nMessage in exception object:\n\t" + e.getMessage()); System.err.println("\nStack trace output:\n"); e.printStackTrace(); System.out.println("\nEnd of stack trace output\n"); } finally { System.err.println("finally clause in divide()"); } System.out.println("Executing code after try block in divide()"); return array[index + 2]; }
If you recompile the program and run it again, it will produce all the output, as before, but with extra information when exceptions are thrown in the divide() method. The new output generated for the ArithmeticException will be:
Message in exception object: / by zero Stack trace output: java.lang.ArithmeticException: / by zero at TryBlockTest.divide(TryBlockTest.java:54) at TryBlockTest.main(TryBlockTest.java:15)
End of stack trace output
The additional output generated for the ArrayIndexOutOfBoundsException will be:
Message in exception object: null Stack trace output: java.lang.ArrayIndexOutOfBoundsException at TryBlockTest.divide(TryBlockTest.java:54) at TryBlockTest.main(TryBlockTest.java:17) End of stack trace output
How It Works
The extra lines of code in each of the catch blocks in the divide() method output the message associated with the exception object e, by calling its getMessage() method. We could have just put e here which would invoke the toString() method for e and, in this case, the class name for e would precede the message. These are a couple of extra println() calls around the call to printStackTrace() to make it easier to find the stack trace in the output. These are called for the standard error stream object, System.err, for consistency with the stack trace output.
The first stack trace, for the arithmetic exception, indicates that the error originated at line 54 in the source file, TryBlockText.java, and the last method call was at line 15 in the same source file. The second stack trace provides similar information about the index-out-of-bounds exception, including the offending index value. As you can see, with the stack trace output, it's very easy to see where the error occurred, and how this point in the program was reached.
The majority of predefined exception classes in Java don't add further information about the conditions that created the exception. The type alone serves to differentiate one exception from another in most cases. This general lack of additional information is because it can only be gleaned, in the majority of cases, by prior knowledge of the computation that is being carried out when the exception occurs, and the only person who is privy to that is you, since you're writing the program.
This should spark the glimmer of an idea. If you need more information about the circumstances surrounding an exception, you're going to have to obtain it, and, equally important, communicate it to the appropriate point in your program. This leads to the notion of defining your own exceptions.
![]() |
![]() |
![]() |