try return finally位元組碼解析
阿新 • • 發佈:2018-11-26
我們經常會遇到一個問題,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。放在idea裡面會提示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”,從位元組碼層面就這麼分析出來。