橋接方法是jdk1.5引入泛型後,為使java泛型方法產生的字節碼與jdk1.5版本之前的字節碼相容由編譯器自動產生的。
可用method.isBridge()
判斷method是否是橋接方法,在產生的字節碼中會有flags標記ACC_BRIDGE, ACC_SYNTHETIC ,根據來自深入理解java虛擬機的一張存取標誌圖可以看到ACC_BRIDGE表示方法是由編譯器產生的橋接方法,ACC_SYNTHETIC表示方法由編譯器自動產生不屬於原始碼。
當子類別繼承父類別(繼承介面)實作抽象泛型方法的時候,編譯器會為子類別自動產生橋接方法
#父类 public abstract class SuperClass<T> { public abstract T get(T t) ; } #子类 public class SubClass extends SuperClass<String> { @Override public String get(String s) { return s; } }
使用javap -v SubClass.class
指令查看類別SubClass的字節碼:
Classfile /Users/xudong/project-maven/test/person-study/dubbo-provider/target/classes/com/monian/dubbo/provider/study/generic/SubClass.class Last modified 2022年7月25日; size 777 bytes MD5 checksum 1328a7043cde4b809a156e7a239335a6 Compiled from "SubClass.java" public class com.monian.dubbo.provider.study.generic.SubClass extends com.monian.dubbo.provider.study.generic.SuperClass<java.lang.String> minor version: 0 major version: 52 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #4 // com/monian/dubbo/provider/study/generic/SubClass super_class: #5 // com/monian/dubbo/provider/study/generic/SuperClass interfaces: 0, fields: 0, methods: 3, attributes: 2 Constant pool: #1 = Methodref #5.#23 // com/monian/dubbo/provider/study/generic/SuperClass."<init>":()V #2 = Class #24 // java/lang/String #3 = Methodref #4.#25 // com/monian/dubbo/provider/study/generic/SubClass.get:(Ljava/lang/String;)Ljava/lang/String; #4 = Class #26 // com/monian/dubbo/provider/study/generic/SubClass #5 = Class #27 // com/monian/dubbo/provider/study/generic/SuperClass #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 LocalVariableTable #11 = Utf8 this #12 = Utf8 Lcom/monian/dubbo/provider/study/generic/SubClass; #13 = Utf8 get #14 = Utf8 (Ljava/lang/String;)Ljava/lang/String; #15 = Utf8 s #16 = Utf8 Ljava/lang/String; #17 = Utf8 MethodParameters #18 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object; #19 = Utf8 Signature #20 = Utf8 Lcom/monian/dubbo/provider/study/generic/SuperClass<Ljava/lang/String;>; #21 = Utf8 SourceFile #22 = Utf8 SubClass.java #23 = NameAndType #6:#7 // "<init>":()V #24 = Utf8 java/lang/String #25 = NameAndType #13:#14 // get:(Ljava/lang/String;)Ljava/lang/String; #26 = Utf8 com/monian/dubbo/provider/study/generic/SubClass #27 = Utf8 com/monian/dubbo/provider/study/generic/SuperClass { public com.monian.dubbo.provider.study.generic.SubClass(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method com/monian/dubbo/provider/study/generic/SuperClass."<init>":()V 4: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/monian/dubbo/provider/study/generic/SubClass; public java.lang.String get(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=2, args_size=2 0: aload_1 1: areturn LineNumberTable: line 11: 0 LocalVariableTable: Start Length Slot Name Signature 0 2 0 this Lcom/monian/dubbo/provider/study/generic/SubClass; 0 2 1 s Ljava/lang/String; MethodParameters: Name Flags s public java.lang.Object get(java.lang.Object); descriptor: (Ljava/lang/Object;)Ljava/lang/Object; flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: checkcast #2 // class java/lang/String 5: invokevirtual #3 // Method get:(Ljava/lang/String;)Ljava/lang/String; 8: areturn LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcom/monian/dubbo/provider/study/generic/SubClass; MethodParameters: Name Flags s synthetic } Signature: #20 // Lcom/monian/dubbo/provider/study/generic/SuperClass<Ljava/lang/String;>; SourceFile: "SubClass.java"
可以看到字節碼中有兩個get方法,第二個方法參數和回傳值類型都是java.lang.Object 並且可以看到flags有對應標誌ACC_BRIDGE, ACC_SYNTHETIC說明此方法就是有編譯器自動產生的橋接方法。再看code屬性:
aload_0:把this變數裝載到操作數堆疊中
aload_1:把方法變數s裝載到操作數棧中
checkcast # 2:校驗棧頂變數s是否為java.lang.String類型
invokevirtual # 3: 呼叫方法public String get(String s)
areturn: 傳回結果
根據上述code解釋可以看出編譯器產生的橋接方法為這個樣子的,橋接方法實際上調用了實際的泛型方法
public String get(String s) { return s; } #桥接方法 public Object get(Object s) { return get((String) s); }
泛型-類型擦除
public class SubClass extends SuperClass<String> { @Override public String get(String s) { return s; } public static void main(String[] args) { SuperClass subClass = new SubClass(); Object s = "hello world"; System.out.println(subClass.get(s)); } }
java的泛型在運行時會進行泛型擦除替換成非泛型上邊界,java虛擬機無法知道準確的類型。上述程式碼能編譯通過並且會呼叫子類別SubClass的橋接方法由橋接方法再去呼叫實際泛型方法。如果定義為SuperClassf7e83be87db5cd2d9a8a0b8117b38cd4 subClass = new SubClass();
那麼get方法入參只能為String變量,因為編譯器在編譯期間會進行類型校驗,不符合型別將直接報編譯失敗。
{ public com.monian.dubbo.provider.study.generic.SuperClass(); descriptor: ()V flags: (0x0001) 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 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/monian/dubbo/provider/study/generic/SuperClass; LocalVariableTypeTable: Start Length Slot Name Signature 0 5 0 this Lcom/monian/dubbo/provider/study/generic/SuperClass<TT;>; public abstract T get(T); descriptor: (Ljava/lang/Object;)Ljava/lang/Object; flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT MethodParameters: Name Flags t Signature: #18 // (TT;)TT; }
為了能夠正確的編譯,可以看到原始碼中父類別SuperClass get方法參數類型為T(T t),而在字節碼層面可以看到,經過編譯後,get方法入參和傳回值型別都為Object。
可以想像一下,如果沒有編譯器自動產生的橋接方法,那麼編譯是不會通過的。父類別SubClass get方法經過編譯後入參和傳回值類型都是Object,而子類別get方法入參和回傳值類型為String,子類別並沒有重寫父類別的get方法(重寫:存取的方法的實現過程進行重新編寫, 回傳值和形參都不能改變)。所有編譯器需要產生一個橋接方法,Object get(Object) 就可以編譯通過了。
主要藉助Spring的BridgeMethodResolver#findBridgedMethod找到被橋接的方法,原理是先找到類別宣告的所有方法,找到與橋接方法簡單名稱和方法參數數量相同的候選方法,若只要一個則直接返回,若有多個則循環判斷方法參數類型是否相同或者候選方法都有相同的方法簽名則從其中任選一個方法作為被橋接的方法。
@Slf4j public class SubClass extends SuperClass<String> { @Override public String get(String s) { return s; } public static void main(String[] args) throws Exception { SubClass subClass = new SubClass(); Method bridgeMethod = subClass.getClass().getDeclaredMethod("get", Object.class); log.info("bridgeMethod is bridge:" + bridgeMethod.isBridge()); log.info("bridgeMethod:" + bridgeMethod.toString()); // 实际泛型方法 Method actualMethod = subClass.getClass().getDeclaredMethod("get", String.class); log.info("actualMethod:" + actualMethod.toString()); // 通过spring #BridgeMethodResolver由桥接方法获取到实际泛型方法 Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(bridgeMethod); log.info("bridgedMethod:" + bridgedMethod.toString()); } }
輸出如下:
#以上是Java橋接方法怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!