Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice to choose fields for equals() implementation

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 ids 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.

like image 221
Andrey Balaguta Avatar asked Mar 16 '12 14:03

Andrey Balaguta


People also ask

What are the important things to consider when implementing equals method?

There are some general principles defined by Java SE that must be followed while implementing the equals() method in Java. The equals() method must be: reflexive: An object x must be equal to itself, which means, for object x, equals(x) should return true. symmetric: for two given objects x and y, x.

What is the default implementation of equals method Java?

The default implementation of equals() in the Object class says that equality is the same as object identity, and income and expenses are two distinct instances.

What happens if we do not override hashCode () and equals () in HashMap?

If you don't override hashcode() then the default implementation in Object class will be used by collections. This implementation gives different values for different objects, even if they are equal according to the equals() method.

How does equals () method work What does it do?

equals() Method. In Java, the String equals() method compares the two given strings based on the data/content of the string. If all the contents of both the strings are the same, it returns true.


2 Answers

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.

like image 199
Gray Avatar answered Nov 13 '22 04:11

Gray


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.

like image 39
pap Avatar answered Nov 13 '22 04:11

pap