
最近看到第三版《Effective Java》中介绍泛型的PECS原则,写的挺好,在此记录下来。
一个自定义的栈假设我们自定义一个带有泛型的栈,保存元素时可以使用E[]或者Object[],代码分别如下所示。这里值得我们参考的点是,当使用@SuppressWarnings(“unchecked”)时,要给出注释,说明为什么可以忽略掉警告。
使用E[]保存元素使用E[]保存元素时,由于不能创建泛型数组,因此需要进行一次强转。
import java.util.Arrays; import java.util.EmptyStackException; public class Stack使用Object[]保存元素{ private static final int DEFAULT_INITIAL_CAPACITY = 16; private E[] elements; private int size; @SuppressWarnings("unchecked") public Stack() { this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(E e) { ensureCapacity(); elements[size++] = e; } public E pop() { if (size == 0) { throw new EmptyStackException(); } E result = elements[--size]; // 消除引用,防止内存泄漏 elements[size] = null; return result; } public boolean isEmpty() { return size == 0; } private void ensureCapacity() { if (elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
使用Object[]保存元素时,每次从数组中获取到元素后需要强转成E类型。
import java.util.Arrays; import java.util.EmptyStackException; public class StackV2PECS原则{ private static final int DEFAULT_INITIAL_CAPACITY = 16; private Object[] elements; private int size; public StackV2() { this.elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(E e) { ensureCapacity(); elements[size++] = e; } public E pop() { if (size == 0) { throw new EmptyStackException(); } // push时要求元素类型是E,因此这里的强转是安全的 @SuppressWarnings("unchecked") E result = (E) elements[--size]; // 消除引用,防止内存泄漏 elements[size] = null; return result; } public boolean isEmpty() { return size == 0; } private void ensureCapacity() { if (elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
PECS是Producer Extends,Consumer Super的缩写,具体的含义我们在下面进行介绍。
Producer Extends现在要扩展Stack的功能,增加一个pushAll方法,我们第一版的实现可能是这样的。
public void pushAll(Iterablesource) { for (E e : source) { push(e); } }
上面的写法在使用时可能会遇到问题,如下图所示。
为了解决这个问题,我们需要做出如下修改,也就是把参数类型改成Iterable extends E>,这是一种有限制的通配符类型(bounded wildcard type),意思是"E的某个子类型的Iterable接口"。pushAll的source参数产生E实例供Stack使用,也就是source是生产者,因此source的类型是Iterable extends E>。
public void pushAll(Iterable extends E> source) {
for (E e : source) {
push(e);
}
}
Consumer Super
现在要扩展Stack的功能,增加一个与pushAll对应的popAll方法,我们第一版的实现可能是这样的。
public void popAll(Collectiondestination) { while (!isEmpty()) { destination.add(pop()); } }
上面的写法在使用时可能会遇到问题,如下图所示。
为了解决这个问题,我们需要做出如下修改,也就是把参数类型改成Collection super E>,,意思是"E的某种超类集合”。popAll的destination参数通过Stack消费E实例,也就是destination是消费者,因此destination的类型是Collection super E>。
总结PECS原则也就是说,如果参数化类型表示一个生产者E,就使用 extends E>,如果参数化类型表示一个消费者E,则使用 super E>。