I have two classes that represent two different database entities. Their relationship is 1:m in db and it is represented in class structures something 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 it to pass because both company1 and company2 have equal lists of employees, but it fails 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 cross reference in classes and thus equals/hashCode methods. But how should I implement equals/hashCode to avoid infinitive recursion?
As it is now, the identity of a company is defined solely by its employees. Likewise, the identity of an employee is defined solely by its company. Do you see how that leads to a mutual logical dependency?
You need to break that logical dependency in your code. How would you logically uniquely identify a company and an employee? Typically you'd do this with some sort of meaningful unique identifier: a name (string), a number (int/long), or some similar combination of primitive fields.
Imho there are 2 versions available. I assume company should be the "leading" class, storing the employees.
- version: In the employee equals, use the "==" to check for object equality on company (not very nice)
- version: assign your company a unique ID and compare that only that company ID in employee equals
hth
Do not compare the list of employees within the Company.equals method. Are there other attributes of Company that are meaningful and could be used to perform the comparison within equals, like a name? Or Stock Symbol?
You have inadvertently set up a recursive dependency between Company
and Employee
. The Company#hashCode()
method needs to compute the individual hashcodes of every Employee, and the Employee#hashCode()
method depends on the Company's hashcode, leading to an infinite recursion.
A company object's hashcode should not depend on the employees in it. The hash code is in some sense the "identity" of the object, which shouldn't change when a new employee is added to it. Same for Employee. The Employee's identity shouldn't change just because he/she moves to a different company.
You'll have to redefine those methods in terms of some meaningful identity attribute. Your code doesn't show it, but both Company
and Employee
must have some other member variables, such as a name. Base the hashCode and equals implementations on that attribute.
来源:https://stackoverflow.com/questions/9918732/overriding-equals-hashcode-on-cross-referencing-classes-in-java-causes-stackover