remove(Object) 应该是 remove(? super E)

Should remove(Object) be remove(? super E)

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

public boolean remove(? super E element)

并且由于这是 Java 中的无效语法,他们不得不坚持使用 Object,这恰好是 super EE 的超类型) E。下面的代码解释了为什么这是有意义的:

List<String> strings = new ArrayList();
strings.add("abc");

Object o = "abc"; // runtime type is String
strings.remove(o);

由于运行时类型是String,所以成功。如果签名是 remove(E),这将在编译时导致错误,但在运行时不会,这是没有意义的。但是,以下 应该 在编译时引发错误,因为该操作注定会失败,因为它的类型在编译时是已知的:

strings.remove(1);

remove 接受一个 Integer 作为参数,它是 而不是 super String,这意味着它实际上永远不会从中删除任何东西集合。

如果remove方法是用参数类型? super E定义的,编译器可以检测到上述情况。

问题:

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

此外,removeAll 的签名应该是

public boolean removeAll(Collection<? super E> collection)

注意我想知道为什么签名不是remove(E),这在this question中询问和解释。我想知道 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.

的子 超类型

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

c.remove(x) 的 post 条件是调用后 x 不存在于 c 中。方法签名 remove(Object) 允许您传递任何对象或 null,无需进一步检查。另一方面,方法签名 ? super Ex 类型上引入了一个前置条件,要求它与 E.

相关

您在 API 中引入的每个前提条件都会使您的 API 更难使用。如果删除前提条件可以让您保留所有 post 条件,那么删除前提条件是个好主意。

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

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 不关心您传递给它的对象的类型。

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

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

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

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

More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this collection contains one or more such elements.

所以参数必须匹配的所有前提条件是你可以在上面使用==equals()Object就是这种情况。这仍然使您能够尝试从 Collection<String> 中删除 Integer。它什么也做不了。

remove(? super E) 完全 等同于 remove(Object),因为 Object 本身就是 E 的超类型,所有对象扩展 Object.

这是一个错误的假设:

because the operation is bound to fail because of its types, which are known at compile-time

这与 .equals 接受对象的推理相同:对象不一定需要具有相同的 class 才能相等。正如 the question @Joe linked:

中指出的那样,考虑具有不同 List 子类型的示例
List<ArrayList<?>> arrayLists = new ArrayList<>();
arrayLists.add(new ArrayList<>());

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

您建议的签名无法做到这一点。