深入理解泛型-重写泛型类方法遇到的问题(涉及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 ...
随机推荐
- 20250110-FortuneWheel 攻击事件:竟然不设滑点,那就体验一下 Force Investment 吧
背景信息 攻击交易:https://app.blocksec.com/explorer/tx/bsc/0xd6ba15ecf3df9aaae37450df8f79233267af41535793ee1 ...
- biancheng-Spring Boot框架
目录http://c.biancheng.net/spring_boot/ 1Spring Boot是什么2创建Spring Boot项目3Spring Boot starter4YAML5Sprin ...
- 一些devops、软件工程的个人感悟
1.devops不是简单的工具,是思想. (1)devops核心在于快速编译构建.自动测试化.自动部署发布 (2)工具只是辅助手段,无论是Jenkins.腾讯蓝盾等等,甚至是手动bat+bash搭建, ...
- law Intermediate walkthrough pg
靶场很简单分数只有10分跟平常做的20分的中级靶场比确实简单 我拿来放松的 算下来30分钟解决战斗 nmap 扫到80端口web界面 是个框架 搜exp https://www.exploit-db. ...
- .NET Core GC压缩(compact_phase)底层原理浅谈
简介 终于来到了GC的最后一个步骤,在此之间,大量预备工作已经完成.万事俱备,只欠东风 清除 如果GC决定不压缩,它将仅执行清除操作.清除操作非常简单,把所有不可到达对象(gap),转换成Free.也 ...
- 小米CR6606,CR6608,CR6609 启用SSH和刷入OpenWRT 23.05.5
上个月极低的价格得到一台CR6606和一台CR6609, 一直没时间研究, 终于趁春节假期把这两个都刷成 OpenWRT 配置说明 CPU: MT7621AT,双核880MHz 内存: NT5CC12 ...
- DeepSeek 全面指南,95% 的人都不知道的9个技巧(建议收藏)
大家好,我是汤师爷~ 最近,DeepSeek这款AI工具爆火国内外. 虽然许多人都开始尝试使用它,但有人吐槽说,没想象中那么牛. 其实问题不在工具,很多人的使用姿势就搞错了,用大炮打蚊子,白白浪费De ...
- 如何让JS代码变的安全?
本文分享自天翼云开发者社区<如何让JS代码变的安全?>,作者:温****双 前端JS代码,直接暴露在浏览器中,任何访问者,都可以随意查看代码.这就导致代码可以被分析.复制.盗用等,进而引发 ...
- Nmap 脚本使用
Nmap 脚本使用 使用 Nmap 脚本是扩展 Nmap 功能的一种高效方式,允许用户执行从简单的服务检测到复杂的漏洞利用的各种任务.通过指定 --script 选项,并结合相应的脚本名称或类型,用户 ...
- Linux安装nodejs npm
1.检查 whereis nodejs whereis npm 2.下载 wget -c https://npm.taobao.org/mirrors/node/v12.12.0/node-v12.1 ...