|
|
|
Sponsored Link •
|
|
Advertisement
|
Exceptions and the method invocation stack
Code inside a try block is in a sense surrounded by the catch clauses
associated with the try block. When an exception is thrown, the
surrounding catch clauses are examined in inside-out order. You can
nest try blocks inside try blocks, in effect building up more and more
layers of catch clauses that surround the code. When a method is
invoked from within a try block, the catch clauses associated with that
try block surround the code in the invoked method as well. If
that method has try blocks and catch clauses, they are added as inner
surrounding layers. What this means is that an exception may be thrown
far up the method invocation stack before landing in a catch clause
that can handle it.
As an example, consider the following exception classes, which are simpler versions of exceptions introduced in examples above:
// In Source Packet in file except/ex7/TemperatureException.java
class TemperatureException extends Exception {
}
// In Source Packet in file except/ex7/TooColdException.java
class TooColdException extends TemperatureException {
}
// In Source Packet in file except/ex7/TooHotException.java
class TooHotException extends TemperatureException {
}
// In Source Packet in file except/ex7/UnusualTasteException.java
class UnusualTasteException extends Exception {
}
When the drinkCoffee() method of class
VirtualPerson is invoked, it throws one of these four
exceptions, chosen at random:
// In Source Packet in file except/ex7/VirtualPerson.java
class VirtualPerson {
public void drinkCoffee(CoffeeCup cup) throws TooColdException,
TemperatureException, UnusualTasteException {
try {
int i = (int) (Math.random() * 4.0);
switch (i) {
case 0:
throw new TooHotException();
case 1:
throw new TooColdException();
case 2:
throw new UnusualTasteException();
default:
throw new TemperatureException();
}
}
catch (TooHotException e) {
System.out.println("This coffee is too hot.");
// Customer will wait until it cools to an
// acceptable temperature.
}
}
//...
}
If variable i in the drinkCoffee() method
above happens to be set to a value of 0 the switch statement will
instantiate and throw a TooHotException. Because the
switch statement itself is enclosed within a try block that has a catch
clause for TooHotException, execution continues at that
catch clause. The program prints out:
This coffee is too hot.
If variable i in the drinkCoffee() method
above happens to be set to the value of 1, the switch statement will
instantiate and throw a TooColdException. When this
exception is thrown, the Java virtual machine will first check the
catch clauses of the try block that surrounds the switch statement. In
this case, however, no catch clause matches the thrown exception.
Because the TooColdException is not caught by the
drinkCoffee() method, the Java virtual machine throws the
exception up the method invocation stack to the method that invoked
drinkCoffee(). As used here, a method invocation
stack (or call stack) is a list of the methods that have
been invoked by a thread, starting with the first method the thread
invoked and ending with the current method. A method invocation stack
shows the path of method invocations a thread took to arrive at the
current method.
A graphical representation of the method invocation stack for
drinkCoffee() is shown in Figure 3. In this figure, the
method invocation stack is shown on the right and the corresponding
Java stack is shown on the left. The Java stack is where
methods keep their state inside the Java virtual machine. Each method
gets a stack frame (or frame), which is pushed onto
the stack when the method is invoked and popped from the stack when the
method completes. The frame is an area in memory that contains the
method's local variables, parameters, return value, and other
information needed by the Java virtual machine to execute the method.
In Figure 3, the stack is shown growing downwards. The top of the stack
is at the bottom of the picture.
Figure 3. The method invocation stack for drinkCoffee()
|
When a method completes by executing a return statement, or by
successfully executing the last statement in a method declared as
void, it is said to complete normally. The Java
virtual machine pops the returning method's stack frame, and continues
executing just after the method invocation in the calling method. The
calling method becomes the current method and its stack frame
becomes the current frame.
When a method throws an exception that it doesn't catch itself, it is said to complete abruptly. Methods do not return a value when they complete abruptly, though they do pass along an exception object.
For example, when the drinkCoffee() method throws a
TooColdException, it completes abruptly. Because the
exception isn't caught by drinkCoffee(), the Java virtual
machine pops drinkCoffee()'s stack frame. It then examines
the next method up the invocation stack, in this case the
serveCustomer() method of VirtualCafe, to see
if it has a catch clause prepared to handle the exception.
Here's the code for VirtualCafe:
// In Source Packet in file except/ex7/VirtualCafe.java
class VirtualCafe {
public static void serveCustomer(VirtualPerson cust,
CoffeeCup cup)throws TemperatureException,
UnusualTasteException {
try {
cust.drinkCoffee(cup);
}
catch (TooColdException e) {
System.out.println("This coffee is too cold.");
// Add more hot coffee...
}
}
}
The serveCustomer() method above does indeed surround its
invocation of drinkCoffee() with a try block that has an
attached catch clause for TooColdException. So the
exception stops here. The Java virtual machine makes the
serveCustomer() method's stack frame current and continues
execution at the first statement inside the catch clause. The program
prints out:
This coffee is too cold.
If variable i in the drinkCoffee() method
above happens to be set to the value of 2, the switch statement will
instantiate and throw an UnusualTasteException. When this
exception is thrown, the Java virtual machine will first check the
catch clauses of the try block that surrounds the switch statement. In
this case, no catch clause matches the thrown exception. The virtual
machine will then pop drinkCoffee()'s stack frame and
examine the serveCustomer() method. But in
serveCustomer(), no catch clause attached to the try block
matches the thrown exception either. The virtual machine will therefore
pop serveCustomer()'s stack frame and examine the next
method up the invocation stack: the main() method of class
Example7.
Here's the code for Example7:
// In Source Packet in file except/ex7/Example7.java
class Example7 {
public static void main(String[] args)
throws TemperatureException {
// Create a new coffee cup.
CoffeeCup cup = new CoffeeCup();
// Create and serve a virtual customer.
try {
VirtualPerson cust = new VirtualPerson();
VirtualCafe.serveCustomer(cust, cup);
}
catch (UnusualTasteException e) {
System.out.println("This coffee has an unusual taste.");
}
}
}
This main() method was farsighted enough to surround its
invocation of serveCustomer() with a try block that
includes a catch clause for UnusualTasteException. Thus,
the Java virtual machine will make the main() method's
stack frame current and will continue execution at the first statement
in the catch clause. The program will print:
This coffee has an unusual taste.
In the UnusualTasteException case, both
drinkCoffee() and serveCoffee() methods
completed abruptly. The Java virtual machine popped two frames from the
Java stack, stopping its popping only when it reached the
main() method.
The last case in this example occurs if the variable i in
the drinkCoffee() method gets set to a value greater than
2. In this case, the switch statement will instantiate and throw a
TemperatureException. When this exception is thrown, the
Java virtual machine will go through its usual procedure of examining
methods for catch clauses and popping frames for methods that can't
handle the exception. The virtual machine will examine
drinkCoffee(), pop its frame, examine
serveCustomer(), pop its frame, examine
main(), and pop its frame. At this point, however, the
virtual machine has run out of frames. It can't go any further up the
method invocation stack because main() was the first
method invoked by the thread.
Because none of the methods on the invocation stack is prepared to
handle the TemperatureException, the exception is
"uncaught." It will be handled by a default handler and result in the
death of the thread. Because this is the main thread of the
Example7 application and the application didn't fire off
any other threads that are still running when the main thread dies, the
application terminates. (A dead thread doesn't always cause the death
of its application, only when a dying thread is the last "non-daemon"
thread running inside the application.) In most Java runtime
environments, the default handler for an uncaught exception will print
out a stack trace when a thread dies. For example, the
java program from JDK 1.1.1 prints the following when the
main thread of Example7 dies due to an uncaught
TemperatureException:
TemperatureException
at VirtualPerson.drinkCoffee(VirtualPerson.java:20)
at VirtualCafe.serveCustomer(VirtualCafe.java:9)
at Example7.main(Example7.java:12)
|
Sponsored Links
|