用了这么多年的 Java 泛型,你对它到底有多了解?|原创

发表于 4年以前  | 总阅读数:693 次

作为一个 Java 程序员,日常编程早就离不开泛型。泛型自从 JDK1.5 引进之后,真的非常提高生产力。一个简单的泛型 T,寥寥几行代码, 就可以让我们在使用过程中动态替换成任何想要的类型,再也不用实现繁琐的类型转换方法。

虽然我们每天都在用,但是还有很多同学可能并不了解其中的实现原理。今天这篇我们从以下几点聊聊 Java 泛型:

  • Java 泛型实现方式
  • 类型擦除带来的缺陷
  • Java 泛型发展史

Java 泛型实现方式

Java 采用**类型擦除(Type erasure generics)**的方式实现泛型。用大白话讲就是这个泛型只存在源码中,编译器将源码编译成字节码之时,就会把泛型『擦除』,所以字节码中并不存在泛型。

对于下面这段代码,编译之后,我们使用 javap -s class 查看字节码。

方法源码

字节码

观察setParam 部分的字节码,从 descriptor 可以看到,泛型 T 已被擦除,最终替换成了 Object

“ps:并不是每一个泛型参数被擦除类型后都会变成 Object 类,如果泛型类型为 T extends String 这种方式,最终泛型擦除之后将会变成 String。

同理getParam 方法,泛型返回值也被替换成了 Object

为了保证 String param = genericType.getParam(); 代码的正确性,编译器还得在这里插入类型转换。

除此之外,编译器还会对泛型安全性防御,如果我们往 ArrayList<String> 添加 Integer,程序编译期间就会报错。

最终类型擦除后的代码等同与如下:

类型擦除带来的缺陷

作为对比,我们再来简单聊下 C# 泛型的实现方式。

**C#**泛型实现方式为「具现化式泛型(Reifiable generics)」,不熟悉的 C#小伙伴可以不用纠结具现化技术概念,我也不了解这些特性--!

简单点来讲,**C#**实现的泛型,无论是在程序源码,还是在编译之后的,甚至是运行期间都是切实存在的。

相对比与 C# 泛型,Java 泛型看起来就像是个「」泛型。Java 泛型只存在程序源码中,编译之后就被擦除,这种缺陷相应的会带来一些问题。

不支持基本数据类型

泛型参数被擦除之后,强制变成了 Object 类型。这么做对于引用类型来说没有什么问题,毕竟 Object 是所有类型的父类型。但是对于 int/long 等八个基本数据类型说,这就难办了。因为 Java 没办法做到int/longObject 的强制转换。

如果要实现这种转换,需要进行一系列改造,改动难度还不小。所以当时 Java 给出一个简单粗暴的解决方案:既然没办法做到转换,那就索性不支持原始类型泛型了。

如果需要使用,那就规定使用相关包装类的泛型,比如 ArrayList<Integer>。另外为了开发人员方便,顺便增加了原生数据类型的自动拆箱/装箱的特性。

正是这种「偷懒」的做法,导致现在我们没办法使用原始类型泛型,又要忍受包装类装箱/拆箱带来的开销,从而又带来运行效率的问题。

运行效率

上面字节码例子我们已经看到,泛型擦除之后类型将会变成 Object。当泛型出现在方法输入位置的时候,由于 Java 是可以向上转型的,这里并不需要强制类型转换,所以没有什么问题。

但是当泛型参数出现在方法的输出位置(返回值)的时候,调用该方法的地方就需要进行向下转换,将 Object 强制转换成所需类型,所以编译器会插入一句 checkcast 字节码。

除了这个,上面我们还说到原始基本数据类型,编译器还需帮助我们进行装箱/拆箱。

所以对于下面这段代码来说:

List<Integer> list = new ArrayList<Integer>();
list.add(66); // 1
int num = list.get(0); // 2

