Reputation: 263148
I was just wondering if it was a good idea to override equals
and hashCode
for mutable collections. This would imply that if I insert such a collection into a HashSet
and then modify the collection, the HashSet
would no longer be able to find the collection. Does this imply that only immutable collections should override equals
and hashCode
, or is this a nuisance Java programmers simply live with?
Upvotes: 25
Views: 2739
Reputation: 2695
equals is used to add/remove elements from collections like CopyOnWriteArraySet, HashSet if hashCode is equal for two different objects, etc. equals need to be symmetric i.e. if B.equals(C) returns true then C.equals(B) should return the same result. Otherwise your add/remove on those XXXSets behave in a confusing manner. Check Overriding equals for CopyOnWriteArraySet.add and remove for how improper overriding of equals affected add/remove operations on collections
Upvotes: -1
Reputation: 120811
You should not override equals and hashCode so that they reflect the mutable member.
Is more my personal point of view. I think hash code and equals are technical terms that should not be used to implement business logic. Imagine: you have two Objects (not only Collections) and ask if they are equals, then there are two different ways to answer them:
But because equals is used by technical stuff (HashMap), you should implement it in a technical way, and build the business logic related equals by something else (something like the comparator interface). And for your collection it means: do not override equals and hashCode (in a way that breaks the technical contract:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
(java doc of Map) ).
Upvotes: 1
Reputation: 81189
A fundamental difficulty with equals
and hashCode
is that there are two logical ways one may define an equivalence relation; some consumers of a class will want one definition, while other consumers of that same class will want another.
I would define the two equivalence relations as follows:
Two object references X and Y are fully equivalent if overwriting X with a reference to Y would not alter the present or future behavior of any members of X or Y.
Two object references X and Y have equivalent state if, in a program which has not persisted the values returned from identity-related hash function, swapping all references to X with all references to Y would leave program state unchanged.
Note that the second definition is primarily relevant in the common scenario where two things hold a references to objects of some mutable type (e.g. arrays), but can be sure that, at least within some particular time-frame of interest, those objects are not going to be exposed to anything that might mutate them. In such a scenario, if the "holder" objects are equivalent in all other regards, their equivalence should depend upon whether the objects they hold meet the second definition of equivalence above.
Note that the second definition does not concern itself with any details of how an object's state might change. Note further that immutable objects could, for either definition of equivalence, report distinct objects with equal content as equal or unequal (if the only way in which X and Y differ is that X.Equals(X) reports true while X.Equals(Y) reports false, that would be a difference, but it would probably be most useful to have such objects use reference identity for the first equivalence relation and equivalence of other aspects for the second.
Unfortunately, because Java only provides one pair of equivalence-defining classes, a class designer must guess which definition of equivalence will be most relevant to consumers of the class. While there's a substantial argument to be made in favor of using the first always, the second is often more practically useful. The biggest problem with the second is that there's no way a class can know when code using the class will want the first equivalence relation.
Upvotes: 0
Reputation: 139931
I think the bigger question is what should happen if someone attempts to add an instance of your FredCollection to a Set twice.
FredCollection c = ...
set.add(c);
set.add(c);
Should the size()
of set
be 2
or 1
after this?
Will you ever have a need to test the "equality" of two different instances of FredCollection
? I think the answer to this question is more important at determining your equals()
/hashcode()
behavior than anything else.
Upvotes: 2
Reputation: 346327
This is not just an issue for collections, but for mutable objects in general (another example: Point2D
). And yes, it is a potential problem that Java programmers eventually learn to take into account.
Upvotes: 1
Reputation: 14159
It's the same as with any mutable class. When you insert an instance into a HashSet and then call a mutating method, you will get into trouble. So, my answer is: Yes, if there's a use for it.
You can of course use an immutable Wrapper for your Collection before adding it to the HashSet.
Upvotes: 2
Reputation: 308848
The problem of deep and shallow equals is bigger than Java; all object oriented languages have to concern themselves with it.
The objects that you add to the collection should override equals and hash code, but the default behavior built into the abstract implementation of the collection interface suffices for the collection itself.
Upvotes: 5
Reputation: 3419
You should override equals
and hashCode
if your class should act like it were a value type. This usually is not the case for collections.
(I don't really have much Java experience. This answer is based on C#.)
Upvotes: 6