java 桥接方法(java 桥接模式 实际应用)

  本篇文章为你整理了java 桥接方法(java 桥接模式 实际应用)的详细内容,包含有java桥接模式举例 java 桥接模式 实际应用 java桥接模式的应用场景 java桥梁模式 java 桥接方法,希望能帮助你了解 java 桥接方法。

  1.桥接方法简介

  桥接方法是jdk1.5引入泛型后,为使java泛型方法生成的字节码与jdk1.5版本之前的字节码兼容由编译器自动生成的。

  可用method.isBridge() 判断method是否是桥接方法,在生成的字节码中会有flags标记 ACC_BRIDGE, ACC_SYNTHETIC ,根据来自深入理解java虚拟机的一张访问标志图可以看到 ACC_BRIDGE表示方法是由编译器产生的桥接方法,ACC_SYNTHETIC表示方法由编译器自动产生不属于源码。

  2. 什么时候会生成桥接方法

  当子类继承父类(继承接口)实现抽象泛型方法的时候,编译器会为子类自动生成桥接方法

  

#父类

 

  public abstract class SuperClass T {

   public abstract T get(T t) ;

  
使用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

   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的桥接方法由桥接方法再去调用实际泛型方法。如果定义为SuperClass String 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

   public abstract T get(T);

   descriptor: (Ljava/lang/Object;)Ljava/lang/Object;

   flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT

   MethodParameters:

   Name Flags

   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 桥接模式 实际应用)的详细内容,想要了解更多 java 桥接方法的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: