OCA/OCP Java Note (11): Exceptions
Author: nex3z
2015-12-25
1. Understanding Exception Types
Java中异常的种类如图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
eg.
void fall() throws 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() {
} 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() {
38: } catch (NullPointerException e) {
40: throw new RuntimeException();
43: throw new Exception();
45: } catch (Exception e) {
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;
抛出异常:
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的子类,下面的代码会给出编译错误:
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
下面的代码会抛出异常:
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) {
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.
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");
抛出异常:
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.
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) {
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 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
public static void main(String[] args) {
} 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.
} catch (NoMoreCarrotsException e ) { // DOES NOT COMPILE
System.out.print("sad rabbit");
public void good() throws NoMoreCarrotsException {
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 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.
public void hop() throws CanNotHopException { }
class Bunny extends Hopper {
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.
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 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) {
8: } catch (Exception e) {
9: System.out.println(e);
10: System.out.println(e.getMessage());
14:private static void hop() {
15: throw new RuntimeException("cannot hop");
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
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)