关于通配符的使用:
在API中使用通配符比较需要技巧,但通配符可以使API代码灵活许多。
如果编写的是广泛使用的类库,一定要适当是使用通配符。通配符有一个原则,即:producer-extends,consumer-super(PECS).就是典型的生产者,消费者模型问题。
举例说明:
在Stack中有push方法,以及pushAll方法
public class Stack<E> implements Cloneable{
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//@SuppressWarnings("unchecked")
public Stack() {
this.elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];
}
public Stack(Stack<E> stack){
this.elements = stack.elements;
this.size = stack.size;
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
size -= 1;
E o = elements[size];
elements[size] = null;
return o;
}
//Ensure space for at least one more element.
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2*size + 1);
}
public Stack clone(){
try{
Stack stack = (Stack)super.clone();
stack.elements = elements.clone();
return stack;
}catch (CloneNotSupportedException ce){
throw new AssertionError();
}
}
public void pushAll(Iterable<E> src) {
for(E e:src){
push(e);
}
}
public void popAll(Collection<E> dst) {
dst.add(pop());
}
}
此时,在pushAll方法中未使用通配符,这样就会出现一个问题,例如,我们在main方法里执行这段代码便会报错:
List<Integer> list = new ArrayList<Integer>();
list.add(734555);
list.add(3234);
list.add(234);
int s = list.set(1, 32444);
System.out.println(s);
Stack<Number> stack = new Stack<Number> ();
stack.push(12);
stack.pushAll(list);
报错原因是:
The method pushAll(Iterable<Number>) in the type Stack<Number> is not applicable for the arguments
(List<Integer>)
因为,虽然Integer是Number的一个子类型,但Iterable<Integer>却并不是Iterable<Number>的子类型。在此时因为push是填入作为生产者,根据原则,可以添加<? extends E> 通配符,如果你暂时还不理解这样做的原因,请先谨记,最开始的时候,楼主也不知道这个原则的目的,但结合经验后,便慢慢理解了,总之,此条规则你可以谨记:
producer-extends,consumer-super(PECS)。
据此,我们便可以将pushAll方法,修改为这样:
public void pushAll(Iterable<? extends E> src) {
for(E e:src){
push(e);
}
}
修改后我们发现,上述的main方法中的代码也不再报错了。其实通配符的使用不了解的人也许会觉得很困难,但只要记住这个(生产者-消费者原则)producer-extends,consumer-super(PECS),通配符的使用会变得相当容易。
同时,Stack还有popAll方法
public void popAll(Collection<E> dst) {
dst.add(pop());
}
当我们对同类型的数据进行操作时,这段代码非常完美,没有任何问题,但这样显然是不够的,代码的灵活性被限制的太低太低了,例如下面这段代码:
我们在原来main方法的基础上,下面加上这段代码:
List<Number> list = new ArrayList<Number>();
list.add(734555);
list.add(3234);
list.add(234);
Stack<Integer> stack = new Stack<Integer> ();
stack.push(12);
stack.popAll(list);
报错原因:
The method popAll(Collection<Number>) in the type Stack<Number> is not applicable for the arguments
(List<Integer>)
原因与pushAll相同,虽然Integer是Number的一个子类型,但Iterable<Integer>却并不是Iterable<Number>的子类型。在此时因为pop是取出作为消费者,根据原则,可以添加<? super E> 通配符,如果你暂时还不理解这样做的原因,请先谨记,最开始的时候,楼主也不知道这个原则的目的,但结合经验后,便慢慢理解了,总之,此条规则在你不理解时刻必须谨记:
producer-extends,consumer-super(PECS)
据此,我们便可以将popAll方法,修改为这样:
public void popAll(Collection<? super E> dst) {
dst.add(pop());
}
同时,请谨记一点,作为数组与集合比较的comparable与comparator始终是消费者,所以始终是<? super E>.
在API中使用通配符比较需要技巧,但通配符可以使API代码灵活许多。
如果编写的是广泛使用的类库,一定要适当是使用通配符。通配符有一个原则,即:producer-extends,consumer-super(PECS).就是典型的生产者,消费者模型问题。
举例说明:
在Stack中有push方法,以及pushAll方法
public class Stack<E> implements Cloneable{
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//@SuppressWarnings("unchecked")
public Stack() {
this.elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];
}
public Stack(Stack<E> stack){
this.elements = stack.elements;
this.size = stack.size;
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
size -= 1;
E o = elements[size];
elements[size] = null;
return o;
}
//Ensure space for at least one more element.
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2*size + 1);
}
public Stack clone(){
try{
Stack stack = (Stack)super.clone();
stack.elements = elements.clone();
return stack;
}catch (CloneNotSupportedException ce){
throw new AssertionError();
}
}
public void pushAll(Iterable<E> src) {
for(E e:src){
push(e);
}
}
public void popAll(Collection<E> dst) {
dst.add(pop());
}
}
此时,在pushAll方法中未使用通配符,这样就会出现一个问题,例如,我们在main方法里执行这段代码便会报错:
List<Integer> list = new ArrayList<Integer>();
list.add(734555);
list.add(3234);
list.add(234);
int s = list.set(1, 32444);
System.out.println(s);
Stack<Number> stack = new Stack<Number> ();
stack.push(12);
stack.pushAll(list);
报错原因是:
The method pushAll(Iterable<Number>) in the type Stack<Number> is not applicable for the arguments
(List<Integer>)
因为,虽然Integer是Number的一个子类型,但Iterable<Integer>却并不是Iterable<Number>的子类型。在此时因为push是填入作为生产者,根据原则,可以添加<? extends E> 通配符,如果你暂时还不理解这样做的原因,请先谨记,最开始的时候,楼主也不知道这个原则的目的,但结合经验后,便慢慢理解了,总之,此条规则你可以谨记:
producer-extends,consumer-super(PECS)。
据此,我们便可以将pushAll方法,修改为这样:
public void pushAll(Iterable<? extends E> src) {
for(E e:src){
push(e);
}
}
修改后我们发现,上述的main方法中的代码也不再报错了。其实通配符的使用不了解的人也许会觉得很困难,但只要记住这个(生产者-消费者原则)producer-extends,consumer-super(PECS),通配符的使用会变得相当容易。
同时,Stack还有popAll方法
public void popAll(Collection<E> dst) {
dst.add(pop());
}
当我们对同类型的数据进行操作时,这段代码非常完美,没有任何问题,但这样显然是不够的,代码的灵活性被限制的太低太低了,例如下面这段代码:
我们在原来main方法的基础上,下面加上这段代码:
List<Number> list = new ArrayList<Number>();
list.add(734555);
list.add(3234);
list.add(234);
Stack<Integer> stack = new Stack<Integer> ();
stack.push(12);
stack.popAll(list);
报错原因:
The method popAll(Collection<Number>) in the type Stack<Number> is not applicable for the arguments
(List<Integer>)
原因与pushAll相同,虽然Integer是Number的一个子类型,但Iterable<Integer>却并不是Iterable<Number>的子类型。在此时因为pop是取出作为消费者,根据原则,可以添加<? super E> 通配符,如果你暂时还不理解这样做的原因,请先谨记,最开始的时候,楼主也不知道这个原则的目的,但结合经验后,便慢慢理解了,总之,此条规则在你不理解时刻必须谨记:
producer-extends,consumer-super(PECS)
据此,我们便可以将popAll方法,修改为这样:
public void popAll(Collection<? super E> dst) {
dst.add(pop());
}
同时,请谨记一点,作为数组与集合比较的comparable与comparator始终是消费者,所以始终是<? super E>.