先来看一下以下的代码,猜猜他们会是什么样的结果:
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
分享到:
相关推荐
这份包含36篇文章的文档集合,旨在深入剖析Java中的不为人知的问题,提供程序员必须掌握的关键知识点,以及如何应对面试中可能遇到的问题。下面将详细讨论这些文档可能涵盖的内容。 1. **内存管理与垃圾回收**:...
- **异常处理**:理解Checked和Unchecked异常的区别,以及如何合理使用try-catch-finally语句。 2. **Android相关**: - **Activity生命周期**:掌握各状态转换,理解onCreate()、onStart()、onResume()、onPause...
第一,谈谈final, finally, finalize的区别。 final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。finally是异常处理语句结构的一部分,表示总是执行。finalize是 Object类的一个方法...
目录 Android 中高级面试必知必会.1 第一章 Java 相关高频面试解析..........3 1. HashMap ......3 2. ArrayList .....25 3.LinkedList.........30 4.Hashset 源码分析........35 5. 内存模型.......47 6. 垃圾回收...
`try`块中捕获异常,`catch`块处理异常,`finally`块确保即使发生异常也能执行的清理代码。 10. **using关键字**:用于确保资源在使用后得到正确释放,常用于 Dispose模式。例如: ```csharp using ...
"Java面试必知必会Gothic主题"可能包含了一系列与Java核心技术、面试技巧以及常见问题相关的资料。下面我们将深入探讨一些Java面试中常见的核心知识点。 1. **基础语法**:面试通常会从Java的基础开始,如数据类型...
8. **错误处理和异常处理**:VB.NET的Try...Catch...Finally结构用于捕获和处理程序运行时的错误。异常是程序运行时发生问题的表示,正确处理异常可以确保程序的稳定性和健壮性。 9. **泛型**:VB.NET的泛型允许...
本文将基于《Java面试必知必系列-V1.0》的指导,提炼出一些关键知识点。 1. **Java基础** - **三大特性**:封装、继承和多态。封装是限制对对象属性的直接访问,通过方法来操作;继承允许创建新的类(子类)继承已...
2. **异常处理**: 学习如何使用try-catch-finally语句来捕获和处理运行时错误,以及了解不同类型的异常,如Checked异常和Unchecked异常的区别。 3. **内存管理与垃圾回收**: 了解Java的内存模型,包括堆内存、栈...
2. 异常处理:理解和掌握Java的异常处理机制,包括try-catch-finally语句的使用,了解受检异常和非受检异常的区别,以及如何自定义异常。 3. 面向对象编程:深入理解Java的面向对象特性,例如类和对象的概念、继承...
7. **错误处理**:VB6.0提供了On Error语句进行错误处理,学习者应理解如何使用Try...Catch...Finally结构来捕获和处理运行时错误。 8. **用户界面设计**:VB6.0的集成开发环境(IDE)提供了直观的拖放式设计,使得...
- `finally` 保证代码块一定会执行,通常用于异常处理。 - `finalize` 是对象被垃圾回收前调用的方法,用于清理资源,但不保证一定会执行。 7. **序列化 `Serializable` 和 `Parcelable`**: - `Serializable` ...
3. **词汇运用**:第5句"If you have a job, do devote yourself to it and finally you’ll succeed."强调了副词"do"在强调动词时的用法,同时提醒学生要全身心投入工作才能成功。第6句"Please do me a favor—...
2. `Then/Next/Finally`: 这些词常用于指示步骤顺序,描述做某事的流程。 3. `Do you know how...?`: 你知道怎么... 这是一个询问对方是否知道某个信息的问句结构。 **阅读填词** 1. `eat`: 吃。 2. `add`: 添加。...
- 这里使用了 “as far as we are aware” 来强调目前所知的信息。 - **方法**:“First of all, we synthesized the modified particles. Then, we used these particles to create the imprinted molecular ...
- "Amy managed to fly across many countries and finally reached Australia."(艾米设法飞过了许多国家,最终到达了澳大利亚。)"to fly"在这里是动词不定式,但在"manage to do"结构中,"to"后的动词通常用原形...
"I finally managed __3__ one..." "manage to do sth." 表示成功地做了某事,因此这里应该填"to get",答案是C。 第四题考查物主代词。"...in __4__ new home all the time." 这里的"new home"指的是鸟的新家,...