【笔记31】利用有限制通配符来提升API的灵活性

如第28条所述,参数化类型是 不可变的(invariant)。对两个不同类型T1和T2而言,List<T1>与List<T2>没有父子类型关系。

1、Extends

有时候,需要的灵活性要比不可变类型所能提供的更多。考虑第26条中的堆栈下面就是他的公共API:

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

假设增加一个方法,按顺序将一系列的元素放到堆栈中:

// pushAll method without wildcard type - deficient;  
public void pushAll(Iterable<E> src) {  
    for (E e: src)  
        push(e);  
}  

如果尝试这样做:

Stack<Number> s = new Stack<Number>();
Iterable<Integer> i = ...;
s.pushAll(integers);

从逻辑上讲,这样应该是允许的,因为Integer是Number的子类,应当允许将Integer放到类型为Number的堆栈中。但实际运行的时候会提示Iterable<Number>与Iterable<Integer>不兼容。原因在于Iterable<Integer>并不是Iterable<Number>的子类型(参数化类型是不可变的)。

Java提供了一种特殊的参数化类型,称为有限制的通配符类型来处理类似的情况。使用有限制的通配符Iterable<? extends E>即可解决这个问题(注意,确定了子类型后,第一个类型便都是自身的子类型),修改后的程序如下:

// Wildcard type for parameter that serves as an E producer  
public void pushAll(Iterable<? extends E> src) {  
    for (E e: src)  
        push(e);  
} 

修改之后,不仅Stack可以正确无误地编译,没有通过初始的pushAll声明进行编译的客户端代码也一样可以。因为Stack及其客户端正无误的进行了编译,你就知道一切都是类型安全的了。

2、Super

对应的,假如我们要编译一个popAll方法,初次尝试如下:

// popAll method without wildcart type - deficient;  
public void popAll(Collection<E> dst) {  
    while(!isEmpty())  
        dst.add(pop());  
}  

如果目标集合的元素类型与堆栈完全匹配,这段代码编译时还是会正确无误的。但是,假设你有一个Stack<Number>和类型Object变量,如果从堆栈中弹出一个元素,并将它保存在该变量中,它的编译和运行都不会出错,考虑如下代码 :

Stack<Number> numberStack = new Stack<Number>();  
Collection<Object> objects = ...;  
numberStack.popAll(objects);  

试着用上述的popAll版本编译这段代码,就会得到一个类似于第一次pushAll时的错误:Collection<Object>不是Collection<Number>的子类型。

对于这种情况,java同样提供了一种对应的有限制通配符来解决,popAll的输入参数类型不应该为“E的集合”,而应该为“E的某种超类的集合”。通配符:Collection<? super E>,根据这种方法修改后的代码如下:

public void popAll(Collection<? super E> dst) {  
    while(!isEmpty())  
        dst.add(pop());  
}  

3、总结

由上面这两种情况可以看出,有限制的通配符类型放宽了检查的类型,为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。如果某个输入参数既是生产者,又是消费者,那么通配符类型就没有什么好处了,因为需要的是严格的类型匹配,这是不用任何通配符而得到的。

下面的助记符便于让你记住要使用哪种通配符类型类型:
PESC表示producter-extends, consumer-super。

如果参数化类型表示一个T生产者,就使用<? extends T>;如果它表示一个T消费者,就使用<? super T>。
在我们的Stack实例中,pushAll的src参数产生E实例供Stack使用,因此src相应的类型为Iterable<? extends E>;popAll的dst参数通过Stack消费E实例,因此dst的相应类型为Collection<? super E>。PECS这个助记符突出了使用通配符类型的基本原则。Naftalin和wadler称之为Get and Put Principle

还要记住所有的comparable和comparator都是消费者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值