通用集合

13

这是Java(1.6)Collection接口的一部分:

public interface Collection<E> extends java.lang.Iterable<E> { 
    /* ... */   
    boolean containsAll(java.util.Collection<?> objects);    
    boolean addAll(java.util.Collection<? extends E> es);    
    boolean removeAll(java.util.Collection<?> objects);    
    boolean retainAll(java.util.Collection<?> objects);
    /* ... */   
}

为什么addAll使用<? extends E>,而removeAll使用<?>

10个回答

12

我不知道,我通过谷歌搜索了解到这里的解释:http://www.ibm.com/developerworks/java/library/j-jtp01255/index.html

以下是拷贝的内容:

泛型集合API中一个常常令人困惑的部分是containsAll()、removeAll()和retainAll()方法的签名。你可能会期望remove()和removeAll()的签名应该是:

interface Collection<E> { 
  public boolean remove(E e);  // not really
  public void removeAll(Collection<? extends E> c);  // not really
}

但实际上是这样的:

interface Collection<E> { 
  public boolean remove(Object o);  
  public void removeAll(Collection<?> c);
}
为什么会这样呢?答案再次在于向后兼容性。x.remove(o)的接口契约意味着“如果o包含在x中,则删除它;否则,不执行任何操作。” 如果x是通用集合,则o不必与x的类型参数兼容。 如果removeAll()被泛型化为只有在其参数是类型兼容的(Collection<? extends E>)时才能调用,那么某些在泛型之前合法的代码序列将变得非法,例如这个序列:
// a collection of Integers
Collection c = new HashSet();
// a collection of Objects
Collection r = new HashSet();
c.removeAll(r);

如果以上代码片段按照显而易见的方式泛型化(将c设为Collection<Integer>,r设为Collection<Object>),那么如果removeAll()方法的签名要求其参数是Collection<? extends E>,而不是一个空操作,则上述代码将无法编译。泛型化类库的一个关键目标是不破坏或改变现有代码的语义,因此remove()、removeAll()、retainAll()和containsAll()必须使用比可能在泛型从头开始设计时更弱的类型约束来定义。


7
对于任何包含类型为 E 的元素的集合,addAll 必须能够处理输入集合不仅仅是 E,而且所有其子类。因此使用了 <? extends E>。如果没有这个,你就不能将 List<Integer> 的所有元素添加到 List<Number> 中,这显然是不正确的。

对于删除操作,限制可以不那么严格,尝试删除某些完全不相关类型的集合中的元素也没有问题。例如,你可以有一个包含 Integer 的集合 Numbers,你知道它只包含 Integer,所以将其传递给 List<Integer> 上的 removeAll 应该可以正常工作,编译器禁止这样做是愚蠢的。

请注意,根据 JavadocremoveAll 可以根据实现情况选择是否抛出 ClassCastException

这是因为在Java中,泛型是不变的。更多细节请参见例如此线程


3

<?><? extends E>限制更少。

从一堆苹果中移除一个橙子没有问题;但将一个橙子添加到苹果集合中是错误的。


我知道你的意思... 到处都是橙汁。那两个人从来就不合得来。+1 - Luchian Grigore
是的,但由于没有办法将一个橙子添加到苹果集合中,因此该集合不可能包含橙子,因此 removeAll() 最好采用 <? extends E>,对吧? - NPE
2
@aix:这使您能够传递一个Fruit(甚至是Object)的集合,并从您的集合中删除所有苹果。 - SLaks

2

当您向收集中添加项目时,应确保它们具有特定类型。

当您删除它们时,只会删除收集中的项目,而不考虑它们的类型。


0

谁在乎你试图移除什么?

添加又是另一回事;我们不想在我们的集合中得到奇怪的东西。

如所请求的;这是一个例子:

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class Main {

    private static class A {

    }

    public static void main(String[] args) {
        Collection<A> collection_A = new ArrayList<A>();

        Collection<String> collection = new ArrayList<String>();

        // no problem to try and remove things that wouldn't be there in the first place; either way, they are gone afterwards
        collection.removeAll(collection_A);

        // we can't allow this; you would end up with things in your collection that don't belong there
        collection.addAll(collection_A);
    }

}

你能用一个或两个具体的例子来说明吗? - NPE

0
一个简单的例子来说明上述内容:
public class Test {

    public static void main(String[] args) {
        List<String> l = new ArrayList<String>();
        System.out.println(l.remove(new Object())); //false
        System.out.println(l.contains(new Object())); //false
//        l.add(new Object()); // does not compile
    }

}

0

使用addAll,您希望能够添加所有属于通用类型子类型的元素。这包括将List<String>的所有元素添加到List<Object>中。我们使用? extends E来接受包含此集合中存储的类型或任何子类型的任何集合。

boolean addAll(java.util.Collection<? extends E> es);
List<Number> numbers = ...;
List<Integer> integers = ...;
numbers.addAll(integers);//works    

boolean addAll(java.util.Collection<E> es);
numbers.addAll(integers);//does not work E != Integer

我们不能使用?,因为这会消除泛型提供的任何安全性。

boolean addAll(java.util.Collection<? extends E> es);
List<Number> numbers = ...;
List<Integer> integers = ...;
List<String> strings = ...;
numbers.addAll(integers);//works
numbers.addAll(strings);//error

boolean addAll(java.util.Collection<?> es);
numbers.addAll(strings);//works - now we have strings in our Number collection

我们可以使用?来删除对象,因为尝试从数字列表中删除字符串不会影响List<Number>
boolean removeAll(java.util.Collection<?> objects);
List<Objects> objects = ...;
List<Integer> integers = ...;
List<Number> numbers = ...;
numbers.removeAll(objects);//works 
numbers.removeAll(integers);//works

boolean removeAll(java.util.Collection<? extends E> objects);
numbers.removeAll(objects);//does not work 
numbers.removeAll(integers);//works

boolean removeAll(java.util.Collection<? super E> objects);
numbers.removeAll(objects);//works 
numbers.removeAll(integers);//does not work

0
Java通过擦除实现泛型。这些信息仅用于编译时。我猜Java集合设计师这样做是为了保留更多与非泛型Java版本的升级兼容性。

0

当您添加一个对象时,它需要是主类型的子类(或子子类等)。当您删除一个对象时,它将返回集合类型。这是多态性实际应用的一个很好的例子。


0

不需要移除限制,因此只需使用<?>,但在添加时我们必须进行检查,然后为类型安全添加,因此addAll带有限制<? extends E>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接