TIP27 优先考虑泛型方法
实现一个合并两个Set容器的方法
无泛型参数的方法
``` public static Set union(Set s1, Set s2){ Set result = new HashSet(s1); result.addAll(s2); return result; } ```
这个方法可以编译,但会有两个uncheck警告。
参考TIP.24,我们将方法改为如下实现:
``` public static <E> Set<E> union(Set<E> s1, Set<E> s2){ Set<E> result = new HashSet<E>(s1); result.addAll(s2); return result; } ```
现在不会有uncheck警告了,但仍有局限: 要求两个参数和返回值的三个集合类完全相同。
泛型方法的特性:无需明确指定类型参数的值
类型推导(type inference): 如果编译器发现union的两个参数都是
Set<String>
类型,那么就会知道类型参数E必须为String。这个过程称为类型推导。泛型静态工厂方法:
考虑创建一个Map:Map<String,List<String>> anagrams = new HashMap<String,List<String>>();
类型参数出现在了变量声明的左右两边,显得有些冗余,因此考虑编写一个泛型静态工厂方法:
public static <K, V> HashMap(K, V) newHashMap(){ return new HashMap<K, V); }
然后用下面这段简洁的代码取代上面那个重复的声明:
Map<String,List<String>> anagrams = newHashMap();
还有个更复杂的应用场景,我们以后会另外展开说明。
TIP28 利用有限制通配符来提升api的灵活性
对于任何两个截然不同的类型Type1和Type2而言,List<Type1>
既不是 List<Type2>
的子类型,也不是它的父类型。请参考TIP25,参数化类型是不可变的。
也许这违背直觉,但很有意义。你可以将任何对象放入一个 List<Object>
中,但只能将一个String对象放入 List<String>
中。
为了解决与此相关的问题,JAVA提供了一种特殊的参数化类型:称作有限制的通配符来处理类似的情况。
常用的两种: List<? extends E>
和 List<? super E>
考虑以下的Stack类:
public class Stack<E>{
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
考虑增加一个方法pushAll,让它按顺序将一系列的元素全部放到堆栈中,第一次尝试如下:
public void pushAll(Iterable<E> src){
for (E e : src){
push(e);
}
}
这个方法编译正确,但并不尽如人意。如果参数的元素类型与堆栈的元素类型完全匹配,就不会有问题。
但假如企图这样使用, 就会发生错误:
//haha,参数化类型是不可变的。
Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers);
很明显,pushAll的输入参数类型不应该为”E的Iterable接口”,而应该为”E的某个子类型的Iterable接口”;所以,使用 <? extends E>
可以解决这个问题。看看修改后的pushAll:
public void pushAll(Iterable<? extends E> src){
for (E e : src){
push(e);
}
}
考虑增加一个方法popAll,与pushAll相对,第一次尝试如下:
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的参数类型不应该为”E的集合”,而应该是”E的超类的集合”,看看修改后的popAll:
public void popAll(Collection<? super E> dst){
while(!isEmpty()){
dst.add(pop());
}
}
结论很明显,为了获得最大的灵活性,要在表示生产者或消费者的输入参数上使用通配符类型。如果某个输入参数既是消费者又是生产者,那么就无需使用通配符类型,因为此时必须要求严格的类型匹配。
PECS producer-extends, consumer-super
- 如果参数化类型表示一个T的生产者,就使用
<? extends T>
。例如pushAll方法的参数,就是提供T对象的生产者。 - 如果参数化类型表示一个T的消费者,就使用
<? super T>
。例如popAll方法的参数,就是需要使用T对象的消费者。