JVM原理(十六):JVM虚拟机类型擦除与泛型发展

1. 泛型

泛型的本质是参数化类型或者参数化多态的应用,即可以将操作的数据类型指定为方法签名中的一种特殊参数,这种参数类型能够用在类、接口和方法的创建中,分别构成泛型类、泛型接口和泛型方法。

泛型让程序员能够以针对泛化的数据类型编写相同的算法,这极大地增强了编程语言的类型系统及抽象能力。

1.1. Java与C#的泛型

Java选择的泛型实现方式叫作“类型擦除式泛型”(Type Erasure Generics ),而C#选择的泛型实现方式是“具现化式泛型”( Reified Generics )。

而Java语言中的泛型则不同,它只在程序源码中存在,在编译后的字节码文件中,全部泛型都被替换为原来的裸类型(Raw Type,稍后我们会讲解裸类型具体是什么)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,Array List<int>与Array List<String其实是同一个类型,由此读者可以想象“类型擦除”这个名字的含义和来源,这也是为什么笔者会把Java泛型安排在语法糖里介绍的原因。

上面这些是Java泛型在编码阶段产生的不良影响,如果说这种使用层次上的差别还可以通过多写几行代码、方法中多加一两个类型参数来解决的话,性能上的差距则是难以用编码弥补的。C#2.0引)了泛型之后,带来的显著优势之一便是对比起Java在执行性能上的提高,因为在使用平台提供的容器类型(如List<T>,Dictionary<TKey,Tle>)时,无须像Java里那样不厌其烦地拆箱和装箱[1],如果在Java中要避免这种损失,就必须构造一个与数据类型相关的容器类(譬如IntFloatHashMap这样的容器)。显然,这除了引入更多代码造成复杂度提高、复用性降低之外,更是丧失了泛型本身的存在价值。
Java的类型擦除式泛型无论在使用效果上还是运行效率上,几乎是全面落后于C#的具现化式泛型,而它的唯一优势是在于实现这种泛型的影响范围上:擦除式泛型的实现几乎只需要在Javac编译器上做出改进即可,不需要改动字节码、不需要改动Java虚拟机,也保证了以前没有使用泛型的库可以直接运行在Java5.0之上。但这种听起来节省工作量甚至可以说是有偷工减料嫌疑的优势就显得非常短视,真的能在当年Java实现泛型的利弊权衡中胜出吗?答案的确是它胜出了,但我们必须在那时的泛型历史背景中去考虑不同实现方式带来的代价。

1.2. 泛型的历史背景

Martin Odersky是Scala缔造者,Java团队找到他,表示对Pizza的泛型很感兴趣,于是就将Pizza语言的泛型单拎出来给Java,本来Pizza的泛型更偏向C#.

可以事实上,因为Java规范中的“二进制向后兼容性”,即一个在JDK 1.2中编译出来的Class文件,必须保证能够在JDK 12乃至以后的版本中也能够正常运行。

所以为了保证这些编译出来的Class文件可以在Java 5.0引入泛型之后继续运行,设计者面前大体上有两条路可以选择:

1)需要泛型化的类型(主要是容器类型),以前有的就保持不变,然后平行地加一套泛型化版本的新类型。

2)直接把已有的类型泛型化,即让所有需要泛型化的已有类型都原地泛型化,不添加任何平行于已有类型的泛型版。

在这个分叉路口,C#走了第一条路,添加了一组System.Collections.Generic的新容器,以前的Svstem.Collections以及System.Collections.Specialized容器类型继续存在。C#的开发人员很快就接受了新的容器,倒也没出现过什么不适应的问题,唯一的不适大概是许多.NET自身的标准库已经把老容器类型当作方法的返回值或者参数使用,这些方法至今还保持着原来的老样子。

但如果相同的选择出现在Java中就很可能不会是相同的结果了,要知道当时.NET才问世两年,而Java已经有快十年的历史了,再加上各自流行程度的不同,两者遗留代码的规模根本不在同一个数量级上。而且更大的问题是Java并不是没有做过第-条路那样的技术决策,在JDK 1.2时,遗留代码规模尚小,Java就引入过新的集合类,并且保留了旧集合类不动。这导致了直到现在标准类库中还有Vector (老)和ArrayList (新)、有Hashtable (老)和HashMap (新)等两套容器代码并存,如果当时再摆弄出像Vector (老)、ArrayList (新)、Vector<T> (老但有泛型)、ArrayList<T> (新且有泛型)这样的容器集合,可能叫骂声会比今天听到的更响更大,如果选择第一条,则会冒出更多的容器集合,不方便使用

1.3. 类型擦除

我们继续以ArrayList为例来介绍Java泛型的类型擦除具体是如何实现的。由于Java选择了第二条路,直接把已有的类型泛型化。要让所有需要泛型化的已有类型,譬如ArrayList, 原地泛型化后变成了ArrayList<T>,而且保证以前直接用Array List的代码在泛型新版本里必须还能继续用这同一一个容器,这就必须让所有泛型化的实例类型,譬如Array List<Integer>、Array List <String这些全部自动成为ArrayList的子类型才能可以,否则类型转换就是不安全的。由此就引出了“裸类型”(Raw Type)的概念,裸类型应被视为所有该类型泛型化实例的共同父类型(Super Type),只有这样,像代码清单10-4中的赋值才是被系统允许的从子类到父类的安全转型。

