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

news/2024/7/8 9:31:43 标签: jvm, python, 开发语言

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属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们在编码时能通过反射手段取得参数化类型的根本依据。


http://www.niftyadmin.cn/n/5536966.html

相关文章

无人机5公里WiFi低延迟图传模组,抗干扰、长距离、低延迟,飞睿智能无线通信新标杆

在科技日新月异的今天&#xff0c;我们见证了无数通信技术的飞跃。从开始的电报、电话&#xff0c;到如今的4G、5G网络&#xff0c;再到WiFi的广泛应用&#xff0c;每一次技术的革新都极大地改变了人们的生活方式。飞睿智能5公里WiFi低延迟图传模组&#xff0c;它以其独特的优势…

为什么salesforce需要设置社区端,而不是使用和内部员工同样的环境

虽然企业可能希望为客户和合作伙伴提供与内部员工相同的环境&#xff0c;但实际上这样做有几个显著的缺点和风险。这些包括&#xff1a; 安全性和权限管理&#xff1a;内部员工的系统通常包含敏感和机密的信息&#xff0c;例如财务数据、内部策略和未发布的产品信息。将客户和合…

【高级篇】第10章 Elasticsearch 集群管理与扩展

在本章中,我们将深入探讨Elasticsearch集群的管理与扩展策略,旨在帮助读者构建一个既能应对大规模数据处理需求,又能保持高可用性和弹性的系统架构。我们将从集群架构设计入手,解析不同节点的角色与配置,然后转向节点发现与配置同步机制,最后讨论水平扩展与容错策略,确保…

spring tx @Transactional 详解 `Advisor`、`Target`、`ProxyFactory

在Spring中&#xff0c;Transactional注解的处理涉及到多个关键组件&#xff0c;包括Advisor、Target、ProxyFactory等。下面是详细的解析和代码示例&#xff0c;解释这些组件是如何协同工作的。 1. 关键组件介绍 1.1 Advisor Advisor是一个Spring AOP的概念&#xff0c;它包…

OpenStack开源虚拟化平台(一)

目录 一、OpenStack背景介绍&#xff08;一&#xff09;OpenStack是什么&#xff08;二&#xff09;OpenStack的主要服务 二、计算服务Nova&#xff08;一&#xff09;Nova组件介绍&#xff08;二&#xff09;Libvirt简介&#xff08;三&#xff09;Nova中的RabbitMQ解析 OpenS…

Anaconda安装及配置+pytorch深度学习环境(2024复旦计算机工作站0705)

目录 前言 &#xff08;补充&#xff1a;四、安装GPU环境的pytorch&#xff09; 正文 一、Pytorch 二、Tensor 三、CUDA 四、其他技巧 五、数据 六、torch.nn 前言 深度学习越来越火啦&#xff0c;深入到各行各业&#xff0c;小北个人也对深度学习很感兴趣&#xff0…

推荐算法学习笔记2.1:基于深度学习的推荐算法-基于共线矩阵的深度推荐算法-NeuralCF模型

NeuralCF模型 NeuralCF模型将矩阵分解和逻辑回归思想进行结合&#xff0c;利用神经网络分别学习用户和物品的隐向量表示&#xff08;Embedding&#xff09;&#xff0c;然后将矩阵分解中的内积互操作替换成神经网络计算&#xff0c;从而更好地从特征中学习到有用的信息。 原论…

element el-table表格切换分页保留分页数据+限制多选数量

el-table表格并没有相关的方法来禁用表头里面的多选按钮 那么我们可以另辟蹊径&#xff0c;来实现相同的多选切换分页&#xff08;保留分页数据&#xff09; 限制多选数量的效果 <el-table:data"tableData"style"width: 100%">// 不使用el-talbe自带…