finally语句中对变量进行赋值的问题

在使用try、catch、finally语句时,出现了一些疑问。看代码:

package test;
public class Test {

    public String test() {
        String t = "";

        try {
            t = "try";
            return t;
        } finally {
            t = "finally";
        }
    }
}

首先程序执行try语句块,把变量t赋值为try,接下来执行finally语句块,把变量t赋值为finally,然后return t,那么程序结果应该显示finally,但是实际结果为try。

使用反编译命令javap -v -p Test看看编译出来的字节码的信息:

运行环境是Windows7(64位) + Java JDK 1.7.0
(提示:很早之前(JDK 1.4.2 之前)的 Sun Javac 已经不再为 finally 语句生成 jsr 和 ret 指令了,
而是改为在每个分支之后冗余代码的形式来实现 finally 语句)

图1
图2

尝试分析一下上面的字节码,如果有什么错误的地方,请指正,感谢!

在此之前,可以先了解一下栈帧、局部变量表、操作数栈的概念,

本地变量表:

图3

可以看到有两个本地变量,一个是this一个是t。

图4

(如果是静态方法,方法内声明的变量在slot中的存放从0开始,以变量声明的顺序存放,如果是普通方法,则slot中0的位置存放this变量,其他在方法中声明的变量从1开始存放,依旧以变量声明的顺序存放。)

异常表:

图5

从异常表可以看到如果从3 ~ 8出现异常,从13开始运行。

常量池:

图6

Code:

图7

这一行是所有的方法都会有的,其中Stack我理解为方法生命周期内栈的最大深度。

Locals是本地变量的slot个数,但是并不代表是stack宽度一致,本地变量是在这个方法生命周期内,局部变量最多的时候,需要多大的宽度来存放数据(double、long会占用两个slot)。

Args_size代表的是入参的个数,不再是slot的个数,也就是传入一个long,也只会记录1。该方法显示的参数指的是this,如果是无参的静态方法Args_size会是0。

    0: ldc           #16    // String; 从常量池#16的位置取出“”这个字符串(对象引用)压栈,此时栈深度为1 
    2: astore_1             // 弹出栈顶的String对象的引用并将其存入slot1处的局部变量t中,此时t=“”,此时栈深度为0
    3: ldc           #18    // String try; 从常量池#18的位置取出“try”这个字符串(对象引用)压栈,此时栈深度为1
    5: astore_1             // 弹出栈顶的String对象的引用并将其存入slot1处的局部变量t中,此时t=“try”,此时栈深度为0
    6: aload_1              // 将slot1处的局部变量t压入栈顶,此时栈深度为1
    7: astore_3             // 弹出栈顶的String对象的引用并将其存入第四个局部变量中,该变量的值为“try”,此时栈深度为0
    8: ldc           #20    // String finally; 从常量池#20的位置取出“finally”这个字符串(对象引用)压栈,此时栈深度为1
   10: astore_1             // 弹出栈顶的String对象的引用并将其存入slot1处的局部变量t中,此时t=“finally”,此时栈深度为0
   11: aload_3              // 将第四个局部变量压入栈顶,此时栈深度为1
   12: areturn              // 正常返回(没有异常发生), 返回栈顶值,也就是第四个局部变量,它指向“try”字符串对象,此时栈深度为0
   13: astore_2             // (如果3 ~ 8中出现异常,则跳到此指令开始执行)弹出栈顶的异常对象引用并将其存入第三个局部变量中
   14: ldc           #20    // String finally,  从常量池#20的位置取出“finally”这个字符串(对象引用)压栈,此时栈深度为1
   16: astore_1             // 弹出栈顶的String对象的引用并将其存入slot1处的局部变量t中,此时t=“finally”,此时栈深度为0
   17: aload_2              // 将存储在第三个局部变量中异常引用压入栈顶,此时栈深度为1
   18: athrow               // 将栈顶的异常抛出,此时栈深度为0

从字节码中可以看到在try块中给局部变量t指向了字符串对象“try”后执行return t时,JVM先把字符串对象“try”的引用缓存到一个局部变量中,该局部变量存储在本地变量表的Slot 3位置。然后执行finally块,在finally块中,局部变量t指向了字符串对象“finally”,当执行完finally块后,把本地变量表的Slot 3位置存储的局部变量返回,此时,该局部变量指向的是字符串对象“try”,所以返回值是“try”而不是“finally”。

所以可以得出结论是:

如果在执行finally块前出现return语句,会把在值先缓存起来,等执行完finally块后,再返回缓存起来的值。

验证一下:

package test;

import java.util.ArrayList;
import java.util.List;

public class Test {

   public static List<String> test() {
        List<String> list = new ArrayList<>();
        try {
            list.add("a");
            return list;
        } finally {
            list = new ArrayList<>();
            list.add("b");
        }
    }

   public static List<String> test1() {
       List<String> list = new ArrayList<>();
       try {
           list.add("a");
           return list;
       } finally {
           list.add("b");
       }
   }

   public static Car test2() {
        Car car = new Car();
        try {
            car.setSize(1);
            return car;
        } finally {
            car = new Car();
            car.setSize(2);
        }
    }

   public static Car test3() {
       Car car = new Car();
       try {
            car.setSize(1);
            return car;
       } finally {
            car.setSize(2);
       }
   }

   public static int test4() {
       int i = 0;
       try {
            i = 1;
            return i;
       } finally {
            i = 2;
       }
   }

   public static int test5() {
      int i = 0;
      try {
          throw new RuntimeException();
      } catch (Exception e) {
          i = 1;
          return i;
      } finally {
          i = 2;
      }
  }

   public static Car test6() {
      Car car = new Car();
      try {
          throw new RuntimeException();
      } catch (Exception e) {
          car.setSize(1);
          return car;
      } finally {
          car.setSize(2);
      }
   }
   public static void main(String[] args) {
       System.out.println("-------在try块中返回-------");
       System.out.println(test());
       System.out.println(test1());
       System.out.println(test2());
       System.out.println(test3());
       System.out.println(test4());
       System.out.println("-------在catch块中返回-------");
       System.out.println(test5());
       System.out.println(test6());
   }
}
class Car {
    private int size;

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    @Override
    public String toString() {
        return "car [size=" + size + "]";
    }
}

控制台:

-------try块中返回-------
[a]
[a, b]
car [size=1]
car [size=2]
1
-------catch块中返回-------
1
car [size=2]

如果是返回基本类型的值,那么在缓存时也是缓存值本身,所以后面在finally块中重新赋值时,方法返回的值不会受finally块中重新赋值的影响;

如果返回的是引用类型的值,那么在缓存时,缓存的是引用类型对象的引用,所以虽然后面在finally块中重新赋值时(重新指向另一个对象),方法返回的值不会受到影响,但是如果是修改对象的属性,那么会影响到返回的值。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值