zoukankan      html  css  js  c++  java
  • 当 return 遇到 try

    .

    .

    .

    .

    .

    今天有同事和我探讨在群里看到的一道有趣的题目,在探讨的过程中让我搞清楚了一些曾经模糊的概念,特此记录下来。

    题目给出如下代码,问运行后打印的结果是什么。

     1 public static void main(String []args) {
     2       System.out.println(fun());
     3 }
     4 public static int fun () {
     5       int x = 0;
     6       try {
     7           x = 1;
     8       } finally {
     9           ++x;
    10       }
    11       try {
    12           return x;
    13       } finally {
    14           ++x;
    15       }
    16 }

    尝试运行,结果如下:(输出 2)

    1 >$ javac -g No1.java
    2 >$ javac No1
    3 2
    4 >$

    为何输出是 2 而不是 3 呢,这个可能让很多小伙伴有所疑惑,我们通过 javap 指令查看字节码来解释这个疑问。

    首先来看一些前置知识——java 字节码指令的 iconst_n、iload_n、istore_n 和 iinc。

    指令 意义
    iconst_n 表示将整型常量 n 推入栈顶,n 的范围是 -1 ≤ n ≤ 5。
    iload_n

    表示将局部变量表中第 n 个槽的整型变量加载到操作数栈顶。

    istore_n

    表示将操作数栈顶的整型值弹出,并存储到局部变量表的第 n 个槽中。

    iinc a b 表示将局部变量表第 a 个槽中的值增加 b,并将结果存回 a 槽。

    好,有了上面四个指令的基础就够了,接下来我们看一下上面代码的字节码。

    >$ javap -c -l No1
      public static int fun();
        Code:
           0: iconst_0                      // 常量 0 入栈顶
           1: istore_0                      // 将栈顶的 0 弹出并存入局部变量表的第 0 个槽中
           2: iconst_1                      // 常量 1 入栈顶
           3: istore_0                      // 弹出栈顶的 1 并存入局部变量表的第 0 个槽中,覆盖原值
           4: iinc          0, 1            // 将局部变量表第 0 个槽中的值加 1,并将结果存回局部变量表第 0 个槽中,覆盖原值
           7: goto          16              // 跳转到 16
          10: astore_1
          11: iinc          0, 1
          14: aload_1
          15: athrow                        // 下面两行是重点,将局部变量表第 0 个槽中的值拷贝了一个副本到局部变量表第 1 个槽中
          16: iload_0                       // 将局部变量表第 0 个槽中的值加载到栈顶
          17: istore_1                      // 弹出栈顶的值并存储到局部变量表第 1 个槽中
          18: iinc          0, 1            // 将局部变量表第 0 个槽中的值加 1,并将结果存回局部变量表第 0 个槽中,覆盖原值
          21: iload_1                       // 重点:将局部变量表中第 1 个槽中的值加载到栈顶(并没有加载第 0 个槽中的值)
          22: ireturn                       // 返回栈顶的值
          23: astore_2
          24: iinc          0, 1
          27: aload_2
          28: athrow
        Exception table:
           from    to  target type
               2     4    10   any
              16    18    23   any

    上面的文字太抽象,可以对照着 图1 的内容来理解。

    图1 执行过程内存图例

    在 图1 中,红色的字表示字节码的行号,黑色的字表示执行此行字节码之后,对应的内存中的值的变化。

    不难看出,在字节码第 16、17 行,将 x 的值从局部变量表的第 0 个槽中拷贝了一个副本,保存在局部变量表的第 1 个槽中,而最后执行 ireturn 指令之前,将此副本加载到了栈顶,因此返回的值是在 finally 运算之前就确定下来了的,此后 finally 中再次对 x 的运算都只是在局部变量表的第 0 个槽中做的,所以并不会影响到 ireturn 指令返回的值。

    总结:

    经过 LZ 几番测试发现,无论在 try 中 return 的变量是否参与了后面在 finally 中的计算,都会被拷贝一个副本出来。

    而 return 没有在 try 块中时,被 return 的变量则不会被拷贝副本。

    由此可见,当 try 遇到 return 时,变量被“特殊照顾”了一下。

    *注意,以上测试仅使用了 int 类型(基本数据类型),没有测试 return 引用类型的情况。

  • 相关阅读:
    SQL2005 镜像配置
    子窗体关闭程序
    asp.net 输出微信自定义菜单json
    教是最好的学
    人为什么要努力?
    《雪国列车》制度与自由
    时间记录APP———Time Meter
    饭饭
    Android编译程序报错:Re-installation failed due to different application signatures.
    我的GTD起步
  • 原文地址:https://www.cnblogs.com/0xcafebabe/p/7792522.html
Copyright © 2011-2022 走看看