跟我学(Effective Java 2)第28条:利用有限制通配符来提升API灵活性

第28条:利用有限制通配符来提升API灵活性

参数化类型是 不可变的(invariant)。换句话说,对于任何两个截然不同的类型tyle1和type2来说,List既不是List的子类型,也不是他的超类型。虽然List不是List的子类型,这与直觉相悖,但是实际上很有意义。你可以将任何对象放进一个List中,却只能将字符串放进中。

有时候,我需要的灵活性要比不可变类型所能提供的更多。

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

假设我们想要增加一个方法,让她按照顺序将一系列的元素全部放到堆栈中。这是第一次尝试:

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<Number>();  
Iterable<Integer> integers = ...;  
numberStack.pushAll(integers); 

但实际运行的时候会提示Iterable与Iterable不兼容。原因在于Iterable并不是Iterable的子类型(参数化类型是不可变的,相应的概念为,数组是协变的),幸运 的是Java提供了一种解决方法,称为有限制的通配符类型来处理这种情况。使用有限制的通配符Iterable<? extends E>即可解决这个问题。

public void pushAll(Iterable<? extends E> src) {  
    for (E e: src)  
        push(e);  
} 

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

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

与未修改的putAll一样,应当允许类型为Number的栈帧放在包括Number在内的父类型中。

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

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

假设有一个List,想通过Function把他简化。他不能通过初始声明进行编译,但是一旦添加了有限制的通配符类型就可以了。

    //限制通配符类型的安全风险
    private static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2){
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

    Set<Integer> integers = new HashSet<>();
    Set<Double> doubles = new HashSet<>();
    //该方法调用并没有出现问题,但运行时会报错。
    Set<Number> numbers = union(integers,doubles);

不过幸运的是,有一种方法可以处理这种错误。如果编译器不能推断你希望它拥有的类型,可以通过显式的类型参数来告诉它要使用那种类型。

Set<Number> numbers = Union.<Number>union(integers, doubles); 

总之,在API中使用通配符类型虽然比较需要技巧,但是使API变得灵活的多,如果在写的是一个将被广泛使用的类库,则一定要适当地利用通配符类型,记住基本的原则:PECS,还要记得所有的comparable和comparator都是消费者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值