先看一道常见的面试题,下面的代码的执行结果是什么?
public static void main(String[] args) {
List<String> list1=new ArrayList<String>();
List<Integer> list2=new ArrayList<Integer>();
System.out.println(list1.getClass()==list2.getClass());
}
首先,我们知道 getClass 方法获取的是对象运行时的类(Class),那么这个问题也就可以转化为 ArrayList
我们直接揭晓答案,运行上面的代码,程序会打印 true。这说明了,虽然在代码中声明了具体的泛型,但是两个 List 对象对应的 Class 是一样的,对它们的类型进行打印,结果都是:
class java.util.ArrayList
也就是说,虽然 ArrayList
泛型的本质是参数化类型,而类型擦除使得类型参数只存在于编译期,在运行时,JVM 并不知道泛型的存在的。
那么为什么要进行泛型的类型擦除呢?
查阅了一些资料,其解释是类型擦除的主要目的是避免过多的创建类而造成的运行时的过度消耗。试想一下,如果用 List 表示一个类型,再用 List 表示另一个类型,以此类推,无疑会引起类型的数量爆炸。
在对类型擦除有了一个大致的了解后,我们再看看下面的几个问题。
上面我们说了,编译完成后会对泛型进行类型擦除。如果想要眼见为实,实际看一下的话应该怎么办呢?那么就需要对编译后的字节码文件进行反编译了,这里使用一个轻量级的小工具 Jad 来进行反编译。
Jad:https://varaneckas.com/jad/
Jad 的使用也很简单。下载解压后,把需要反编译的字节码文件放在目录下,然后在命令行里执行下面的命令就可以在同目录下生成反编译后的 .java 文件了:
jad -sjava Test.class
好了。工具准备好了,下面我们就看一下不同情况下的类型擦除。
2. 不同情况下的类型擦除
当类定义中的类型参数没有任何限制时,在类型擦除后,会被直接替换为 Object。在下面的例子中,
左侧为编译前的代码,右侧为通过字节码文件反编译得到的代码
当类定义中的类型参数存在限制时,在类型擦除中替换为类型参数的上界或者下界。下面的代码中,经过擦除后 T 被替换成了 Integer:
比较下面两边的代码可以看到,在擦除方法中的类型参数时,和擦除类定义中的类型参数一致。无限制时直接擦除为 Object,有限制时则会被擦除为上界或下界:
估计对 Java 反射比较熟悉小伙伴要有疑问了,反射中的 getTypeParameters 方法可以获得类、数组、接口等实体的类型参数。如果类型被擦除了,那么能获取到什么呢?我们来尝试一下使用反射来获取类型参数:
System.out.println(Arrays.asList(list1.getClass().getTypeParameters()));
执行结果如下:
[E]
同样,如果打印 Map 对象的参数类型:
Map<String,Integer> map=new HashMap<>();
System.out.println(Arrays.asList(map.getClass().getTypeParameters()));
最终也只能够获取到:
[K, V]
可以看到通过 getTypeParameters 方法只能获取到泛型的参数占位符,而不能获得代码中真正的泛型类型。
使用泛型的好处之一,就是在编译的时候能够检查类型安全。但是通过上面的例子,我们知道运行时是没有泛型约束的。那么是不是就意味着,在运行时可以把一个类型的对象能放进另一类型的 List 呢?
我们先看看正常情况下,直接调用 add 方法会有什么报错:
当我们尝试将 User 类型的对象放入 String 类型的数组时,泛型的约束会在编译期间就进行报错:提示提供的User类型对象不适用于 String 类型数组。
既然编译时不行,那么我们就在运行时写入。借助真正运行的 class 是没有泛型约束这一特性,使用反射在运行时写入:
public class ReflectTest {
static List<String> list = new ArrayList<>();
public static void main(String[] args) {
list.add("1");
ReflectTest reflectTest =new ReflectTest();
try {
Field field = ReflectTest.class.getDeclaredField("list");
field.setAccessible(true);
List list=(List) field.get(reflectTest);
list.add(new User());
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行上面的代码,不仅在编译期间可以通过语法检查,并且也可以正常地运行,我们使用 debug 来看一下数组中的内容:
可以看到,虽然数组中声明的泛型类型是 String,但是仍然成功的放入了 User 类型的对象。
那么,如果我们在代码中尝试取出这个 User 对象,程序还能正常执行吗,我们在上面代码的最后再加上一句:
System.out.println(list.get(1));
再次执行代码,程序运行到最后的打印语句时,报错如下:
异常提示:User 类型的对象无法被转换成 String 类型。这是否也就意味着,在取出对象时存在强制类型转换呢?我们来看一下 ArrayList 中 get 方法的源码:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
可以看到,在取出元素时会将这个元素强制类型转换成泛型中的类型。
也就是说在上面的代码中,最后会尝试强制把 User 对象转换成 String 类型,在这一阶段程序会报错。通过这一过程,也再次证明了泛型可以对类型安全进行检测。
下面我们看一个稍微有点复杂的例子。
首先声明一个接口,然后创建一个实现该接口的类:
public interface Fruit<T> {
T get(T param);
}
public class Apple implements Fruit<Integer> {
@Override
public Integer get(Integer param) {
return param;
}
}
按照之前我们的理解,在进行类型擦除后应该是这样的:
public interface Fruit {
Object get(Object param);
}
public class Apple implements Fruit {
@Override
public Integer get(Integer param) {
return param;
}
}
但是,如果真是这样的话那么代码是无法运行的。
虽然 Apple 类中也有一个 get 方法,但是与接口中的方法参数不一致,也就是说没有覆盖接口中的方法。
针对这种情况,编译器会通过添加一个桥接方法来满足语法上的要求,同时保证了基于泛型的多态能够有效。
我们反编译上面代码生成的字节码文件:
可以看到,编译后的代码中生成了两个 get 方法。
参数为 Object 的 get 方法负责实现 Fruit 接口中的同名方法。然后,在实现类中又额外添加了一个参数为 Integer 的 get 方法,这个方法也就是理论上应该生成的带参数类型的方法。
最终,用接口方法调用额外添加的方法。通过这种方式构建了接口和实现类的关系,类似于起到了桥接的作用,因此也被称为桥接方法。通过这种机制保证了泛型情况下的 Java 多态性。
本文由面试中常见的一道面试题入手,介绍了 Java 中泛型的类型擦除相关知识。通过这一过程,也便于大家理解为什么平常总是说 Java 中的泛型是一个伪泛型,同时也有助于大家认识到 Java 中泛型的一些缺陷。了解类型擦除的原因以及原理,相信能够方便大家在日常的工作中更好的使用泛型。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/NqcD4pJ-jgAXDTrYbpe1aQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。