• 深入剖析java的try…catch…finally语句


    一、前言

    前些天参加面试的时候有一道题:

    public class test {
        public static void main(String[] args){
            try {
                return;
            } 
            finally{
                System.out.println("finally...");
            }
        }
    }
    以上程序的执行结果是什么?

    当时觉得finally块肯定会被执行到的,而这段程序在try块里就已经返回了,所以选了“编译出现错误”这个选项,回来之后验证了一下,结果是输出“finally…”,越发觉得这个问题很有趣。

    二、剖析

    1. 从字节码分析

    为了更好的说明问题,选用一篇博客里的例子[1]进行说明,源码:

    public class Test {
        @SuppressWarnings("finally")
        public static final String test() {
            String t = "";
     
            try {
                t = "try";
                return t;
            } catch (Exception e) {
                t = "catch";
                return t;
            } finally {
                t = "finally";
            }
        }
     
        public static void main(String[] args) {
            System.out.print(Test.test());
        }
    }

    按照一般的思路,首先程序执行try语句块,把变量t赋值为try,由于没有发现异常,接下来执行finally语句块,把变量t赋值为finally,然后return t,则t的值是finally,最后t的值就是finally,程序结果应该显示finally,但是实际结果为try。为什么会这样,我们不妨先看看这段代码编译出来的class对应的字节码,看虚拟机内部是如何执行的。

    我们用javap -verbose Test 来显示目标文件(.class文件)字节码信息。

    系统运行环境:win7 64位

    jdk信息:java version "1.8.0_05",Java(TM) SE Runtime Environment (build 1.8.0_05-b13),Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)

    编译出来的字节码部分信息,我们只看test方法,其他的先忽略掉:

    public static final java.lang.String test();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
        Code:
          stack=1, locals=4, args_size=0
             0: ldc           #2                  // String 
             2: astore_0      
             3: ldc           #3                  // String try
             5: astore_0      
             6: aload_0       
             7: astore_1      
             8: ldc           #4                  // String finally
            10: astore_0      
            11: aload_1       
            12: areturn       
            13: astore_1      
            14: ldc           #6                  // String catch
            16: astore_0      
            17: aload_0       
            18: astore_2      
            19: ldc           #4                  // String finally
            21: astore_0      
            22: aload_2       
            23: areturn       
            24: astore_3      
            25: ldc           #4                  // String finally
            27: astore_0      
            28: aload_3       
            29: athrow        
          Exception table:
             from    to  target type
                 3     8    13   Class java/lang/Exception
                 3     8    24   any
                13    19    24   any
          LineNumberTable:
            line 5: 0
            line 8: 3
            line 9: 6
            line 14: 8
            line 10: 13
            line 11: 14
            line 12: 17
            line 14: 19
          StackMapTable: number_of_entries = 2
               frame_type = 255 /* full_frame */
              offset_delta = 13
              locals = [ class java/lang/String ]
              stack = [ class java/lang/Exception ]
               frame_type = 74 /* same_locals_1_stack_item */
              stack = [ class java/lang/Throwable ]

    观察Code部分:

    第[0-2]行,给第0个变量赋值“”,也就是String t="";

    第[3-5]行,也就是执行try语句块 赋值语句 ,也就是 t = "try";

    第[6-7]行,重点是第7行,把第t对应的值"try"赋给第1个变量,但是这里面第1个变量并没有定义这个比较奇怪;

    第[8-10] 行,对第0个变量进行赋值操作,也就是t="finally";

    第[11-12]行,把第1个变量对应的值返回;

    通过字节码,我们知道,在return之前,虚拟机会创建一个中间变量,我们暂时可以称为t’,然后把t的值赋值给t’,接下来去执行finally块的内容,最后返回t’,所以即使在finally块里修改了t,但是return返回的是t’,所以最后输出的是t’的内容,如下图所示:

    1

    2. 如果try和finally块里面修改的是可变的对象

    class myObject{
        private int value = 0;
    
        public myObject(int value){
            this.value = value;
        }
        
        public void setValue(int value){
            this.value = value;
        }
    
        public void print(){
            System.out.println("obj:" + this + ",value:" +  value);
        }
    
    }
    
    public class Test {
        @SuppressWarnings("finally")
        public static final myObject test() {
            myObject myObj = null;
     
            try {
                myObj = new myObject(1);
                System.out.println("in try block");
                myObj.print();
                return myObj;
            } catch (Exception e) {
                myObj.setValue(2);
                return myObj;
            } finally {
                myObj.setValue(3);
            }
        }
     
        public static void main(String[] args) {
            myObject newObj = test();
            System.out.println("after return");
            newObj.print();
        }
    }

    输出:

    in try block
    obj:myObject@15db9742,value:1
    after return
    obj:myObject@15db9742,value:3

    在这个例子中,即使myObj会赋值给myObj’,然而他们都指向同一个对象,因此在finally块中对这个对象的修改当然反映到myObj’中。

    3. try和finally块中都有return语句

    还是用String的例子,不过finally语句增加了return语句

    public class Test {
        @SuppressWarnings("finally")
        public static final String test() {
            String t = "";
     
            try {
                t = "try";
                return t;
            } catch (Exception e) {
                t = "catch";
                return t;
            } finally {
                t = "finally";
    return t;
            }
        }
     
        public static void main(String[] args) {
            System.out.print(Test.test());
        }
    }

    最后输出:

    finally

    可见这种情况,执行完finally之后就直接返回了。

    4. catch语句被执行的情况并且有return语句

    public class Test {
        @SuppressWarnings("finally")
        public static final String test() {
            String t = "";
     
            try {
                t = "try";
                Integer.parseInt(null);
                return t;
            } catch (Exception e) {
                t = "catch";
                return t;
            } finally {
                t = "finally";
            }
        }
     
        public static void main(String[] args) {
            System.out.print(Test.test());
        }
    }

    最后输出:

    catch

    可见try和catch语句里都存在使用中间变量的情况。

    5. catch块中抛出异常的情况

    public class Test {
        @SuppressWarnings("finally")
        public static final String test() {
            String t = "";
     
            try {
                t = "try";
                Integer.parseInt(null);
                return t;
            } catch (Exception e) {
                t = "catch";
                Integer.parseInt(null);
                return t;
            } finally {
                t = "finally";
            }
        }
     
        public static void main(String[] args) {
            System.out.print(Test.test());
        }
    }

    输出:

    Exception in thread "main" java.lang.NumberFormatException: null
        at java.lang.Integer.parseInt(Integer.java:542)
        at java.lang.Integer.parseInt(Integer.java:615)
        at Test.test(Test.java:12)
        at Test.main(Test.java:20)

    执行过程大概是,执行try块,Integer.parseInt(null)语句抛出异常,进入catch语句,然后又抛出异常,然后执行finally块,对t进行赋值,因为finally没有返回,所以执行完之后,catch把异常抛出。

    如果finally块中有return语句呢,在finally块最后加多一条语句”return t;”,最后输出:

    finally

    6. finally块中抛出异常

    public class Test {
        @SuppressWarnings("finally")
        public static final String test() {
            String t = "";
     
            try {
                t = "try";
                return t;
            } catch (Exception e) {
                t = "catch";
                return t;
            } finally {
                t = "finally";
                String.valueOf(null);
                return t;
            }
        }
     
        public static void main(String[] args) {
            System.out.print(Test.test());
        }
    }

    输出:

    Exception in thread "main" java.lang.NullPointerException[Finished in 1.1s with exit code 1]
        at java.lang.String.<init>(String.java:166)
        at java.lang.String.valueOf(String.java:2993)
        at Test.test(Test.java:14)
        at Test.main(Test.java:20)

    可见执行到finally块,产生异常之后会终止当前的其他操作,向上抛出异常。

    三、小结

    • try,catch,finally语句中,如果在try/catch块中存在return语句,finally块没有return语句,try/catch块会产生一个临时变量(t’)存储return 语句中的变量(t),如果这个变量类型是值类型或者不可变对象,则在finally块中对变量t的修改不会影响到try/catch中返回的结果;如果是可变对象类型,则结果会影响;
    • 如果finally块中有return语句,则try和catch中的return语句都会忽略;
    • 如果finally块中抛出异常,则停止try…catch…finally中的其他操作,直接向上抛出异常。

    四、参考

    1. java中关于try、catch、finally中的细节分析

    2. 从Java代码到字节码(1)

  • 相关阅读:
    点击弹出层以外的区域隐藏弹出层
    css3 animation 动画属性简介
    IdentityServer4 接入自己的用户体系
    分布式事务的实现
    微服务分布式数据管理的挑战
    微服务的数据自治
    SkyWalking 分布式追踪系统
    创建、改进和控制微服务API的版本和契约
    富领域模型和贫血领域模型
    cenos 安装git
  • 原文地址:https://www.cnblogs.com/harrymore/p/9175205.html
Copyright © 2020-2023  润新知