Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing equals and hashCode for objects with circular references in Java

I have two classes defined such that they both contain references to the other object. They look similar to this (this is simplified; in my real domain model class A contains a list of B and each B has a reference back to parent A):

public class A {      public B b;     public String bKey;      @Override     public int hashCode() {         final int prime = 31;         int result = 1;         result = prime * result + ((b == null) ? 0 : b.hashCode());         result = prime * result + ((bKey == null) ? 0 : bKey.hashCode());         return result;     }     @Override     public boolean equals(Object obj) {         if (this == obj)             return true;         if (obj == null)             return false;         if (!(obj instanceof A))             return false;         A other = (A) obj;         if (b == null) {             if (other.b != null)                 return false;         } else if (!b.equals(other.b))             return false;         if (bKey == null) {             if (other.bKey != null)                 return false;         } else if (!bKey.equals(other.bKey))             return false;         return true;     } }  public class B {      public A a;     public String aKey;      @Override     public int hashCode() {         final int prime = 31;         int result = 1;         result = prime * result + ((a == null) ? 0 : a.hashCode());         result = prime * result + ((aKey == null) ? 0 : aKey.hashCode());         return result;     }     @Override     public boolean equals(Object obj) {         if (this == obj)             return true;         if (obj == null)             return false;         if (!(obj instanceof B))             return false;         B other = (B) obj;         if (a == null) {             if (other.a != null)                 return false;         } else if (!a.equals(other.a))             return false;         if (aKey == null) {             if (other.aKey != null)                 return false;         } else if (!aKey.equals(other.aKey))             return false;         return true;     } } 

The hashCode and equals have been generated by Eclipse using both fields of both A and B. The problem is that calling the equals or hashCode method on either object results in a StackOverflowError since they both call the other object's equals and hashCode method. For example the following program will fail with StackOverflowError using the above objects:

    public static void main(String[] args) {          A a = new A();         B b = new B();         a.b = b;         b.a = a;          A a1 = new A();         B b1 = new B();         a1.b = b1;         b1.a = a1;          System.out.println(a.equals(a1));     } 

If there is something inherently wrong with having a domain model defined with circular relationships in this way then please let me know. As far as I can tell though this is a fairly common scenario, correct?

What is best practice for defining hashCode and equals in this case? I want to keep all fields in the equals method so that it is a true deep equality comparison on the object but I don't see how I can with this problem. Thanks!

like image 480
Tom Avatar asked Jan 14 '12 15:01

Tom


People also ask

How does its implementation use hashCode and equals methods of objects?

Java hashCode() An object hash code value can change in multiple executions of the same application. If two objects are equal according to equals() method, then their hash code must be same. If two objects are unequal according to equals() method, their hash code are not required to be different.

Can 2 objects render same hashCode?

It is perfectly legal for two objects to have the same hashcode. If two objects are equal (using the equals() method) then they have the same hashcode.

What is the relationship between hashCode () and equals () method in Java?

General contract associated with hashCode() methodIf two objects are equal(according to equals() method) then the hashCode() method should return the same integer value for both the objects.


2 Answers

I agree with the comment of I82Much that you should avoid having B referencing their parent: it's information duplication, which usually only leads to trouble, but you might need to do so in your case.

Even if you leave the parent reference in B, as far as hash codes are concerned you should completely ignore the parent reference and only use the true inner variables of B to build the hash code.

The As are just containers and their value is fully determined by their content, which is the values of the contained Bs, and so should their hash keys.

If A is an unordered set, you must be very careful that the hash code you are building from the B values (or B hash codes) is not dependent on some ordering. For example, if the hash code is build by adding and multiplying the hash codes of the contained B's in some sequence, you should first order the hash codes by increasing order before computing the result of the sums/multiplications. Similarly, A.equals(o) must not depend on the ordering of the Bs (if unordered set).

Note that if you are using a java.util.Collection within A, then just fixing the Bs hash code by ignoring the parent reference will automatically give valid A hash codes since the Collections have good hash codes by default (ordering or not).

like image 164
toto2 Avatar answered Oct 09 '22 01:10

toto2


In a typical model, most entities have a unique ID. This ID is useful in various use-cases (in particular: Database retreival/lookup). IIUC, the bKey field is supposed to be such a unique ID. Thus, the common practice for comparing such entities is to compare their ID:

@Override public boolean equals(Object obj) {     if (obj == null)         return false;     if (!getClass().equals(obj.getClass()))         return false;     return this.bKey.equals(((B) obj).bKey); }   @Override public int hashCode() { return bKey.hashCode(); } 

You may ask: "what happens if two B objects have the same ID but different state (value of their fields are different)". Your code should make sure that such things do not happen. This will be a problem regardless of how you implement equals() or hashCode() because it essentially means that you have two different versions of the same entity in your system and you won't be able to tell which is the correct one.

like image 33
Itay Maman Avatar answered Oct 09 '22 02:10

Itay Maman