最近看到第三版《Effective Java》中介绍泛型的PECS原则,写的挺好,在此记录下来。
一个自定义的栈
假设我们自定义一个带有泛型的栈,保存元素时可以使用E[]或者Object[],代码分别如下所示。这里值得我们参考的点是,当使用@SuppressWarnings(“unchecked”)时,要给出注释,说明为什么可以忽略掉警告。
使用E[]保存元素
使用E[]保存元素时,由于不能创建泛型数组,因此需要进行一次强转。
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack<E> {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private E[] elements;
private int size;
/**
* elements数组中只能包含E类型的元素(调用push方法向栈中添加元素),因此这里的强转是安全的。
*/
@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[]保存元素
使用Object[]保存元素时,每次从数组中获取到元素后需要强转成E类型。
import java.util.Arrays;
import java.util.EmptyStackException;
public class StackV2<E> {
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原则
PECS是Producer Extends,Consumer Super的缩写,具体的含义我们在下面进行介绍。
Producer Extends
现在要扩展Stack的功能,增加一个pushAll方法,我们第一版的实现可能是这样的。
public void pushAll(Iterable<E> source) {
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(Collection<E> destination) {
while (!isEmpty()) {
destination.add(pop());
}
}
上面的写法在使用时可能会遇到问题,如下图所示。
为了解决这个问题,我们需要做出如下修改,也就是把参数类型改成Collection<? super E>,,意思是"E的某种超类集合”。popAll的destination参数通过Stack消费E实例,也就是destination是消费者,因此destination的类型是Collection<? super E>,如下所示。
public void popAll(Collection<? super E> destination) {
while (!isEmpty()) {
destination.add(pop());
}
}
总结
PECS原则也就是说,如果参数化类型表示一个生产者E,就使用<? extends E>,如果参数化类型表示一个消费者E,则使用<? super E>。java.util.ArrayList中也有类似的代码,大家也可以参考下~
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}