先来看一下以下的代码,猜猜他们会是什么样的结果:
1
public
class
FinallyIssue {
2
public
static
void
main(String[] args) {
3
System.out.println(
"
finallyReturnTest :
"
);
4
System.out.println(
"
return value :
"
+
finallyReturnTest(
1
));
5
System.out.println(
"
return value :
"
+
finallyReturnTest(
-
1
));
6
7
System.out.println(
"
finallyBreakTest :
"
);
8
System.out.println(
"
return value :
"
+
finallyBreakTest(
true
));
9
System.out.println(
"
return value :
"
+
finallyBreakTest(
false
));
10
11
System.out.println(
"
valueChangeInFinallyTest :
"
);
12
System.out.println(
"
return value :
"
+
valueChangeInFinallyTest());
13
14
System.out.println(
"
valueChangeReturnInFinallyTest :
"
);
15
System.out.println(
"
return value :
"
+
valueChangeReturnInFinallyTest());
16
17
System.out.println(
"
refValueChangeInFinallyTest :
"
);
18
System.out.println(
"
return name :
"
+
refValueChangeInFinallyTest().name);
19
}
20
21
private
static
boolean
finallyReturnTest(
int
value) {
22
try
{
23
if
(value
>
0
) {
24
return
true
;
25
}
else
{
26
return
false
;
27
}
28
}
finally
{
29
return
false
;
30
}
31
}
32
33
private
static
boolean
finallyBreakTest(
boolean
value) {
34
while
(value) {
35
try
{
36
return
true
;
37
}
finally
{
38
break
;
39
}
40
}
41
return
false
;
42
}
43
44
private
static
int
valueChangeInFinallyTest() {
45
int
i
=
10
;
46
int
j
=
1
;
47
try
{
48
i
=
100
;
49
j
=
2
;
50
System.out.println(
"
try : i =
"
+
i);
51
System.out.println(
"
try : j =
"
+
j);
52
return
i;
53
}
catch
(Exception e) {
54
e.printStackTrace();
55
}
finally
{
56
i
=
1000
;
57
j
=
3
;
58
System.out.println(
"
finally : i =
"
+
i);
59
System.out.println(
"
finally : j =
"
+
j);
60
}
61
62
return
i;
63
}
64
65
private
static
int
valueChangeReturnInFinallyTest() {
66
int
i
=
10
;
67
int
j
=
1
;
68
try
{
69
i
=
100
;
70
j
=
2
;
71
System.out.println(
"
try : i =
"
+
i);
72
System.out.println(
"
try : j =
"
+
j);
73
return
i;
74
}
catch
(Exception e) {
75
e.printStackTrace();
76
}
finally
{
77
i
=
1000
;
78
j
=
3
;
79
System.out.println(
"
finally : i =
"
+
i);
80
System.out.println(
"
finally : j =
"
+
j);
81
return
i;
82
}
83
}
84
85
private
static
Person refValueChangeInFinallyTest() {
86
Person p
=
new
Person();
87
try
{
88
p.name
=
"
person1
"
;
89
System.out.println(
"
try : Person name is :
"
+
p.name);
90
return
p;
91
}
catch
(Exception e) {
92
e.printStackTrace();
93
}
finally
{
94
p.name
=
"
person2
"
;
95
System.out.println(
"
finally : Person name is :
"
+
p.name);
96
}
97
98
p.name
=
"
person3
"
;
99
System.out.println(
"
out : Person name is :
"
+
p.name);
100
101
return
p;
102
}
103
104
static
class
Person {
105
public
String name;
106
}
107
}
这样一段代码的结果会是什么呢?
以下是运行结果:
finallyReturnTest
:
return value :
false
return value :
false
finallyBreakTest
:
return value :
false
return value :
false
valueChangeInFinallyTest
:
try : i =
100
try : j =
2
finally : i =
1000
finally : j =
3
return value :
100
valueChangeReturnInFinallyTest
:
try : i =
100
try : j =
2
finally : i =
1000
finally : j =
3
return value :
1000
refValueChangeInFinallyTest
:
try : Person
name is : person1
finally :
Person name is : person2
return name :
person2
这个结果很出乎我的意料,我们知道
finally总是会在
try-catch语句块执行完后执行,不管
try语句块中是否已经返回或者抛出了异常。
但是在上面的代码测试中,如果
finally语句块中有
return、
break、
continue等语句,那么它们会覆盖
try语句块中的
return、
break、
continue的语句,如以上的
finallyReturnTest()、
finallyBreakTest()、
valueChangeReturnInFinallyTest()三个函数。
另外,如果在
finally语句块中修改要返回的值类型变量的值,则这些修改不会保存下来,如
valueChangeInFinallyTest()函数;如果要返回的值是引用类型,则修改引用类型的内部成员的值会保存下来。
如何解释这个结果呢?
问题解释
结合《深入
Java虚拟机(第二版)》这本书和代码编译后产生的二进制指令代码,我对以上问题做了部分解释,鉴于我的才疏学浅,有些观点是有误的,希望高手指正(有误的观点容易引起误导,这也是所以我一直非常小心,奈何水平有限,有些时候难免出错)。
在《深入
Java虚拟机(第二版)》的第
18章中提到,在早期的
Java中,
finally的行为是通过
JSR指令来实现的,并且为这个指令引入了微型子程序的概念。我的理解,所谓微型子程序就是在函数
A中嵌入一个不完整的函数
B的调用。比如在这本书上的一个例子:
private
static
int
microSubroutine(
boolean
bValue) {
try
{
if
(bValue) {
return
1
;
}
return
0
;
}
finally
{
System.out.println(
"
finally
"
);
}
}
会生成以下的二进制代码:
0 iload_0
1 ifeq 11
4 iconst_1
5 istore_1
6 jsr 24
9 iload_1
10 ireturn
11 iconst_0
12 istore_1
13 jsr 24
16 iload_1
17 ireturn
18 astore_2
19 jsr 24
22 aload_2
23 athrow
24 astore_3
25 getstatic #7 <Field
java.io.PrintStream out>
28 ldc #1 <String
“finally”>
30 invokevirtual #8 <Method
void println(java.lang.String)>
33 ret 3
如上,
24前缀的代码行以后的部分就是微型子程序,在每一个出口之前都会用
JSR调用这个微型子例程序,在这个微型子例程序返回(
ret)后,返回调用
JSR指令的下一条指令,然后返回(
ireturn、
athrow)。
jsr
指令和
ret
指令的格式如下:
jsr branchbyte1,
branchbyte2
把返回地址压栈,跳转至
((branchbyte1<<8) |
branchbyte2)的位置继续之行。
ret index
返回在
index指示的局部变量中存储的值(位置)。
在上面的二进制代码中,每次通过
jsr 24跳转到微型子程序,它先将返回地址(
jsr 24指令的下一条指令的地址)保存在
index为
3的局部变量中,执行完微型子程序后,通过
ret 3返回到调用
jsr 24指令的下一条指令执行,并最终执行返回。
可是后来(有人说是自
1.4.2后),
JVM中取消了
jsr指令了,所有
finally内部的代码都内联到源代码中了(二进制的源代码)。所以以上的代码在之后的编译器中会产生如下的二进制代码:
0 iload_0
[bValue]
1 ifeq
14
4 getstatic
java.lang.System.out : java.io.PrintStream [16]
7 ldc <String
"finally"> [94]
9 invokevirtual
java.io.PrintStream.println(java.lang.String) : void [24]
12 iconst_1
13 ireturn
14 getstatic
java.lang.System.out : java.io.PrintStream [16]
17 ldc <String
"finally"> [94]
19 invokevirtual
java.io.PrintStream.println(java.lang.String) : void [24]
22 iconst_0
23 ireturn
24 astore_1
25 getstatic java.lang.System.out : java.io.PrintStream
[16]
28 ldc <String
"finally"> [94]
30 invokevirtual
java.io.PrintStream.println(java.lang.String) : void [24]
33 aload_1
34 athrow
额,貌似有点偏题了,以上的描述是为了解释《深入
Java虚拟机(第二版)》中对
finally描述过时的描述。下面让我们来真正的解决这个问题。还是从生成的
Java二进制代码入手。
首先来看一下
valueChangeInFinallyTest()函数的二进制代码(注释了打印语句,使代码简洁):
//int i =
10
0 bipush
10
2 istore_0
[i]
//int j =
1
3 iconst_1
4 istore_1
[j]
//i =
100
5 bipush
100
7 istore_0
[i]
//j =
2
8 iconst_2
9 istore_1
[j]
//
保存i的值,因为它是要返回的
10 iload_0
[i]
11 istore 4
//--------------------------------
内联finally语句块(开始)----------------------
//i =
1000
13 sipush
1000
16 istore_0
[i]
//j =
3
17 iconst_3
18 istore_1
[j]
//--------------------------------
内联finally语句块(结束)----------------------
//
加载保存后的i的值,并返回。这里返回的是finally语句块执行前的i(由istore
4语句缓存起来)的值,因而在finally语句块中任何对i的操作并不会保留下来。这是在没有异常发生的情况下。
19 iload 4
21 ireturn
22 astore_2
[e]
23 aload_2
[e]
24 invokevirtual
java.lang.Exception.printStackTrace() : void [104]
//--------------------------------
内联finally语句块(开始)----------------------
27 sipush
1000
30 istore_0
[i]
31 iconst_3
32 istore_1
[j]
//--------------------------------
内联finally语句块(结束)----------------------
33 goto 45
36 astore_3
//--------------------------------
内联finally语句块(开始)----------------------
37 sipush
1000
40 istore_0
[i]
41 iconst_3
42 istore_1
[j]
//--------------------------------
内联finally语句块(结束)----------------------
//
而在异常发生但没有被正确处理的情况下,返回值已经没有什么意义了。
43 aload_3
44 athrow
//
这里是在有异常发生,并且异常得到了正确处理的情况下返回的,此时在finally语句块中对i的操作就会保存下来,并返回给调用者。
45 iload_0 [i]
46 ireturn
相信以上的注释已经能很好的的解决这个问题了(注:这里
j的存在是为了证明在内联
finally语句块的时候,它只缓存返回值
i,而无须缓存其他变量的值,如
j的值)。需要特别注意的一点是,如果正常返回的话,
finally
语句块中修改
i
的值是保存不下来的,但是如果出现异常,并被正常捕获后,在
finally
语句块中修改的
i
的值就会保存下来了。
那么对
valueChangeReturnInFinallyTest()函数中的现象如何解释呢?对这个问题解释,首先要理解
ireturn的指令。
ireturn指令没有操作数,它把当前操作栈的栈顶的
int值作为默认的操作数。
ireturn
指令会弹出当前栈顶的
int
值,将其压入调用者的操作栈中,同时忽略当前操作栈中的其他值,即函数正常返回
。因而如果在不优化的情况下,在
finally语句块中的
return语句会返回当前栈顶的
int值(修改后的
i值),然后函数返回,此时栈上的其他操作数就被忽略了,并且原本应该执行的
ireturn语句也不会之行了。这种方式甚至会忽略抛出的异常,即使当前方法有异常抛出,它的调用方法还是认为它正常返回了。
如果查看优化后的
valueChangeReturnInFinallyTest()方法的二进制源码后,会发现当前的代码更加简洁了。但是它还是没有避免在
finally
语句块中使用
return
后,会忽略没有捕获到的异常的问题。
//int i =
10
0 bipush
10
2 istore_0
[i]
//int j =
1
3 iconst_1
4 istore_1
[j]
//i =
100
5 bipush
100
7 istore_0
[i]
//j =
2
8 iconst_2
9 istore_1
[j]
10 goto 22
//catch
block
13 astore_2
[e]
14 aload_2
[e]
15 invokevirtual
java.lang.Exception.printStackTrace() : void [104]
18 goto
22
21 pop
//--------------------------------
内联finally语句块(开始)----------------------
//i =
100
22 sipush
1000
25 istore_0
[i]
//j =
3
26 iconst_3
27 istore_1
[j]
//--------------------------------
内联finally语句块(结束)----------------------
//
返回finally语句块中i的值
28 iload_0
[i]
29 ireturn
经过以上的解释,我想对
refValueChangeInFinallyTest()函数中的现象就比较好解释了,因为当进入
finally语句块的时候,保存的只是
Person实例的一个引用,在
finally语句块中依然可以通过引用操作
Person内部成员的,因而在
finally语句块中的修改才能保存下来。
而经过编译器优化后的
finallyReturnTest()和
finallyBreakTest()函数生成的二进制代码就成一样的了:
0 iload_0
[value]
1 ifeq
8
4 goto
8
7 pop
8 iconst_0
9 ireturn
分享到:
相关推荐
amazon 好评书籍 Get to grips with automated web testing with the amazing power of Selenium WebDriver About This Book ... Finally, you will learn how to perform Data-Driven and Behavior-Driven tests.
第一,谈谈final, finally, finalize的区别。 final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。finally是异常处理语句结构的一部分,表示总是执行。finalize是 Object类的一个方法...
09年新书,很好的知无线电 网络中的频谱接入和管理,下面是IEEE的书评: The book is divided into three parts. Part one is a general introduction to wire- less communication systems, reviewing ...
Finally, it describes the representative applications of human-centered cognitive computing, including robot technology, emotional communication system, and medical cognitive system.
构建更加全面的传感网络,包括温湿度传 感网,气流传感网,做到对用能场所的更全面感 知。可以通过传感器的安装,相当于给机房装了 一个很精准的CT,对机房实现多角度旋转、缩放 观察的虚拟三维清晰图像,温湿度...
10.4.1 多态——运行方知结果 280 10.4.2 重载也不简单 280 10.4.3 使用多态构建车队 283 10.5 在多态的环境中拨开迷雾 284 10.5.1 神秘的Class类 284 10.5.2 覆盖不再神秘 285 10.5.3 instanceof运算符——让...
10.4.1 多态——运行方知结果 280 10.4.2 重载也不简单 280 10.4.3 使用多态构建车队 283 10.5 在多态的环境中拨开迷雾 284 10.5.1 神秘的Class类 284 10.5.2 覆盖不再神秘 285 10.5.3 instanceof运算符——让...
这些问题对于认真学习java的人都要必知的,当然如果你只是初学者就没必要那么严格了,那如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列。 一、到底要怎么样初始化! 本问题讨论...
异常处理语句throw, try-catch, try-finally Checked 和 Uncheckedchecked, unchecked fixed 语句Fixed lock 语句Lock (1) foreach 语句为数组或对象集合中的每个元素重复一个嵌入语句组。foreach 语句用于循环访问...