Maison  >  Article  >  Java  >  Comment utiliser la méthode de pontage Java

Comment utiliser la méthode de pontage Java

王林
王林avant
2023-04-20 13:07:111049parcourir

1. Introduction à la méthode bridge

La méthode bridge est automatiquement générée par le compilateur après l'introduction des génériques dans jdk1.5, afin de rendre le bytecode généré par la méthode générique java compatible avec le bytecode avant jdk1.5.

Vous pouvez utiliser method.isBridge() pour déterminer si la méthode est une méthode de pont. Il y aura des indicateurs marqués ACC_BRIDGE, ACC_SYNTHETIC dans le bytecode généré, sur la base d'une carte d'indicateurs d'accès d'En profondeur. Compréhension des machines virtuelles Java Vous pouvez voir que la méthode de représentation ACC_BRIDGE est une méthode de pont générée par le compilateur et que la méthode de représentation ACC_SYNTHETIC est automatiquement générée par le compilateur et n'appartient pas au code source. method.isBridge()判断method是否是桥接方法,在生成的字节码中会有flags标记 ACC_BRIDGE, ACC_SYNTHETIC ,根据来自深入理解java虚拟机的一张访问标志图可以看到 ACC_BRIDGE表示方法是由编译器产生的桥接方法,ACC_SYNTHETIC表示方法由编译器自动产生不属于源码。

Comment utiliser la méthode de pontage 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();

Comment utiliser la méthode de pontage Java

2. Quand la méthode bridge sera-t-elle générée

Quand la classe enfant hérite du parent Lorsqu'une classe (interface héritée) implémente une méthode générique abstraite, le compilateur génère automatiquement une méthode pont pour la sous-classe

{
  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;
}

Utilisez la commande javap -v SubClass.class pour afficher le bytecode de la classe SubClass :

@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());
  }
}

Vous pouvez voir qu'il existe deux méthodes get dans le bytecode. Le deuxième paramètre de méthode et le type de valeur de retour sont tous deux java.lang.Object. Vous pouvez également voir que les indicateurs ont les indicateurs correspondants ACC_BRIDGE et ACC_SYNTHETIC. , indiquant que cette méthode est automatiquement compilée par la méthode du pont généré. Regardez à nouveau l'attribut de code :

aload_0 : Chargez cette variable dans la pile d'opérandes

aload_1 : Chargez la variable de méthode s dans la pile d'opérandes

Comment utiliser la méthode de pontage Javacheckcast #2 : Vérifiez si la variable s en haut de la pile est java .lang. String type

🎜invokevirtual # 3 : Appelez la méthode public String get(String s)🎜🎜areturn : Renvoie le résultat 🎜🎜D'après l'explication du code ci-dessus, nous pouvons voir que la méthode bridge générée par le compilateur ressemble à ceci. . La méthode bridge appelle en fait la méthode générique 🎜rrreee🎜Effacement de type générique🎜rrreee🎜Les génériques de Java seront effacés de manière générique et remplacés par une limite supérieure non générique au moment de l'exécution, et la machine virtuelle Java ne peut pas connaître le type exact. Le code ci-dessus peut être compilé et appellera la méthode bridge de la sous-classe SubClass. La méthode bridge appelle ensuite la méthode générique réelle. S'il est défini comme SuperClassf7e83be87db5cd2d9a8a0b8117b38cd4 subClass = new SubClass();, alors le paramètre d'entrée de la méthode get ne peut être qu'une variable String, car le compilateur effectuera une vérification de type lors de la compilation, et les types non conformes seront signalés directement. La compilation a échoué. 🎜🎜3. Pourquoi générer des méthodes génériques🎜rrreee🎜Afin de compiler correctement, vous pouvez voir que le type de paramètre de méthode d'obtention de la classe parent SuperClass dans le code source est T (T t), et au niveau du bytecode, vous pouvez le voir après compilation , les paramètres d'entrée et les types de valeurs de retour de la méthode get sont tous deux Object. 🎜🎜Vous pouvez imaginer que s'il n'y a pas de méthode de pontage générée automatiquement par le compilateur, alors la compilation ne réussira pas. Après la compilation, les paramètres d'entrée de la méthode get de la classe parent SubClass et le type de valeur de retour sont Object, tandis que les paramètres d'entrée de la méthode get de la sous-classe et le type de valeur de retour sont String, la sous-classe ne remplace pas la méthode get de la classe parent (réécriture : méthode d'accès The le processus de mise en œuvre doit être réécrit et la valeur de retour et les paramètres formels ne peuvent pas être modifiés). Tous les compilateurs doivent générer une méthode bridge, et Object get(Object) peut être compilé et transmis. 🎜🎜4. Obtenez la méthode générique réelle basée sur la méthode bridge 🎜🎜 Utilisez principalement BridgeMethodResolver#findBridgedMethod de Spring pour trouver la méthode pontée. Le principe est de rechercher d'abord toutes les méthodes déclarées par la classe et de trouver les candidats avec le même nom simple et. nombre de paramètres de méthode comme méthode de pont. S'il n'y a qu'une seule méthode, elle sera renvoyée directement. S'il y a plusieurs méthodes, elle effectuera une boucle pour déterminer si les types de paramètres de méthode sont les mêmes ou si les méthodes candidates ont la même signature de méthode. , puis l'un d'entre eux sera sélectionné comme méthode pontée. 🎜rrreee🎜Le résultat est le suivant :🎜🎜🎜🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer