我们经常会遇到一个问题,try 里面return了,finally还会执行吗?例如下面一个例子:
public class TryFinally {
public static void main(String[] args) {
System.out.println(f1());
}
public static String f1() {
String str = "hello";
try{
return str;
}
finally{
str = "world";
}
}
}
输出是hello,而不是world。但是str="world"会执行。我们通过字节码来解释:
Classfile /F:/code/java/test/out/production/test/TryFinally.class
Last modified Nov 19, 2018; size 788 bytes
MD5 checksum 33506d15d7a020f1e9a7598ef4536991
Compiled from "TryFinally.java"
public class TryFinally
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#29 // java/lang/Object."<init>":()V
#2 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #7.#32 // TryFinally.f1:()Ljava/lang/String;
#4 = Methodref #33.#34 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String #35 // hello
#6 = String #36 // world
#7 = Class #37 // TryFinally
#8 = Class #38 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 LTryFinally;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 f1
#21 = Utf8 ()Ljava/lang/String;
#22 = Utf8 str
#23 = Utf8 Ljava/lang/String;
#24 = Utf8 StackMapTable
#25 = Class #39 // java/lang/String
#26 = Class #40 // java/lang/Throwable
#27 = Utf8 SourceFile
#28 = Utf8 TryFinally.java
#29 = NameAndType #9:#10 // "<init>":()V
#30 = Class #41 // java/lang/System
#31 = NameAndType #42:#43 // out:Ljava/io/PrintStream;
#32 = NameAndType #20:#21 // f1:()Ljava/lang/String;
#33 = Class #44 // java/io/PrintStream
#34 = NameAndType #45:#46 // println:(Ljava/lang/String;)V
#35 = Utf8 hello
#36 = Utf8 world
#37 = Utf8 TryFinally
#38 = Utf8 java/lang/Object
#39 = Utf8 java/lang/String
#40 = Utf8 java/lang/Throwable
#41 = Utf8 java/lang/System
#42 = Utf8 out
#43 = Utf8 Ljava/io/PrintStream;
#44 = Utf8 java/io/PrintStream
#45 = Utf8 println
#46 = Utf8 (Ljava/lang/String;)V
{
public TryFinally();
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
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTryFinally;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #3 // Method f1:()Ljava/lang/String;
6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
LineNumberTable:
line 3: 0
line 4: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
public static java.lang.String f1();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: ldc #5 // String hello
2: astore_0
3: aload_0
4: astore_1
5: ldc #6 // String world
7: astore_0
8: aload_1
9: areturn
10: astore_2
11: ldc #6 // String world
13: astore_0
14: aload_2
15: athrow
Exception table:
from to target type
3 5 10 any
LineNumberTable:
line 6: 0
line 8: 3
line 11: 5
line 8: 8
line 11: 10
line 12: 14
LocalVariableTable:
Start Length Slot Name Signature
3 13 0 str Ljava/lang/String;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 10
locals = [ class java/lang/String ]
stack = [ class java/lang/Throwable ]
}
SourceFile: "TryFinally.java"
关键的只有这段代码:
Code:
stack=1, locals=3, args_size=0
0: ldc #5 // String hello
2: astore_0
3: aload_0
4: astore_1
5: ldc #6 // String world
7: astore_0
8: aload_1
9: areturn
10: astore_2
11: ldc #6 // String world
13: astore_0
14: aload_2
15: athrow
Exception table:
from to target type
3 5 10 any
1、首先分析这段代码,有两个 ldc #6和astore_0,这句话的意思是把常量池里面world加到操作数栈里面然后将它放到本地变量表索引为0的位置,我们思考为什么会有两次?那是因为str="world"是在finally里面执行的,所以在返回之前一定会执行。也就是,如果不抛出异常正常就会执行,如果抛出异常也就是在areturn之后,也会执行。
2、我们注意到,athrow也就是抛出异常之前,aload_2,也就是把本地变量表里面索引为2的数据作为放到操作数栈里面然后抛出去,这个抛出数据就是异常,就是在astore_2里面放进去的异常。我们从这里知道了,本地变量表里面索引为2的位置的内容就是异常。
3、最后我们看areturn之前,把本地变量表里面索引为1的数据load进来操作数栈里面,也就是我们return的是本地变量表里面索引为1的数据,进过上面的分析,我们知道这个值是“hello”,而不是“world”。
所以我们最终看到的是输出了“hello”而不是“world”,从字节码层面就这么分析出来。