OCA/OCP Java Note (11): Exceptions

1. Understanding Exception Types

  Java中异常的种类如图1所示。

图1

图1

  Error用于指示严重错误,程序不应尝试从错误中恢复。RuntimeException和其子类表示运行时异常,用于指示意外的非致命异常,也被称为unchecked exception。

  Runtime (Unchecked) exception是异常的一个种类,并不是指在程序运行期间(run time)发生的异常。所有异常都发生在程序运行期间,与之相对的是编译时(compile time),编译时可能会发生编译错误。

  Checked exception包括Exception和其没有继承RuntimeException的子类,对于checked exception,Java要求代码要么处理,要么在函数上声明(handle or declare rule)。

  以上内容总结如图2。

图2

图2

eg. 

void fall() throws Exception {
    throw new Exception();
}

2. Using a try Statement

2.1. Adding a finally Block

  finally总会执行,但有一种特例:如果在try或catch中调用了System.exit() ,finally就不会执行。

2.2. Catching Various Types of Exceptions

  Java会按照在代码中出现的顺序检查各个catch,如果有不可能被执行到的catch,就会给出编译错误。比如在catch子类异常前catch了父类异常。

eg. 

class AnimalsOutForAWalk extends RuntimeException { }
class ExhibitClosed extends RuntimeException { }
class ExhibitClosedForLunch extends ExhibitClosed { }
public void visitMonkeys() {
    try {
        seeAnimal();
    } catch (ExhibitClosed e) {
        System.out.print("not today");
    } catch (ExhibitClosedForLunch e) {  // DOES NOT COMPILE
        System.out.print("try back later");
    }
}

2.3. Throwing a Second Exception

eg. 

30: public String exceptions() {
31:     String result = "";
32:     String v = null;
33:         try {
34:             try {
35:                 result += "before";
36:                 v.length();
37:                 result += "after";
38:             } catch (NullPointerException e) {
39:                 result += "catch";
40:                 throw new RuntimeException();
41:             } finally {
42:                 result += "finally";
43:                 throw new Exception();
44:             }
45:         } catch (Exception e) {
46:         result += "done";
47:     }
48:     return result;
49: }

第36行抛出NullPointerException,37行被跳过;40行抛出RuntimeException,41行后的finally仍会被执行。最后result 为:
before catch finally done

3. Recognizing Common Exception Types

3.1. Runtime Exceptions

  Runtime exception不强制要求被处理或声明,可由JVM或程序员抛出。

3.1.1. ArithmeticException

  尝试进行除以0的运算时由JVM抛出。

eg. 

int answer = 11 / 0;

抛出异常:
Exception in thread "main" java.lang.ArithmeticException: / by zero

3.1.2. ArrayIndexOutOfBoundsException

  使用非法索引访问数组时由JVM抛出。

eg. 

int[] countsOfMoose = new int[3];
System.out.println(countsOfMoose[-1]);

抛出异常:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1

3.1.3. ClassCastException

  转换为子类,但对象并不是子类的实例时,由JVM抛出。编译器会首先对转换进行检查,更复杂的转换错误会在运行时抛出异常。

eg. Integer不是String的子类,下面的代码会给出编译错误:

String type = "moose";
Integer number = (Integer) type;  // DOES NOT COMPILE

下面的代码会抛出异常:
String type = "moose";
Object obj = type;
Integer number = (Integer) obj;

抛出异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

3.1.4. IllegalArgumentException

  指示传入的参数不合法或不合适,由程序员抛出。

eg. 对于如下的代码:

public static void setNumberEggs(int numberEggs) {
    if (numberEggs < 0)
        throw new IllegalArgumentException("# eggs must not be negative");
    this.numberEggs = numberEggs;
}

调用setNumberEggs(-2) ,会抛出:
Exception in thread "main" java.lang.IllegalArgumentException: # eggs must not be negative

3.1.5. NullPointerException

  访问对象时发现null引用,由JVM抛出。

eg. 

String name;
public void printLength() throws NullPointerException {
    System.out.println(name.length());
}

抛出异常:
Exception in thread "main" java.lang.NullPointerException

3.1.6. NumberFormatException

  将字符转换为数值类型,字符格式不正确时,由程序员抛出。