对于①处,编译器要做就是增加基本类型的装箱即可。但是对于第二步来说,编译器首先需要将 Object 强制转换成 Integer,接着编译器还需要进行拆箱。

类型擦除之后,上面代码等同于:

List list = new ArrayList();
list.add(Integer.valueOf(66));
int num = ((Integer) list.get(0)).intValue();

如果上面泛型代码在 C# 实现,就不会有这么多额外步骤。所以 Java 这种类型擦除式泛型实现方式无论使用效果与运行效率,还是全面落后于 C# 的具现化式泛型。

运行期间无法获取泛型实际类型

由于编译之后,泛型就被擦除,所以在代码运行期间,Java 虚拟机无法获取泛型的实际类型。

下面这段代码,从源码上两个 List 看起来是不同类型的集合,但是经过泛型擦除之后,集合都变为 ArrayList。所以 if语句中代码将会被执行。

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if (li.getClass() == lf.getClass()) { // 泛型擦除,两个 List 类型是一样的
    System.out.println("6666");
} 

这样代码看起来就有点反直觉,这对新手来说不是很友好。

另外还会给我们在实际使用中带来一些限制,比如说我们没办法直接实现以下代码:

最后再举个例子,比如说我们需要实现一个泛型 List 转换成数组的方法,我们就没办法直接从 List 去获取泛型实际类型,所以我们不得不额外再传入一个 Class 类型,指定数组的类型:

public static <E> E[] convert(List<E> list, Class<E> componentType) {
    E[] array = (E[]) Array.newInstance(componentType, list.size());
    ....
}

从上面的例子我们可以看到,Java 采用类型擦除式实现泛型,缺陷很多。那为什么 Java 不采用 C# 的那种泛型实现方式?或者说采用一种更好实现方式?

这个问题等我们了解 Java 泛型机制的历史,以及当时 Java 语言的现状,我们才能切身体会到当时 Java 采用这种泛型实现方式的原因。

Java 泛型历史背景

Java 泛型最早是在 JDK5 的时候才被引入,但是泛型思想最早来自来自 C++ 模板(template)。1996 年 Martin Odersky(Scala 语言缔造者) 在刚发布的 Java 的基础上扩展了泛型、函数式编程等功能,形成一门新的语言-「Pizza」。

后来,Java 核心开发团队对 Pizza 的泛型设计深感兴趣,与 Martin 合作,一起合作开发的一个新的项目「Generic Java」。这个项目的目的是为了给 Java 增加泛型支持,但是不引入函数式编程等功能。最终成功在 Java5 中正式引入泛型支持。

泛型移植过程,一开始并不是朝着类型擦除的方向前进,事实 Pizza 中泛型更加类似于 C# 中的泛型。

但是由于 Java 自身特性,自带严格的约束,让 Martin 在Generic Java 开发过程中,不得不放弃了 Pizza 中泛型设计。

这个特性就是,Java 需要做到严格的向后兼容性。也就是说一个在 JDK1.2 编译出来 Class 文件,不仅能在 JDK 1.2 能正常运行,还得必须保证在后续 JDK,比如 JDK12 中也能保证正常的运行。

这种特性是明确写入 Java 语言规范的,这是一个对 Java 使用者的一个严肃承诺。

“这里强调一下,这里的向后兼容性指的是二进制兼容性,并不是源码兼容性。也不保证高版本的 Class 文件能够运行在低版本的 JDK 上。

现在困难点在于,Java 1.4.2 之前都没有支持泛型,而 Java5 之后突然要支持泛型,还要让 JDK1.4 之前编译的程序能在新版本中正常运行,这就意味着以前没有的限制,就不能突然增加。

举个例子:

ArrayList arrayList=new ArrayList();
arrayList.add("6666");
arrayList.add(Integer.valueOf(666));

没有泛型之前, List 集合是可以存储不同类型的数据,那么引入泛型之后,这段代码必须的能正确运行。

