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