本文转载自微信公众号“程序”,转载请联系程序公众号。作为一名Java程序员,日常的编程早已离不开泛型。自从引入JDK1.5以来,泛型确实提高了生产力。一个简单的泛型T,只需几行代码,就可以让我们在使用过程中动态地替换成任何想要的类型,而无需实现繁琐的类型转换方法。虽然我们每天都在使用它,但是仍然有很多同学可能不了解其实现原理。在今天的文章中,我们将从以下几点来谈谈Java泛型:Java泛型的实现方式类型擦除缺陷Java泛型的发展历史Java泛型的实现方式Java采用**类型擦除泛型**的方式来实现泛型。用大白话来说,这种泛型只存在于源代码中。当编译器将源代码编译成字节码时,会将泛型“抹掉”,所以字节码中没有泛型。对于下面的代码,编译后我们使用javap-sclass查看字节码。方法源码字节码观察setParam部分的字节码,从描述符可以看出,泛型T被擦掉了,最后换成了Object。》ps:不是每个泛型参数在类型擦除后都会变成Object类,如果泛型类型是TextendsString,泛型擦除后最终会变成String。同理getParam方法,泛型返回值也换成了Object。为了保证Stringparam=genericType.getParam();代码的正确性,编译器不得不在这里插入类型转换。另外,编译器也会对泛型进行安全性防御,如果我们ArrayList中添加Integer,程序编译时会报错,类型擦除后的最终代码等同于:对比类型擦除带来的缺陷,简单说一下C#泛型的实现。**C#**泛型的实现方法是“可具体化的泛型(Reifiablegenerics)”,不熟悉C#的人不用担心具体化技术的概念,我不懂这些特性——!换句话说,格**C#**实现的nerics无论是在程序源码中,还是在编译之后,甚至在运行时,都是真实存在的。与C#泛型相比,Java泛型看起来像“伪”泛型。Java泛型仅存在于程序源代码中,编译后即被抹去。这种缺陷会相应地引起一些问题。不支持基本数据类型的泛型参数被擦除后,强制变成Object类型。对引用类型这样做没有错,毕竟Object是所有类型的超类型。但是对于int/long等八种基本数据类型来说,这是有难度的。因为Java无法在int/long和Object之间进行转换。如果要实现这种转换,需要进行一系列的修改,而且修改量不小。所以当时Java给出了一个简单粗暴的解决方案:既然没有办法转换,那就干脆不支持原始类型泛型。如果需要使用,指定相关包装类的泛型,如ArrayList。另外为了方便开发者,顺便增加了原生数据类型自动拆箱/装箱的功能。正是这种“偷懒”的做法导致我们无法使用原始类型泛型,不得不忍受包装类的装箱/拆箱带来的开销,进而带来运行效率问题。运行效率我们在上面的字节码例子中看到,泛型擦除后类型会变成Object。当泛型出现在方法的输入位置时,由于Java可以向上转化,不需要强制类型转换,所以没有问题。但是当泛型参数出现在方法的输出位置(返回值)时,调用方法的地方需要向下转换,将Object转换成需要的类型,所以编译器会插入一个checkcast字节码。除此之外,我们上面也提到了原始的基本数据类型,编译器也需要帮我们进行装箱/拆箱。所以对于下面这段代码:Listlist=newArrayList();list.add(66);//1intnum=list.get(0);//2对于①,编译器需要的都是你所要做的就是添加基本类型的拳击。但是对于第二步,编译器首先需要将Object转换为Integer,然后编译器需要拆箱。类型擦除后,上述代码等价于:Listlist=newArrayList();list.add(Integer.valueOf(66));intnum=((Integer)list.get(0)).intValue();如果上面的泛型代码是用C#实现的,那么就不会多出那么多步骤了。因此,Java的类型擦除泛型实现方式在使用效果和运行效率上都落后于C#的可实现泛型。运行时无法获取泛型的实际类型由于泛型在编译后被擦除,Java虚拟机在代码执行过程中无法获取泛型的实际类型。下面的代码中,从源码中看,这两个List似乎是不同类型的集合,但是经过泛型擦除后,集合变成了ArrayList。所以if语句里面的代码会被执行。ArrayListli=newArrayList();ArrayListlf=newArrayList();if(li.getClass()==lf.getClass()){//泛型擦除,两个两个列表类型都是一样的System.out.println("6666");}这段代码看起来有点反直觉,对新手不是很友好。另外,也会给我们的实际使用带来一些限制。例如,我们不能直接实现下面的代码:最后举个例子,比如我们需要实现一个将泛型List转换为数组的方法,我们不能直接从List中获取实际的泛型类型,我们有传入一个额外的类类型来指定数组的类型:publicstaticE[]convert(Listlist,ClasscomponentType){E[]array=(E[])Array.newInstance(componentType,list.size());....}从上面的例子可以看出,Java使用类型擦除来实现泛型,存在很多缺陷。那为什么Java不采用C#的泛型实现方式呢?还是采用更好的实现方式?这个问题有待我们了解Java泛型机制的历史和当时Java语言的现状,才能了解当时的Java。这种通用实现的原因。Java泛型的历史背景Java泛型最早是在JDK5中引入的,但是泛型的思想最早来自于C++模板。1996年,MartinOdersky(Scala语言的创造者)在新发布的Java的基础上扩展了泛型、函数式编程等功能,形成了一门新的语言——“Pizza”。后来,Java核心开发团队对Pizza的泛型设计产生了浓厚的兴趣,与Martin合作,共同开发了一个新项目“GenericJava”。该项目的目的是为Java添加对泛型的支持,而不是引入函数式编程等特性。最终成功在Java5中正式引入泛型支持。通用移植过程一开始并没有朝着类型擦除的方向发展。事实上,Pizza中的泛型更类似于C#中的泛型。但是由于Java自身的特点和严格的约束,Martin在GenericJava的开发过程中不得不放弃了Pizza中的泛型设计。这个特性是Java需要做到严格的向后兼容。也就是说,一个在JDK1.2中编译出来的Class文件,不仅能在JDK1.2中正常运行,还必须保证在后续的JDK中也能正常运行,比如JDK12。此功能被明确写入Java语言规范,是对Java用户的郑重承诺。》这里要强调的是,这里的向后兼容指的是二进制兼容,而不是源代码兼容,不能保证高版本的Class文件可以在低版本的JDK上运行,现在的难点在于Java1.4.2之前是不支持泛型的,但是Java5突然支持了泛型,JDK1.4之前编译的程序在新版本中可以正常运行,也就是说之前没有限制,不能突然增加。例如Example:ArrayListarrayList=newArrayList();arrayList.add("6666");arrayList.add(Integer.valueOf(666));在没有泛型之前,List集合可以存储不同类型的数据,所以引入泛型之后,这段代码必须能够运行为了保证这些旧的Clas文件在Java5之后能够正常运行,设计者基本上有两条路:需要泛型的容器(主要是容器类型),部分保持之前不变,增加一套新的泛型ve并行处理。直接泛化现有类型,而不添加现有类型的任何新泛型版本。如果Java采用第一种方式来实现,那么我们现在可能有两套集合类型。以ArrayList为例,一组是普通的java.util.ArrayList,另一组可能是java.util.generic.ArrayList。采用这种方案后,如果开发中需要使用泛型特性,那就直接使用新的类型。另外,旧代码也可以不加修改直接在新版JDK中运行。这个方案看似没问题,其实C#就是采用了这个方案。但是为什么Java没有使用这个解决方案呢?这是因为C#当时才发布两年,历史代码不多。如果旧代码需要使用通用的特性,就会很快进行改造。但是Java不同。那时Java已经发布十年了,很多程序已经在生产环境中运行和部署了。可想而知,其中有许多历史密码。如果这些应用程序需要在新版本的Java中使用泛型,那么将需要大量的源代码改动,开发工作量可想而知。另外,在Java5之前,我们已经有了两套集合容器,一套用于Vector/Hashtable等容器,一套用于ArrayList/HashMap。这两组容器的存在,其实也造成了一些不便。对于新的Java开发人员来说,他们必须了解两者之间的区别。如果此时为泛型引入新的类型,那么四套容器将同时并存。想想这张图,一个新接触的开发者,面对四套容器,不知该如何选择。Java究竟是如何做到这一点的,想必会有更多人吐槽Java。所以Java选择了第二种方式,使用类型擦除,只需要改Javac编译器,不需要改字节码,不需要改虚拟机,也保证了以往没有泛型的代码可以仍然在新的JDK运行中使用。但是,第二种方式并不意味着必须使用类型擦除实现。如果有足够的时间好好设计,或许会有更好的解决方案。当年遗留下来的技术债只能由瓦尔哈拉项目来偿还。这个项目从2014年开始成立,最初是为了解决JDK10现有语言的各种缺陷。但是结果我们也知道现在JDK14已经出来了,只完成了一小部分目标,核心目标还没有解决,可见这次改动的难度。总结一下这篇文章,我们先从Java泛型的底层实现说起,然后举几个例子让大家知道目前的泛型实现存在一些缺陷。那么我们带入Java泛型的历史背景,站在Java核心开发者的角度,就能理解为什么Java泛型如此现实和无奈。最后,作为Java开发者,让我们少抱怨Java的缺点,多了解。相信未来Java核心开发者一定会解决泛型存在的缺陷,让我们拭目以待。帮助信息https://www.zhihu.com/question/38940308https://www.zhihu.com/question/28665443https://hllvm-group.iteye.com/group/topic/25910http://blog.zhaojie。me/2010/05/why-java-sucks-and-csharp-rocks-4-generics.htmlhttp://blog.zhaojie.me/2010/04/why-java-sucks-and-csharp-rocks-2-原始类型和对象方向.htmlhttps://en.wikipedia.org/wiki/Generics_in_Javahttps://www.zhihu.com/question/34621277/answer/59440954https://www.artima.com/scalazine/文章/origins_of_scala.html