环境 IDEA2017.3、JDK1.8
- 案例一
public static void main(String[] args) {
TimeTest timeTest = new TimeTest();
System.out.println(timeTest.getMap());
}
public Map getMap() {
Map map = new HashMap<>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
return map;
}catch (Exception e) {
map.put("KEY", "CATCH");
}finally {
map = null;
}
return map;
}
结果
{KEY=TRY}
字节码分析
0 new #7
3 dup //复制栈顶元素并再次入栈(这里为什么会复制一次入栈,一个引用是虚拟机为了执行下面的init方法,另一个是为了给用户执行其他操作)
4 invokespecial #8 >
7 astore_1 //将map的引用地址存到局部变量表1
8 aload_1 //局部变量表1入栈(9-18是put操作)
9 ldc #9
11 ldc #10
13 invokeinterface #11 count 3
18 pop
19 aload_1 //局部变量表1入栈(20-29是put操作)
20 ldc #9
22 ldc #12
24 invokeinterface #11 count 3
29 pop
30 aload_1 //局部变量表1入栈
31 astore_2 //出栈存储到局部变量表2[map(key,try)]
32 aconst_null //定义一个null引用并入栈
33 astore_1 //null引用存储到局部变量表1
34 aload_2 //局部变量表2入栈
35 areturn //return map
36 astore_2 //这里没搞清楚。异常出栈??
37 aload_1 //局部变量表1入栈(38-47是put操作)
38 ldc #9
40 ldc #14
42 invokeinterface #11 count 3
47 pop
48 aconst_null //定义一个null引用并入栈
49 astore_1 //null引用存储到局部变量表1
50 goto 58 (+8) //跳转58行
53 astore_3 //这里也没搞清楚。还是异常出栈??
54 aconst_null //定义一个null引用并入栈
55 astore_1 //null引用存储到局部变量表1
56 aload_3 //局部变量表3入栈
57 athrow //抛出栈顶异常
58 aload_1 //局部变量表1入栈
59 areturn //return map
其实字节码只需要看到35行就可以,map里最后存入的是map(key,try),然后保存到局部变量表2处,之后执行finally里的代码,定义一个null入栈,之后出栈保存到局部变量表1处,return返回的是局部变量表2处的引用,也就是map。还有就是从字节码可以看出try和catch后面都会跟一个finally(这个是jdk多少版本后才这样的,具体忘了)
- 案例二
public static void main(String[] args) {
TimeTest timeTest = new TimeTest();
System.out.println(timeTest.getMap());
}
public Map getMap() {
Map map = new HashMap<>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
}catch (Exception e) {
map.put("KEY", "CATCH");
}finally {
map = null;
}
return map;
}
结果
null
字节码分析
0 new #7
3 dup
4 invokespecial #8 >
7 astore_1 //将map的引用地址存到局部变量表1
8 aload_1 //局部变量表1入栈(9-18是put操作)
9 ldc #9
11 ldc #10
13 invokeinterface #11 count 3
18 pop
19 aload_1 //局部变量表1入栈(20-29是put操作)
20 ldc #9
22 ldc #12
24 invokeinterface #11 count 3
29 pop
30 aconst_null //定义一个null引用并入栈
31 astore_1 //null引用存储到局部变量表1
32 goto 57 (+25) //跳转57行
35 astore_2
36 aload_1 //局部变量表1入栈(37-46是put操作)
37 ldc #9
39 ldc #14
41 invokeinterface #11 count 3
46 pop
47 aconst_null //定义一个null引用并入栈
48 astore_1 //null引用存储到局部变量表1
49 goto 57 (+8) //跳转57行
52 astore_3
53 aconst_null //定义一个null引用并入栈
54 astore_1 //null引用存储到局部变量表1
55 aload_3 //局部变量表3入栈
56 athrow //抛出栈顶异常
57 aload_1 //局部变量表1入栈
58 areturn //return map(null)
- 案例三
public static void main(String[] args) {
TimeTest timeTest = new TimeTest();
System.out.println(timeTest.getMap());
}
public Map getMap() {
Map map = new HashMap<>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
int i = 1/0;
}catch (Exception e) {
map.put("KEY", "CATCH");
return map;
}finally {
map = null;
}
return map;
}
结果
{KEY=CATCH}
字节码分析
0 new #7
3 dup
4 invokespecial #8 >
7 astore_1 //将map的引用地址存到局部变量表1
8 aload_1 //局部变量表1入栈(9-18是put操作)
9 ldc #9
11 ldc #10
13 invokeinterface #11 count 3
18 pop
19 iconst_1 //常量1入栈
20 iconst_0 //常量0入栈
21 idiv //1、0出栈相除
22 istore_2 //结果存到局部变量表2
23 aload_1 //局部变量表1入栈(24-33是put操作)
24 ldc #9
26 ldc #12
28 invokeinterface #11 count 3
33 pop
34 aconst_null //定义一个null引用并入栈
35 astore_1 //null引用存储到局部变量表1
36 goto 64 (+28) //跳转64行(从而证明如果try中没有异常和return将返回null)
39 astore_2
40 aload_1 //局部变量表1入栈(41-50是put操作)
41 ldc #9
43 ldc #14
45 invokeinterface #11 count 3
50 pop
51 aload_1 //局部变量表1入栈
52 astore_3 //出栈然后存到局部变量表3[map(key,catch)]
53 aconst_null //定义一个null引用并入栈
54 astore_1 //null引用存储到局部变量表1
55 aload_3 //局部变量表3入栈
56 areturn //return map
57 astore 4
59 aconst_null //定义一个null引用并入栈
60 astore_1 //存到局部变量表1
61 aload 4 //加载局部变量表4处异常
63 athrow //抛出异常
64 aload_1 //局部变量表1入栈
65 areturn //null
通过前三个案例,可以看出如果try或者catch中的代码执行了并且有return,那么finally中的代码并没有覆盖掉原值,否则覆盖。是这样吗?继续看下面的例子!
- 案例四
public static void main(String[] args) {
TimeTest timeTest = new TimeTest();
System.out.println(timeTest.getMap());
}
public Map getMap() {
Map map = new HashMap<>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
return map;
}catch (Exception e) {
map.put("KEY", "CATCH");
}finally {
map.put("KEY", "FINALLY");
}
return map;
}
结果
{KEY=FINALLY}
字节码分析
0 new #7
3 dup
4 invokespecial #8 >
7 astore_1 //将map的引用地址存到局部变量表1
8 aload_1 //局部变量表1入栈(9-18是put操作)
9 ldc #9
11 ldc #10
13 invokeinterface #11 count 3
18 pop
19 aload_1 //局部变量表1入栈(20-29是put操作)
20 ldc #9
22 ldc #12
24 invokeinterface #11 count 3
29 pop
30 aload_1 //局部变量表1入栈
31 astore_2 //栈顶元素出栈保存到局部变量表2
32 aload_1 //局部变量表1入栈(33-42是put操作)
33 ldc #9
35 ldc #13
37 invokeinterface #11 count 3
42 pop
43 aload_2 //局部变量表2入栈
44 areturn //返回栈顶引用(注意这里局部变量表1和2存储的都是map引用地址,虽然31行备份了map但是备份的是引用地址,所以33-42行对引用的修改影响到了局部变量表2,导致返回{KEY=FINALLY})
//下面不分析了
45 astore_2
46 aload_1
47 ldc #9
49 ldc #15
51 invokeinterface #11 count 3
56 pop
57 aload_1
58 ldc #9
60 ldc #13
62 invokeinterface #11 count 3
67 pop
68 goto 85 (+17)
71 astore_3
72 aload_1
73 ldc #9
75 ldc #13
77 invokeinterface #11 count 3
82 pop
83 aload_3
84 athrow
85 aload_1
86 areturn
案例四虽然try中有return了,但是finally里的代码对它产生了影响。为什么?看字节码分析,可以看出来,虽然执行finally前对map进行了备份(保存到局部变量表2),但是备份的是引用地址,最终导致对局部变量表1中的引用地址所指map内容修改影响到了局部变量表2中备份的内容。
这里可以比较一下案例一和案例四在此处的代码,如下:
//案例一
30 aload_1 //局部变量表1入栈
31 astore_2 //出栈存储到局部变量表2[map(key,try)]
32 aconst_null //定义一个null引用并入栈
33 astore_1 //null引用存储到局部变量表1
34 aload_2 //局部变量表2入栈
35 areturn //return map
//案例四
30 aload_1 //局部变量表1入栈
31 astore_2 //栈顶元素出栈保存到局部变量表2
32 aload_1 //局部变量表1入栈(33-42是put操作)
33 ldc #9
35 ldc #13
37 invokeinterface #11 count 3
42 pop
43 aload_2 //局部变量表2入栈
44 areturn //return map
执行finally前,都是对局部变量表1处的map进行备份(备份到局部变量表2处)。案例一是生成一个null引用,然后放到局部变量表1处覆盖原引用,案例二是修改局部变量表1处引用里的内容。但是返回的是局部变量表2处的备份。仔细想就明白了。所在最终导致一个定义null没生效,一个修改内容生效。这里是引用类型,如果是基本类型呢,如下把map改成int?
public int getInt() {
int i = 1;
try {
i = 2;
return i;
}catch (Exception e) {
i = 3;
}finally {
i = 4;
}
return i;
}
我把结果写下面,原理大家自己分析,明白刚刚说的应该就可以知道这个的结果了
2
- 案例五
public static void main(String[] args) {
TimeTest timeTest = new TimeTest();
System.out.println(timeTest.getMap());
}
public Map getMap() {
Map map = new HashMap<>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
return map;
}catch (Exception e) {
map.put("KEY", "CATCH");
}finally {
map.put("KEY", "FINALLY");
map = null;
}
return map;
}
结果
{KEY=FINALLY}
原来同上
- 案例六
public static void main(String[] args) {
TimeTest timeTest = new TimeTest();
System.out.println(timeTest.getMap());
}
public Map getMap() {
Map map = new HashMap<>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
return map;
}catch (Exception e) {
map.put("KEY", "CATCH");
}finally {
map.put("KEY", "FINALLY");
map = null;
return map;
}
}
结果
null
字节码分析
0 new #7
3 dup
4 invokespecial #8 >
7 astore_1 //将map的引用地址存到局部变量表1
8 aload_1 //局部变量表1入栈(9-18是put操作)
9 ldc #9
11 ldc #10
13 invokeinterface #11 count 3
18 pop
19 aload_1 //局部变量表1入栈(20-29是put操作)
20 ldc #9
22 ldc #12
24 invokeinterface #11 count 3
29 pop
30 aload_1 //局部变量表1入栈
31 astore_2 //栈顶元素出栈保存到局部变量表2
32 aload_1 //局部变量表1入栈(33-42是put操作)
33 ldc #9
35 ldc #13
37 invokeinterface #11 count 3
42 pop
43 aconst_null //定义一个null引用并入栈
44 astore_1 //null引用存储到局部变量表1
45 aload_1 //加载局部变量表1
46 areturn //返回null
47 astore_2
48 aload_1
49 ldc #9
51 ldc #15
53 invokeinterface #11 count 3
58 pop
59 aload_1
60 ldc #9
62 ldc #13
64 invokeinterface #11 count 3
69 pop
70 aconst_null
71 astore_1
72 aload_1
73 areturn
74 astore_3
75 aload_1
76 ldc #9
78 ldc #13
80 invokeinterface #11 count 3
85 pop
86 aconst_null
87 astore_1
88 aload_1
89 areturn
- 案例七
public static void main(String[] args) {
TimeTest timeTest = new TimeTest();
System.out.println(timeTest.getMap());
}
public Map getMap() {
Map map = new HashMap<>();
map.put("KEY", "INIT");
try {
map.put("KEY", "TRY");
return map;
}catch (Exception e) {
map.put("KEY", "CATCH");
return map;
}finally {
map.put("KEY", "FINALLY");
}
}
结果
{KEY=FINALLY}
可以看一下案例五