eg. 

Integer.parseInt("abc");

抛出异常:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"

3.2. Checked Exceptions

  Checked exception都可以由JVM或程序员抛出。

3.2.1. FileNotFoundException

  尝试引用不存在的文件时抛出,是IOException的子类。

3.2.2. IOException

  读写文件出现问题时抛出。

3.3. Errors

  错误继承Error类,由JVM抛出,不应被处理或声明。

3.3.1. ExceptionInInitializerError

  静态初始化器抛出异常且未处理时,由JVM抛出。

eg. 

static {
    int[] countsOfMoose = new int[3];
    int num = countsOfMoose[-1];
}
public static void main(String[] args) { }

抛出关于两个异常信息:
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.ArrayIndexOutOfBoundsException: -1

3.3.2. StackOverflowError

  方法调用自身次数过多(无限递归)时,由JVM抛出。

eg. 

public static void doNotCodeThis(int num) {
    doNotCodeThis(1);
}

抛出错误:
Exception in thread "main" java.lang.StackOverflowError

3.3.3. NoClassDefFoundError

  类的代码在编译时可用,而在运行时不可用时,由JVM抛出。

4. Calling Methods That Throw Exceptions

  Checked exception必须被处理或声明,否则会出现编译错误。

eg. 

class NoMoreCarrotsException extends Exception {}
public class Bunny {
    public static void main(String[] args) {
        eatCarrot();  // DOES NOT COMPILE
    }
    private static void eatCarrot() throws NoMoreCarrotsException {
    }
}

上面的代码在main() 中eatCarrot() 的异常没有被处理,导致编译错误。以下两种main() 可以正常编译:
public static void main(String[] args) throws NoMoreCarrotsException {// declare exception
    eatCarrot();
}

public static void main(String[] args) {
    try {
        eatCarrot();
    } catch (NoMoreCarrotsException e ) {// handle exception
        System.out.print("sad rabbit");
    }
}

  编译器会检查无法运行到的代码(unreachable code),声明但不抛出异常是合法的,但尝试catch没有抛出的异常会导致编译错误。

eg. 

public void bad() {
    try {
        eatCarrot();
    } catch (NoMoreCarrotsException e ) {  // DOES NOT COMPILE
        System.out.print("sad rabbit");
    }
}
public void good() throws NoMoreCarrotsException {
    eatCarrot();
}
private static void eatCarrot() { }

4.1. Subclasses

  子类尝试重写父类方法或者实现接口方法时,不允许在函数签名上添加新的checked exception。

eg. 

class CanNotHopException extends Exception { }
class Hopper {
    public void hop() { }
}
class Bunny extends Hopper {
    public void hop() throws CanNotHopException { }  // DOES NOT COMPILE
}

  子类可以声明更少的异常

eg. 

class Hopper {
public void hop() throws CanNotHopException { }
}
class Bunny extends Hopper {
    public void hop() { }
}

  子类也可以声明抛出父类或者接口方法所抛出异常的子类。

eg. 

class Hopper {
public void hop() throws Exception { }
}
class Bunny extends Hopper {
    public void hop() throws CanNotHopException { }
}

  上面的规则只适用于checked exception。子类可以声明新的runtime exception,因为对runtime exception的声明是冗余的,方法可以任意抛出任何runtime exception而不需要进行声明。

eg. 

class Hopper {
    public void hop() { }
}
class Bunny extends Hopper {
    public void hop() throws IllegalStateException { }
}

4.2. Printing an Exception

  有三种方式可以打印出异常:让Java使用默认方式打印,打印异常消息,或者打印栈跟踪信息。

eg. 下面的例子使用这三种方式打印异常:

5: public static void main(String[] args) {
6:     try {
7:         hop();
8:     } catch (Exception e) {
9:         System.out.println(e);
10:        System.out.println(e.getMessage());
11:        e.printStackTrace();
12:    }
13:}
14:private static void hop() {
15:    throw new RuntimeException("cannot hop");
16:}

输出为:
java.lang.RuntimeException: cannot hop
cannot hop
java.lang.RuntimeException: cannot hop
at trycatch.Handling.hop(Handling.java:15)
at trycatch.Handling.main(Handling.java:7)