第31项:利用有限制通配符来提升API的灵活性

  如28项所述,参数化类型是不可变的(invariant)。换句话说,对于任何两个截然不同的类型Type1和Type2而言,List既不是List的子类型,也不是它的超类型。虽然List不是List的子类型,这与直觉相悖,但是实际上很有意义。你可以将任何对象放进一个List中,却只能将字符串放进List中。因为List不能完成List所能做的所有事情,因此它不是子类型(由Liskov替换主体,第10项)。

  有时候,我们需要的灵活性要比不可变类型所能提供的更多,考虑第29项中的Stack类,下面是它的公共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);
}

  这个方法编译时正确无误,但是并非尽如人意。如果Iterable src的元素类型与堆栈的完全匹配,就没有问题。但是假如有一个Stack,并且调用了push(intVal),这里的intVal就是Integer类型。这是可以的,因为Integer是Number的一个子类型。因此从逻辑上来说,下面这个方法应该也可以:

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

  但是,如果尝试这么做,就会得到下面的错误信息,因为如前所述,参数化类型是不可变的:

StackTest.java:7: error: incompatible types: Iterable<Integer>
cannot be converted to Iterable<Number>
        numberStack.pushAll(integers);
                            ^

  幸运的是,有一种解决办法。Java提供了一种特殊的参数化类型,称作有限制的通配符类型(bounded wildcard type ),来处理类似的情况。pushAll的输入参数类型不应该为“E的Iterable接口”,而应该为“E的某个子类型的Iterable接口”,有一个通配符类型证符合此意:Iterable<? Extends E>。(使用关键字extends有些误导:回忆以下第29项中的说法,确定子类型(subtype)后,每个类型便都是自身的子类型,即便它没有将自身扩展。)我们修改以下pushAll来使用这个类型:

// Wildcard type for a parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
   
    for (E e : src)
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值