Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String>().getClass(); System.out.println(c1 == c2); /* Output true */
显然在平时使用中,ArrayList<Integer>()
和new ArrayList<String>()
是彻底不一样的类型,可是在这里,程序却的的确确会输出true
。编程
这就是Java泛型的类型擦除形成的,由于不论是ArrayList<Integer>()
仍是new ArrayList<String>()
,都在编译器被编译器擦除成了ArrayList
。那编译器为何要作这件事?缘由也和大多数的Java让人不爽的点同样——兼容性。因为泛型并非从Java诞生就存在的一个特性,而是等到SE5才被加入的,因此为了兼容以前并未使用泛型的类库和代码,不得不让编译器擦除掉代码中有关于泛型类型信息的部分,这样最后生成出来的代码实际上是『泛型无关』的,咱们使用别人的代码或者类库时也就不须要关心对方代码是否已经『泛化』,反之亦然。设计模式
在编译器层面作的这件事(擦除具体的类型信息),使得Java的泛型先天都存在一个让人很是难受的缺点:数组
在泛型代码内部,没法得到任何有关泛型参数类型的信息。
List<Integer> list = new ArrayList<Integer>(); Map<Integer, String> map = new HashMap<Integer, String>(); 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.
咱们期待的是获得泛型参数的类型,可是实际上咱们只获得了一堆占位符。this
public class Main<T> { public T[] makeArray() { // error: Type parameter 'T' cannot be instantiated directly return new T[5]; } }
咱们没法在泛型内部建立一个T
类型的数组,缘由也和以前同样,T
仅仅是个占位符,并无真实的类型信息,实际上,除了new
表达式以外,instanceof
操做和转型(会收到警告)在泛型内部都是没法使用的,而形成这个的缘由就是以前讲过的编译器对类型信息进行了擦除。spa
同时,面对泛型内部形如T var;
的代码时,记得多念几遍:它只是个Object,它只是个Object……设计
public class Main<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Main<String> m = new Main<String>(); m.set("findingsea"); String s = m.get(); System.out.println(s); } } /* Output findingsea */
虽然有类型擦除的存在,使得编译器在泛型内部其实彻底没法知道有关T
的任何信息,可是编译器能够保证重要的一点:内部一致性,也是咱们放进去的是什么类型的对象,取出来仍是相同类型的对象,这一点让Java的泛型起码仍是有用武之地的。code
代码片断四展示就是编译器确保了咱们放在t
上的类型的确是T
(即使它并不知道有关T
的任何类型信息)。这种确保其实作了两步工做:对象
set()
处的类型检验blog
get()
处的类型转换
这两步工做也成为边界动做。
public class Main<T> { public List<T> fillList(T t, int size) { List<T> list = new ArrayList<T>(); for (int i = 0; i < size; i++) { list.add(t); } return list; } public static void main(String[] args) { Main<String> m = new Main<String>(); List<String> list = m.fillList("findingsea", 5); System.out.println(list.toString()); } } /* Output [findingsea, findingsea, findingsea, findingsea, findingsea] */
代码片断五一样展现的是泛型的内部一致性。
如上看到的,但凡是涉及到确切类型信息的操做,在泛型内部都是没法共工做的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。
public class Main<T> { public T create(Class<T> type) { try { return type.newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { Main<String> m = new Main<String>(); String s = m.create(String.class); } }
代码片断六展现了一种用类型标签生成新对象的方法,可是这个办法很脆弱,由于这种办法要求对应的类型必须有默认构造函数,遇到Integer
类型的时候就失败了,并且这个错误还不能在编译器捕获。
进阶的方法能够用限制类型的显示工厂和模板方法设计模式来改进这个问题,具体能够参见《Java编程思想 (第4版)》P382。
public class Main<T> { public T[] create(Class<T> type) { return (T[]) Array.newInstance(type, 10); } public static void main(String[] args) { Main<String> m = new Main<String>(); String[] strings = m.create(String.class); } }
代码片断七展现了对泛型数组的擦除补偿,本质方法仍是经过显示地传递类型标签,经过Array.newInstance(type, size)
来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。
以上,泛型的第二部分的结束。