interface Equalable {
boolean equals(Equalable other);
}
interface Hashable extends Equalable {
int hashCode();
}
例如,HashSet的定义可能如下所示:
class HashSet<T extends Hashable> ...
它可以防止一些初学者常犯的错误——在没有实现equals/hashCode的情况下使用项目集合。
interface Equalable {
boolean equals(Equalable other);
}
interface Hashable extends Equalable {
int hashCode();
}
class HashSet<T extends Hashable> ...
java.lang.Object中的默认实现是有意义的。通常情况下,它已经足够好了。在JPA / web应用程序中,我很少甚至从未覆盖equals和hashCode。
一个更好的问题可能是:对于不可变值对象(例如String、Long等),为什么不能像C#中那样重写==运算符以调用equals()?我看到的错误比默认的equals/hashCode没有做正确的事情要多得多。例如,
Long x = obj.getId();
Long y = obj2.getId();
if (x == y) { // oops, probably meant x.equals(y)! }
这是一个公平的问题,为什么默认方法没有像默认的Object.clone()一样被锁定在标记接口后面呢?虽然有一个默认实现,但你必须明确地承认你想要使用它,通过实现Cloneable接口。同样可以有类似的标记接口,如Collectible或Equatable,然后集合方法的签名可以是Equatable而不是Object。
在@Kowser的答案中添加有关'chaos'的注释:
等式的隐含逻辑要求它必须满足以下特征:
显然,如果Object中未定义equals方法,则会破坏“对称”和“传递”,这相当于破坏了等式的内在逻辑。
就我个人而言,如果它们在一个接口中,我会把它们都放在那里,以避免至少一类equals
/hashCode
错误。
我认为你会想要在Object中实现一个回退机制,这意味着无论是否有接口,所有东西都将有一个实现。
我怀疑很多都是历史原因;今天编写Java程序看起来与过去相比有很大的不同。
嗯,不确定,但当Java 1.0发布时,泛型还不存在。它们是在2004年的Java 5.0中添加的...因此,您的建议无法在Java 1.0中实现。
TreeSet
可以要求键实现Comparable
,如果没有指定Comparator
,那么为什么HashSet不能要求Hashable
呢?这不仅涉及到泛型。 - meriton实际上,这只是为了方便起见,这样做更好。好吧,如果没有.equals方法,想想要实现对象相等需要做些什么:
AreEqual(Object obj1,Object obj2) {
if(!obj1 instanceof Equalable) return false;
if(!obj2 instanceof Equalable) return false;
return ((Equalable)(obj1).equals((Equalable)obj2);
}
即使是hashCode也是如此。有时候,引用相等已经足够了。如果你让HashSet只接受实现了Hashable的对象,那么即使你只想要引用相等,你也必须显式地将你的类变成Hashable。这样会降低一个本来很好的数据结构的通用性。
最好让Object具有默认(有时足够)的.equals和.hashCode函数,并为新手造成一些问题,而不是让语言的经常使用者通过更多的繁文缛节。
任何对象,无论其类型如何,都可以明智地回答它是否等同于任何其他对象,即使另一个对象的类型是它从未听说过的。如果它从未听说过另一个对象的类型,仅此事实就足以让它报告它与后者对象不等同。
这是一个通用实现。如果需要,您应该覆盖实现。否则,您将拥有一个合理的默认实现。
至少equals是必须的。为基本操作创建接口可能会涉及太多开销。
contains
方法,Java
应该做什么?我认为默认实现(比较引用)是一个不错的决定。这样,您就不必为集合中使用的每个类自己实现equals
和hashcode
。最初,在Java中没有泛型。为了解决这个问题,允许任何Object
成为任何集合的成员,因此任何Object
都需要hashCode
和equals
。现在已经太深入人心,无法改变。