大概有限制通配符的使用是源于Java的泛型的不可变性,所谓的不可变性就是说对于两个Set<T1>和Set<T2>,不管T1和T2谁是谁的父类,Set<T1>和Set<T2>都不会是父子类的关系。
Java泛型的不可变性在应用中可能会遇到一些不方便的地方,尽管它很安全,比如一段这样的代码,加入我们要自己实现一个简单的List类如下:
public class MyList<T> { private T[] elements = null; private int cursor = -1; private static final int DEFAULT_CAPACITY = 10; public MyList() { this(DEFAULT_CAPACITY); } public MyList(int capacity) { @SuppressWarnings("unchecked") T[] t = (T[]) new Object[capacity]; elements = t; } private void allocateNew() { @SuppressWarnings("unchecked") T[] t = (T[]) new Object[elements.length * 2]; for (int i = 0; i < elements.length; i++) { t[i] = elements[i]; } elements = t; } public MyList<T> add(T t) { if (cursor >= elements.length - 1) { allocateNew(); } cursor++; elements[cursor] = t; return this; } public T get(int i) { return elements[i]; } public int size() { return elements.length; } public MyList<T> addAll(MyList<T> myList) { int size = myList.size(); for (int i = 0; i < size; i++) { add(myList.get(i)); } return this; }}
很简单,内部维护一个数组去实现列表,下面是使用这个类的代码:
public class InvariantTester { public static void main(String[] args) { MyList<CharSequence> charSeqList = new MyList<CharSequence>(); charSeqList.add("s").add("t").add("r").add("i").add("n").add("g"); MyList<String> stringList = new MyList<String>(); stringList.add("s").add("t").add("r").add("i").add("n").add("g"); charSeqList.addAll(stringList); }}
这里前面的charSeqList.add("s").add("t").add("r").add("i").add("n").add("g")这步是不会报错的,而后边的charSeqList.addAll(stringList)是通不过编译的,原因就是泛型的不可变性,这里就要通过有限制的通配符类型去解决了,很简单,修改一下addAll的方法声明就可以了:
public MyList<T> addAll(MyList<? extends T> myList) { int size = myList.size(); for (int i = 0; i < size; i++) { add(myList.get(i)); } return this; }
这里就是有限制通配符类型的一个典型应用,当然还可以使用<? super T>这种限制方式的。
为了在泛型的方法参数上获得最大限度的灵活性,就需要有限制通配符类型的参与了,这里需要重点介绍的是使用有限制通配符类型时的PECS(producer-extends, consumer-super)原则,也就是<? extends T>和<? super T>的使用时机选择的原则。
如果类型是一个生产者,那么就使用extends,如果是消费者,那么就用super。
比如前面的例子,addAll(MyList<? extends T> myList)这个方法的参数是为了给MyList类消费的,所以参数是生产者,下面再举个消费者的例子。还是上边的MyList类,我再添加一个方法进去:
public void copyTo(MyList<? super T> dstList) { dstList.addAll(this); }
基本就是addAll()方法的逆方法,这里的参数dstList就是一个消费者了,同样,稍稍修改一下InvariantTester,加入这个方法的使用:
public class InvariantTester { public static void main(String[] args) { MyList<CharSequence> charSeqList = new MyList<CharSequence>(); charSeqList.add("s").add("t").add("r").add("i").add("n").add("g"); MyList<String> stringList = new MyList<String>(); stringList.add("s").add("t").add("r").add("i").add("n").add("g"); charSeqList.addAll(stringList); stringList.copyTo(charSeqList); }}
到这里,有限制通配符类型就大致叙述到这里了,实际应用中可能会遇到非常复杂的应用,到时候就要具体问题具体分析了,我在工作中基本上用不到,也谈不上有什么经验了。