一文搞懂Java橋接方法
1.橋接方法簡(jiǎn)介
橋接方法是jdk1.5引入泛型后,為使java泛型方法生成的字節(jié)碼與jdk1.5版本之前的字節(jié)碼兼容由編譯器自動(dòng)生成的。
可用method.isBridge()
判斷method是否是橋接方法,在生成的字節(jié)碼中會(huì)有flags標(biāo)記 ACC_BRIDGE, ACC_SYNTHETIC ,根據(jù)來自深入理解java虛擬機(jī)的一張?jiān)L問標(biāo)志圖可以看到 ACC_BRIDGE表示方法是由編譯器產(chǎn)生的橋接方法,ACC_SYNTHETIC表示方法由編譯器自動(dòng)產(chǎn)生不屬于源碼。
2. 什么時(shí)候會(huì)生成橋接方法
當(dāng)子類繼承父類(繼承接口)實(shí)現(xiàn)抽象泛型方法的時(shí)候,編譯器會(huì)為子類自動(dòng)生成橋接方法
#父類 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的字節(jié)碼:
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"
可以看到字節(jié)碼中有兩個(gè)get方法,第二個(gè)方法參數(shù)和返回值類型都是java.lang.Object 并且可以看到flags有相應(yīng)標(biāo)志ACC_BRIDGE, ACC_SYNTHETIC說明此方法就是有編譯器自動(dòng)生成的橋接方法。再看code屬性:
aload_0:把this變量裝載到操作數(shù)棧中
aload_1:把方法變量s裝載到操作數(shù)棧中
checkcast # 2:校驗(yàn)棧頂變量s是否為java.lang.String類型
invokevirtual # 3: 調(diào)用方法 public String get(String s)
areturn: 返回結(jié)果
根據(jù)上述code解釋可以看出編譯器生成的橋接方法為這個(gè)樣子的,橋接方法實(shí)際上調(diào)用了實(shí)際的泛型方法
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的泛型在運(yùn)行時(shí)會(huì)進(jìn)行泛型擦除替換成非泛型上邊界,java虛擬機(jī)無法知道準(zhǔn)確的類型。 上述代碼能編譯通過并且會(huì)調(diào)用子類SubClass的橋接方法由橋接方法再去調(diào)用實(shí)際泛型方法。如果定義為SuperClass<String> subClass = new SubClass();
那么get方法入?yún)⒅荒転镾tring變量,因?yàn)榫幾g器在編譯期間會(huì)進(jìn)行類型校驗(yàn),不符合類型將直接報(bào)編譯失敗。
3. 為什么生成泛型方法
{ 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方法參數(shù)類型為T(T t),而在字節(jié)碼層面可以看到,經(jīng)過編譯后,get方法入?yún)⒑头祷刂殿愋投紴镺bject。
可以想象一下,如果沒有編譯器自動(dòng)生成的橋接方法,那么編譯是不會(huì)通過的。父類SubClass get方法經(jīng)過編譯后入?yún)⒑头祷刂殿愋投紴镺bject,而子類get方法入?yún)⒑头祷刂殿愋蜑镾tring,子類并沒有重寫父類的get方法(重寫:訪問的方法的實(shí)現(xiàn)過程進(jìn)行重新編寫, 返回值和形參都不能改變)。所有編譯器需要生成一個(gè)橋接方法,Object get(Object) 就可以編譯通過了。
4. 根據(jù)橋接方法獲取實(shí)際泛型方法
主要借助Spring的BridgeMethodResolver#findBridgedMethod找到被橋接的方法,原理是首先找到類聲明的所有方法,找到與橋接方法簡(jiǎn)單名稱和方法參數(shù)數(shù)量相同的候選方法,若只要一個(gè)則直接返回,若有多個(gè)則循環(huán)判斷方法參數(shù)類型是否相同或者候選方法都有相同的方法簽名則從其中任選一個(gè)方法作為被橋接的方法。
@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()); // 實(shí)際泛型方法 Method actualMethod = subClass.getClass().getDeclaredMethod("get", String.class); log.info("actualMethod:" + actualMethod.toString()); // 通過spring #BridgeMethodResolver由橋接方法獲取到實(shí)際泛型方法 Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(bridgeMethod); log.info("bridgedMethod:" + bridgedMethod.toString()); } }
輸出如下:
以上就是一文搞懂Java橋接方法的詳細(xì)內(nèi)容,更多關(guān)于Java橋接方法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Springboot @Cacheable 注解(指定緩存位置)
這篇文章主要介紹了詳解Springboot @Cacheable 注解(指定緩存位置),使用? @Cacheable ?注解就可以將運(yùn)行結(jié)果緩存,以后查詢相同的數(shù)據(jù),直接從緩存中取,不需要調(diào)用方法,需要的朋友可以參考下2023-09-09Spring Boot報(bào)錯(cuò):No session repository could be auto-configured
這篇文章主要給大家介紹了關(guān)于Spring Boot報(bào)錯(cuò):No session repository could be auto-configured, check your configuration的解決方法,文中給出了詳細(xì)的解決方法,對(duì)遇到這個(gè)問題的朋友們具有一定參考價(jià)值,需要的朋友下面來一起看看吧。2017-07-07關(guān)于SpringBoot整合redis使用Lettuce客戶端超時(shí)問題
使用到Lettuce連接redis,一段時(shí)間后不操作,再去操作redis,會(huì)報(bào)連接超時(shí)錯(cuò)誤,在其重連后又可使用,糾結(jié)是什么原因?qū)е碌哪兀旅嫘【幗o大家?guī)砹薙pringBoot整合redis使用Lettuce客戶端超時(shí)問題及解決方案,一起看看吧2021-08-08SpringBoot整合TomCat實(shí)現(xiàn)本地圖片服務(wù)器代碼解析
這篇文章主要介紹了SpringBoot整合TomCat實(shí)現(xiàn)本地圖片服務(wù)器代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08