深入理解泛型-重写泛型类方法遇到的问题(涉及JVM反编译字节码)
下面的代码DateInterval类想重写父类Pair<LocalDate>中的setSecond方法,保证设置的第二个日期要在第一个日期之后,不能出现second早于first的情况。这里存在两种写法,报错写法使用的是Object作为参数类型,成功写法使用LocalDate。
public class Pair<T>{
private T first;
private T second;
public T getFirst(){...}
public T getSecond(){...}
public void setFirst(T first){...}
public void setSecond(T second){...}
}
public class DateInterval extends Pair<LocalDate>{
//报错写法
public void setSecond(Object secondDate){
if (secondDate instanceof LocalDate) {
if (((LocalDate)secondDate).compareTo(getFirst()) >= 0) {
super.setSecond((LocalDate) secondDate);
}
}
}
//成功写法
public void setSecond(LocalDate secondDate){
if (secondDate.compareTo(getFirst()) >= 0){
super.setSecond(secondDate);
}
}
public static void main(String[] args) {
Pair<LocalDate> pair = new DateInterval(LocalDate.of(1991,8,16),LocalDate.of(1992,8,16));
pair.setSecond(LocalDate.of(1970,8,16));
}
}
第一个问题:为什么选择使用Object呢?
因为了解泛型擦除原理,所以在重写方法时选择了Object作为参数类型,以为可以成功覆写却失败。
第二个问题:为什么选择使用LocalDate呢?
逻辑上讲,泛型擦除对开发者是不可见的,开发者使用Pair<LocalDate>后,自然认为字段的类型是LocalDate,所以使用LocalDate进行覆写合情合理。
为了解决报错疑惑,我们先来一步一步分析下成功写法下的整个流程,即:main方法中的下面这句是如何执行的?
pair.setSecond(LocalDate.of(1970,8,16));
因为使用父类型Pair作为pair变量的静态类型,所以在调用setSecond方法时,编译器会将其翻译为调用Pair类的setSecond(Object)方法等待多态执行:
39: invokevirtual #3 // Method com/company/Pair.setSecond:(Ljava/lang/Object;)V
虚拟机执行这行指令时,会采用动态分派来挑选出实际要执行的代码段,简而言之,虚拟机会先从DateInterval类开始找起,找能匹配描述符 setSecond:(Ljava/lang/Object;)V 的方法,但是DateInterval中其实只有setSecond:(Ljava/time/LocalDate)V 这个方法,并不存在setSecond:(Ljava/lang/Object;)V,所以DateInterval没有匹配成功。虚拟机会继续向上找,找到了DateInterval的父类Pair,并且找到了匹配成功的方法,所以实际上这行代码其实是执行父类Pair.setSecond(Object)。
注:描述符为描述方法的一种格式,方法名 参数列表 返回值这样的格式,见上段的例子setSecond。
那么此时问题出现了,本想通过重写来实现功能扩展,结果还是调用了父类的Object为参数的方法,为了解决这个问题,编译器设计了桥方法,即编译器在DateInterval类中自动插入一个setSecond:(Ljava/lang/Object;)V方法,方法内部调用的是setSecond:(Ljava/time/LocalDate)V 这个方法来实现搭桥引路,即下面反编译字节码中第五行指令:
5: invokevirtual #11 // Method setSecond:(Ljava/time/LocalDate;)V
public void setSecond(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #10 // class java/time/LocalDate
5: invokevirtual #11 // Method setSecond:(Ljava/time/LocalDate;)V
了解了以上问题,就可以清楚报错写法的错误原因了。简单来说就是,编译器会自动添加一个桥方法,如果开发者再定义一个参数为Object的方法,那么DateInterval中就出现了两个描述符完全一样的两个方法。
假如允许这样的写法存在,那么虚拟机执行的时候应该用哪个方法呢?虚拟机也会迷惑(虚拟机执行的时候使用参数类型和返回值类型确定一个方法),所以不允许这样做。
考虑DateInterval重写getSecond方法,那么依照上面的分析,虚拟机也会自动生成一个返回值为Object类型的getSecond方法,并且在方法内部调用DateInterval重写的getSecond方法,DateInterval内部实际上会有两个getSecond方法:
LocalDate getSecond();
Object getSecond();
这种写法在开发阶段是不允许的,因为这两个方法的方法名称和参数完全一致,但对虚拟机来说,因为采用参数类型和返回值类型确定一个方法,所以虚拟机是可以区分的开的,不会出现错误。
实际上,这种桥方法会运用在重写时子类的返回类型小于父类的情况,不仅仅运用在泛型中。
重点总结:当某类继承一个泛型类,并且需要重写其中的方法时,编译器会自动添加一个桥方法,来保证多态的实现。
深入理解泛型-重写泛型类方法遇到的问题(涉及JVM反编译字节码)的更多相关文章
- 从字节码指令看重写在JVM中的实现
Java是解释执行的.包含动态链接的特性.都给解析或执行期间提供了非常多灵活扩展的空间.面向对象语言的继承.封装和多态的特性,在JVM中是怎样进行编译.解析,以及通过字节码指令怎样确定方法调用的版本号 ...
- 深入理解 python 虚拟机:字节码教程(2)——控制流是如何实现的?
深入理解 python 虚拟机:字节码教程(2)--控制流是如何实现的? 在本篇文章当中主要给大家分析 python 当中与控制流有关的字节码,通过对这部分字节码的了解,我们可以更加深入了解 pyth ...
- 深入理解Java之泛型
原文出处: absfree 1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过 ...
- 深入理解什么是Java泛型?泛型怎么使用?【纯转】
本篇文章给大家带来的内容是介绍深入理解什么是Java泛型?泛型怎么使用?有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助. 一.什么是泛型 “泛型” 意味着编写的代码可以被不同类型的对象所 ...
- java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题
参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
- C#高级语法之泛型、泛型约束,类型安全、逆变和协变(思想原理)
一.为什么使用泛型? 泛型其实就是一个不确定的类型,可以用在类和方法上,泛型在声明期间没有明确的定义类型,编译完成之后会生成一个占位符,只有在调用者调用时,传入指定的类型,才会用确切的类型将占位符替换 ...
- 泛型学习第四天——List泛型终结:什么是List泛型,泛型筛选,泛型排序
为什么要用泛型集合? 在C# 2.0之前,主要可以通过两种方式实现集合: a.使用ArrayList 直接将对象放入ArrayList,操作直观,但由于集合中的项是Object类型,因此每次使用都必须 ...
- C++泛型 && Java泛型实现机制
C++泛型 C++泛型跟虚函数的运行时多态机制不同,泛型支持的静态多态,当类型信息可得的时候,利用编译期多态能够获得最大的效率和灵活性.当具体的类型信息不可得,就必须诉诸运行期多态了,即虚函数支持的 ...
- 【学习笔记】C#中的泛型和泛型集合
一.什么是泛型? 泛型是C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework.类型参数使得设计某些类和方法成为可能,例如,通过使用泛型类型参数T,可以大 ...
- Scala 深入浅出实战经典 第42讲:scala 泛型类,泛型函数,泛型在spark中的广泛应用
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
随机推荐
- 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-10- 标签页(tab)操作 - 上篇 (详细教程)
1.简介 本来按照计划这一系列的文章应该介绍Context和Page两个内容的,但是宏哥看了官方文档和查找资料发现其实和宏哥在Python+Playwright系列文章中的大同小异,差不了多少,再在这 ...
- sshd 启动失败
解决方法 yum remove openssh yum install openssh openssh-server openssh-clients systemctl start sshd syst ...
- vue3.0实现炫酷的随机验证码功能
先上图 接下来楼一眼实现代码 这里说明一下,我用到了vue3.0,vant3.0以及阿里图标,vant 很人性化针对vue3.0新出了个vant3.0版本,阿里则是适配vue3.0的.我们将verif ...
- 微软宣布更新SymCrypt加密库,新增对PQC算法的支持
转载链接:https://mp.weixin.qq.com/s/aWXzPTWhxFpJVP1s0iwAtw 2024年9月9日,微软(Microsoft)在其博客中宣布,已开始在其开源核心加密库Sy ...
- [ARC 058 - E]Iroha and Haiku
传送门 解题步骤 首先可以发现题目范围非常小,尤其是\(X,Y,Z\),所以考虑类似状压.数位dp.双向搜索等算法. 官方题解中给的是数位dp,那我这里就讲讲状压了 对于\(N \leq 40\),很 ...
- 【译】融入人工智能的 eShop – 全面的智能应用示例
原文 | Jeremy Likness 翻译 | 郑子铭 人工智能 (AI) 是一种强大的工具,它可以增强您的应用程序,提供更好的个性化定制体验,满足客户的独特需求,同时提高内部运营的质量和效率.虽然 ...
- 记一次腾讯云轻量级服务器安装mysql配置完成后,外网无法访问问题
一.配置信息正常 1.防火墙配置通过 2.mysql端口正常启动netstat -antlp | grep 3306 3.配置都正常,但是telnet访问不通超时Operation timed out ...
- FreeSql学习笔记——7.分组聚合
前言 分组就是将元数据通过某些条件划分为组,而聚合就是对这些组进行整合操作:在sqlserver数据库中使用的关键字group by使符合条件的集合通过某些字段分好组,再使用聚合函数(如max() ...
- DeepSeek-R1的“思考”艺术,你真的了解吗?
大家好~,这里是AI粉嫩特攻队!今天咱们来聊聊一个有趣的话题--DeepSeek-R1到底什么时候会"思考",什么时候又会选择"偷懒"? 最近有朋友问我:&qu ...
- Openlayers 距离环绘制
思路:利用layer的StyleFunction 来使地图移动或者放缩的时候,使圆保持在地图中心 /** * 绘制距离环 * @param {number} distance 每环间隔距离,单位:米 ...