Effective Java 阅读笔记
第五章 泛型
第 26 条 不要使用原生类型
随着泛型的普及,这条没什么可说的。
如果不知道具体类型,可以使用<?>
来代替。
第 27 条 消除 unchecked 警告
原生类型到泛型转换时,编译会有警告,可以使用@SuppressWarnings("unchecked")
来消除警告。并且应该在尽可能小的范围内使用 SuppressWarnings 注解
第 28 条 List 优于数组
数组是协变(Covariant),泛型是不变的(Invariant)。即子类数组可以赋值给父类数组,但是子类泛型不可赋值给父类泛型。
例子:
Object[] objectArray = new Long[1];
objectArray[0] = ""; // 抛出 java.lang.ArrayStoreException
这段代码是可以通过编译的,但是在运行时会抛出异常。
而下面这段代码直接编译不通过:java: 不兼容的类型: java.util.ArrayList<java.lang.Long>无法转换为java.util.List<java.lang.Object>
把运行时异常提前到了编译期,这无疑是非常好的:
// 编译报错
ArrayList<Object> list = new ArrayList<Long>();
泛型擦除:为了向前兼容,泛型检查只在编译期有效,运行时ArrayList<String>
和ArrayList
是没有区别的。
同时,由于泛型擦除,使得泛型和数组不能同时使用,因此无法创建出泛型数组(比如List<String>[]
)。
而且其他类型转化为泛型时,在运行时并不能检查,(T) other
这种类型转换其实就是转换成了Object。
第 29 条 优先考虑泛型
第 30 条 优先考虑泛型方法
实现一个类的时候,如果类或者方法比较通用,那就可以进行泛型实现,实现时可以使用通配符对泛型进行限定。
第 31 条 利用通配符提升灵活性
泛型是不变的,那就可以利用通配符来提升灵活性。
比如 Son 类继承 Father 类时,如果一个方法能处理 List<Father>
,正常来说应该也可以处理子类,那方法的参数就可以使用List<? extends Father>
,这样可以提升灵活性。更进一步,如果不是非要限定List类型,那么参数也可以使用Collection<? extends Father>
或者是Iterable<? extends Father>
这样的来提高灵活性。
但是不要使用通配符作为返回类型。
通配符基本原则:producer-extends, consumer-super (PECS),并且所有comparable和comparator都是消费者。
简单来说就是
<? extends T>
用于声明泛型集合中的元素是"生产者",即从集合中读取元素。你可以从中读取元素,但不能往里面写入元素。用于方法参数时,表示方法中只会使用或读取泛型类型,而不会修改它。<? super T>
用于声明泛型集合中的元素是"消费者",即可以向集合中写入元素。你可以往里面写入元素,但不能从中读取元素。用于方法参数时,表示方法中会修改泛型类型或向其中添加元素。
第 32 条 谨慎同时使用泛型和可变参数
可变参数底层就是一个数组,数组是协变的,不应和泛型一块使用。
但是也不是完全禁用,泛型可变参数方法需要使用@SaveVarargs
注解进行注释,此时编译期不再警告。
更推荐需要可变参数的时候传入一个List<>
,比如方法void aMethod(List<?>... lists)
推荐写成void aMethod(List<List<?>> lists)
第 33 条 优先考虑类型安全的异构容器
异构容器(Heterogeneous Container)是指可以存储不同类型元素的数据结构或容器。与之相对的是同构容器(Homogeneous Container),同构容器只能存储相同类型的元素。
使用泛型容器的时候,可以考虑保存Class<?>
对象来进行安全的类型转换。
但是由于泛型擦除原因,没有List<String>.class
或者List<Integer>.class
这样的类型,它们都是List.class