应删除(对象)删除(?super E)

时间:2015-06-11 15:06:19

标签: java generics collections super contravariance

this answer中,我尝试解释为什么Collection方法add具有签名add(E)removeremove(Object)。我想出了正确的签名应该是

的想法
public boolean remove(? super E element)

由于这是Java中无效的语法,因此他们必须坚持Object,恰好是任何super E的{​​{1}}(E的超类型)。以下代码解释了为什么这有意义:

E

由于运行时类型是String,因此成功。如果签名是List<String> strings = new ArrayList(); strings.add("abc"); Object o = "abc"; // runtime type is String strings.remove(o); ,这将在编译时导致错误,但在运行时不会导致错误,这没有任何意义。但是,以下应该在编译时引发错误,因为操作因其类型而绑定失败,这在编译时是已知的:

remove(E)

strings.remove(1); remove作为参数, not Integer,这意味着它永远无法从集合中删除任何内容。

如果使用参数类型super String定义remove方法,编译器可能会检测到上述情况。

问题

我的理论是正确的? super E应该有一个逆变remove参数而不是? super E,因此上面示例中显示的类型不匹配可以被编译器过滤掉? Java Collections Framework的创建者选择使用Object而不是Object是正确的,因为? super E会导致语法错误,而不是使通用系统复杂化,他们只是同意使用? super E代替Object

此外,super的签名应该是

removeAll

请注意,我 想要知道签名不是public boolean removeAll(Collection<? super E> collection) 的原因,remove(E)会对其进行询问和解释。我想知道remove是否应该是逆变(remove(? super E)),而remove(E)代表协方差。

工作的一个示例如下:

List<Number> nums = new ArrayList();
nums.add(1);
nums.remove(1); // fails here - Integer is not super Number

重新考虑我的签名,它实际上应该允许E的子超类型。

4 个答案:

答案 0 :(得分:3)

remove(? super E) 完全等同于remove(Object),因为Object本身是E的超类型,所有对象都延伸Object }。

答案 1 :(得分:3)

这是一个错误的假设:

  

因为它的类型在编译时已知,操作必然会失败

.equals接受一个对象的原因相同:对象不一定需要具有相同的类才能相等。请考虑使用List的不同子类型的此示例,如the question @Joe linked中所述:

List<ArrayList<?>> arrayLists = new ArrayList<>();
arrayLists.add(new ArrayList<>());

LinkedList<?> emptyLinkedList = new LinkedList<>();
arrayLists.remove(emptyLinkedList); // removes the empty ArrayList and returns true

使用您提出的签名是不可能的。

答案 2 :(得分:2)

我认为集合框架的设计者决定保持remove无类型,因为它是一种有效的解决方案,可以让您保持后置条件而不会引入先决条件或危及类型安全。

c.remove(x)的后置条件是x中不存在来电c后的情况。方法签名remove(Object)允许您传递任何对象或null,而无需进一步检查。另一方面,方法签名? super E引入了x类型的前提条件,要求它与E相关。

您在API中引入的每个前提条件都会使您的API更难使用。如果删除前置条件可以保留所有后置条件,则最好删除前置条件。

请注意,删除错误类型的对象不一定是错误。这是一个小例子:

class Segregator {
    private final Set<Integer> ints = ...
    private final Set<String> strings = ...
    public void addAll(List<Object> data) {
        for (Object o : data) {
            if (o instanceof Integer) {
                ints.add((Integer)o);
            }
            if (o instanceof String) {
                strings.add((String)o);
            }
        }
    }
    // Here is the method that becomes easier to write:
    public void removeAll(List<Object> data) {
        for (Object o : data) {
            ints.remove(o);
            strings.remove(o);
        }
    }
}

请注意removeAll方法的代码比addAll的代码更简单,因为remove并不关心传递给它的对象的类型。< / p>

答案 3 :(得分:0)

在您的问题中,您已经解释了为什么它不能(或不应该)remove(E)

但也有一个原因,它不应该是remove(? super E)。想象一下你有一个未知类型的对象的一些代码。您仍可能想尝试从该列表中删除该对象。请考虑以下代码:

public void removeFromList(Object o, Collection<String> col) {
    col.remove(o);
}

现在你的论点是,remove(? super E)是更安全的方式。但我说它不一定是。看看Javadoc of remove()。它说:

  

更正式地,如果此集合包含一个或多个此类元素,则删除元素e(o == null?e == null:o.equals(e))。

因此,参数必须匹配的所有前提条件是您可以在其上使用==equals()Object就是这种情况。这仍然可以让您尝试从Integer中删除Collection<String>。它不会做任何事情。