第29条:优先考虑泛型
Java泛型是增强代码类型安全性和可读性的核心工具。不使用泛型的代码会产生大量运行时强制转换,既不安全也容易出错。
一般来说,为集合声明参数化、jdk使用泛型方法都不太困难,很少会自己编写一个泛型,但这仍然值得我们花时间去学习。
优点
- 类型安全:在编译期捕获类型错误,避免ClassCastException。
java
// 非泛型 - 编译时无法发现类型错误
List list = new ArrayList();
list.add("string");
Integer i = (Integer) list.get(0); // 运行时ClassCastException
// 泛型 - 编译时发现错误
List<String> list = new ArrayList<>();
list.add("string");
Integer i = list.get(0); // 编译错误
-
表达力强:代码清楚地声明了它所操作的数据类型。
-
消除强制转换:使代码更简洁,减少"代码杂音"。
java
// 使用泛型前
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制转换
// 使用泛型后
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 自动转换
- 促进重用:泛型算法和数据结构可以安全地用于多种类型。
实践
例如编写一个泛型栈,我们一步步来实现。
实现一个简单的栈
java
// 优先设计为泛型类
public class Stack<E> {
private E[] elements;
private int size = 0;
public Stack(int initialCapacity) {
elements = new E[initialCapacity];
}
public void push(E e) {
elements[size++] = e;
}
public E pop() {
E result = elements[--size];
elements[size] = null; // 消除过时引用
return result;
}
}

因为泛型数组是不可具体化的。
两种解决方式:
- 方法1:创建Object数组强转为泛型数组
不过,你要确保非受检的转换是安全的。

- 方法2:将elements改为Object数组
同时在pop时强转为E。

当然这也是一个非受检的警告。
都可以通过@SuppressWarnings("unchecked")来消除。

根据第27条的建议,我们只要在包含未受检转换的任务上禁止警告,而不是在整个pop方法上禁止就可以。
两种解决方式的区别:
- 方法1只会转换一次,方法2在每次pop时都会进行转换。
- 方法1的可读性会更强,但会造成堆污染,虽然在此情况下并无危害。
堆污染其实就是因为泛型擦除,泛型变量指向错误类型的对象。
有限制类型参数
java
// 使用有界类型参数
public static <T extends Comparable<T>> T max(Collection<T> coll) {
T candidate = null;
for (T element : coll) {
if (candidate == null || element.compareTo(candidate) > 0) {
candidate = element;
}
}
return candidate;
}
可以限制泛型类型,例如只能是Comparable的子类型。
在泛型参数声明中,extends用于同时表示"继承自类"和"实现接口"。
注意点
看过第28条建议的会疑惑这不是相矛盾了,第28条鼓励优先使用列表而非数组。实际上不可能总是或者总想在泛型中使用列表。Java并不是生来就支持列表,因此有些泛型如 ArrayList,必须在数组上实现。为了提升性能,其他泛型如HashMap也在数组上实现。
总结
总而言之,使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全,也更加容易。在设计新类型的时候,要确保它们不需要这种转换就可以使用。这通常意味着要把类做成是泛型的。只要时间允许,就把现有的类型都泛型化。这对于这些类型的新用户来说会变得更加轻松,又不会破坏现有的客户端。