《Effective Java》阅读笔记-第五章

发布时间 2023-12-12 16:36:58作者: AliveCats

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