当 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 引用类型的情况。

 

posted on 2017-11-06 12:21  0xCAFEBABE  阅读(352)  评论(0编辑  收藏  举报

导航