remove(Object) 应该是 remove(? super E)
Should remove(Object) be remove(? super E)
在 this answer 中,我试图解释为什么 Collection 方法 add
具有签名 add(E)
而 remove
是 remove(Object)
。我想到正确的签名应该是
public boolean remove(? super E element)
并且由于这是 Java 中的无效语法,他们不得不坚持使用 Object
,这恰好是 super E
(E
的超类型) 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 E
在 x
类型上引入了一个前置条件,要求它与 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
您建议的签名无法做到这一点。
在 this answer 中,我试图解释为什么 Collection 方法 add
具有签名 add(E)
而 remove
是 remove(Object)
。我想到正确的签名应该是
public boolean remove(? super E element)
并且由于这是 Java 中的无效语法,他们不得不坚持使用 Object
,这恰好是 super E
(E
的超类型) 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 E
在 x
类型上引入了一个前置条件,要求它与 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<ArrayList<?>> arrayLists = new ArrayList<>();
arrayLists.add(new ArrayList<>());
LinkedList<?> emptyLinkedList = new LinkedList<>();
arrayLists.remove(emptyLinkedList); // removes the empty ArrayList and returns true
您建议的签名无法做到这一点。