1. 泛型的缺点
泛型(参数化类型)有个优点,即确定了就不会改变,例如List
2. 有限制通配符
为了加入更多的通用性,Java提供了一种特殊的参数化类型,有限制通配符类型,如下代码,使用了泛型E,在生成实例的时候确定类型,addAll可以添加E及其子类,不会报错。
import java.util.ArrayList;
import java.util.List;
/**
* @author: qujundong
* @date: 2020/11/29 下午3:25
* @description:
*/
public class BoundedWildcard<E> {
private List<E> list = new ArrayList<>();
public void pushAll(Iterable<? extends E> input){
for(E e : input)
list.add(e);
}
@Override
public String toString() {
return "BoundedWildcard{" +
"list=" + list +
'}';
}
public static void main(String[] args) {
BoundedWildcard<Object> card = new BoundedWildcard<>();
List<String> strs = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
strs.add("abc");
strs.add("bcd");
ints.add(1);
ints.add(2);
card.pushAll(strs);
card.pushAll(ints);
System.out.println(card);
}
}
3. 上界通配符,下界通配符
所以为了获得最大限度的灵活度,要在表示生产者或者消费者的输入参数上使用通配符类型。,但是如果输入既是生产者又是消费者,那就不要通配符。
如果参数化类型表示一个生产者T,就用<? extends T>,如果表示一个消费者T,就用<? super T>。
package item31;
import java.util.ArrayList;
import java.util.List;
/**
* @author: qujundong
* @date: 2020/11/29 下午3:25
* @description:
*/
public class BoundedWildcard<E> {
private List<E> list = new ArrayList<>();
public void pushAll(Iterable<? extends E> input){
for(E e : input)
list.add(e);
}
public void consumer(List<? super E> target, E src){
target.add(src);
}
public void product(List<? extends E> src){
for(E e : src){
list.add(e);
}
}
@Override
public String toString() {
return "BoundedWildcard{" +
"list=" + list +
'}';
}
}
上述代码consumer和product分别表示消费者和生产者,可以记为PECS:prodcucer-extends,consumer-super。这部分的内容在前文泛型详解中也说过。
两种声明swap函数的方式比较
// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);
这两个声明中的哪一个更可取,为什么? 在公共API中,第二个更好,因为它更简单。 你传入一个列表(任何列表),该方法交换索引的元素。 没有类型参数需要担心。 通常,如果类型参数在方法声明中只出现一次,请将其替换为通配符。 如果它是一个无限制的类型参数,请将其替换为无限制的通配符; 如果它是一个限定类型参数,则用限定通配符替换它。
第二个swap
方法声明有一个问题。 这个简单的实现不会编译:
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
因为通配符类型列表无法修改,因此对上述代码进行改进得到:
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
// Private helper method for wildcard capture
private static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
swapHelper
方法知道该列表是一个List <E>
。 因此,它知道从这个列表中获得的任何值都是E类型,并且可以安全地将任何类型的E
值放入列表中。 这个稍微复杂的swap
的实现可以干净地编译。 它允许我们导出基于通配符的漂亮声明,同时利用内部更复杂的泛型方法。 swap
方法的客户端不需要面对更复杂的swapHelper
声明,但他们从中受益。 辅助方法具有我们认为对公共方法来说过于复杂的签名。
总结:在你的API中使用通配符类型,虽然棘手,但使得API更加灵活。 如果编写一个将被广泛使用的类库,正确使用通配符类型应该被认为是强制性的。 记住基本规则: producer-extends, consumer-super(PECS)。 还要记住,所有Comparable
和Comparator
都是消费者。