1、异常机制概述
异常机制是java程序设计中非常重要的一部分,在一定程度上保证了java程序的鲁棒性。程序执行过程中,遇到异常时会以throw形式抛出或者系统内部抛出,如下所示:
if (arg == null){
throw new NullPointerException();
}
有抛出异常,就有捕获异常,以try{}catch(){}形式捕获,如下所示:
try {
// code that might generate exceptions
}
catch (Type e0){}
catch (Type e1){}
1.1 标准异常分类及层次结构
Java核心库内建了一些通用的异常类,这些异常库均继承于Throwable类,如Exception类。
Throwable类层次结构如下图所示:
Throwable主分为Error和Exception类。
Error:系统产生,应用程序(程序员编写的应用层程序)无法捕获,程序员在程序运行时是没有办法处理Error的。
Exception:异常处理的核心,应用程序可以捕获,程序员可根据程序运行期间抛出的Exception种类进行针对性处理。
上述Throwable又可分为受检查异常和不受检查异常:
不受检查异常:包含Error和RuntimeException;不受检查的异常自动被java虚拟机抛出。
受检查异常:除了Error和RuntimeException。
2、异常控制块和return
2.1 异常使用语法
异常控制块,也就是try..catch...finally,下图是常用的异常捕获结构:
try {
// code that might generate exceptions
throw new xxxException();// might throw by kernal
}
catch (ArithmeticException e){
// handle
}
catch (ArrayIndexOutOfBounds e){
// handle
}
finally {
// handle
}
try语句块中抛出的异常可能来自java内核,也可能来自定义的异常对象。
控制块执行顺序:先执行try块,如try块抛出异常,则执行相应的catch块,最后执行finally块,如下程序所示:
public class ExceptionTest {
private static int count = 0;
public static void main(String[] args){
while (true){
try {
if (count++ == 0){
int a = 10/0;
}
}
catch (ArithmeticException e){
System.out.println("ArithmeticException");
}
finally {
System.out.println("count = " + count);
if (count == 2){
break;
}
}
}
}
}
程序输出为:
ArithmeticException
count = 1
count = 2
上述例子表明,正常情况下,finally最终均会得到执行;其实无论try块或catch块中是否存在return语句,finally块均会得到执行。
但是,finally真的总会执行吗?
public class ExceptionTest {
private static int count = 0;
public static void main(String[] args){
while (true){
try {
System.exit(0);
if (count++ == 0){
int a = 10/0;
}
}
catch (ArithmeticException e){
System.out.println("ArithmeticException");
}
finally {
System.out.println("count = " + count);
if (count == 2){
break;
}
}
}
}
}
程序没有任何打印输出,说明finally并没有执行。
其实,当jvm退出或控制块中有System.exit()存在时,finally并不会执行。
2.2 嵌入return语句
return语句的嵌入,会稍微复杂一些,但理解了异常处理机制,就可迎刃而解。
return语句嵌入指的是在try..catch..finally控制块中嵌入return语句,嵌入的位置可能在try块、catch块以及finally块。
下面通过三个用例来说明嵌入return语句对控制块执行的影响。
例1:
public class ExceptionTest {
int a = 0;
public int f() {
try {
a = 100;
int b = 10/0;
return a;
}
catch (ArithmeticException e) {
a = 200;
System.out.println("ArimthmeticException");
return a;
}
finally {
a = 300;
}
}
public static void main(String[] args){
ExceptionTest exceptionTest = new ExceptionTest();
System.out.println(exceptionTest.f());
}
}
程序输出:
ArimthmeticException
200
程序输出结果可以看出,虽然finally最终修改了a值,但返回的结果仍是catch块中的a的结果。原因是try块、catch块、finally块在执行完块内程序时,如果有return返回,会将该返回值压入栈中,做一个值拷贝。
上述程序中,虽然finally修改了a的值,a的值确实也变成了300,但是catch块中已经保存了a=200时的一份拷贝,因此返回的是拷贝后的值,而不是最终真实的a值。
例2:
public class ExceptionTest {
int a = 0;
public int f() {
try {
a = 100;
int b = 10/0;
return a;
}
catch (ArithmeticException e) {
a = 200;
System.out.println("ArimthmeticException");
return a;
}
finally {
a = 300;
return a;
}
}
public static void main(String[] args){
ExceptionTest exceptionTest = new ExceptionTest();
System.out.println(exceptionTest.f());
}
}
程序执行结果为:
ArimthmeticException
300
例2说明try块、catch块、finally块中若有return语句,则执行return顺序优先级为finally块 > catch块 > try块。
例3:
public class ExceptionTest {
int a = 0;
public int f() throws Exception{
try {
a = 100;
int b = 10/0;
return a;
}
catch (ArithmeticException e) {
a = 200;
System.out.println("ArimthmeticException");
throw new Exception();
}
finally {
a = 300;
return a;
}
}
public static void main(String[] args){
ExceptionTest exceptionTest = new ExceptionTest();
try{
System.out.println(exceptionTest.f());
}
catch(Exception e){
e.printStackTrace();
System.out.println("截获异常catch");
}finally{
System.out.println("异常处理finally");
}
}
}
程序执行结果为:
ArimthmeticException
300
异常处理finally
程序结果中没有“截获异常catch”输出,说明f()方法没有抛出异常。
本例说明如果异常控制块中有return语句,会覆盖该方法最终的异常抛出。
总结:
例1、例2目的:异常控制块中的return语句会对最后返回的值产生影响。
例3目的:异常控制块中return语句可能会覆盖最终的异常抛出。
例3的结果表明:finally控制块中尽可能不要使用return语句。
3、异常处理表
java程序抛出异常时,是如何响应对应的分支的呢?
其实,catch块是通过一个异常处理表查询对应的异常处理分支,每个方法在编译生成字节码时,均会生成一个异常处理表,如下所示:
public class ExceptionTest {
int a = 0;
public void f(){
try {
a = 100;
int b = 10/0;
}
catch (ArithmeticException e) {
a = 200;
}
catch (NullPointerException e){
a = 300;
}
catch (ArrayIndexOutOfBoundsException e){
a = 400;
}
finally {
System.out.println(a);
}
}
public static void main(String[] args){
ExceptionTest exceptionTest = new ExceptionTest();
exceptionTest.f();
}
}
对应f()方法的部分字节码如下所示:
66: astore_1
67: aload_0
68: sipush 400
71: putfield #2 // Field a:I
74: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
77: aload_0
78: getfield #2 // Field a:I
81: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
84: goto 100
87: astore_2
88: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
91: aload_0
92: getfield #2 // Field a:I
95: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
98: aload_2
99: athrow
100: return
Exception table:
from to target type
0 11 24 Class java/lang/ArithmeticException
0 11 45 Class java/lang/NullPointerException
0 11 66 Class java/lang/ArrayIndexOutOfBoundsException
0 11 87 any
24 32 87 any
45 53 87 any
66 74 87 any
87 88 87 any
可以看到,异常处理表中包含所有catch块的异常分支,当try块抛出异常时,会查询异常处理表,找到对应的异常响应分支,执行对应的处理程序,异常处理表中包含try块的区域等信息。