美文网首页
JVM - 关于try-catch-finally语句的一个疑问

JVM - 关于try-catch-finally语句的一个疑问

作者: 琼珶和予 | 来源:发表于2019-04-22 15:43 被阅读0次

      今天我在《深入理解Java虚拟机》这本书中看到一个特别有意思的例子,作者在书中举了一个try-catch-finally的例子,然后给出了答案。我看着答案,感觉自己想的不一样,当时我以为答案错了,然后自己在编译器上照着写了一下那个例子,发现最终的答案跟书上是一模一样的。瞬间我就懵逼了,难道我学的假Java?(其实归根结底还是自己菜😂)。

    1. 提出问题

      当时书中的例子是这样的:

    public class Demo {
        public static void main(String[] args) {
            Demo demo = new Demo();
            System.out.println("val = " + demo.inc());
        }
    
        public int inc() {
            int x;
            try {
                x = 1;
                return x;
            } catch (Exception e) {
                x = 2;
                return x;
            } finally {
                x = 3;
            }
        }
    }
    

      重点在inc方法里面,main方法只是用来测试的。当时书中说,如果在try语句块没有抛出异常,inc方法最终会返回1。大家看到这里似乎没有发现问题。
      我来解释一下我的理解,地球人都知道,在try-catch-finally语句中,如果try语句块中会return,那么在return之前会执行finally语句块里面的代码。此时,大家会不会恍然大悟,对啊,在return之前会执行finally语句块里面的代码。在上面的例子中,我们在finally语句块中将x赋值为3,然后再return x,为什么x还是为1呢?
      从血淋淋的现实中,我们可以知道,我们的Java还没有学到家。那原因是什么呢?接下来我将从两个方面解释这个现象。

    2. 从class文件来看

      上面的Java文件经过javac工具编译之后,会生成一个对应的class文件。我们用编译器(我使用的是idea)打开看看:

    public class Demo {
        public Demo() {
        }
        public static void main(String[] args) {
            Demo demo = new Demo();
            System.out.println("val = " + demo.inc());
        }
        public int inc() {
            byte var3;
            try {
                byte x;
                try {
                    x = 1;
                    byte var2 = x;
                    return var2;
                } catch (Exception var7) {
                    x = 2;
                    var3 = x;
                }
            } finally {
                boolean var9 = true;
            }
            return var3;
        }
    }
    

      楼主使用的jdk版本是1.8.0_201,可能版本不同编译出来的是不一样的。
      从class文件,我们可以直观的知道,虽然我们在finally语句块中给x赋值为3,但是编译之后,finally语句里面根本没有对x进行赋值。实际上的是,在try语句块中,return之前已经将x赋值给一个名为var2的变量,然后最后返回也是var2变量。所以,finally语句块里面对x的修改根本没有起作用。
      从class文件里面,我们可以非常直观的知道答案。接下来,我们将从字节码命令来分析这个问题。

    3. 从字节码命令来看

      我们使用javap工具来打印Demo 的class文件的具体信息,通过如下的命令即可:

    javap -verbose class文件的路径
    

      打印之后,我们得到inc方法的字节码命令,如下(jdk版本不同,信息可能不一样):

      public int inc();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=5, args_size=1
             0: iconst_1
             1: istore_1
             2: iload_1
             3: istore_2
             4: iconst_3
             5: istore_1
             6: iload_2
             7: ireturn
             8: astore_2
             9: iconst_2
            10: istore_1
            11: iload_1
            12: istore_3
            13: iconst_3
            14: istore_1
            15: iload_3
            16: ireturn
            17: astore        4
            19: iconst_3
            20: istore_1
            21: aload         4
            23: athrow
    

      在分析之前,我先说一下:

    1. 这里只是展示inc方法的部分信息,并没有展示全部。
    2. 为了简单起见,这里只分析0 ~ 7行命令。

      我们先来看一下0 ~ 1两行指令:

             0: iconst_1 // 将int型变量1推至栈顶。
             1: istore_1 // 将栈顶的变量赋值给第二个本地变量,也就是x = 1。
    

      然后是2~3两行指令:

             2: iload_1 // 将第二个本地变量推至栈顶,这里表示的意思是将1放在栈顶
             3: istore_2 // 将栈顶的变量值赋值给第三个本地变量(这里我们假设第三个本地变量是`returnValue`),表示的意思相当于将栈顶的x的值用另一个变量来保存。
    

      然后是4~5两行指令:

             4: iconst_3 // 将int类型变量3推至栈顶
             5: istore_1 // 将栈顶变量赋值给第二个本地变量,也就是x = 3。这部分的指令就表示finally语句块的代码。
    

      然后是6~7两行指令:

             6: iload_2 // 将第三个本地变量推至栈顶,也就是将`returnValue`放在栈顶。
             7: ireturn // 返回int值。
    

      最后的ireturn指令表示获取栈顶变量的值。

    4. 总结

      到这里,我们就清楚了上面的问题,为什么在finally语句块给x赋值为3,最后返回还是1,我来简单的总结一下。

    在调用finally语句块代码之前,jdk会将需要return的值保存在另一个变量(returnValue)中,最后返回也是returnValue变量。

    相关文章

      网友评论

          本文标题:JVM - 关于try-catch-finally语句的一个疑问

          本文链接:https://www.haomeiwen.com/subject/qwvygqtx.html