When writing unit-tests, I often face the situation when equals()
for some object in tests -- in assertEquals
-- should work differently from how it works in actual environment. Take for example some interface ReportConfig
. It has id
and several other fields. Logically, one config equals to another one when their id
s match. But when it comes to testing some specific implementation, say, XmlReportConfig
, obviously I want to match all fields. One solution is not to use equals
in tests and just iterate over the object properties or fields and compare them, but it doesn't seem like a good solution.
So, apart from this specific type of situations, I want to sort out what are best practices to implement equals, semantically, not technically.
I would not think about the unit test when writing a
equals()
both are different.You define the equality of each object with one or group of properties by implementing
equals()
andhashcode()
.In your test if you want to compare all the properties of the object, then obviously you need to call each method.
I think it is better to treat them separately.
Copied from
Object.equals(Object obj)
javadoc:Indicates whether some other object is "equal to" this one.
The equals method implements an equivalence relation on non-null object references:
That's pretty clear to me, that is how equals should work. As for which fields to choose, you choose whichever combination of fields is required to determine whether some other object is "equal to" this one.
As for your specific case, if you, in your test, need a broader scope for equality, then you implement that in your test. You shouldn't hack your equals method just to make it fit.
I think the only best practice when overriding the
equals()
method is common sense.There are no rules, apart from the equivalence definition of the Java API. Once you've chosen that definition of the equality, you have to apply it to your
hashCode()
method as well.By that I mean, as a developer, you, your co-workers and maintainers should know when your instance of let's say a
Watermelon
equals another Object instance.You should use all significant variables, ie variables whose value is not derived from the others, in equals.
This is from Effective Java:
If you want to match ids because it's a unique identifier for that class then just compare the id value, don't use equals in that case.
If you have a unique identifier, the language doesn't allow you to enforce that there is no other object with that identifier or that the rest of variables' values match. However, you can define that in the documentation of the class and you can use assertions in the equals implementation or elsewhere as it is an invariant given by your class' semantics.
In Java the
equals
method really should be considered to be "identity equals" because of how it integrates withCollection
andMap
implementations. Consider the following:If
Foo
identity is theid
field then the 2ndfooSet.add(...)
should not add another element to theSet
but should returnfalse
sincefoo1
andfoo2
have the sameid
. If you defineFoo.equals
(and hashCode) method to include both theid
and thestuff
fields then this might be broken since theSet
may contain 2 references to the object with the same id field.If you are not storing your objects in a
Collection
(orMap
) then you don't have to define theequals
method this way, however it is considered by many to be bad form. If in the future you do store it in aCollection
then things will be broken.If I need to test for equality of all fields, I tend to write another method. Something like
equalsAllFields(Object obj)
or some such.Then you would do something like:
In addition, a proper practice is to not define
equals
methods which take into account mutable fields. The problem also gets difficult when we start talking about class hierarchies. If a child object definesequals
as a combination of its local fields and the base classequals
then its symmetry has been violated:Some more reading I would highly recommend is the "Pitfall #3: Defining equals in terms of mutable fields" section in this great page:
Some additional links:
Oh, and just for posterity, regardless of what fields you choose to compare to determine equality, you need to use the same fields in the
hashCode
calculation.equals
andhashCode
must be symmetric. If two objects are equals, they must have the same hash-code. The opposite is not necessarily true.