利用有限制通配符来提升API的灵活性
作者:互联网
转载自:https://www.jianshu.com/p/67a4a891ad19
假设我们想要增加一个方法,让它按顺序将一系列的元素全部放在堆栈中。第一次尝试如下:
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}
这个方法编译时正确无误,但是并非尽如人意。如果Iterable的src元素类型与堆栈的完全匹配,就没有问题。但是假如有一个Stack<Number>,并且调用了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来使用这个类型:
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
修改之后,不仅Stack可以正确无误的编译,没有通过初始化pushAll声明进行编译的客户端代码也一样可以。因为Stack及其客户端正确无误的进行了编译,你就知道一切都是类型安全的了。
现在假设想要编写一个pushAll方法,使之与popAll方法相呼应。popAll方法从堆栈中弹出每个元素,并将这些元素添加到指定的集合中。初次尝试编写的popAll方法可能像下面这样:
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>的子类型。这一次通配符同样提供了一种解决办法。popAll的输入类型不应该为“E的集合”,而应该为“E的某种超类的集合“
:(这里的超类是确定的,因此E是它自身的一个超类型)。仍有一个通配符类型正符合此意:Collection<? super E>。让我们修改popAll来使用它:
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
做了这个变动之后,Stack和客户端代码就可以正确无误的编译了。
结论很明显:为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符
。如果某个输入参数即是生产者,又是消费者,那么通配符类型对你就没有好处了:因为你需要的是严格的类型匹配,这是不用任何通配符而得到的。
下面的助记便于让你记住要使用哪种通配符类型:
PECS stands for producer-extends, consumer-super
换句话说,如果参数化类型表示一个生产者T,就使用<? extends T>
;如果它表示一个消费者T,就使用<? super T>
。在我们的Stack示例中,pushAll的src参数产生E实例供Stack使用,因此src相应的类型为Iterable<? extends E>;popAll的src参数通过Stack消费E实例,因此dst相应的类型为Colletion<? super E>。PECS这个助记符突出了使用通配符类型的基本原则。Naftalin和Wadler称之为Get and Put Principle。
标签:灵活性,通配符,popAll,pushAll,API,类型,Stack,Iterable 来源: https://www.cnblogs.com/zysun999/p/16443074.html