其他分享
首页 > 其他分享> > 利用有限制通配符来提升API的灵活性

利用有限制通配符来提升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