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!
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 ofBto build the hash code.The
As are just containers and their value is fully determined by their content, which is the values of the containedBs, and so should their hash keys.If
Ais an unordered set, you must be very careful that the hash code you are building from theBvalues (orBhash codes) is not dependent on some ordering. For example, if the hash code is build by adding and multiplying the hash codes of the containedB‘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 theBs (if unordered set).Note that if you are using a
java.util.CollectionwithinA, then just fixing theBs hash code by ignoring the parent reference will automatically give validAhash codes since theCollections have good hash codes by default (ordering or not).