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!
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.
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.
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.
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 A
s are just containers and their value is fully determined by their content, which is the values of the contained B
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 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 B
s (if unordered set).
Note that if you are using a java.util.Collection
within A
, then just fixing the B
s hash code by ignoring the parent reference will automatically give valid A
hash codes since the Collection
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:
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With