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 语句)
尝试分析一下上面的字节码,如果有什么错误的地方,请指正,感谢!
在此之前,可以先了解一下栈帧、局部变量表、操作数栈的概念,
本地变量表:
可以看到有两个本地变量,一个是this一个是t。
(如果是静态方法,方法内声明的变量在slot中的存放从0开始,以变量声明的顺序存放,如果是普通方法,则slot中0的位置存放this变量,其他在方法中声明的变量从1开始存放,依旧以变量声明的顺序存放。)
异常表:
从异常表可以看到如果从3 ~ 8出现异常,从13开始运行。
常量池:
Code:
这一行是所有的方法都会有的,其中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块中重新赋值时(重新指向另一个对象),方法返回的值不会受到影响,但是如果是修改对象的属性,那么会影响到返回的值。
- finally语句中对变量进行赋值的问题
- 关于使用select语句对变量赋值的注意事项
- 关于C/C++中switch语句case中变量不能初始化赋值的问题
- JavaScript 对变量进行赋值的条件运算符
- vb循环中变量赋值的问题
- Java中变量赋值的问题
- Python 中变量的赋值问题
- java中finally语句快会不会执行的问题
- oracle和sqlserver在过程sql中通过select对变量进行赋值的区别及实例
- Flex中对combobox赋值的问题
- C常见问题之对结构变量的整体赋值问题
- 程序员对finally语句的误解?
- orcle中如何使用动态游标来对变量进行赋值!
- Qt 中对临时变量变量进行翻译的方法
- 对被final修饰的变量进行赋值的几种方法
- 【java】对被final修饰的变量进行赋值的几种方法
- SQLServer对select语句返回多条记录给变量赋值时的处理情况
- Java面试题-try...finally结构中try中有return那么finally中的语句的执行时间问题
- sql复杂查询:A表与B表一对多,查询A表所有用户对应B表中的数据,去除B表中的日期最大值
- 斐波那契数列
- 成为一个不惑、不忧、不惧的人(梁启超演讲节选)
- jQuery-$ is not a function
- js中const,var,let区别
- finally语句中对变量进行赋值的问题
- 关于定位锚点 返回相应位置的两种方式
- 算法模板——Tarjan算法
- JS组件系列——Bootstrap组件福利篇:几款好用的组件推荐(二)
- JPA Predicate复杂查询排序
- MySQL学习(四)【MySQL应用优化】
- Java读取XML文件的内容
- Windows下使用GetOpt函数使用
- TypeError: __init__() got an unexpected keyword argument 'shape'