Overriding equals/hashCode on cross referencing classes in Java causes StackOverflowError

两盒软妹~` 提交于 2019-12-07 17:18:56

问题


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?


回答1:


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.




回答2:


Imho there are 2 versions available. I assume company should be the "leading" class, storing the employees.

  1. version: In the employee equals, use the "==" to check for object equality on company (not very nice)
  2. version: assign your company a unique ID and compare that only that company ID in employee equals

hth




回答3:


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?




回答4:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!