Best practice to choose fields for equals() implementation
You should use all significant variables, ie variables whose value is not derived from the others, in equals.
This is from Effective Java:
For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object.
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.
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()
and hashcode()
.
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.
what are best practices to implement equals, semantically, not technically.
In Java the equals
method really should be considered to be "identity equals" because of how it integrates with Collection
and Map
implementations. Consider the following:
public class Foo() {
int id;
String stuff;
}
Foo foo1 = new Foo(10, "stuff");
fooSet.add(foo1);
...
Foo foo2 = new Foo(10, "other stuff");
fooSet.add(foo2);
If Foo
identity is the id
field then the 2nd fooSet.add(...)
should not add another element to the Set
but should return false
since foo1
and foo2
have the same id
. If you define Foo.equals
(and hashCode) method to include both the id
and the stuff
fields then this might be broken since the Set
may contain 2 references to the object with the same id field.
If you are not storing your objects in a Collection
(or Map
) then you don't have to define the equals
method this way, however it is considered by many to be bad form. If in the future you do store it in a Collection
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:
assertTrue(obj1.equalsAllFields(obj2));
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 defines equals
as a combination of its local fields and the base class equals
then its symmetry has been violated:
Point p = new Point(1, 2);
// ColoredPoint extends Point
ColoredPoint c = new ColoredPoint(1, 2, Color.RED);
// this is true because both points are at the location 1, 2
assertTrue(p.equals(c));
// however, this would return false because the Point p does not have a color
assertFalse(c.equals(p));
Some more reading I would highly recommend is the "Pitfall #3: Defining equals in terms of mutable fields" section in this great page:
How to Write an Equality Method in Java
Some additional links:
- Implementing hashCode() and equals()
- Graceful Blog - Values, Equals, and Hashcodes
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
and hashCode
must be symmetric. If two objects are equals, they must have the same hash-code. The opposite is not necessarily true.
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:
- It is reflexive: for any non-null reference value x, x.equals(x) should return true.
- It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
- For any non-null reference value x, x.equals(null) should return false.
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.