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表示方法由编译器自动产生不属于源码。
当子类继承父类(继承接口)实现抽象泛型方法的时候,编译器会为子类自动生成桥接方法
#父类 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();
{ 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()); } }
checkcast #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 commeSuperClassf7e83be87db5cd2d9a8a0b8117b38cd4 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!