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();
}
void fall() throws Exception { throw new Exception(); }
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");
}
}
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"); } }
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: }
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: }
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
before catch finally done
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;
int answer = 11 / 0;
int answer = 11 / 0;

抛出异常:
Exception in thread "main" java.lang.ArithmeticException: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
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]);
int[] countsOfMoose = new int[3]; System.out.println(countsOfMoose[-1]);
int[] countsOfMoose = new int[3];
System.out.println(countsOfMoose[-1]);

抛出异常:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -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"; Integer number = (Integer) type; // DOES NOT COMPILE
String type = "moose";
Integer number = (Integer) type;  // DOES NOT COMPILE

下面的代码会抛出异常:
String type = "moose";
Object obj = type;
Integer number = (Integer) obj;
String type = "moose"; Object obj = type; Integer number = (Integer) obj;
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
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
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;
}
public static void setNumberEggs(int numberEggs) { if (numberEggs < 0) throw new IllegalArgumentException("# eggs must not be negative"); this.numberEggs = numberEggs; }
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
Exception in thread "main" java.lang.IllegalArgumentException: # eggs must not be negative
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());
}
String name; public void printLength() throws NullPointerException { System.out.println(name.length()); }
String name;
public void printLength() throws NullPointerException {
    System.out.println(name.length());
}

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

3.1.6. NumberFormatException

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

eg. 

Integer.parseInt("abc");
Integer.parseInt("abc");
Integer.parseInt("abc");

抛出异常:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
Exception in thread "main" java.lang.NumberFormatException: For input string: "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) { }
static { int[] countsOfMoose = new int[3]; int num = countsOfMoose[-1]; } public static void main(String[] args) { }
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
Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.ArrayIndexOutOfBoundsException: -1
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);
}
public static void doNotCodeThis(int num) { doNotCodeThis(1); }
public static void doNotCodeThis(int num) {
    doNotCodeThis(1);
}

抛出错误:
Exception in thread "main" java.lang.StackOverflowError
Exception in thread "main" java.lang.StackOverflowError
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 {
}
}
class NoMoreCarrotsException extends Exception {} public class Bunny { public static void main(String[] args) { eatCarrot(); // DOES NOT COMPILE } private static void eatCarrot() throws NoMoreCarrotsException { } }
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");
}
}
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"); } }
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() { }
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() { }
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
}
class CanNotHopException extends Exception { } class Hopper { public void hop() { } } class Bunny extends Hopper { public void hop() throws CanNotHopException { } // DOES NOT COMPILE }
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() { }
}
class Hopper { public void hop() throws CanNotHopException { } } class Bunny extends Hopper { public void hop() { } }
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 { }
}
class Hopper { public void hop() throws Exception { } } class Bunny extends Hopper { public void hop() throws CanNotHopException { } }
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 { }
}
class Hopper { public void hop() { } } class Bunny extends Hopper { public void hop() throws IllegalStateException { } }
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:}
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:}
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)
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)
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)