在前面的文章中,有详细地介绍java字节码相关的知识,有兴趣的可以提前了解一下。
从字节码角度分析 异常_finally
示例代码如下:
- // 从字节码角度分析:异常_finally
- public class T16_ByteAnalyseFinally {
- public static void main(String[] args) {
- int i = 0;
- try {
- i = 10;
- } catch (Exception e){
- i = 20;
- } finally {
- i = 30;
- }
- }
- }
上述代码通过:javap -v T16_ByteAnalyseFinally.class进行反编译,得到如下字节码。
- public static void main(java.lang.String[]);
- descriptor: ([Ljava/lang/String;)V
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=4, args_size=1
- 0: iconst_0
- 1: istore_1 // 0 -> i
- 2: bipush 10 // try ----------------------
- 4: istore_1 // 10 -> i |
- 5: bipush 30 // finally |
- 7: istore_1 // 30 -> i |
- 8: goto 27 // return -------------------
- 11: astore_2 // catch Exception -> e -----
- 12: bipush 20 // |
- 14: istore_1 // 20 -> i |
- 15: bipush 30 // finally |
- 17: istore_1 // 30 -> i |
- 18: goto 27 // return -------------------
- 21: astore_3 // catch any -> slot 3 ------
- 22: bipush 30 // finally |
- 24: istore_1 // 30 -> I |
- 25: aload_3 // <- slot 3 |
- 26: athrow // throw -------------------
- 27: return
- Exception table:
- from to target type
- 2 5 11 Class java/lang/Exception
- 2 5 21 any // 剩余的异常类型,比如 Error
- 11 15 21 any // 剩余的异常类型,比如 Error
- LineNumberTable: ...
- LocalVariableTable:
- Start Length Slot Name Signature
- 12 3 2 e Ljava/lang/Exception;
- 0 28 0 args [Ljava/lang/String;
- 2 26 1 i I
- StackMapTable: ...
上述说明:
- 可以看到 finally 中的代码被复制了 【3】 份,分别放入 try 流程, catch 流程以入 catch 剩余的异常类型流程
面试题:finally 出现了 return
代码示例如下:
- // 从字节码角度分析:finally 出现了 return
- public class T17_ByteAnalyseFinallyReturn {
- public static void main(String[] args) {
- int result = test();
- System.out.println(result);
- }
-
- public static int test() {
- try {
- return 10;
- } finally {
- return 20;
- }
- }
- }
先问问自己:上面的题目输出什么?
上述代码通过:javap -v T17_ByteAnalyseFinallyReturn.class进行反编译,得到如下字节码。
- public static int test();
- descriptor: ()I
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=2, args_size=0
- 0: bipush 10 // <- 10 放入栈顶
- 2: istore_0 // 10 -> slot 0 (从栈顶移除了)
- 3: bipush 20 // <- 20 放入栈顶
- 5: ireturn // 返回栈顶 int(20)
- 6: astore_1 // catch any -> slot 1
- 7: bipush 20 // <- 20 放入栈顶
- 9: ireturn // 返回栈顶 int(20)
- Exception table:
- from to target type
- 0 3 6 any
- LineNumberTable: ...
- StackMapTable: ...
上述说明:
- 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 为准
- 至于字节码中第2行,似乎没啥用,且留个伏笔,看下个例子
- 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常!可以试一下下面的代码。
下面的代码会发生除0异常,但 finally 后面有 return 语句,会将除0异常(ArithmeticException: / by zero)吞掉。
- // 从字节码角度分析:finally 出现了 return
- public class T18_ByteAnalyseFinallyReturn2 {
- public static void main(String[] args) {
- int result = test();
- System.out.println(result);
- }
-
- public static int test() {
- try {
- int i = 1 / 0;
- return 10;
- } finally {
- return 20;
- }
- }
- }
面试题2:finally 对返回值影响
同样问问自己,下面的题目输出什么?
- // 从字节码角度分析:finally 出现了 return
- public class T19_BateAnalyseFinallyReturn3 {
- public static void main(String[] args) {
- int result = test();
- System.out.println(result);
- }
-
- public static int test() {
- int i = 10;
- try {
- return i;
- } finally {
- i = 20;
- }
- }
- }
上述代码通过:javap -v T19_BateAnalyseFinallyReturn3.class进行反编译,得到如下字节码。
- public static int test();
- descriptor: ()I
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=3, args_size=0
- 0: bipush 10 // <- 10 放入栈顶
- 2: istore_0 // 10 -> i
- 3: iload_0 // <- i(10)
- 4: istore_1 // 10 -> slot 1, 暂存至 slot 1, 目的是为了固定返回值
- 5: bipush 20 // <- 20 放入栈顶
- 7: istore_0 // 20 -> i
- 8: iload_1 // <- slot 1(10) 载入 slot 1 暂存的值
- 9: ireturn // 返回栈顶的 int(10)
- 10: astore_2
- 11: bipush 20
- 13: istore_0
- 14: aload_2
- 15: athrow
- Exception table:
- from to target type
- 3 5 10 any
- LineNumberTable: ...
- LocalVariableTable:
- Start Length Slot Name Signature
- 3 13 0 i I
上述说明:
- return i; 对应的字节码为 istore_1 目的是将 10 存入局部变量表1号槽位,目的是为了固定返回值;
- 只要不在 finally 代码块中 return ,其异常是不会被吞掉;
总结:
- 可以看到 finally 中的代码被复制了 N 份,分别放入 try 流程, catch 流程以入 catch 剩余的异常类型流程
- 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 为准
- 如果在 finally 中出现了 return,会吞掉异常!这是非常危险的操作,意味着程序出错了也不会报错!
所以,日常代码中:不能在 finally 代码块中进行 return 操作。另外我们在阿里巴巴开发手册中也能找到类似的说明,如下:
文章最后,给大家推荐一些受欢迎的技术博客链接:
- Hadoop相关技术博客链接
- Spark 核心技术链接
- JAVA相关的深度技术博客链接
- 超全干货--Flink思维导图,花了3周左右编写、校对
- 深入JAVA 的JVM核心原理解决线上各种故障【附案例】
- 请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”
- 聊聊RPC通信,经常被问到的一道面试题。源码+笔记,包懂
欢迎扫描下方的二维码或 搜索 公众号“10点进修”,我们会有更多、且及时的资料推送给您,欢迎多多交流!
评论记录:
回复评论: