Classic Tutorials for Java Beginners

Exceptions in Java

The full story of exceptions in the Java language and virtual machine

1 2 3 4 Page 2
Page 2 of 4
// In Source Packet in file except/ex3/VirtualCafe.java
class VirtualCafe {
    public static void serveCustomer(VirtualPerson cust,
        CoffeeCup cup) {
        try {
            cust.drinkCoffee(cup);
            System.out.println("Coffee is just right.");
        }
        catch (TemperatureException e) {
            // This catches TooColdException, TooHotException,
            // as well as TemperatureException.
            System.out.println("Coffee is too cold or too hot.");
            // Deal with an irate customer...
        }
        // THIS WON'T COMPILE, BECAUSE THIS catch clause
        // WILL NEVER BE REACHED.
        catch (TooColdException e) {
            System.out.println("Coffee is too cold.");
        }
    }
}
// In Source Packet in file except/ex4/VirtualCafe.java
// This class compiles fine.
class VirtualCafe {
    public static void serveCustomer(VirtualPerson cust,
        CoffeeCup cup) {
        try {
            cust.drinkCoffee(cup);
            System.out.println("Coffee is just right.");
        }
        catch (TooColdException e) {
            System.out.println("Coffee is too cold.");
            // Deal with an irate customer...
        }
        catch (TemperatureException e) {
            // This catches TooHotException as well
            // as TemperatureException.
            System.out.println(
                "There's temperature trouble in this coffee.");
            // Deal with an irate customer...
        }
    }
}

Embedding information in an exception object

When you throw an exception, you are performing a kind of structured go-to from the place in your program where an abnormal condition was detected to a place where it can be handled. The Java virtual machine uses the class of the exception object you throw to decide which catch clause, if any, should be allowed to handle the exception. But an exception doesn't just transfer control from one part of your program to another, it also transmits information. Because the exception is a full-fledged object that you can define yourself, you can embed information about the abnormal condition in the object before you throw it. The catch clause can then get the information by querying the exception object directly.

The Exception class allows you to specify a String detail message that can be retrieved by invoking getMessage() on the exception object. When you define an exception class of your own, you can give client programmers the option of specifying a detail message like this:

// In Source Packet in file except/ex5/UnusualTasteException.java
class UnusualTasteException extends Exception {
    UnusualTasteException() {
    }
    UnusualTasteException(String msg) {
        super(msg);
    }
}

Given the above declaration of UnusualTasteException, client programmers could create an instance in one of two ways:

  1. new UnusualTasteException()
  2. new UnusualTasteException("This coffee tastes like tea.")

A catch clause can then query the object for a detail string, like this:

// In Source Packet in file except/ex5/VirtualCafe.java
class VirtualCafe {
    public static void serveCustomer(VirtualPerson cust,
        CoffeeCup cup) {
        try {
            cust.drinkCoffee(cup);
            System.out.println("Coffee tastes just right.");
        }
        catch (UnusualTasteException e) {
            System.out.println(
                "Customer is complaining of an unusual taste.");
            String s = e.getMessage();
            if (s != null) {
                System.out.println(s);
            }
            // Deal with an unhappy customer...
        }
    }
}

When you need to embed more information into an exception object than you can represent with a String, you can add data and access methods to your exception class. For example, you could define the temperature exception classes like this:

// In Source Packet in file except/ex6/TemperatureException.java
abstract class TemperatureException extends Exception {
    private int temperature; // in Celsius
    public TemperatureException(int temperature) {
        this.temperature = temperature;
    }
    public int getTemperature() {
        return temperature;
    }
}
// In Source Packet in file except/ex6/TooColdException.java
class TooColdException extends TemperatureException {
    public TooColdException(int temperature) {
        super(temperature);
    }
}
// In Source Packet in file except/ex6/TooHotException.java
class TooHotException extends TemperatureException {
    public TooHotException(int temperature) {
        super(temperature);
    }
}

Given a TemperatureException family as defined above, catch clauses can query the exception object to find out the precise temperature that caused the problem. The temperature field of the exception object must be set when the object is created, as in:

// In Source Packet in file except/ex6/VirtualPerson.java
class VirtualPerson {
    private static final int tooCold = 65;
    private static final int tooHot = 85;
    public void drinkCoffee(CoffeeCup cup) throws
        TooColdException, TooHotException {
        int temperature = cup.getTemperature();
        if (temperature <= tooCold) {
            throw new TooColdException(temperature);
        }
        else if (temperature >= tooHot) {
            throw new TooHotException(temperature);
        }
        //...
    }
    //...
}

Wherever the exception is caught, the catch clause can easily determine the actual temperature of the coffee and act accordingly, as in:

// In Source Packet in file except/ex6/VirtualCafe.java
class VirtualCafe {
    public static void serveCustomer(VirtualPerson cust,
        CoffeeCup cup) {
        try {
            cust.drinkCoffee(cup);
            System.out.println("Coffee is just right.");
        }
        catch (TooColdException e) {
            int temperature = e.getTemperature();
            System.out.println("Coffee temperature is "
                + temperature + " degrees Celsius.");
            if (temperature > 55 && temperature <= 65) {
                System.out.println("Coffee is cooling off.");
                // Add more hot coffee...
            }
            else if (temperature > 0 && temperature <= 55) {
                System.out.println("Coffee is too cold.");
                // Give customer a new cup of coffee with the
                // proper temperature...
            }
            else if (temperature <= 0) {
                System.out.println("Coffee is frozen.");
                // Deal with an irate customer...
            }
        }
        catch (TooHotException e) {
            int temperature = e.getTemperature();
            System.out.println("Coffee temperature is "
                + temperature + " degrees Celsius.");
            if (temperature >= 85 && temperature < 100) {
                System.out.println("Coffee is too hot.");
                // Ask customer to let it cool a few minutes...
            }
            else if (temperature >= 100 && temperature < 2000) {
                System.out.println(
                    "Both coffee and customer are steamed.");
                // Deal with an irate customer...
            }
            else if (temperature >= 2000) {
                System.out.println(
                    "The coffee is plasma.");
                // Deal with a very irate customer...
            }
        }
    }
}

The program could deal with the temperature problem differently depending upon the coffee's actual temperature. If the coffee is just a little cold, the program could add more hot coffee to the cup. If the coffee is so cold that the customer's lips were instantly frozen to the cup, alternative measures could be taken.

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:

1 2 3 4 Page 2
Page 2 of 4