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!
First of all, are you sure you want to override
Equals()
andGetHashCode()
? In most scenearios you should be fine with the default referential equality.But, let's suppose not. Than, what is the appropriate equality semantics you want?
For example let's say each
A
has agetB
field of typeB
and eachB
has agetA
field of typeA
. Leta1
anda2
be twoA
objects, have the same fields and the samegetB
(same as in "same memory address")b1
. Area1
anda2
equal? Supposeb1.getA
is the same asa1
(same as in "same memory address") but not the same asa2
. Do you still want to considera1
anda2
equal?If not, don't override anything and use the default referential equality.
If yes, then here is a solution: Let
A
have aint GetCoreHashCode()
function that does not depend ongetB
element, (but depend on other fields). LetB
have aint GetCoreHashCode()
function that does not depend on getA element, (but depend on other fields). Now letint GetHashCode()
function ofA
depend uponthis.GetCoreHashCode()
andgetB.GetCoreHashCode()
and likewise forB
, and you are done.Here are a few libraries for this purpose :
https://code.google.com/p/deep-equals/
http://sourceforge.net/projects/unitils/
https://github.com/SQiShER/java-object-diff
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 ofB
to build the hash code.The
A
s are just containers and their value is fully determined by their content, which is the values of the containedB
s, 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 theB
values (orB
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 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 theB
s (if unordered set).Note that if you are using a
java.util.Collection
withinA
, then just fixing theB
s hash code by ignoring the parent reference will automatically give validA
hash codes since theCollection
s have good hash codes by default (ordering or not).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:
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()
orhashCode()
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.You could have two flavors of
equals
-- the override ofObject.equals
and one that's better suited for recursion. The recursive equality check takes an A or B -- whichever is the other class of this one -- which is the object you're calling the recursive equality on behalf of. If you're calling it on behalf ofthis.equals
, you pass innull
. For instance:So, following
A.equals
:A.equals
calls `recursiveEquality(otherA, null)this.b != null
, we end up in the third if-else block, which callsb.recursiveEquality(other.b, this)
B.recursiveEquality
, we hit the first if-else block, which simply asserts that ourA
is the same one that was passed to us (ie, that the circular reference isn't broken)B.recursiveEquality
by checkingaKey
(depending on your invariants, you may want to assert something based on what happened in step 3).B.recursiveEquality
returnsA.recursiveEquality
by checkingbKey
, possibly with similar assertsA.equals
returns the result of the recursive equality check