下面的代码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反编译字节码)的更多相关文章

  1. 从字节码指令看重写在JVM中的实现

    Java是解释执行的.包含动态链接的特性.都给解析或执行期间提供了非常多灵活扩展的空间.面向对象语言的继承.封装和多态的特性,在JVM中是怎样进行编译.解析,以及通过字节码指令怎样确定方法调用的版本号 ...

  2. 深入理解 python 虚拟机:字节码教程(2)——控制流是如何实现的?

    深入理解 python 虚拟机:字节码教程(2)--控制流是如何实现的? 在本篇文章当中主要给大家分析 python 当中与控制流有关的字节码,通过对这部分字节码的了解,我们可以更加深入了解 pyth ...

  3. 深入理解Java之泛型

    原文出处: absfree 1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过 ...

  4. 深入理解什么是Java泛型?泛型怎么使用?【纯转】

    本篇文章给大家带来的内容是介绍深入理解什么是Java泛型?泛型怎么使用?有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助. 一.什么是泛型 “泛型” 意味着编写的代码可以被不同类型的对象所 ...

  5. java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题

    参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...

  6. C#高级语法之泛型、泛型约束,类型安全、逆变和协变(思想原理)

    一.为什么使用泛型? 泛型其实就是一个不确定的类型,可以用在类和方法上,泛型在声明期间没有明确的定义类型,编译完成之后会生成一个占位符,只有在调用者调用时,传入指定的类型,才会用确切的类型将占位符替换 ...

  7. 泛型学习第四天——List泛型终结:什么是List泛型,泛型筛选,泛型排序

    为什么要用泛型集合? 在C# 2.0之前,主要可以通过两种方式实现集合: a.使用ArrayList 直接将对象放入ArrayList,操作直观,但由于集合中的项是Object类型,因此每次使用都必须 ...

  8. C++泛型 && Java泛型实现机制

    C++泛型  C++泛型跟虚函数的运行时多态机制不同,泛型支持的静态多态,当类型信息可得的时候,利用编译期多态能够获得最大的效率和灵活性.当具体的类型信息不可得,就必须诉诸运行期多态了,即虚函数支持的 ...

  9. 【学习笔记】C#中的泛型和泛型集合

    一.什么是泛型? 泛型是C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework.类型参数使得设计某些类和方法成为可能,例如,通过使用泛型类型参数T,可以大 ...

  10. Scala 深入浅出实战经典 第42讲:scala 泛型类,泛型函数,泛型在spark中的广泛应用

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

随机推荐

  1. 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-10- 标签页(tab)操作 - 上篇 (详细教程)

    1.简介 本来按照计划这一系列的文章应该介绍Context和Page两个内容的,但是宏哥看了官方文档和查找资料发现其实和宏哥在Python+Playwright系列文章中的大同小异,差不了多少,再在这 ...

  2. sshd 启动失败

    解决方法 yum remove openssh yum install openssh openssh-server openssh-clients systemctl start sshd syst ...

  3. vue3.0实现炫酷的随机验证码功能

    先上图 接下来楼一眼实现代码 这里说明一下,我用到了vue3.0,vant3.0以及阿里图标,vant 很人性化针对vue3.0新出了个vant3.0版本,阿里则是适配vue3.0的.我们将verif ...

  4. 微软宣布更新SymCrypt加密库,新增对PQC算法的支持

    转载链接:https://mp.weixin.qq.com/s/aWXzPTWhxFpJVP1s0iwAtw 2024年9月9日,微软(Microsoft)在其博客中宣布,已开始在其开源核心加密库Sy ...

  5. [ARC 058 - E]Iroha and Haiku

    传送门 解题步骤 首先可以发现题目范围非常小,尤其是\(X,Y,Z\),所以考虑类似状压.数位dp.双向搜索等算法. 官方题解中给的是数位dp,那我这里就讲讲状压了 对于\(N \leq 40\),很 ...

  6. 【译】融入人工智能的 eShop – 全面的智能应用示例

    原文 | Jeremy Likness 翻译 | 郑子铭 人工智能 (AI) 是一种强大的工具,它可以增强您的应用程序,提供更好的个性化定制体验,满足客户的独特需求,同时提高内部运营的质量和效率.虽然 ...

  7. 记一次腾讯云轻量级服务器安装mysql配置完成后,外网无法访问问题

    一.配置信息正常 1.防火墙配置通过 2.mysql端口正常启动netstat -antlp | grep 3306 3.配置都正常,但是telnet访问不通超时Operation timed out ...

  8. FreeSql学习笔记——7.分组聚合

    前言   分组就是将元数据通过某些条件划分为组,而聚合就是对这些组进行整合操作:在sqlserver数据库中使用的关键字group by使符合条件的集合通过某些字段分好组,再使用聚合函数(如max() ...

  9. DeepSeek-R1的“思考”艺术,你真的了解吗?

    大家好~,这里是AI粉嫩特攻队!今天咱们来聊聊一个有趣的话题--DeepSeek-R1到底什么时候会"思考",什么时候又会选择"偷懒"? 最近有朋友问我:&qu ...

  10. Openlayers 距离环绘制

    思路:利用layer的StyleFunction 来使地图移动或者放缩的时候,使圆保持在地图中心 /** * 绘制距离环 * @param {number} distance 每环间隔距离,单位:米 ...