Java桥接方法怎么使用

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 {  public abstract T get(T t) ;}#子类public class SubClass extends SuperClass {  @Override  public String get(String s) {    return s;  }}

登录后复制

使用javap -v SubClass.class命令查看类SubClass的字节码:

立即学习“Java免费学习笔记(深入)”;

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  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: 2Constant pool:   #1 = Methodref          #5.#23         // com/monian/dubbo/provider/study/generic/SuperClass."":()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                  #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;  #21 = Utf8               SourceFile  #22 = Utf8               SubClass.java  #23 = NameAndType        #6:#7          // "":()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."":()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;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 {  @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 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."":()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      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找到被桥接的方法,原理是首先找到类声明的所有方法,找到与桥接方法简单名称和方法参数数量相同的候选方法,若只要一个则直接返回,若有多个则循环判断方法参数类型是否相同或者候选方法都有相同的方法签名则从其中任选一个方法作为被桥接的方法。

@Slf4jpublic class SubClass extends SuperClass {  @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桥接方法怎么使用的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2627532.html

(0)
上一篇 2025年3月7日 01:06:47
下一篇 2025年2月26日 11:30:27

AD推荐 黄金广告位招租... 更多推荐

相关推荐

发表回复

登录后才能评论