Arraylist<Integer> ilist = new Arraylist<Integer>();
ArrayList<String> slist=new ArrayList<String>();
ArrayList list;//裸类型
list = ilist;
list = slist;

接下来面对的问题:如何实现裸类型

  1. 一种是在运行期间由Java虚拟机来自动地、真实地构造出ArrayList<Integer>类型

  2. 另外一种是索性简单粗暴地直接在编译时把ArrayList<Integer>还原回ArrayList,只在元素访问、修改时自动插入一些强制类型转换和检查指令。

类型擦除前和类型擦除后的对比

public static void main(string[]args){
    Map<String,String> map =new HashMap<String,string>();
    map.put("hello","你好");
    map.put("how are you?","吃了没?");
    System.out.println(map.get("hello"));
    System.out.println(map.get("how are you?"));
}
public static void main(string[]args){
    Map<String,String> map =new HashMap();
    map.put("hello","你好");
    map.put("how are you?","吃了没?");
    System.out.println((String) map.get("hello"));
    System.out.println((String) map.get("how are you?"));
}

把这段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了裸类型。

2. 产生的问题

1.这种情况下,一旦把泛型信息擦除后,到要插入强制转型代码的地方就没办法往下做了,因为不,支持int、long与Object之间的强制转型。当时Java给出的解决方案一如既往的简单粗暴:既然没法转换那就索性别支持原生类型的泛型了吧,你们都用ArrayList<Integer>、 ArrayList<Long>, 反正都做了自动的强制类型转换,遇到原生类型时把装箱、拆箱也自动做了得了。这个决定后面导致了无数构造包装类和装箱、拆箱的开销,成为Java泛型慢的重要原因,也成为今天Valhalla项目要重点解决的问题之。

2.我们去写一个泛型版本的从List到数组的转换方法,由于不能从List中取得参数化类型T,所以不得不从一个额外参数中再传入一个数组的组件类型进去,实属无奈。

擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们在编码时能通过反射手段取得参数化类型的根本依据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/773102.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手动访问mongo和ES插入和查询

