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

来源:互联网 发布:java数字转化为字符串 编辑:程序博客网 时间:2024/06/05 08:46

在使用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块中返回-------1car [size=2]

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

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

阅读全文
0 0