3分钟快速搞懂Java的桥接方法
什么是桥接方法?
Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法。
我们可以通过Method
类的isBridge
方法来判断一个方法是否是桥接方法。
在字节码文件中,桥接方法会被标记为ACC_BRIDGE
和ACC_SYNTHETIC
,其中ACC_BRIDGE
用于表示该方法是由编译器产生的桥接方法,ACC_SYNTHETIC
用于表示该方法是由编译器自动生成。
什么时候生成桥接方法?
为了实现哪些Java语言特性会生成桥接方法?最常见的两种情况就是协变返回值类型和类型擦除,因为它们导致了父类方法的参数和实际调用的方法参数类型不一致。下面我们通过两个例子更好地理解一下。
协变返回类型
协变返回类型是指子类方法的返回值类型不必严格等同于父类中被重写的方法的返回值类型,而可以是更 “具体” 的类型。
在Java 1.5添加了对协变返回类型的支持,即子类重写父类方法时,返回的类型可以是子类方法返回类型的子类。下面看一个例子:
public class Parent {Number get() {return 1;}
}
public class Child extends Parent {@OverrideInteger get() {return 1;}
}
Child类重写其父类Parent的get方法,Parent的get方法返回类型为Number,而Child类中get方法返回类型为Integer。
将这段代码进行编译,再反编译:
javac Child.java
javap -v -c Child.class
结果如下:
public class Child extends Parent
......省略部分结果......java.lang.Integer get();descriptor: ()Ljava/lang/Integer;flags:Code:stack=1, locals=1, args_size=10: iconst_11: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4: areturnLineNumberTable:line 5: 0java.lang.Number get();descriptor: ()Ljava/lang/Number;flags: ACC_BRIDGE, ACC_SYNTHETICCode:stack=1, locals=1, args_size=10: aload_01: invokevirtual #3 // Method get:()Ljava/lang/Integer;4: areturnLineNumberTable:line 1: 0
从上面的结果可以看到,有一个方法java.lang.Number get(), 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGE
和ACC_SYNTHETIC
,就是我们前面所说的桥接方法。
这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual
指令再调用方法java.lang.Integer get()。
**编译器这么做的原因是什么呢?**因为在JVM方法中,返回类型也是方法签名的一部分,而桥接方法的签名和其父类的方法签名一致,以此就实现了协变返回值类型。
类型擦除
泛型是Java 1.5才引进的概念,在这之前是没有泛型的概念的,但泛型代码能够很好地和之前版本的代码很好地兼容,这是为什么呢?
这是因为,在编译期间Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,这就叫做类型擦除。
当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法,例如:
public class Parent<T> {void set(T t) {}
}
public class Child extends Parent<String> {@Overridevoid set(String str) {}
}
Child类在继承其父类Parent的泛型方法时,明确指定了泛型类型为String,将这段代码进行编译,再反编译:
public class Child extends Parent<java.lang.String>
......省略部分结果......void set(java.lang.String);descriptor: (Ljava/lang/String;)Vflags:Code:stack=0, locals=2, args_size=20: returnLineNumberTable:line 5: 0void set(java.lang.Object);descriptor: (Ljava/lang/Object;)Vflags: ACC_BRIDGE, ACC_SYNTHETICCode:stack=2, locals=2, args_size=20: aload_01: aload_12: checkcast #2 // class java/lang/String5: invokevirtual #3 // Method set:(Ljava/lang/String;)V8: returnLineNumberTable: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) {}
}
编译器为了让子类有一个与父类的方法签名一致的方法,就在子类自动生成一个与父类的方法签名一致的桥接方法。
如何获取桥接方法的实际方法
在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;
}
我们再看一下再次筛选的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);
}
总结以上源码就是,通过判断方法名、参数的个数以及泛型类型参数来获取桥接方法的实际方法。
3分钟快速搞懂Java的桥接方法相关推荐
- 5分钟快速搞懂位、字节、字、字长
位(bit) 位是最基本的概念,在计算机中,由于只有逻辑0和逻辑1的存在,因此很多东西.动作.数字都要表示为一串二进制的字码例如: 1001 0000 1101等等.其中每一个逻辑0或者1便是一个位. ...
- 一文快速搞懂Kudu到底是什么
文章目录 引言 文章传送门: Kudu 介绍 背景介绍 新的硬件设备 Kudu 是什么 Kudu 应用场景 Kudu 架构 数据模型 分区策略 列式存储 整体架构 Kudu Client 交互 Kud ...
- 快速搞懂平面设计视觉思维的窍门
在这个商业氛围很浓的社会中,各种设计海报让人眼花缭乱,如何脱颖而出?需要靠设计的视觉冲击力.所以做平面设计中,要掌握好视觉设计思维,才能更胜一筹.这里给大家讲几个小窍门,让你们快速搞懂平面设计视觉思维 ...
- JVM - 结合代码示例彻底搞懂Java内存区域_对象在堆-栈-方法区(元空间)之间的关系
文章目录 Pre 示例demo 总体关系 代码示例论证 反汇编 Pre JVM - 结合代码示例彻底搞懂Java内存区域_线程栈 | 本地方法栈 | 程序计数器 中我们探讨了线程栈中的内部结构 ,大家 ...
- java开发可重用代码包工具包_[Java教程]彻底搞懂Java开发工具包(JDK)安装及环境变量配置...
[Java教程]彻底搞懂Java开发工具包(JDK)安装及环境变量配置 0 2021-01-04 04:00:04 安装并配置JDK环境变量,不但要知道怎样做,也要知道为什么这样做,知其然知其所以然. ...
- 5张图搞懂Java深浅拷贝
微信搜一搜 「bigsai」 关注这个专注于Java和数据结构与算法的铁铁 文章收录在github/bigsai-algorithm 欢迎star收藏 如果本篇对你有帮助,记得点赞收藏哦! 在开发.刷 ...
- 一文搞懂 Java 线程中断
转载自 一文搞懂 Java 线程中断 在之前的一文<如何"优雅"地终止一个线程>中详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程 ...
- 搞懂Java的反射机制
搞懂Java的反射机制 1.什么是反射? java的反射机制是指可以在运行状态下获取类和对象的所有属性和方法. 2.反射的作用? 1.在运行时获取一个类/对象的成员变量和方法 2.在运行时创建一个类的 ...
- 搞懂 Java HashMap 源码
HashMap 源码分析 前几篇分析了 ArrayList , LinkedList ,Vector ,Stack List 集合的源码,Java 容器除了包含 List 集合外还包含着 Set 和 ...
最新文章
- 计算机网络-TCP协议
- RuoYi(分离版) 使用代码生成器添加子模块(idea版)
- input在type=number时去右边上下箭头
- mingus python_【Python图像特征的音乐序列生成】关于mingus一个bug的修复,兼改进情感模型...
- 从零开始学前端:对象序列化与反序列化、冒泡排序、数组去重 --- 今天你学习了吗?(JS:Day11)
- Linux常用的基本命令10
- Spring 定时执行任务重复执行多次
- 【OpenCV CUDA】OpenCV和Cuda结合编程
- 【Tableau Server 企业日常问题 21】Tableau Mobile 安卓apk 下载 (可以手机预览)
- UI中经常出现的下拉框下拉自动筛选效果的实现
- 中国5G牌照或于今日发放 我国将正式进入5G时代
- bat脚本学习——拖拽文件到bat下载
- 代码随想录第二天 leetcode 977、209、59
- 阿里云服务器中安装配置MYSQL数据库完整教程
- 9轴陀螺仪KF系列算法的轴向问题
- sub( ,amp;) C语言,C语言与C++不同之函数定义
- 聊聊WPF中字体的设置
- 云端安装神经网络环境(cuda10.1ppc64)
- js实现pdf文件查看
- 这5个坑我踩过,希望你可以避免