Overriding equals / hashCode in cross-reference classes in Java raises StackOverflowError
I have two classes that represent two different database objects. Their ratio is 1: m in db and is represented in class structures like this:
public class Company {
private List<Employee> employees;
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
public class Employee {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Now I want to override equals / hashCode on these classes. Eclipse generates the following code for me:
public class Company {
private List<Employee> employees;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((employees == null) ? 0 : employees.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Company other = (Company) obj;
if (employees == null) {
if (other.employees != null)
return false;
} else if (!employees.equals(other.employees))
return false;
return true;
}
}
public class Employee {
private Company company;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((company == null) ? 0 : company.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (company == null) {
if (other.company != null)
return false;
} else if (!company.equals(other.company))
return false;
return true;
}
}
If I run the following test:
public class EqualsTest {
@Test
public void testEquals() {
Company company1 = new Company();
Employee employee1 = new Employee();
employee1.setCompany(company1);
company1.setEmployees(Arrays.asList(employee1));
Company company2 = new Company();
Employee employee2 = new Employee();
employee2.setCompany(company2);
company2.setEmployees(Arrays.asList(employee2));
assertThat(company1, is(company2));
}
}
I expect this to pass because both company1 and company2 have equal employee lists, but it doesn't work with StackOverflowError:
java.lang.StackOverflowError
at java.util.AbstractList$Itr.<init>(AbstractList.java:318)
at java.util.AbstractList$Itr.<init>(AbstractList.java:318)
at java.util.AbstractList$ListItr.<init>(AbstractList.java:377)
at java.util.AbstractList.listIterator(AbstractList.java:315)
at java.util.AbstractList.listIterator(AbstractList.java:284)
at java.util.AbstractList.equals(AbstractList.java:502)
at com.test.Company.equals(Company.java:37)
at com.test.Employee.equals(Employee.java:35)
at java.util.AbstractList.equals(AbstractList.java:507)
at com.test.Company.equals(Company.java:37)
at com.test.Employee.equals(Employee.java:35)
at java.util.AbstractList.equals(AbstractList.java:507)
at com.test.Company.equals(Company.java:37)
at com.test.Employee.equals(Employee.java:35)
...
I understand that the reason for this failure is the cross-reference in the classes and thus the equals / hashCode methods. But how am I supposed to implement equals / hashCode to avoid infinitive recursion?
source to share
As it is now, the identity of a company is determined solely by its employees. Likewise, an employee's identity is determined solely by his company. Do you see how this leads to mutual logical dependence?
You need to break this logical dependency in your code. How do you logically uniquely identify a company and an employee? You usually do this with some meaningful unique identifier: name (string), number (int / long), or some similar combination of primitive fields.
source to share
Imho there are 2 versions available. I believe that the company should be the "leading" class for keeping employees.
- version: In employee equals, use "==" to check for equality of objects on a company (not very nice).
- : assign your company a unique identifier and compare that only that employee's company identifier is
HTH
source to share
You accidentally set up a recursive dependency between Company
and Employee
. The method Company#hashCode()
must compute the individual hash codes for each Employee, and the method Employee#hashCode()
depends on the company hash code, which results in infinite recursion.
The hash code of a company object should be independent of employees. The hash code is, in a sense, the "identity" of the object, which should not change when a new employee is added to it. The same goes for the employee. An employee's personality should not change just because he or she moves to another company.
You will have to override these methods in terms of some meaningful identity attribute. Your code does not show it, but both Company
, and Employee
should have some other member variables, such as the name. HashCode basics and peer implementations on this attribute.
source to share