有这样一个类:
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public static <E> Set<E> union(Set<E> s1,Set s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
public void push(E e){
ensureCapacity();
elements[size++] = e;
}
public boolean isEmpty(){
return size == 0;
}
public E pop(){
if(size == 0)
throw new EmptyStackException();
E result = elements[size--];
elements[size] = null;
return result;
}
/**
* 检验是否到达顶点,到达的话,扩充数组
*/
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
现在需要增加一个方法:
public void pushAll(Iterable<E> src){
src.forEach(e->push(e));
}
这个方法在编译的时候正确无误,但是也不能总是正确,比如:
public static void main(String[] args) {
Stack<Number> s = new Stack<>();
List<Integer> list = new ArrayList<>();
s.pushAll(list);
}
在这种情况下,由于泛型的不可变性,导致不能添加,编译无法通过,但是从理解层面上来说,这应该是被允许的。number是可以接受integer类型的
为了增加方法的灵活性,可以这样编写:
public void pushAll(Iterable<? extends E> src){
src.forEach(e->push(e));
}
与pushAll相对应的,我们在新增一个popAll 方法:
public void popAll(Collection<E> dst){
while(!isEmpty())
dst.add(pop());
}
和上面方法相似,这个方法初一看并没有什么不妥。但是并不总是正确:
比如我想传递一个List<Object>进去接收,就像这样:
public static void main(String[] args) {
Stack<Number> s = new Stack<>();
List<Object> list = new ArrayList<>();
s.popAll(list);
}
同样编译无法通过,Collection<Number> c = new ArrayList<Object>()这是错误的:
但是从实际角度出发,这应该是被允许的,List<Object> 列表是可以添加Number类型的,所以这个方法依然有漏洞:
这个时候可以修改如下:
public void popAll(Collection<? super E> dst){
while(!isEmpty())
dst.add(pop());
}
这样的话,编译可以通过,而且类型也是安全的:
结论很明显。为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用统配符类型。如果某个输入参数既是生产者也是消费者,那么统配符就不在适用了。
PECS: producer-extends,cunsumer-super;
个人理解:如果参数表示这个类的生产者,就应该用extends,如果是这个类的消费者,就应该用super.
对 union进行修改:
public static <E> Set<E> union(Set<E> s1,Set s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
由于s1,s2,对于整个类来说是属于生产者,所以应该用extends:
public static <E> Set<E> union(Set<? extends E > s1,Set<? extends E> s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
注意返回类型依然是set<E>.不要用通配符类型作为返回类型。除了为用户提供额外额灵活性外,它也会要求用户必须使用通配符类型。
统配符类型对于用户来说应该是无形的,如果用户必须考虑通配符类型,类的API或许就会出错。
有这样一个方法:
public static <T extends Comparable<T>> T max(List<T> list){
Iterator<T> i = list.iterator();
T result = i.next();
while(i.hasNext()){
T t = i.next();
if(t.compareTo(result) > 0)
result = t;
}
return result;
}
根据原则该如何修改呢?
public static <T extends Comparable<? super T>> T max(List<? extends T> list){
Iterator<? extends T> i = list.iterator();
T result = i.next();
while(i.hasNext()){
T t = i.next();
if(t.compareTo(result) > 0)
result = t;
}
return result;
}
我们来分析一下:
- list中的泛型,对于类来说,无疑是生产者,生产出最大值,所以应该是extends
- comparable中的泛型,对于整个类来说,是用来消费产生顺序关系的,所以应该用super
针对于一下方法:
public static <E> void swap(List<E> list,int i,int j){};
public static void swap(List<?> list,int i,int j);
这两种方法,那种方法更好呢?第二种会更好一些,因为它更加简单,在整个静态方法中,泛型其实只出现了一次,,这种情况下,是可以用通配符取代它的。但是第二个方法有一个问题:
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 static <E> void swapHelper(List<E> list,int i,int j){
list.set(i,list.set(j,list.get(i)));
}
这样既能提供简洁的api,也能达到捕获通配符的目的: