我在想是否重写可变集合的equals
和hashCode
方法是个好主意。这将意味着如果我将这样的集合插入到一个HashSet
中并修改了集合,HashSet
将不能再找到它。这是否意味着只有不可变集合应该重写equals
和hashCode
方法,或者这只是Java程序员必须应付的麻烦?
我在想是否重写可变集合的equals
和hashCode
方法是个好主意。这将意味着如果我将这样的集合插入到一个HashSet
中并修改了集合,HashSet
将不能再找到它。这是否意味着只有不可变集合应该重写equals
和hashCode
方法,或者这只是Java程序员必须应付的麻烦?
如果你的类应该像值类型一样运作,那么你需要重写equals
和hashCode
方法。但是对于集合来说,通常情况下并不需要这么做。
(我并没有太多Java经验,这个答案是基于C#的。)
深度和浅度相等的问题比Java更为复杂;所有面向对象语言都必须关注此问题。
您添加到集合中的对象应该覆盖equals和hashCode方法,但默认行为内置于集合接口的抽象实现中足以满足集合本身的要求。
对于任何可变类,情况都是一样的。当您将一个实例插入到HashSet中并调用一个变异方法时,您会遇到麻烦。所以,我的答案是:如果有用途,就可以使用。
当然,在将其添加到HashSet之前,您可以使用不可变包装器来包装您的集合。
我认为更重要的问题是,如果有人试图将FredCollection的一个实例添加到Set两次,应该发生什么。
FredCollection c = ...
set.add(c);
set.add(c);
在此之后,set
的 size()
应该是 2
还是 1
?
你是否会需要测试两个不同的 FredCollection
实例的“相等性”?我认为这个问题的答案比其他任何事情都更重要,以确定你的 equals()
/hashcode()
行为。
这不仅仅是在集合中的问题,也是在可变对象中普遍存在的问题(另一个例子:Point2D
)。而且,是Java程序员最终需要考虑到的潜在问题。
你不应该覆盖equals和hashCode方法,以便它们反映可变成员。
这更多是我的个人观点。我认为哈希码和equals是技术术语,不应用于实现业务逻辑。想象一下:你有两个对象(不仅仅是集合),并询问它们是否相等,那么有两种不同的答案:
但是,由于equals被技术性的东西(如HashMap)使用,因此应以技术性的方式实现它,并通过其他方式(例如比较器接口)构建与业务逻辑相关的equals。 对于您的集合,这意味着:不要覆盖equals和hashCode(以违反技术合同的方式)。
注意:如果将可变对象用作映射键,必须非常小心。如果对象的值以会影响equals比较的方式改变,则映射的行为未指定。 (Map的 Java 文档)。equals
和hashCode
的一个基本困难是,有两种逻辑方式可以定义等价关系;一些类的使用者将希望有一种定义,而该类的其他使用者将希望另一种定义。
我将定义这两个等价关系如下:
如果用对Y的引用覆盖X不会改变X或Y的任何成员的现在或未来行为,则两个对象引用X和Y完全等价。
如果在一个程序中,该程序没有持久化与标识相关的哈希函数返回的值,那么交换所有对X的引用和所有对Y的引用将不会改变程序状态,则两个对象引用X和Y具有等效状态。
请注意,第二个定义主要与两个东西持有某些可变类型(例如数组)的对象的常见情况相关,但可以确定,在感兴趣的特定时间范围内,这些对象不会暴露给可能会改变它们的任何东西。在这种情况下,如果“持有者”对象在所有其他方面都是等价的,则它们的等价性应取决于它们所持有的对象是否符合上述“第二”等价性定义。
请注意,第二个定义不涉及对象状态如何改变的任何细节。此外,请注意,对于等价关系的任何一个定义,不可变对象都可以将具有相同内容的不同对象报告为相等或不相等(如果X和Y之间唯一的区别是X.Equals(X)报告为true而X.Equals(Y)报告为false,那将是一个差异,但最有用的可能是让这些对象使用引用标识符进行第一个等价关系和其他方面的等价关系进行第二个等价关系。不幸的是,由于Java只提供了一对定义等价关系的类,类设计者必须猜测哪种等价关系的定义对类的消费者最相关。虽然有很多理由支持始终使用第一个等价关系,但第二个等价关系通常更实用。第二个等价关系的最大问题是,类无法知道使用该类的代码何时需要第一个等价关系。
equals 用于向集合中添加/删除元素,例如 CopyOnWriteArraySet、HashSet 等,如果两个不同对象的 hashCode 相等,则它们可以使用 equals 进行操作。equals 需要对称,即如果 B.equals(C) 返回 true,则 C.equals(B) 应该返回相同的结果。否则,在这些 XXXSets 上进行添加/删除操作会表现出令人困惑的行为。请查看 Overriding equals for CopyOnWriteArraySet.add and remove,了解如何不正确地重写 equals 如何影响集合上的添加/删除操作。