1、手动访问mongo 1.1、mongo连接数据库 1.2、mongo插入和查询 db.hmf_test.insert( { "aoeId": "1", "aoeAes": "吴秀梅", "aoeSm4": "北京xx网络技术有限公司.", "aoeSm4_a": "…

【BUUCTF-PWN】4-ciscn_2019_n_1

参考&#xff1a;BUUCTF-ciscn_2019_n_1 - 纸鸢asahi - 博客园 (cnblogs.com) buuctf 刷题记录_PWN ciscn_2019_n_1 - MuRKuo - 博客园 (cnblogs.com) 从题海中入门&#xff08;四&#xff09;ciscn_2019_n_1 - FreeBuf网络安全行业门户 ciscn_2019_n_1 ——两种解法_0x4134800…

抗震支吊架安装

抗震支吊架系统安装指导 设计要求&#xff1a; 本工程采用抗震支吊架系统&#xff0c;请根据深化设计提供的图纸及安装材料表等进行安装。 材料要求&#xff1a; 符合 CJ/T476-2015《建筑机电设备抗震支吊架通用技术条件》及 CECS 420:2015《抗震支吊架安装及验收规程》 槽…

Go语言--延迟调用defer、获取命令行参数、局部变量以及全局变量

延迟调用defer 关键字 defer 用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。注意&#xff0c;defer语句只能出现在函数或方法的内部。 defer 语句经常被用于处理成对的操作&#xff0c;如打开、关闭、连接、断开连接、加锁、释放锁。通过defer 机制&#xff0…

生态共建 | 华宇TAS应用中间件与新华三服务器完成兼容互认证

近日&#xff0c;华宇TAS应用中间件完成与新华三技术有限公司的R4930系列和R4970 G7服务器的兼容适配&#xff0c;认证测试报告显示&#xff0c;双方产品兼容性良好&#xff0c;运行稳定、安全&#xff0c;可以满足用户对双方功能的要求。 新华三技术有限公司 新华三技术有限公…

Pandas数据清洗实战:精准捕捉并优雅过滤异常值,让数据分析更可靠!

1.describe()&#xff1a;查看每一列的描述性统计量 # 导包 import numpy as np import pandas as pddf pd.DataFrame(datanp.random.randint(0,10,size(5,3)),indexlist("ABCDE"),columns["Python","NumPy","Pandas"]) dfdf.descri…

SQL MINUS 运算符:查找数据集之间的差异

在 SQL 中&#xff0c;MINUS 运算符在查询中起着至关重要的作用&#xff0c;它允许开发人员识别和检索存在于一个数据集中但不存在于另一个数据集中的记录。本文探讨了 SQL 中 MINUS 运算符的功能、用法和实际应用&#xff0c;强调了它在数据分析和操作任务中的重要性。 理解 …

adobe pdf设置默认打开是滚动而不是单页视图

上班公司用adobe pdf&#xff0c;自己还不能安装其它软件。 每次打开pdf&#xff0c;总是默认单页视图&#xff0c;修改滚动后&#xff0c;下次打开又 一样&#xff0c;有时候比较烦。 后面打开编辑->首选项&#xff0c; 如下修改&#xff0c;下次打开就是默认滚动了

开源六轴协作机械臂myCobot280实现交互式乘法!让学习充满乐趣

本文经作者Fumitaka Kimizuka 授权我们翻译和转载。 原文链接&#xff1a;myCobotに「頷き」「首振り」「首傾げ」をしてもらう &#x1f916; - みかづきブログ・カスタム 引言 Fumitaka Kimizuka 创造了一个乘法表系统&#xff0c;帮助他的女儿享受学习乘法表的乐趣。她可以…

FPGA问题

fpga 问题 ep2c5t144 开发板 第一道坎&#xff0c;安装软件&#xff1b;没有注册&#xff0c;无法产生sop文件&#xff0c;无法下载 没有相应的库的quartus ii版本&#xff0c;需要另下载 第二道坎&#xff0c;模拟器的下载&#xff0c;安装&#xff1b; 第三道&#xff0c;v…

Camera Raw:红眼

Camera Raw 的红眼 Red Eye面板可高效地修正照片中的红眼现象。 红眼现象通常是由于闪光灯直接照射到眼睛内的视网膜所引起的&#xff0c;在摄影中常见于低光环境下的拍摄&#xff0c;尤其是在人物和宠物照片中。 在一些老照片中可能存在红眼现象&#xff0c;现代摄影技术基本上…

图像的反转

图像颜色的反转一般分为两种&#xff1a;一种是灰度图片的颜色反转&#xff0c;另一种是彩色图像的颜色反转。 本节使用的原图如下&#xff1a; 1.1 灰度图像颜色反转 灰度图像每个像素点只有一个像素值来表示&#xff0c;色彩范围在0-255之间&#xff0c;反转方法255-当前像…

Anaconda+Pycharm两个软件从头到尾下载流程

前言&#xff1a; 1、使用教程前&#xff0c;请将电脑上的所有的Python卸载掉。再下载Anaconda&#xff0c;Anaconda这个软件里面就含有python。 彻底删除python方法&#xff1a; 1、计算机——属性——高级系统设置——环境变量 2、查看电脑用户自己设计的环境变量&#x…

探索如何赋予对象迭代魔法,轻松实现非传统解构赋值的艺术

前言 今天下午在网上冲浪过程中看到这样一个问题 面试题&#xff1a;如何让 var [a, b] {a: 1, b: 2} 解构赋值成功&#xff1f; 据说是某大厂面试题&#xff0c;于是我学习了一下这个问题&#xff0c;写下这篇文章记录一下。 学习过程 要想解决这个问题首先要知道什么是解…

运维锅总详解计算机缓存

本文从OSI模型中的每一层缓存介绍、常见开源中间件缓存举例、TCP/IP协议栈中的缓存机制、操作系统中的缓存、访问缓存数据的时间范围统计等方面对计算机中的缓存进行详细介绍。希望对您有所帮助&#xff01; 一、OSI模型中的每一层缓存 1. 物理层&#xff08;Physical Layer&…

人工智能系列-numpy(一)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” Numpy是python语言的一个拓展程序库&#xff0c;支持大量的维度数组与矩阵计算&#xff0c;此外也针对数组运算提供大量的数学函数库 NumPy支持的数据类型比Python内置的类型要…

SwiftUI中List的liststyle样式及使用详解添加、移动、删除、自定义滑动

SwiftUI中的List可是个好东西&#xff0c;它用于显示可滚动列表的视图容器&#xff0c;类似于UITableView。在List中可以显示静态或动态的数据&#xff0c;并支持垂直滚动。List是一个数据驱动的视图&#xff0c;当数据发生变化时&#xff0c;列表会自动更新。针对List&#xf…

PyMuPDF 操作手册 - 09 API - Page属性方法和简短说明

文章目录 一、Page属性方法和简短说明一、Page属性方法和简短说明 https://pymupdf.readthedocs.io/en/latest/page.html Method/Attribute属性方法Short Description简短说明Page.add_caret_annot()仅限 PDF:添加插入符号注释Page.add_circle_annot()仅限 PDF:添加圆圈…

前端面试题7(单点登录)

如何实现单点登录 单点登录&#xff08;Single Sign-On&#xff0c;简称SSO&#xff09;是一种允许用户在多个应用系统中只需登录一次&#xff0c;就可以访问所有相互信任的应用系统的认证技术。实现前端单点登录主要依赖于后端的支持和一些特定的协议&#xff0c;如OAuth、Ope…

Postman使用教程

传统接口风格 RESTful风格 使用Postman完成测试用例目标&#xff1a; Postman教程 &#xff08;1&#xff09;准备工作&#xff0c;下载Postman新建 &#xff08;2&#xff09;登录接口调试-获取验证码 &#xff08;3&#xff09;登录接口调试-登录 &#xff08;4&#xff09;…