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);}@Overridepublic 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);}}@Overridepublic 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都是消费者。
