try..catch..finally

环境 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}

可以看一下案例五

你可能感兴趣的:(try..catch..finally)