文章是对两位博主的总结,提炼,原文如下链接:
从字节码看try catch finally的return如何执行
Java中try catch finally语句中含有return语句的执行情况(总结版)
测试代码很简单,如下:
Test.java
public class Test {
public int get() {
try{
return 0;
} catch (Exception e) {
e.printStackTrace();
return 1;
} finally {
return 2;
}
}
}
执行$ javap -verbose Test.class
$ javap -verbose Test.class
Classfile /E:/workspace/java/Test.class
Last modified 2018-1-29; size 405 bytes
MD5 checksum f8f6002de3931b2e95125679f2ce1f6c
Compiled from "Test.java"
public class Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#17 // java/lang/Object."<init>":()V
#2 = Class #18 // java/lang/Exception
#3 = Methodref #2.#19 // java/lang/Exception.printStackTrace :()V
#4 = Class #20 // Test
#5 = Class #21 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 get
#11 = Utf8 ()I
#12 = Utf8 StackMapTable
#13 = Class #18 // java/lang/Exception
#14 = Class #22 // java/lang/Throwable
#15 = Utf8 SourceFile
#16 = Utf8 Test.java
#17 = NameAndType #6:#7 // "<init>":()V
#18 = Utf8 java/lang/Exception
#19 = NameAndType #23:#7 // printStackTrace:()V
#20 = Utf8 Test
#21 = Utf8 java/lang/Object
#22 = Utf8 java/lang/Throwable
#23 = Utf8 printStackTrace
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init> ":()V
4: return
LineNumberTable:
line 1: 0
public int get();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1
2: iconst_2
3: ireturn
4: astore_1
5: aload_1
6: invokevirtual #3 // Method java/lang/Exception.prin tStackTrace:()V
9: iconst_1
10: istore_2
11: iconst_2
12: ireturn
13: astore_3
14: iconst_2
15: ireturn
Exception table:
from to target type
0 2 4 Class java/lang/Exception
0 2 13 any
4 11 13 any
LineNumberTable:
line 4: 0
line 9: 2
line 5: 4
line 6: 5
line 7: 9
line 9: 11
StackMapTable: number_of_entries = 2
frame_type = 68 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 72 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
}
SourceFile: "Test.java"
直接看重点 Exception table,字节码其他内容不做解释,想了解请移步Class文件格式,并参考JVM字节码手册
Exception table:
from to target type
0 2 4 Class java/lang/Exception
0 2 13 any
4 11 13 any
JVM8虚拟机规范中的Code属性的标准结构如下:
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
看代码就已经比较好理解了:从start_pc(开始的pc指针)执行到end_pc(结束的pc指针),假如发生了catch_type类型的异常,就跳转到异常处理的pc指针处执行(handler_pc)
从0到2执行,要是有java/lang/Exception异常,就跳转到target(目标)4行执行;假如是其他的异常(any),就跳到13行执行;同时发生java/lang/Exception异常后,执行4-11行时假如又发生任意异常(any),就跳到13行执行。
jvm虚拟机就是这样通过异常表来执行的。
因此上述字节码中,有三段重复的字节码执行,带有3个return指令,第一个是没有异常情况下执行的,第二个是我们遇到在catch代码块中指定的异常情况下执行的,第三个是遇到我们未指定的异常情况下执行的。
总结一下,在编译时候,由于JVM没有那么智能,不能像人一样识别finally,因此我们把finally中的代码块放到了try语句块的return之前,并且把try代码块中return的value保存下来,之后根据以下情况(下一段落)来做处理,所以如果未发生异常,那么字节码会顺序执行。如果有异常,那么根据这个异常是否属于在catch中,跳转执行不同的字节码段
try catch finally和return其他状况总结如下:
try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:
情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
来源:CSDN
作者:一只白喵喵
链接:https://blog.csdn.net/qq_43186095/article/details/104008375