博客地址:Java泛型:类型擦除
前情回顾Java泛型:泛型类、泛型接口和泛型方法
类型擦除 代码片段一Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList ().getClass(); System.out.println(c1 == c2); /* Output true */
显然在平时使用中,ArrayList
这就是Java泛型的类型擦除造成的,因为不管是ArrayList
在编译器层面做的这件事(擦除具体的类型信息),使得Java的泛型先天都存在一个让人非常难受的缺点:
代码片段二在泛型代码内部,无法获得任何有关泛型参数类型的信息。
Listlist = new ArrayList (); Map map = new HashMap (); System.out.println(Arrays.toString(list.getClass().getTypeParameters())); System.out.println(Arrays.toString(map.getClass().getTypeParameters())); /* Output [E] [K, V] */
关于getTypeParameters()的解释:
Returns an array of TypeVariable objects that represent the type variables declared by the generic declaration represented by this GenericDeclaration object, in declaration order. Returns an array of length 0 if the underlying generic declaration declares no type variables.
我们期待的是得到泛型参数的类型,但是实际上我们只得到了一堆占位符。
代码片段三public class Main{ public T[] makeArray() { // error: Type parameter "T" cannot be instantiated directly return new T[5]; } }
我们无法在泛型内部创建一个T类型的数组,原因也和之前一样,T仅仅是个占位符,并没有真实的类型信息,实际上,除了new表达式之外,instanceof操作和转型(会收到警告)在泛型内部都是无法使用的,而造成这个的原因就是之前讲过的编译器对类型信息进行了擦除。
同时,面对泛型内部形如T var;的代码时,记得多念几遍:它只是个Object,它只是个Object……
代码片段四public class Main{ private T t; public void set(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Main m = new Main (); m.set("findingsea"); String s = m.get(); System.out.println(s); } } /* Output findingsea */
虽然有类型擦除的存在,使得编译器在泛型内部其实完全无法知道有关T的任何信息,但是编译器可以保证重要的一点:内部一致性,也是我们放进去的是什么类型的对象,取出来还是相同类型的对象,这一点让Java的泛型起码还是有用武之地的。
代码片段四展现就是编译器确保了我们放在t上的类型的确是T(即便它并不知道有关T的任何类型信息)。这种确保其实做了两步工作:
set()处的类型检验
get()处的类型转换
这两步工作也成为边界动作。
代码片段五public class Main{ public List fillList(T t, int size) { List list = new ArrayList (); for (int i = 0; i < size; i++) { list.add(t); } return list; } public static void main(String[] args) { Main m = new Main (); List list = m.fillList("findingsea", 5); System.out.println(list.toString()); } } /* Output [findingsea, findingsea, findingsea, findingsea, findingsea] */
代码片段五同样展示的是泛型的内部一致性。
擦除的补偿如上看到的,但凡是涉及到确切类型信息的操作,在泛型内部都是无法共工作的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。
代码片段六public class Main{ public T create(Class type) { try { return type.newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { Main m = new Main (); String s = m.create(String.class); } }
代码片段六展示了一种用类型标签生成新对象的方法,但是这个办法很脆弱,因为这种办法要求对应的类型必须有默认构造函数,遇到Integer类型的时候就失败了,而且这个错误还不能在编译器捕获。
进阶的方法可以用限制类型的显示工厂和模板方法设计模式来改进这个问题,具体可以参见《Java编程思想 (第4版)》P382。
代码片段七public class Main{ public T[] create(Class type) { return (T[]) Array.newInstance(type, 10); } public static void main(String[] args) { Main m = new Main (); String[] strings = m.create(String.class); } }
代码片段七展示了对泛型数组的擦除补偿,本质方法还是通过显示地传递类型标签,通过Array.newInstance(type, size)来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。
以上,泛型的第二部分的结束。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64658.html
摘要:然而中的泛型使用了类型擦除,所以只是伪泛型。总结本文介绍了泛型的使用,以及类型擦除相关的问题。一般情况下泛型的使用比较简单,但是某些情况下,尤其是自己编写使用泛型的类或者方法时要注意类型擦除的问题。 简介 Java 在 1.5 引入了泛型机制,泛型本质是参数化类型,也就是说变量的类型是一个参数,在使用时再指定为具体类型。泛型可以用于类、接口、方法,通过使用泛型可以使代码更简单、安全。然...
摘要:可以看到,如果我们给泛型类制定了上限,泛型擦除之后就会被替换成类型的上限。相应的,泛型类中定义的方法的类型也是如此。参考语言类型擦除下界通配符和的区别 本篇博客主要介绍了Java类型擦除的定义,详细的介绍了类型擦除在Java中所出现的场景。 1. 什么是类型擦除 为了让你们快速的对类型擦除有一个印象,首先举一个很简单也很经典的例子。 // 指定泛型为String List list1 ...
摘要:静态变量是被泛型类的所有实例所共享的。所以引用能完成泛型类型的检查。对于这个类型系统,有如下的一些规则相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。事实上,泛型类扩展都不合法。 前言 和C++以模板来实现静多态不同,Java基于运行时支持选择了泛型,两者的实现原理大相庭径。C++可以支持基本类型作为模板参数,Java却只能接受类作为泛型参数;Java可以在泛型类的方法中取得...
阅读 891·2021-10-13 09:39
阅读 1480·2021-10-11 10:57
阅读 2589·2019-08-26 13:53
阅读 2536·2019-08-26 12:23
阅读 3679·2019-08-23 18:30
阅读 3744·2019-08-23 18:08
阅读 2522·2019-08-23 18:04
阅读 2958·2019-08-23 16:28