3分钟快速搞懂Java的桥接方法
什么是桥接方法?
Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法。
我们可以通过Method
类的isBridge
方法来判断一个方法是否是桥接方法。
在字节码文件中,桥接方法会被标记为ACC_BRIDGE
和ACC_SYNTHETIC
,其中ACC_BRIDGE
用于表示该方法是由编译器产生的桥接方法,ACC_SYNTHETIC
用于表示该方法是由编译器自动生成。
文章持续更新,微信搜索「万猫学社第一时间阅读,关注后回复「电子书」,免费获取12本Java必读技术书籍。
什么时候生成桥接方法?
为了实现哪些Java语言特性会生成桥接方法?最常见的两种情况就是协变返回值类型和类型擦除,因为它们导致了父类方法的参数和实际调用的方法参数类型不一致。下面我们通过两个例子更好地理解一下。
文章持续更新,微信搜索「万猫学社第一时间阅读,关注后回复「电子书」,免费获取12本Java必读技术书籍。
协变返回类型
协变返回类型是指子类方法的返回值类型不必严格等同于父类中被重写的方法的返回值类型,而可以是更 "具体" 的类型。
在Java 1.5添加了对协变返回类型的支持,即子类重写父类方法时,返回的类型可以是子类方法返回类型的子类。下面看一个例子:
public class Parent {
Number get() {
return 1;
}
}
public class Child extends Parent {
@Override
Integer get() {
return 1;
}
}
Child类重写其父类Parent的get方法,Parent的get方法返回类型为Number,而Child类中get方法返回类型为Integer。
将这段代码进行编译,再反编译:
javac Child.java
javap -v -c Child.class
文章持续更新,微信搜索「万猫学社第一时间阅读,关注后回复「电子书」,免费获取12本Java必读技术书籍。
结果如下:
public class Child extends Parent
......省略部分结果......
java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
flags:
Code:
stack=1, locals=1, args_size=1
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 5: 0
java.lang.Number get();
descriptor: ()Ljava/lang/Number;
flags: ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #3 // Method get:()Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 1: 0
从上面的结果可以看到,有一个方法java.lang.Number get(), 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGE
和ACC_SYNTHETIC
,就是我们前面所说的桥接方法。
这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual
指令再调用方法java.lang.Integer get()。
编译器这么做的原因是什么呢?因为在JVM方法中,返回类型也是方法签名的一部分,而桥接方法的签名和其父类的方法签名一致,以此就实现了协变返回值类型。
文章持续更新,微信搜索「万猫学社第一时间阅读,关注后回复「电子书」,免费获取12本Java必读技术书籍。
类型擦除
泛型是Java 1.5才引进的概念,在这之前是没有泛型的概念的,但泛型代码能够很好地和之前版本的代码很好地兼容,这是为什么呢?
这是因为,在编译期间Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,这就叫做类型擦除。
当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法,例如:
public class Parent<T> {
void set(T t) {
}
}
public class Child extends Parent<String> {
@Override
void set(String str) {
}
}
文章持续更新,微信搜索「万猫学社第一时间阅读,关注后回复「电子书」,免费获取12本Java必读技术书籍。
Child类在继承其父类Parent的泛型方法时,明确指定了泛型类型为String,将这段代码进行编译,再反编译:
public class Child extends Parent<java.lang.String>
......省略部分结果......
void set(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags:
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 5: 0
void set(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: 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 set:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
从上面的结果可以看到,有一个方法void set(java.lang.Object), 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGE
和ACC_SYNTHETIC
,就是我们前面所说的桥接方法。
这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual
指令再调用方法void set(java.lang.String)。
编译器这么做的原因是什么呢?因为Parent类在类型擦除之后,变成这样:
public class Parent<Object> {
void set(Object t) {
}
}
编译器为了让子类有一个与父类的方法签名一致的方法,就在子类自动生成一个与父类的方法签名一致的桥接方法。
文章持续更新,微信搜索「万猫学社第一时间阅读,关注后回复「电子书」,免费获取12本Java必读技术书籍。
如何查找桥接方法的实际方法
在Spring Framework中已经实现了查找桥接方法的实际方法的功能,就在spring-core模块中的BridgeMethodResolver类中,像这样直接使用就行了:
method = BridgeMethodResolver.findBridgedMethod(method);
findBridgedMethod方法是怎么实现的呢?我们来分析一下源码(spring-core的版本为5.2.8.RELEASE):
public static Method findBridgedMethod(Method bridgeMethod) {
// 如果不是桥连方法,就直接返回原方法。
if (!bridgeMethod.isBridge()) {
return bridgeMethod;
}
// 先从本地缓存读取,缓存中有则直接返回。
Method bridgedMethod = cache.get(bridgeMethod);
if (bridgedMethod == null) {
List<Method> candidateMethods = new ArrayList<>();
// 以方法名称和入参个数相等为筛选条件。
MethodFilter filter = candidateMethod ->
isBridgedCandidateFor(candidateMethod, bridgeMethod);
// 递归该类及其所有父类上的所有方法,符合筛选条件就添加进来。
ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass()
, candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) {
// 如果符合筛选条件的方法个数为1,则直接采用;
// 否则,调用searchCandidates方法再次筛选。
bridgedMethod = candidateMethods.size() == 1 ?
candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod);
}
// 如果找不到实际方法,则返回原来的桥连方法。
if (bridgedMethod == null) {
// A bridge method was passed in but we couldn't find the bridged method.
// Let's proceed with the passed-in method and hope for the best...
bridgedMethod = bridgeMethod;
}
// 把查找的结果放入内存缓存。
cache.put(bridgeMethod, bridgedMethod);
}
return bridgedMethod;
}
文章持续更新,微信搜索「万猫学社第一时间阅读,关注后回复「电子书」,免费获取12本Java必读技术书籍。
我们再看一下再次筛选的searchCandidates方法是如何实现的:
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
if (candidateMethods.isEmpty()) {
return null;
}
Method previousMethod = null;
boolean sameSig = true;
// 遍历候选方法的列表
for (Method candidateMethod : candidateMethods) {
// 对比桥接方法的泛型类型参数和候选方法是否匹配,如果匹配则直接返回该候选方法。
if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
return candidateMethod;
}
else if (previousMethod != null) {
// 如果不匹配,则判断所有候选方法的参数列表是否相等。
sameSig = sameSig && Arrays.equals(candidateMethod.getGenericParameterTypes()
, previousMethod.getGenericParameterTypes());
}
previousMethod = candidateMethod;
}
// 如果所有候选方法的参数列表全相等,则返回第一个候选方法。
return (sameSig ? candidateMethods.get(0) : null);
}
总结以上源码就是,通过判断方法名、参数的个数以及泛型类型参数来获取桥接方法的实际方法。
微信公众号:万猫学社
微信扫描二维码
关注后回复「电子书」
获取12本Java必读技术书籍
3分钟快速搞懂Java的桥接方法的更多相关文章
- 一文彻底搞懂Java中的环境变量
一文搞懂Java环境变量 记得刚接触Java,第一件事就是配环境变量,作为一个初学者,只知道环境变量怎样配,在加上各种IDE使我们能方便的开发,而忽略了其本质的东西,只知其然不知其所以然,随着不断的深 ...
- 【Java】15分钟快速体验阿里Java诊断工具Arthas
[墙裂推荐]15分钟快速体验阿里Java诊断工具Arthas : https://alibaba.github.io/arthas/arthas-tutorials?language=cn&i ...
- [转帖]五分钟彻底搞懂你一直没明白的Linux内存管理
五分钟彻底搞懂你一直没明白的Linux内存管理 https://cloud.tencent.com/developer/article/1462476 现在的服务器大部分都是运行在Linux上面的,所 ...
- 一文教你快速搞懂速度曲线规划之S形曲线(超详细+图文+推导+附件代码)
本文介绍了运动控制终的S曲线,通过matlab和C语言实现并进行仿真:本文篇幅较长,请自备茶水: 请帮忙点个赞
- 一文搞懂Java引用拷贝、浅拷贝、深拷贝
微信搜一搜 「bigsai」 专注于Java和数据结构与算法的铁铁 文章收录在github/bigsai-algorithm 在开发.刷题.面试中,我们可能会遇到将一个对象的属性赋值到另一个对象的情况 ...
- 一文搞懂Java引用拷贝、深拷贝、浅拷贝
刷题.面试中,我们可能会遇到将一个对象的属性赋值到另一个对象的情况,这种情况就叫做拷贝.拷贝与Java内存结构息息相关,搞懂Java深浅拷贝是很必要的! 在对象的拷贝中,很多初学者可能搞不清到底是拷贝 ...
- 轻松搞懂Java中的自旋锁
前言 在之前的文章<一文彻底搞懂面试中常问的各种“锁”>中介绍了Java中的各种“锁”,可能对于不是很了解这些概念的同学来说会觉得有点绕,所以我决定拆分出来,逐步详细的介绍一下这些锁的来龙 ...
- [转]快速搞懂Gson的用法
原文地址:http://coladesign.cn/fast-understand-the-usage-of-gson/ 谷歌gson这个Java类库可以把Java对象转换成JSON,也可以把JSON ...
- JavaScript彻底搞懂apply和call方法
彻底搞懂JavaScript中的apply和call方法 call和apply都是为了改变某个函数运行的context上下文而存在的,即为了改变函数体内部this的指向.因为JavaScript的函数 ...
随机推荐
- 网页中Office和pdf相关文件导出
最近被派去维护和开发一些做了一半.年久失修的项目.有一部分内容是关于word文件导出,顺带着把excel.pdf文件的导出也调研下吧,我想未来开发我应该会遇到的,遂做了下笔记分享给需要的人. 由于项目 ...
- oss文件上传删除(批量删除)处理
博主用的是阿里云的oss 首先先在阿里云下载安装sdk,相关的sdk下载请自行到阿里云下载 文档地址 https://help.aliyun.com/document_detail/85580.h ...
- 历时两年零三个月,从刚毕业的外包到现在的阿里P7offer,我只做了这几件事
前言 最近,金九银十在即,很多人都在准备面试,特别给大家总结了 Java 程序员面试必备题,这份面试清单是我从 去年开始收集的,一方面是给公司招聘用,另一方面是想用它来挖掘我在 Java 技术栈中的技 ...
- Elements-of-Python01_Input/Output
(内容包括输入input,输出print,注释comment,预测类型转换eval,命名与赋值Name & Bestow) 输入Input 和 输出Print Python中利用input() ...
- 标准库之collections
collections 模块----Python标准库,是数据结构常用模块 常用类型有: 计数器(Counter) dict的子类,计算可hash的对象: 双端队列(deque) 类似于list ...
- 免费在线使用Ayoa:让思维导图更简单
在学习和工作中,我们都会遇到需要使用思维导图的情况,导图可以很好地帮助我们处理工作,完成记录和分享,是一个非常便捷的工具.今天小编就想和大家分享一款非常简单好用的思维导图软件,Ayoa. 之所以要将它 ...
- 手把手为大家演示fat32转ntfs操作过程,一看就会
fat32和ntfs是两种我们较为常见的u盘或者硬盘格式.它们都有着各自的特点,但是相比之下,使用ntfs文件格式我们可以做出很多fat32不能实现的功能.在日常生活中,我们会面临到需要把fat32转 ...
- Linux中redis服务开启
集群模式设置为no 就可以开启服务 cluster-enable no
- 三. Vue组件化
1. 认识组件化 1.1 什么是组件化 人面对复杂问题的处理方式 任何一个人处理信息的逻辑能力都是有限的,所以当面对一个非常复杂的问题时我们不太可能一次性搞定一大堆的内容. 但是我们人有一种天生的能力 ...
- 异步任务-----django-celery
异步任务 ---- django-celery 大家的知道celery的使用,网上也有很多的教程.因为最近在使用django来完成项目,无意间发现发现有个东西叫django-celery,比cel ...