关于java中通配符的使用规则

来源:互联网 发布:淘宝手机回收站 编辑:程序博客网 时间:2024/05/21 17:54
关于通配符的使用:
在API中使用通配符比较需要技巧,但通配符可以使API代码灵活许多。
如果编写的是广泛使用的类库,一定要适当是使用通配符。通配符有一个原则,即:producer-extends,consumer-super(PECS).就是典型的生产者,消费者模型问题。
举例说明:
在Stack中有push方法,以及pushAll方法

public class Stack<E> implements Cloneable{
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    //@SuppressWarnings("unchecked")
    public Stack() {
        this.elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];
    }
    
    public Stack(Stack<E> stack){
        this.elements = stack.elements;
        this.size = stack.size;
    }
    
    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }
    
    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        size -= 1;
        E o = elements[size];
        elements[size] = null;
        return o;
    }
    
    //Ensure space for at least one more element.
    private void ensureCapacity() {
        if(elements.length == size)
            elements = Arrays.copyOf(elements, 2*size + 1);
    }
    
    public Stack clone(){
        try{
            Stack stack = (Stack)super.clone();
            stack.elements = elements.clone();
            return stack;
        }catch (CloneNotSupportedException ce){
            throw new AssertionError();
        }
        
    }
    
    public void pushAll(Iterable<E>  src) {
        for(E e:src){
            push(e);
        }
    }
    
    public void popAll(Collection<E> dst) {
            dst.add(pop());
    }
}

此时,在pushAll方法中未使用通配符,这样就会出现一个问题,例如,我们在main方法里执行这段代码便会报错:
List<Integer> list = new ArrayList<Integer>();
list.add(734555);
list.add(3234);
list.add(234);
int s = list.set(1, 32444);
System.out.println(s);

Stack<Number> stack = new Stack<Number> ();
stack.push(12);
stack.pushAll(list);

报错原因是:
The method pushAll(Iterable<Number>) in the type Stack<Number> is not applicable for the arguments
 (List<Integer>)

因为,虽然Integer是Number的一个子类型,但Iterable<Integer>却并不是Iterable<Number>的子类型。在此时因为push是填入作为生产者,根据原则,可以添加<? extends E> 通配符,如果你暂时还不理解这样做的原因,请先谨记,最开始的时候,楼主也不知道这个原则的目的,但结合经验后,便慢慢理解了,总之,此条规则你可以谨记:
producer-extends,consumer-super(PECS)。

据此,我们便可以将pushAll方法,修改为这样:
    public void pushAll(Iterable<? extends E>  src) {
        for(E e:src){
            push(e);
        }
    }
修改后我们发现,上述的main方法中的代码也不再报错了。其实通配符的使用不了解的人也许会觉得很困难,但只要记住这个(生产者-消费者原则)producer-extends,consumer-super(PECS),通配符的使用会变得相当容易。

同时,Stack还有popAll方法

    public void popAll(Collection<E> dst) {
            dst.add(pop());
    }

当我们对同类型的数据进行操作时,这段代码非常完美,没有任何问题,但这样显然是不够的,代码的灵活性被限制的太低太低了,例如下面这段代码:
我们在原来main方法的基础上,下面加上这段代码:
        List<Number> list = new ArrayList<Number>();
        list.add(734555);
        list.add(3234);
        list.add(234);
        
        Stack<Integer> stack = new Stack<Integer> ();
        stack.push(12);
        
        stack.popAll(list);

报错原因:
The method popAll(Collection<Number>) in the type Stack<Number> is not applicable for the arguments
 (List<Integer>)

 原因与pushAll相同,虽然Integer是Number的一个子类型,但Iterable<Integer>却并不是Iterable<Number>的子类型。在此时因为pop是取出作为消费者,根据原则,可以添加<? super E> 通配符,如果你暂时还不理解这样做的原因,请先谨记,最开始的时候,楼主也不知道这个原则的目的,但结合经验后,便慢慢理解了,总之,此条规则在你不理解时刻必须谨记:
 producer-extends,consumer-super(PECS)

据此,我们便可以将popAll方法,修改为这样:
    public void popAll(Collection<? super E> dst) {
            dst.add(pop());
    }

同时,请谨记一点,作为数组与集合比较的comparable与comparator始终是消费者,所以始终是<? super E>.
0 0
原创粉丝点击