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) ;
} #子类
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的桥接方法由桥接方法再去调用实际泛型方法。如果定义为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<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;
}

为了能够正确的编译,可以看到源码中父类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 桥接方法的更多相关文章

  1. 初识Java泛型以及桥接方法

    泛型的由来 在编写程序时,可能会有这样的需求:容器类,比如java中常见的list等.为了使容器可以保存多种类型的数据,需要编写多种容器类,每一个容器类中规定好了可以操作的数据类型.此时可能会有Int ...

  2. Java系列:关于Java中的桥接方法

    这两天在看<Java核心技术 卷1>的泛型相关章节,其中说到了在泛型子类中override父类的泛型方法时,编译器会自动生成一个桥接方法,这块有点看不明白. 书上的例子代码如下: publ ...

  3. 【java编程】java中什么是bridge method(桥接方法)

    https://blog.csdn.net/mhmyqn/article/details/47342577 https://www.cnblogs.com/strinkbug/p/5019453.ht ...

  4. java反射的补充:桥接方法以及Spring中一些工具类

    在上一篇博文中:http://www.cnblogs.com/guangshan/p/4660564.html 源码中有些地方用到了 this.bridgedMethod = BridgeMethod ...

  5. Java反射中method.isBridge() 桥接方法

    桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法.我们可以通过Method.isBridge()方法来判断一个方法是 ...

  6. java中什么是bridge method(桥接方法)

    java中什么是bridge method(桥接方法) https://blog.csdn.net/z69183787/article/details/81115524

  7. Java基础之Bridge method(桥接方法)

    1.什么是桥接方法 桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法. 判断方法 我们可以通过 Method.isB ...

  8. 3分钟快速搞懂Java的桥接方法

    什么是桥接方法? Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法. 我们可以通过Method类的isBridge方法来判断一个方法是否是桥 ...

  9. java中的桥接方法

    本文转载自java中什么是bridge method(桥接方法) 导语 在看spring-mvc的源码的时候,看到在解析handler方法时,有关于获取桥接方法代码,不明白什么是桥接方法,经过查找资料 ...

随机推荐

  1. Redis 为何使用近似 LRU 算法淘汰数据,而不是真实 LRU?

    在<Redis 数据缓存满了怎么办?>我们知道 Redis 缓存满了之后能通过淘汰策略删除数据腾出空间给新数据. 淘汰策略如下所示: 设置过期时间的 key volatile-ttl.vo ...

  2. 被迫开始学习Typescript —— class

    TS 的 class 看起来和 ES6 的 Class 有点像,基本上差别不大,除了 可以继承(实现)接口.私有成员.只读等之外. 参考:https://typescript.bootcss.com/ ...

  3. VMWare中CentOS安装VM-Tools

    查看CD-ROM驱动器的设备信息 可以通过下面几个命令来查看 dmesg命令 dmesg | egrep -i --color 'cdrom|dvd|cd/rw|writer' /proc/sys/d ...

  4. SpringBoot从Eclipse添加的Tomcat容器中启动

    SpringBoot的Web项目,想要在Eclipse中的Tomcat容器中启动运行需要做下面这两处改动 pom.xml <packaging>war</packaging> ...

  5. linux篇-linux mysql数据库定时备份

    1在linux上面创建一个文件夹,并且进行备份 cd /home mkdir backup cd backup 2创建一个脚本 Vi imaginebase.sh #!/bin/bash mysqld ...

  6. HIVE 数据分析

    题目要求: 具体操作: ①hive路径下建表:sale create table sale (day_id String, sale_nbr String, buy_nbr String, cnt S ...

  7. find-文件搜索

    根据条件搜索文件. 语法 find 目录 [选项] 选项 -name <filename> 按文件目录名查找.文件目录名可使用通配符"*",即表示模糊匹配所有字符. - ...

  8. Mac下iTerm2安装rzsz后上传下载失败解决

    背景描述 mac环境,安装了iTerm2,需要使用ssh登陆linux服务器.服务器登陆需要经过以下步骤 输入token 输入登陆选项 输入IP 因此写了expect脚本来完成自动输入 但是在上传下载 ...

  9. Halodoc使用 Apache Hudi 构建 Lakehouse的关键经验

    Halodoc 数据工程已经从传统的数据平台 1.0 发展到使用 LakeHouse 架构的现代数据平台 2.0 的改造.在我们之前的博客中,我们提到了我们如何在 Halodoc 实施 Lakehou ...

  10. 漏洞修复之Oracle系列

    Oracle 11g CVE-2012-1675(远程投毒)漏洞修复. 数据库版本 Oracle 11g 11.2.0.4.0非RAC 漏洞编号 CVE-2012-1675 漏洞介绍 Oracle允许 ...