当前位置: 首页 > 科技观察

破案了!关于JavaGenericErasure

时间:2023-03-23 12:11:50 科技观察

大家好,我是“福尔摩斯”的二哥!由于最近经常在知乎上回答问题,所以马上把吴杜吃过的瓜发上来。我心想,这下XX彻底凉了。没想到报警的是骗钱案,当时我就炸了!不好的后遗症,让我满脑子都是破案,破案,破案,直到现在,还没有完全平息。这不,在《教妹学Java》专栏的第46讲:Generics:abouttypeerasure中,有读者提出了这样一个问题。我当时就决定:我一定要破这个案子!Java泛型这种表面一套,一套背后一套的做法,实在是可恶。妈的,为了让大家学点真本事,二哥也是拼了命。要简要回顾类型擦除,请查看以下代码片段。publicclassCmower{publicstaticvoidmethod(ArrayListlist){System.out.println("Arraylistlist");}publicstaticvoidmethod(ArrayListlist){System.out.println("Arraylistlist");}}从表层意识上,我们会认为ArrayList列表和ArrayList列表是两个不同的类型,因为String和Date是两个不同的类。但是,由于类型擦除,上面的代码将无法编译——编译器会提示错误:'method(ArrayList)'clasheswith'method(ArrayList)';bothmethodshavesameerasure意味着两个method()方法在类型擦除后的方法签名完全相同,这在Java中是不允许的。根据我们的假设:如果Java能够实现真正意义上的泛型,那么两个method()方法可以同时存在,就像方法重载一样。publicclassCmower{publicstaticvoidmethod(Stringlist){}publicstaticvoidmethod(Datelist){}}为什么Java不能实现真正意义上的泛型?其背后的原因是什么?一、兼容性Java在2004年已经积累了比较丰富的生态,如果将一个已有的类修改为泛型类,所有用户都需要重新修改源码并编译,这将导致之前构建的土地彻底毁坏Java1.4。想象一下,以前你的代码运行的很好,但是因为JDK的升级,所有的源代码都无法编译运行,会不会很痛苦?你再也不会爱上Java了吗?类型擦除完美实现了兼容性,Java1.5之后的类可以使用泛型,Java1.4之前没有使用泛型的类也可以保留,无需任何修改就可以运行在新版本的Java虚拟机上。老用户不受影响,新用户可以自由选择使用泛型,可谓一箭双雕。第二,并不是说“真正的泛型无法实现”Pizza,1996年的实验语言,在Java的基础上扩展了泛型。披萨教程地址:http://pizzacompiler.sourceforge.net/doc/tutorial.html在这里插入Java版本历史,大家有个时间轴的概念。1995年5月23日,Java语言诞生了。1996年1月,JDK1.0诞生。1997年2月18日,JDK1.1发布。1998年2月,JDK1.1下载量超过200万次。2000年5月8日,2000年5月29日JDK1.3发布,2004年9月30日18:00发布JDK1.4,J2SE1.5发布。也就是说,Pizza在JDK1.0版本上实现了“真正意义上的”。泛型,我举了两个例子,大家一看就明白了。第一个是StoreSomething,一个通用类,其标识符是大写A而不是熟悉的大写T。classStoreSomething{Asomething;StoreSomething(Asomething){this.something=something;}voidset(Asomething){this.something=something;}Aget(){returnssomething;}}这个A可以是任何合法的Java类型(比如字符串和整数):StoreSomethinga=newStoreSomething("I'mastring!");StoreSomethingb=newStoreSomething(17+4);b.set(9);inti=b.get();Strings=a.get();认真看这段代码,你会发现这就是我们想要的“真正的泛型”!A既可以是引用类型String,也可以是Basic数据类型int。注意Java1.5实现的泛型不允许是基本数据类型,只能是包装类型(比如Integer)。另外,Pizza的泛型类型也可以直接使用new关键字声明,Pizza编译器会根据构造函数的参数推断出具体的对象类型,是String还是int。要知道,由于Java泛型的类型擦除,我们开发者无法知道一个ArrayList到底是ArrayList还是ArrayList。ArrayListints=newArrayList();ArrayListstrs=newArrayList();System.out.println(ints.getClass());System.out.println(strs.getClass());输出:classjava.util.ArrayListclassjava.util.ArrayList都是ArrayList。那么,为什么Java没有采用Pizza的“真正泛型”呢?想必这是大家非常关心的一个问题。事实上,Java的核心开发团队对Pizza的泛型设计非常感兴趣,并与Pizza的设计者Martin和Phil取得了联系,创建了一个新项目GenericJava,试图在Java中加入泛型支持,但是不介绍Pizza的其他特性,比如函数式编程。这里有一些关于维基百科的更多信息。MartinOdersky是德国计算机科学家。他和其他人设计了Scala编程语言和GenericJava(以及之前的Pizza)。他实现的GenericJava编译器成为了Java编译器javac的基础。从事后的角度来看,Pizza的泛型设计和函数式编程非常具有历史前瞻性。不过,当时Java的核心开发团队似乎并不想将函数式编程引入Java。以至于Java在1.4之前还不支持泛型,为什么到了Java1.5就突然支持泛型了?当然,是时候不支持了。在没有泛型之前,我们可以这样写代码:ArrayListlist=newArrayList();list.add("沉默之王二");list.add(newDate());到底是String类型还是Date类型,可以脑补一下进入ArrayList看似很方便,但是检索的时候就悲剧了。字符串=list.get(1);这是获得它的方法吗?决不。必须添加强制转换。字符串=(字符串)list.get(1);但是我们知道,这行代码在运行的时候难免会出错:Exceptioninthread"main"java.lang.ClassCastException:java.util.Datecannotbecasttojava.lang.String这个返回是“兼容性”的问题。与其他编程语言不同,Java语言背负着沉重的历史包袱。在1.5之前,大量程序已经部署在生产环境中。这时候如果一刀切,原来没有使用泛型的代码会被直接干掉,后果不堪设想。Java一直强调兼容性。我认为这是Java能够被广泛使用的主要原因之一。开发人员不必担心升级Java版本。一段可以在JDK1.4上运行的代码放在JDK1.5上仍然可以运行。这里必须说明一下,J2SE1.5的发布是Java语言发展史上一个重要的里程碑。为了显示这个版本的重要性,J2SE1.5也被正式更名为JavaSE5.0,之后还会有JavaSE6.0、JavaSE7.0。...但是Java不支持高版本的JDK编译生成的字节码文件运行在低版本的JRE(JavaRuntimeEnvironment)上。对于泛型,兼容性在哪里?仔细看下面的代码。ArrayListints=newArrayList();ArrayListstrs=newArrayList();ArrayListlist;list=ints;list=strs;如果要实现泛型,必须保证前面的代码不受影响,上面的代码必须能够编译运行。我应该怎么办?我们只能做类型擦除!所谓“表面一回事,背后又一回事”!编译前检查一下泛型,ArrayList只能放Integer,ArrayList只能放String。这样你就不用担心类型转换错误了。但是在编译后的字节码文件中,并没有泛型类型,所有的对象都是放着的。Java的魔力就在这里。表面上看一切都是对象,但是出于性能的考虑,还有int、double等原始类型,但是原始类型不兼容Object,所以我们只能写ArrayList,开销很大。内存空间中的代码。这可能是Java泛型被抱怨的原因之一。好消息是,Valhalla项目正在努力解决这些由泛型擦除引起的遗留问题。Valhalla项目:一个正在进行的OpenJDK项目,计划为未来的Java版本添加改进的泛型支持。源码地址:http://openjdk.java.net/projects/valhalla/希望给我们带来真正意义上的泛型?也许JDK17会在9月份上市?如何?类型擦除的情况可以关闭对吧?我是“夏洛克·福尔摩斯”的二哥,下期见~本文转载自微信公众号《沉默之王二》,可通过以下二维码关注。转载本文请联系沉默王二公众号。