Java 的 try-catch 写法咱们常常用到,按照固定的方式咱们能够很容易捕获和处理异常:java
try {
....
} catch( e ) {
...
} finally {}
复制代码
可是,咱们会在某些写法上出现误区,这里将这些误区称为陷阱,本文会具体分析这些陷阱的产生原理,避免你们未来入坑。bash
//demo1
public static int testTryCatch() {
try {
int i = 1/0;// step1
} catch(Exception e) { //2
System.out.println("hello!");
return 1;
}finally{
return 2;
}
}
复制代码
在 demo1
代码的 step1
位置,发生了个除以 0
的异常,按照代码的逻辑将走到 catch
代码块。catch
代码块执行了一个 return
指令返回了字面量 1
。可是在 finally
语句块中也执行了一个 return
指令,那么最后函数将返回那个值呢?函数
咱们执行该函数将获得结果:ui
>"hello!"
>2
复制代码
也就是执行finally
语句块中的指令,这是为何呢?咱们来看下这段代码编译出来的字节码:spa
Code:
stack=2, locals=1, args_size=0
0: iconst_1
1: iconst_0
2: idiv
3: istore_0
4: goto 20
7: astore_0
8: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #22 // String hello!
13: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: goto 20
19: pop
20: iconst_2
21: ireturn
Exception table:
from to target type
0 4 7 Class java/lang/Exception
0 19 19 any
复制代码
上面 JVM
字节码中 0~2
行表明 try
语句块, 当执行到 idiv
除法指令的时候,出现了异常。这时候咱们须要查一下咱们的异常向量表,异常向量表包含两条数据:code
Exception
类型数据,跳转到第 7 条指令Throwable
,跳转到 19 语句块try
语句块中的 idiv
指令位于第 2 行,属于区间 [0,4] ,所以,当该语句发生异常的时候将执行向量表的第一条记录,跳转到代码 7 。astore_0
,将一个引用对象(其实是一个 Exception
对象)存入局部变量表,而后执行 System.out.println
指令。以后,并无按照咱们的代码写法直接执行 return 1
, return
指令被舍弃,而是跳转到 代码行 20 的位置,也就是 finally
代码块ireturn
指令返回。所以,实际上编译器在编译咱们的
try-catch
语法代码的时候,当发现咱们的finally
中有return
语句的时候,将舍弃掉catch
代码块中的ireturn
指令对象
//code 2
public static int testFunc(MyObj obj) {
try {
int i = 1/0;
return 10;
} catch(Exception e) { //2
obj.v= 2;
return obj.v;
}finally{
obj.v= 3;
}
}
复制代码
经过咱们对 例子1 的分析,咱们知道 finally
语句将会插入到 catch
语句的 return
代码以前。那么按照这种结论咱们的 例子2 就能够转化为:索引
catch(Exception e) {
obj.v = 2;
obj.v= 3;//finally 语句块
return obj.v;
}
复制代码
也就是说 例子2 中函数的结果应该是: 3 。 可是,结论老是事与愿违的,实际上返回的是 2。咱们仍是看下它编译的字节码:ip
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: iconst_0
2: idiv //异常
3: istore_1
4: aload_0
5: iconst_3
6: putfield #16 // Field david/support/Demos$MyObj.v:I
9: bipush 10
11: ireturn //catch 代码块
12: astore_1
13: aload_0
14: iconst_2
15: putfield #16 // Field david/support/Demos$MyObj.v:I
18: aload_0
19: getfield #16 // Field david/support/Demos$MyObj.v:I
22: istore_3
23: aload_0
24: iconst_3
25: putfield #16 // Field david/support/Demos$MyObj.v:I
28: iload_3
29: ireturn
30: astore_2
31: aload_0
32: iconst_3
33: putfield #16 // Field david/support/Demos$MyObj.v:I
36: aload_2
37: athrow
Exception table:
from to target type
0 4 12 Class java/lang/Exception
0 4 30 any
12 23 30 any
复制代码
try
语句块中的除法指令,对应代码第 2 行的时候发生的异常。 对应第 0 条向量表,跳转到 12 行 catch
语句块Demos$MyObj
对象的 v
属性中Demos$MyObj
对象的 v
属性,存入到索引 [3] 的局部变量中finally
指令块,将对象的 v
属性变成 3从上面的编译的代码咱们能够看到,
例子2
中,finally
语句块中并无return
指令的时候,编译器将会先把结果存入一个临时变量(上面的索引 [3] 局部变量)中,而后执行完finally
语句后,读取临时变量中的值,执行return
语句,而且,此时对象中的属性也由于执行了finally语句,而变成了 3 。get