首頁  >  文章  >  Java  >  Java橋接方法怎麼使用

Java橋接方法怎麼使用

王林
王林轉載
2023-04-20 13:07:111050瀏覽

1.橋接方法簡介

橋接方法是jdk1.5引入泛型後,為使java泛型方法產生的字節碼與jdk1.5版本之前的字節碼相容由編譯器自動產生的。

可用method.isBridge()判斷method是否是橋接方法,在產生的字節碼中會有flags標記ACC_BRIDGE, ACC_SYNTHETIC ,根據來自深入理解java虛擬機的一張存取標誌圖可以看到ACC_BRIDGE表示方法是由編譯器產生的橋接方法,ACC_SYNTHETIC表示方法由編譯器自動產生不屬於原始碼。

Java橋接方法怎麼使用

2. 何時會產生橋接方法

當子類別繼承父類別(繼承介面)實作抽象泛型方法的時候,編譯器會為子類別自動產生橋接方法

#父类
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變量,因為編譯器在編譯期間會進行類型校驗,不符合型別將直接報編譯失敗。

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方法參數類型為T(T t),而在字節碼層面可以看到,經過編譯後,get方法入參和傳回值型別都為Object。

可以想像一下,如果沒有編譯器自動產生的橋接方法,那麼編譯是不會通過的。父類別SubClass get方法經過編譯後入參和傳回值類型都是Object,而子類別get方法入參和回傳值類型為String,子類別並沒有重寫父類別的get方法(重寫:存取的方法的實現過程進行重新編寫, 回傳值和形參都不能改變)。所有編譯器需要產生一個橋接方法,Object get(Object) 就可以編譯通過了。

4. 根據橋接方法取得實際泛型方法 

主要藉助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橋接方法怎麼使用

#

以上是Java橋接方法怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除