Exceptions


(来源:http://www.artima.com)

Objects and Java Seminar by Bill Venners
Lecture Handout

Agenda

  • Introduce exceptions
  • Demonstrate throwing and catching
  • Show how to embed info in an exception
  • Look at exceptions and the stack
  • Define the throws clause
  • Talk about checked vs. unchecked
  • Describe finally clauses

Exceptions in Java

  • A method throws an exception on an abnormal condition that it can't handle
  • Throwing an exception is like throwing a beeping, flashing red ball
  • Somewhere, you hope, this ball will be caught and the problem handled
  • A structured "go to" from a place where an error occurs to a place that knows how to handle the error

Exception Classes

  • In Java, exceptions are objects: instances of java.lang.Throwable or one of its subclasses.

  • Exceptions are thrown for errors that can often be caught and handled
  • Errors are thrown for more serious problems
  • Your code should throw exceptions

Choosing an Exception Class

  • When you need to throw an exception, you can choose an already existing exception or make one of your own

    1 // In file except/ex1/TemperatureException.java
    2 class TemperatureException extends Exception {
    3 }
    
    1 // In file except/ex1/TooColdException.java
    2 class TooColdException extends TemperatureException {
    3 }
    
    1 // In file except/ex1/TooHotException.java
    2 class TooHotException extends TemperatureException {
    3 }
    
  • Exception class should indicate the type of problem that caused the exception
  • Often, exception classes may have no fields or methods

Throwing Exceptions

  • To throw an exception, you use the throw keyword:

    throw new TooColdException();
    
  • The JVM or Java APIs can throw exceptions as well
  • Here's an example of code that explicitly throws temperature exceptions:

     1 // In file except/ex1/VirtualPerson.java
     2 class VirtualPerson {
     3 
     4     private static final int tooCold = 65;
     5     private static final int tooHot = 85;
     6 
     7     public void drinkCoffee(CoffeeCup cup) throws
     8         TooColdException, TooHotException {
     9 
    10         int temperature = cup.getTemperature();
    11         if (temperature <= tooCold) {
    12             throw new TooColdException();
    13         }
    14         else if (temperature >= tooHot) {
    15             throw new TooHotException();
    16         }
    17         //...
    18     }
    19     //...
    20 }
    
     1 // In file except/ex1/CoffeeCup.java
     2 class CoffeeCup {
     3     // 75 degrees Celsius: the best temperature
     4     // for coffee
     5     private int temperature = 75;
     6     public void setTemperature(int val) {
     7         temperature = val;
     8     }
     9     public int getTemperature() {
    10         return temperature;
    11     }
    12     //...
    13 }
    

Catching Exceptions

  • To catch an exception in Java, you write a try block with one or more catch clauses:

     1 // In file except/ex1/Example1.java
     2 class Example1 {
     3     public static void main(String[] args) {
     4 
     5         int temperature = 0;
     6         if (args.length > 0) {
     7             try {
     8                 temperature =
     9                     Integer.parseInt(args[0]);
    10             }
    11             catch(NumberFormatException e) {
    12                 System.out.println(
    13                   "Must enter integer as first argument.");
    14                 return;
    15             }
    16         }
    17         else {
    18             System.out.println(
    19               "Must enter temperature as first argument.");
    20             return;
    21         }
    22 
    23         // Create a new coffee cup and set the
    24         // temperature of its coffee.
    25         CoffeeCup cup = new CoffeeCup();
    26         cup.setTemperature(temperature);
    27 
    28         // Create and serve a virtual customer.
    29         VirtualPerson cust = new VirtualPerson();
    30         VirtualCafe.serveCustomer(cust, cup);
    31     }
    32 }
    
  • The lower case character e is a reference to the thrown (and caught) NumberFormatException object
  • Can also have multiple catch clauses:

     1 // In file except/ex1/VirtualCafe.java
     2 class VirtualCafe {
     3 
     4     public static void serveCustomer(VirtualPerson cust,
     5         CoffeeCup cup) {
     6 
     7         try {
     8             cust.drinkCoffee(cup);
     9             System.out.println("Coffee is just right.");
    10         }
    11         catch (TooColdException e) {
    12             System.out.println("Coffee is too cold.");
    13             // Deal with an irate customer...
    14         }
    15         catch (TooHotException e) {
    16             System.out.println("Coffee is too hot.");
    17             // Deal with an irate customer...
    18         }
    19     }
    20 }
    
  • Catch clauses are examined in their order of appearance in the source file

Matching Exceptions

  • A catch clause for a superclass type will catch a thrown subclass exception.

     1 // In file except/ex2/VirtualCafe.java
     2 class VirtualCafe {
     3 
     4     public static void serveCustomer(
     5         VirtualPerson cust, CoffeeCup cup) {
     6 
     7         try {
     8             cust.drinkCoffee(cup);
     9             System.out.println(
    10                 "Coffee is just right.");
    11         }
    12         catch (TemperatureException e) {
    13             // This catches TooColdException,
    14             // TooHotException, as well as
    15             // TemperatureException.
    16             System.out.println(
    17                 "Coffee is too cold or too hot.");
    18             // Deal with an irate customer...
    19         }
    20     }
    21 }
    
  • Catch clauses for subclass types must precede catch clauses for superclass types. Thus, this won't compile:

     1 // In file except/ex3/VirtualCafe.java
     2 class VirtualCafe {
     3 
     4     public static void serveCustomer(
     5         VirtualPerson cust, CoffeeCup cup) {
     6 
     7         try {
     8             cust.drinkCoffee(cup);
     9             System.out.println(
    10                 "Coffee is just right.");
    11         }
    12         catch (TemperatureException e) {
    13             // This catches TooColdException,
    14             // TooHotException, as well as
    15             // TemperatureException.
    16             System.out.println(
    17                 "Coffee is too cold or too hot.");
    18             // Deal with an irate customer...
    19         }
    20         // THIS WON'T COMPILE, BECAUSE THIS
    21         // CATCH CLAUSE WILL NEVER BE REACHED.
    22         catch (TooColdException e) {
    23             System.out.println(
    24                 "Coffee is too cold.");
    25         }
    26     }
    27 }
    
  • This alternate ordering compiles fine:

     1 // In file except/ex4/VirtualCafe.java
     2 // This class compiles fine.
     3 class VirtualCafe {
     4 
     5     public static void serveCustomer(
     6         VirtualPerson cust, CoffeeCup cup) {
     7 
     8         try {
     9             cust.drinkCoffee(cup);
    10             System.out.println(
    11                 "Coffee is just right.");
    12         }
    13         catch (TooColdException e) {
    14             System.out.println(
    15                 "Coffee is too cold.");
    16             // Deal with an irate customer...
    17         }
    18         catch (TemperatureException e) {
    19             // This catches TooHotException as well
    20             // as TemperatureException.
    21             System.out.println(
    22               "There's temperature trouble in this coffee.");
    23             // Deal with an irate customer...
    24         }
    25     }
    26 }
    

Embedding Information in an Exception

  • A thrown exception doesn't just transfer control from one part of your program to another (a "structured go-to"), 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:

    1 // In file except/ex5/UnusualTasteException.java
    2 class UnusualTasteException extends Exception {
    3     public UnusualTasteException() {
    4     }
    5     public UnusualTasteException(String msg) {
    6         super(msg);
    7     }
    8 }
    
  • Client programmers could then create an instance in either of two ways:
    new UnusualTasteException();
    // or
    new UnusualTasteException("This coffee tastes like tea.");
    
  • A catch clause can then query the object for a detail string, like this:

     1 // In file except/ex5/VirtualCafe.java
     2 class VirtualCafe {
     3 
     4     public static void serveCustomer(
     5         VirtualPerson cust, CoffeeCup cup) {
     6 
     7         try {
     8             cust.drinkCoffee(cup);
     9             System.out.println(
    10                 "Coffee tastes just right.");
    11         }
    12         catch (UnusualTasteException e) {
    13             System.out.println(
    14                 "Customer is complaining of an unusual taste.");
    15             String s = e.getMessage();
    16             if (s != null) {
    17                 System.out.println(s);
    18             }
    19             // Deal with an unhappy customer...
    20         }
    21     }
    22 }
    

Embedding More than a String

  • If you need more than a String, you can add data and access methods to your exception class:

     1 // In file except/ex6/TemperatureException.java
     2 abstract class TemperatureException
     3     extends Exception {
     4 
     5     private int temperature; // in Celsius
     6     public TemperatureException(int temperature) {
     7         this.temperature = temperature;
     8     }
     9     public int getTemperature() {
    10         return temperature;
    11     }
    12 }
    
    1 // In file except/ex6/TooColdException.java
    2 class TooColdException
    3     extends TemperatureException {
    4 
    5     public TooColdException(int temperature) {
    6         super(temperature);
    7     }
    8 }
    
    1 // In file except/ex6/TooHotException.java
    2 class TooHotException
    3     extends TemperatureException {
    4 
    5     public TooHotException(int temperature) {
    6         super(temperature);
    7     }
    8 }
    
  • The temperature field of the exception object must be set when the object is created, as in:
     1 // In file except/ex6/VirtualPerson.java
     2 class VirtualPerson {
     3 
     4     private static final int tooCold = 65;
     5     private static final int tooHot = 85;
     6 
     7     public void drinkCoffee(CoffeeCup cup) throws
     8         TooColdException, TooHotException {
     9 
    10         int temperature = cup.getTemperature();
    11         if (temperature <= tooCold) {
    12             throw new TooColdException(temperature);
    13         }
    14         else if (temperature >= tooHot) {
    15             throw new TooHotException(temperature);
    16         }
    17         //...
    18     }
    19     //...
    20 }
    
  • A catch clause can then easily determine the actual temperature of the coffee and act accordingly, as in:

     1 // In file except/ex6/VirtualCafe.java
     2 class VirtualCafe {
     3 
     4     public static void serveCustomer(
     5         VirtualPerson cust, CoffeeCup cup) {
     6 
     7         try {
     8             cust.drinkCoffee(cup);
     9             System.out.println("Coffee is just right.");
    10         }
    11         catch (TooColdException e) {
    12 
    13             int temperature = e.getTemperature();
    14             System.out.println("Coffee temperature is "
    15                 + temperature + " degrees Celsius.");
    16 
    17             if (temperature > 55 && temperature <= 65) {
    18 
    19                 System.out.println(
    20                     "Coffee is cooling off.");
    21                 // Add more hot coffee...
    22             }
    23             else if (temperature > 0
    24                 && temperature <= 55) {
    25 
    26                 System.out.println("Coffee is too cold.");
    27                 // Give customer a new cup of coffee with
    28                 // the proper temperature...
    29             }
    30             else if (temperature <= 0) {
    31 
    32                 System.out.println("Coffee is frozen.");
    33                 // Deal with an irate customer...
    34             }
    35         }
    36         catch (TooHotException e) {
    37 
    38             int temperature = e.getTemperature();
    39             System.out.println("Coffee temperature is "
    40                 + temperature + " degrees Celsius.");
    41             if (temperature >= 85 && temperature < 100) {
    42                 System.out.println("Coffee is too hot.");
    43                 // Ask customer to let it cool a
    44                 // few minutes...
    45             }
    46             else if (temperature >= 100
    47                 && temperature < 2000) {
    48 
    49                 System.out.println(
    50                   "Both coffee and customer are steamed.");
    51                 // Deal with an irate customer...
    52             }
    53             else if (temperature >= 2000) {
    54 
    55                 System.out.println(
    56                     "The coffee is basically plasma.");
    57                 // Deal with a very irate customer...
    58             }
    59         }
    60     }
    61 }
    

Exceptions and the Method Invocation Stack

  • Code inside a try block is "surrounded" by the catch clauses
  • Can nest try blocks inside try blocks, building up more layers of catch clauses that surround the code
  • When a method is invoked from within a try block, that try block's catch clauses surround the code in the invoked method too
  • When an exception is thrown, the surrounding catch clauses are examined in inside-out order
  • An exception may be thrown far up the method invocation stack before landing in a catch clause that can handle it

A Method Invocation Stack Example

  • Simple exception classes:
    // In file TemperatureException.java
    1 // In file except/ex7/TemperatureException.java
    2 class TemperatureException extends Exception {
    3 }
    
    1 // In file except/ex7/TooColdException.java
    2 class TooColdException
    3     extends TemperatureException {
    4 }
    
    1 // In file except/ex7/TooHotException.java
    2 class TooHotException
    3     extends TemperatureException {
    4 }
    
    1 // In file except/ex7/UnusualTasteException.java
    2 class UnusualTasteException extends Exception {
    3 }
    
  • The drinkCoffee() method of VirtualPerson throws exceptions randomly:
     1 // In file except/ex7/VirtualPerson.java
     2 class VirtualPerson {
     3 
     4     public void drinkCoffee(CoffeeCup cup)
     5         throws TooColdException,
     6         TemperatureException,
     7         UnusualTasteException {
     8 
     9         try {
    10             int i = (int) (Math.random() * 4.0);
    11             switch (i) {
    12             case 0:
    13                 throw new TooHotException();
    14 
    15             case 1:
    16                 throw new TooColdException();
    17 
    18             case 2:
    19                 throw new UnusualTasteException();
    20 
    21             default:
    22                 throw new TemperatureException();
    23             }
    24         }
    25         catch (TooHotException e) {
    26             System.out.println(
    27                 "This coffee is too hot.");
    28             // Customer will wait until it cools
    29             // to an acceptable temperature.
    30         }
    31     }
    32     //...
    33 }
    
  • If i is zero, drinkCoffee() will throw TooHotException
  • If i is one, drinkCoffee() will throw TooColdException:

     1 // In file except/ex7/VirtualCafe.java
     2 class VirtualCafe {
     3 
     4     public static void serveCustomer(
     5         VirtualPerson cust, CoffeeCup cup)
     6         throws TemperatureException,
     7         UnusualTasteException {
     8 
     9         try {
    10             cust.drinkCoffee(cup);
    11         }
    12         catch (TooColdException e) {
    13             System.out.println(
    14                 "This coffee is too cold.");
    15             // Add more hot coffee...
    16         }
    17     }
    18 }
    
  • If i is two, drinkCoffee() will throw UnusualTasteException:

     1 // In file except/ex7/Example7.java
     2 class Example7 {
     3     public static void main(String[] args)
     4         throws TemperatureException {
     5 
     6         // Create a new coffee cup.
     7         CoffeeCup cup = new CoffeeCup();
     8 
     9         // Create and serve a virtual customer.
    10         try {
    11             VirtualPerson cust = new VirtualPerson();
    12             VirtualCafe.serveCustomer(cust, cup);
    13         }
    14         catch (UnusualTasteException e) {
    15             System.out.println(
    16                 "This coffee has an unusual taste.");
    17         }
    18     }
    19 }
    
  • If i is three or four, drinkCoffee() will throw TemperatureException:

    TemperatureException
      at VirtualPerson.drinkCoffee(VirtualPerson.java:20)
      at VirtualCafe.serveCustomer(VirtualCafe.java:9)
      at Example7.main(Example7.java:12)
    

The Throws Clause

  • Java requires that a method declare in a throws clause the exceptions that it may throw
  • A method's throws clause indicates to client programmers what exceptions they may have to deal with when they invoke the method
  • Only exceptions that will cause a method to complete abruptly should appear in its throws clause:

     1 // In file except/ex7/VirtualPerson.java
     2 class VirtualPerson {
     3 
     4     public void drinkCoffee(CoffeeCup cup)
     5         throws TooColdException,
     6         TemperatureException,
     7         UnusualTasteException {
     8 
     9         try {
    10             int i = (int) (Math.random() * 4.0);
    11             switch (i) {
    12             case 0:
    13                 throw new TooHotException();
    14 
    15             case 1:
    16                 throw new TooColdException();
    17 
    18             case 2:
    19                 throw new UnusualTasteException();
    20 
    21             default:
    22                 throw new TemperatureException();
    23             }
    24         }
    25         catch (TooHotException e) {
    26             System.out.println(
    27                 "This coffee is too hot.");
    28             // Customer will wait until it cools
    29             // to an acceptable temperature.
    30         }
    31     }
    32     //...
    33 }
    
  • The calling method must either catch the exception or declare it in its own throws clause:

     1 // In file except/ex7/VirtualCafe.java
     2 class VirtualCafe {
     3 
     4     public static void serveCustomer(
     5         VirtualPerson cust, CoffeeCup cup)
     6         throws TemperatureException,
     7         UnusualTasteException {
     8 
     9         try {
    10             cust.drinkCoffee(cup);
    11         }
    12         catch (TooColdException e) {
    13             System.out.println(
    14                 "This coffee is too cold.");
    15             // Add more hot coffee...
    16         }
    17     }
    18 }
    
     1 // In file except/ex7/Example7.java
     2 class Example7 {
     3     public static void main(String[] args)
     4         throws TemperatureException {
     5 
     6         // Create a new coffee cup.
     7         CoffeeCup cup = new CoffeeCup();
     8 
     9         // Create and serve a virtual customer.
    10         try {
    11             VirtualPerson cust = new VirtualPerson();
    12             VirtualCafe.serveCustomer(cust, cup);
    13         }
    14         catch (UnusualTasteException e) {
    15             System.out.println(
    16                 "This coffee has an unusual taste.");
    17         }
    18     }
    19 }
    
  • Overridden methods can only throw exceptions declared in the throws clause of the superclass's implementation of the method or subclasses of those exceptions

Checked and Unchecked Exceptions

  • Only checked exceptions need appear in throws clauses
  • Whether an exception is "checked" or "unchecked" is determined by its position in the hierarchy of throwable classes:

  • Placing an exception in a throws clause forces client programmers who invoke your method to deal with the exception, either by:
    • Catching it, or
    • Declaring it in their own throws clause

Finally Clauses

  • Many ways to exit a block:
    • Execute past closing curly brace.
    • Execute a break, continue, or return.
    • Exit because of a thrown exception.
  • To ensure that something happens (such as closing a file) upon exiting a block, no matter how the block is exited, use a finally clause:

    try {
        // Block of code with multiple
        // exit points
    }
    finally {
        // Block of code that must always
        // be executed when the try block
        // is exited, no matter how the
        // try block is exited
    }
    
  • Every try must have at least one catch or finally. If both, finally must go last:

     1 // In file except/ex8/VirtualPerson.java
     2 class VirtualPerson {
     3 
     4     public void drinkCoffee(CoffeeCup cup) {
     5 
     6         try {
     7             int i = (int) (Math.random() * 4.0);
     8             switch (i) {
     9             case 0:
    10                 throw new TooHotException();
    11 
    12             case 1:
    13                 throw new TooColdException();
    14 
    15             case 2:
    16                 throw new UnusualTasteException();
    17 
    18             default:
    19                 System.out.println("This coffee is great!");
    20             }
    21         }
    22         catch (TooHotException e) {
    23             System.out.println("This coffee is too hot.");
    24         }
    25         catch (TooColdException e) {
    26             System.out.println("This coffee is too cold.");
    27         }
    28         catch (UnusualTasteException e) {
    29             System.out.println(
    30                 "This coffee is too strong.");
    31         }
    32         finally {
    33             System.out.println(
    34                 "Can I please have another cup?");
    35         }
    36     }
    37     //...
    38 }
    
  • If a TooColdException exception is thrown:

    This coffee is too cold.
    Can I please have another cup?
    
  • Exiting a finally clause with a break, continue, return, or thrown exception overrules the reason the try block was exited.

Summary

  • Exceptions give you a structured way to jump out of context, from a place where an error occured to the place where the error can be handled
  • Exceptions represent a "broken contract"
  • Throw unchecked exceptions to indicate client software bugs (client has breached the contract)
  • Throw checked or unchecked exceptions to indicate you can't fulfill your end of the contract
  • Use a different exception class for each type of abnormal condition that may cause your method to complete abruptly

Exercises

Problem 1.

In the Exceptions/examples/ex7 directory of the sample code, compile the Example7 application and run it enough times that you witness all four possible outcomes.

While still in the Exceptions/examples/ex7 directory, create a new class called TastesLikeDishwaterException. Make this new class extend UnusualTasteException.

In the same Exceptions/examples/ex7 directory, edit VirtualPerson.java. Increase the multipler of the random number returned from Math.random() from 4.0 to 5.0. Add another case statement to the switch (for case 3) that throws a new TastesLikeDishwaterException. Recompile the application and run it enough times that you get to see what happens when VirtualPerson throws TastesLikeDishwaterException.

Problem 2.

Still in the Exceptions/examples/ex7 directory of the sample code, edit the Example7.java file. Add a catch clause for TastesLikeDishwaterException immediately below the catch clause for UnusualTasteException. In the catch clause for TastesLikeDishwaterException, simply print the string "Bleeech! This coffee tastes like dishwater!" to the standard output. Attempt to compile the program to see what kind of error message you get.

Get the program to compile by changing only the placement or position of the catch clause for TastesLikeDishwaterException. Run the program enough times to see what gets printed out when VirtualPerson throws TastesLikeDishwaterException.

Problem 3.

Still in the Exceptions/examples/ex7 directory of the sample code, edit the VirtualCafe.java file. Surround the entire try/catch block in the serveCustomer() method with another try block. Add a catch clause to this outer try block for TastesLikeDishwaterException. (Don't delete the catch clause for TastesLikeDishwaterException that you added to Example7 in Problem 2.) In this catch clause for TastesLikeDishwaterException in VirtualCafe.java, print the string "Mmmm, this dishwater tastes delicious!" to the standard output. Compile the program and run it enough times to see what gets printed out when VirtualPerson throws TastesLikeDishwaterException.

Problem 4.

Still in the Exceptions/examples/ex7 directory of the sample code, edit TastesLikeDishwaterException.java. Change this class so that whenever someone creates a new instance of TastesLikeDishwaterException, they are required to supply a floating point percentage (between 0.0 and 1.0) describing the fluffiness of the suds. In your catch clause for TastesLikeDishwaterException in VirtualCafe.java, extract this floating point value and print it to the standard output along with the message, "Mmmm, this dishwater tastes delicious!". You'll have to change VirtualPerson.java so that it supplies this floating point value whenever it creates a new TastesLikeDishwaterException. Compile all these changes and run the application enough times that you are able to experience the immense satisfaction of seeing the suds coefficient printed out by the application.

Problem 5.

Still in the Exceptions/examples/ex7 directory of the sample code, edit VirtualCafe.java. The serveCustomer() method should now have an inner and outer try block. Add a finally clause to the inner try block. Inside the finally clause, print the string "The thread finally got here." to the standard output. Compile this file and run the Example7 application several times. Make sure you understand the order in which the strings are printed by the application.