Die Bridge-Methode folgt der Einführung von Generika in jdk1.5, um den von der generischen Java-Methode generierten Bytecode mit dem vorherigen Bytecode kompatibel zu machen jdk1.5 Automatisch vom Compiler generiert.
Sie können method.isBridge()
verwenden, um festzustellen, ob es sich bei der Methode um eine Bridge-Methode handelt. Im generierten Bytecode sind Flags mit der Bezeichnung ACC_BRIDGE und ACC_SYNTHETIC vorhanden - Tiefes Verständnis der Java Virtual Machine. Aus einem Zugriffsflagdiagramm können Sie ersehen, dass die ACC_BRIDGE-Darstellungsmethode eine vom Compiler generierte Bridge-Methode ist und die ACC_SYNTHETIC-Darstellungsmethode automatisch vom Compiler generiert wird und nicht zum Quellcode gehört . 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; }# 🎜🎜# Verwenden Sie den Befehl
javap -v SubClass.class
, um den Bytecode der SubClass-Klasse anzuzeigen: @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()); } }
aload_0: Laden Sie diese Variable in den Operandenstapel
aload_1: Laden Sie die Methodenvariable s in den Operandenstapel
#🎜 🎜 #checkcast # 2: Überprüfen Sie, ob die Variable s oben im Stapel vom Typ java.lang.String istinvokevirtual # 3: Rufen Sie die Methode public auf String get(String s)
#🎜🎜#areturn : Ergebnis zurückgeben #🎜🎜##🎜🎜#Gemäß der obigen Codeerklärung ist ersichtlich, dass die vom Compiler generierte Bridge-Methode tatsächlich die eigentliche generische Methode #🎜 aufruft 🎜#rrreee#🎜🎜#Pan Type-type erasure#🎜🎜#rrreee#🎜🎜#Java-Generika werden zur Laufzeit generisch gelöscht und durch nicht generische Obergrenzen ersetzt, und die Java Virtual Machine kann den genauen Typ nicht kennen. Der obige Code kann kompiliert werden und ruft die Bridge-Methode der Unterklasse SubClass auf. Anschließend ruft die Bridge-Methode die eigentliche generische Methode auf. Wenn es alsSuperClassf7e83be87db5cd2d9a8a0b8117b38cd4 subClass = new SubClass();
definiert ist, kann der Eingabeparameter der get-Methode nur eine String-Variable sein, da der Compiler während der Kompilierung eine Typprüfung durchführt Nicht konforme Typen werden direkt gemeldet. #🎜🎜##🎜🎜#3. Warum generische Methoden generieren? ) und Auf Bytecode-Ebene können wir sehen, dass nach der Kompilierung sowohl der Eingabe- als auch der Rückgabewerttyp der get-Methode Object sind. #🎜🎜##🎜🎜#Sie können sich vorstellen, dass die Kompilierung nicht erfolgreich ist, wenn keine vom Compiler automatisch generierte Überbrückungsmethode vorhanden ist. Nach der Kompilierung sind die Eingabeparameter der Unterklasse für die Get-Methode und der Rückgabewerttyp der Unterklasse Object, während die Eingabeparameter der Unterklasse für die Get-Methode und der Rückgabewerttyp String sind. Die Unterklasse überschreibt die Get-Methode der übergeordneten Klasse nicht (umschreiben: Zugriffsmethode). Der Implementierungsprozess muss neu geschrieben werden und der Rückgabewert und die formalen Parameter können nicht geändert werden. Alle Compiler müssen eine Brückenmethode generieren, und Object get(Object) kann kompiliert und übergeben werden. #🎜🎜##🎜🎜#4. Holen Sie sich die eigentliche generische Methode basierend auf der Bridge-Methode #🎜🎜##🎜🎜#Verwenden Sie hauptsächlich Springs BridgeMethodResolver#findBridgedMethod, um die überbrückte Methode zu finden von der Klasse und finden Sie Kandidatenmethoden mit demselben einfachen Namen und der gleichen Anzahl von Methodenparametern wie die Brückenmethode. Wenn es nur eine gibt, wird sie direkt zurückgegeben. Wenn es mehr als eine gibt, wird eine Schleife durchgeführt, um zu bestimmen, ob die Die Methodenparametertypen sind gleich oder die Kandidatenmethoden haben alle die gleiche Methodensignatur. Wählen Sie dann eine davon als Methode aus. Die überbrückte Methode. #🎜🎜#rrreee#🎜🎜#Die Ausgabe ist wie folgt: #🎜🎜##🎜🎜##🎜🎜##🎜🎜#Das obige ist der detaillierte Inhalt vonSo verwenden Sie die Java-Bridging-Methode. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!