Java泛型的PECS原则

最近看到第三版《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();
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值