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