为了保证这些旧的 Clas 文件能在 Java5 之后正常运行,设计者基本有两条路:

  1. 需要泛型化的容器(主要是容器类型),以前有的保持不变,平行增加一套新的泛型化的版本。
  2. 直接把已有的类型原地泛型化,不增加任何新的已有类型的泛型版本。

如果 Java 采用第一条路实现方式,那么现在我们可能就会有两套集合类型。以 ArrayList 为例,一套为普通的 java.util.ArrayList,一套可能为 java.util.generic.ArrayList<T>

采用这种方案之后,如果开发中需要使用泛型特性,那么直接使用新的类型。另外旧的代码不改动,也可以直接运行在新版本 JDK 中。

这套方案看起来没什么问题,实际上C# 就是采用这套方案。但是为什么 Java 却没有使用这套方案那?

这是因为当时 C# 才发布两年,历史代码并不多,如果旧代码需要使用泛型特性,改造起来也很快。但是 Java 不一样,当时 Java 已经发布十年了,已经有很多程序已经运行部署在生产环境,可以想象历史代码非常多。

如果这些应用在新版本 Java 需要使用泛型,那就需要做大量源码改动,可以想象这个开发工作量。

另外 Java 5 之前,其实我们就已经有了两套集合容器,一套为 Vector/Hashtable 等容器,一套为 ArrayList/ HashMap。这两套容器的存在,其实已经引来一些不便,对于新接触的 Java 的开发人员来说,还得学习这两者的区别。

如果此时为了泛型再引入新类型,那么就会有四套容器同时并存。想想这个画面,一个新接触开发人员,面对四套容器,完全不知道如何下手选择。如何 Java 真的这么实现了,想必会有更多人吐槽 Java。

所以 Java 选择第二条路,采用类型擦除,只需要改动 Javac 编译器,不需要改动字节码,不需要改动虚拟机,也保证了之前历史没有泛型的代码还可以在新的 JDK 中运行。

但是第二条路,并不代表一定需要使用类型擦除实现,如果有足够时间好好设计,也许会有更好的方案。

当年留下的技术债,现在只能靠 Valhalla 项目来还了。这个项目从2014 年开始立项,原本计划在 JDK10 中解决现有语言的各种缺陷。但是结果我们也知道了,现在都 JDK14 了,还只是完成少部分目标,并没有解决核心目标,可见这个改动的难度啊。

总结

本文我们先从 Java 泛型底层实现方式开始聊起,接着举了几个例子,让大家了解现在泛型实现方式存在一些缺陷。

然后我们带入 Java 泛型历史背景,站在 Java 核心开发者的角度,才能了解 Java 泛型这么现实无奈原因。

最后作为 Java 开发者,让我们对于现在 Java 一些不足,少些抱怨,多一些理解吧。相信之后 Java 核心开发人员肯定会解决泛型现有的缺陷,让我们拭目以待。

帮助资料

  1. https://www.zhihu.com/question/38940308
  2. https://www.zhihu.com/question/28665443
  3. https://hllvm-group.iteye.com/group/topic/25910
  4. http://blog.zhaojie.me/2010/05/why-java-sucks-and-csharp-rocks-4-generics.html
  5. http://blog.zhaojie.me/2010/04/why-java-sucks-and-csharp-rocks-2-primitive-types-and-object-orientation.html
  6. https://en.wikipedia.org/wiki/Generics_in_Java
  7. https://www.zhihu.com/question/34621277/answer/59440954
  8. https://www.artima.com/scalazine/articles/origins_of_scala.html

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/ceoIJaT1HPtWdXiUPC74Qw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Java 中验证时间格式的 4 种方法 2年以前  |  3870次阅读
Java经典面试题答案解析(1-80题) 4年以前  |  3652次阅读
CentOS 配置java应用开机自动启动 4年以前  |  2798次阅读
IDEA依赖冲突分析神器—Maven Helper 4年以前  |  2773次阅读
SpringBoot 控制并发登录的人数教程 4年以前  |  2452次阅读
 目录