在this answer中,我尝试解释为什么Collection方法add
具有签名add(E)
而remove
为remove(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
的子和超类型。
答案 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>
。它不会做任何事情。