为什么不Collection.remove(Object o)通用?
好像Collection<E>
有boolean remove(E o);
然后,当您不小心尝试从中删除(例如)Set<String>
而不是每个String时Collection<String>
,这将是编译时错误,而不是以后的调试问题。
为什么不Collection.remove(Object o)通用?
好像Collection<E>
有boolean remove(E o);
然后,当您不小心尝试从中删除(例如)Set<String>
而不是每个String时Collection<String>
,这将是编译时错误,而不是以后的调试问题。
Answers:
乔什·布洛赫(Josh Bloch)和比尔·普(Bill Pugh)在《Java Puzzlers IV:幻影参考威胁,克隆人的攻击和复仇的复仇》中提到了这个问题。
乔什·布洛赫(Josh Bloch)说(6:41),他们试图泛化Map的get方法,remove方法和其他方法,但是“这根本没有用”。
如果仅允许将集合的通用类型作为参数类型,则无法生成太多合理的程序。由他给出的例子是一个交叉点List
的Number
S和
List
的Long
第
remove()
(in Map
和in Collection
)不是通用的,因为您应该能够将任何类型的对象传递给remove()
。删除的对象不必与您传递给的对象具有相同的类型remove()
;它只要求它们相等。从本说明书中remove()
,remove(o)
将删除对象e
,使得(o==null ? e==null : o.equals(e))
是true
。请注意,没有什么要求,o
并且e
必须是相同的类型。这是因为该equals()
方法接受一个Object
as参数,而不仅仅是与该对象相同的类型。
尽管通常已经equals()
定义了许多类,以便其对象只能等于其自己的类的对象,这确实是正确的,但事实并非总是如此。例如,的规范List.equals()
说两个List对象都是相同的,并且都是相同的内容,即使它们都是的不同实现,也是如此List
。所以回来的例子在这个问题上,可以有一个Map<ArrayList, Something>
和我打电话给remove()
一个LinkedList
作为参数,并应删除这与相同内容的列表的关键。如果remove()
是通用的并且限制了它的参数类型,那将是不可能的。
equals()
方法通用化?我可以看到键入安全而不是这种“自由主义者”方法会带来更多好处。我认为当前实现的大多数情况是因为错误进入了我们的代码,而不是因为这种remove()
方法带来的灵活性。
equals()
方法” 是什么意思?
T
必须在类上声明为类型参数,并且Object
没有任何类型参数。没有一种类型可以引用“声明类”。
Equality<T>
与equals(T other)
。然后,您可能拥有remove(Equality<T> o)
并且o
仅仅是可以与另一个对象进行比较的一个对象T
。
因为如果类型参数是通配符,则不能使用通用的remove方法。
我似乎回想起了Map的get(Object)方法遇到的这个问题。在这种情况下,get方法不是通用的,尽管应该合理地期望将其传递给与第一个type参数相同类型的对象。我意识到,如果您要传递带有通配符作为第一个类型参数的Maps,那么如果该参数是通用的,则无法使用该方法从Map中获取元素。不能真正满足通配符参数,因为编译器无法保证类型正确。我推测add是泛型的原因是,您应该在将其添加到集合之前保证类型正确。但是,在删除对象时,如果类型不正确,则无论如何都不匹配。
我可能没有很好地解释它,但是对我来说似乎很合逻辑。
除了其他答案外,该方法还应接受Object
谓词的另一个原因。考虑以下示例:
class Person {
public String name;
// override equals()
}
class Employee extends Person {
public String company;
// override equals()
}
class Developer extends Employee {
public int yearsOfExperience;
// override equals()
}
class Test {
public static void main(String[] args) {
Collection<? extends Person> people = new ArrayList<Employee>();
// ...
// to remove the first employee with a specific name:
people.remove(new Person(someName1));
// to remove the first developer that matches some criteria:
people.remove(new Developer(someName2, someCompany, 10));
// to remove the first employee who is either
// a developer or an employee of someCompany:
people.remove(new Object() {
public boolean equals(Object employee) {
return employee instanceof Developer
|| ((Employee) employee).company.equals(someCompany);
}});
}
}
关键是要传递给remove
方法的对象负责定义equals
方法。这样,构建谓词变得非常简单。
yourObject.equals(developer)
:作为集合API中记录java.sun.com/javase/6/docs/api/java/util/...
equals
方法的约束,即对称性。只要您的对象满足equals / hashCode规范,即可将remove方法绑定到其规范,因此任何实现都可以自由地进行比较。另外,您的谓词对象不会实现该.hashCode()
方法(无法始终实现等于),因此remove调用将永远不适用于基于Hash的集合(如HashSet或HashMap.keys())。与ArrayList一起使用真是幸运。
Collection.remove
,而不会违反合同(如果顺序与等号一致)。而且,经过改写的带有equals调用的各种ArrayList(或我认为AbstractCollection)仍然可以正确实现合同-如果您违反equals
合同,那么如果它不能按预期工作,那是您的错。
假设一个人的集合Cat
,和一些类型的对象引用Animal
,Cat
,SiameseCat
,和Dog
。询问集合是否包含Cat
或SiameseCat
引用所引用的对象似乎是合理的。询问它是否包含该Animal
引用所引用的对象似乎有些狡猾,但这仍然是完全合理的。毕竟,所讨论的对象可能是Cat
,并且可能会出现在集合中。
此外,即使对象碰巧不是a Cat
,也可以毫无疑问地说它是否出现在集合中-只需回答“不,就不”。某种类型的“查找样式”集合应该能够有意义地接受任何超类型的引用,并确定对象是否存在于集合中。如果传入的对象引用是不相关的类型,则该集合不可能包含它,因此查询在某种意义上是没有意义的(它将始终回答“否”)。但是,由于没有任何方法可以将参数限制为子类型或超类型,因此最简单的方法是简单地接受任何类型,并对与集合的类型无关的任何对象回答“否”。
Comparable
可以比较的类型的参数化方式)。那么,允许人们传递不相关类型的东西是不合理的。
A
和B
一种类型的,并X
和Y
另一个,使得A
> B
,和X
> Y
。任一A
> Y
和Y
< A
,或X
> B
和B
< X
。仅当量级比较了解两种类型时,这些关系才能存在。相比之下,对象的相等性比较方法可以简单地声明自己与任何其他类型的任何对象都不相等,而不必了解所讨论的其他类型的任何内容。类型的对象Cat
可能不知道它是否是...
FordMustang
,但说它是否等于这样的对象(答案显然是“否”)应该没有困难。
Remove不是通用方法,因此使用非通用集合的现有代码仍将编译并且仍具有相同的行为。
有关详细信息,请参见http://www.ibm.com/developerworks/java/library/j-jtp01255.html。
编辑:评论者问为什么add方法是通用的。[...删除了我的解释...]第二位评论者回答了firebird84的问题,比我好得多。
另一个原因是由于接口。这是显示它的示例:
public interface A {}
public interface B {}
public class MyClass implements A, B {}
public static void main(String[] args) {
Collection<A> collection = new ArrayList<>();
MyClass item = new MyClass();
collection.add(item); // works fine
B b = item; // valid
collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}
remove()
它不是协变的。但是,问题是是否应该允许这样做。ArrayList#remove()
通过值相等而不是引用相等来工作。您为什么期望a B
等于a A
?在您的示例中可以,但是这是一个奇怪的期望。我希望您MyClass
在这里提供一个论点。
因为它将破坏现有的(Java5之前的)代码。例如,
Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);
现在您可能会说上面的代码是错误的,但是假设o来自不同种类的对象(即,它包含字符串,数字,对象等)。您要删除所有匹配项,这是合法的,因为remove会忽略非字符串,因为它们不相等。但是,如果将其设置为remove(String o),它将不再起作用。