Java泛型(generics)是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。之前set都是没有<>,可以接受任何类型的参数,而错误插入可以通过编译和运行,直到取出结果时才会报错。Java深度历险(五)——Java泛型
泛型与类型擦除有关,下面一段简单讲了下原理和特性
正确理解泛型概念的首要前提是理解类型擦除(type erasure)。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的
List<Object>
和List<String>
等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。
很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:
- 泛型类并没有自己独有的Class类对象。比如并不存在
List<String>.class
或是List<Integer>.class
,而只有List.class
。- 静态变量是被泛型类的所有实例所共享的。对于声明为
MyClass<T>
的类,访问其中的静态变量的方法仍然是MyClass.myStaticVar
。不管是通过new MyClass<String>
还是new MyClass<Integer>
创建的对象,都是共享一个静态变量。- 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型
MyException<String>
和MyException<Integer>
的。对于JVM来说,它们都是MyException
类型的。也就无法执行与异常对应的catch语句。
第二十三条:请不要再新代码中使用原生态类型
因为泛型可以将错误提前在编译时发现,所有建议尽量使用泛型而不是原生态类型(即Set<Object>
而不是Set
)。其中Set<Object>
是参数化类型,表示可以包含任何对象类型的一个集合;Set<?>
是一个通配符,表示只能包含某种位置对象类型的一个集合;Set是原生态类型。前两种是安全的,最后一种是不安全的
第二十四条:消除非受检警告
尽可能消除每一个非受检警告。如果确定警告本身没问题,可以用注解@SuppressWarnings("unchecked")
.
第二十五条:列表优先于数组
数组和泛型的不同有两点:
- 数组是协变的,如果sub是super的子类,那么sub[]就是super[]的子类。但是对于不同类型T1和T2,
List<T1>
既不是List<T2>
的超类,也不是子类。 - 数组是具体化的,在运行时才检查元素类型约束;泛型是通过擦除来实现,编译时会强化类型信息。
不能创建泛型数组,因为泛型数组会带来语法问题。当得到泛型数组创建错误时(通常是混合使用泛型和数组),最好的办法是有限使用集合类型List<E>
.
在基于性能的考虑下,也可优先用数组 - -
第二十六条:优先考虑泛型 && 第二十七条:优先考虑泛型方法
主要讲了泛型类和泛型方法的使用。其中重要的就在于你何时进行类型转换,转换时要保证安全性。同时注意<T extends T>
的运用
第二十七条:利用有限制通配符来提升API灵活性
因为List<A>
和List<B>
互不为子类,所以会出现以下情况:
|
|
如果使用注释里的push和pop,会因为Error:(31, 37) java: 不兼容的类型: java.lang.Iterable<java.lang.Integer>无法转换为java.lang.Iterable<java.lang.Number>
这类不兼容问题而无法通过编译。因为虽然Integer是Number的子类,但是Iterable用了泛型,所以Iterable<Integer>
不是Iterable<Number>
的子类,无法转换。处理这种问题就需要利用有限通配符。
有限通配符分为<? extends T>
(表示T的某个子类)和 <? super T>
(表示T的某个父类)两种。一般再用通配符时遵守PECS原则:producer-extends,consumer-super。比如pushAll的参数src产生E的实例供stack使用,则用extends;popAll消费了Stack的E实例,则用super。同时,所有的comparablehe comparator都是消费者。
第二十九条:类型安全的异构容器
类型安全的异构容器主要是将Class<?>
作为key,Object作为value。在插入的时候保证type!=null,取出的时候通过type.cast进行类型转换,从而实现类型检查。比如下面的favorite(重点关注12行)
|
|
但是这里每个类都只能有一个value,感觉不是很实用,难道要value再是一个map?或者主要是这种Class<?>
背后的类型令牌的思想吧。验证一个类的类型除了用instanceof还可以用c1.asSubclass(c)函数判断c1是不是c的子类,如果是的话该函数返回c1。具体可以看这篇文章.
另外他讲了下Collections的checkedList<E>
等包装类,他就是对普通List<E>
做了包装,操作时进行typeCheck保证插入的对象严格是E类。
本文采用创作共用保留署名-非商业-禁止演绎4.0国际许可证,欢迎转载,但转载请注明来自http://thousandhu.github.io,并保持转载后文章内容的完整。本人保留所有版权相关权利。
本文链接:http://thousandhu.github.io/2016/10/09/第五章-泛型-effective-java/