简体   繁体   English

在Java中为具有循环引用的对象实现equals和hashCode

[英]Implementing equals and hashCode for objects with circular references in Java

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): 它们看起来与此相似(这是简化的;在我的实际域模型中,类A包含一个B列表,每个B都有对父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. Eclipse使用A和B的两个字段生成了hashCodeequals 。问题是,在两个对象中的任何一个上调用equalshashCode方法都会导致StackOverflowError因为它们都在调用另一个对象的equalshashCode方法。 For example the following program will fail with StackOverflowError using the above objects: 例如,以下程序使用上述对象将失败并出现StackOverflowError

    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? 在这种情况下,定义hashCodeequals最佳实践是什么? 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. 我想将所有字段保留在equals方法中,这样就可以对对象进行真正的深度相等比较,但是我看不到如何解决这个问题。 Thanks! 谢谢!

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. 我同意I82的意见,因此您应该避免让B引用他们的父母:这是信息重复,通常只会导致麻烦,但是您可能需要这样做。

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. 即使将父引用保留在B ,就哈希码而言,您也应该完全忽略父引用,而仅使用B真实内部变量来构建哈希码。

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. A只是容器,它们的值完全取决于它们的内容(即所包含的B的值),因此哈希键也应如此。

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. 如果A是无序集合,则必须非常小心,以确保从B值(或B哈希码)构建的哈希码不依赖于某些排序。 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. 例如,如果哈希码是通过将包含的B的哈希码按一定顺序相乘并相乘而构建的,则在计算求和/乘法结果之前,应首先按递增顺序对哈希码进行排序。 Similarly, A.equals(o) must not depend on the ordering of the B s (if unordered set). 同样, A.equals(o)不得依赖于B的排序(如果是无序集合)。

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). 请注意,如果您在A内使用java.util.Collection ,则仅通过忽略父引用来固定B的哈希码将自动给出有效的A哈希码,因为Collection默认情况下具有良好的哈希码(有序或无序) 。

In a typical model, most entities have a unique ID. 在典型模型中,大多数实体具有唯一的ID。 This ID is useful in various use-cases (in particular: Database retreival/lookup). 该ID在各种用例中都非常有用(尤其是:数据库恢复/查找)。 IIUC, the bKey field is supposed to be such a unique ID. IIUC,bKey字段应该是这样的唯一ID。 Thus, the common practice for comparing such entities is to compare their ID: 因此,比较此类实体的常见做法是比较其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)". 您可能会问:“如果两个B对象具有相同的ID但状态不同(它们的字段的值不同),会发生什么情况”。 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. 无论您如何实现equals()hashCode()这都将是一个问题,因为它实质上意味着您在系统中拥有同一实体的两个不同版本,并且您将无法分辨出哪个是正确的。

You could have two flavors of equals -- the override of Object.equals and one that's better suited for recursion. 您可能有两种equals -重写Object.equals和一种更适合递归的方式。 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. 递归相等检查采用A或B(以此类推的另一类为准),这是您要代表其调用递归相等的对象。 If you're calling it on behalf of this.equals , you pass in null . 如果您代表this.equals调用它,则传入null For instance: 例如:

A {
    ...
    @Override
    public boolean equals(Object obj) {
        // check for this, null, instanceof...
        A other = (A) obj;
        return recursiveEquality(other, null);
    }

    // package-private, optionally-recursive equality
    boolean recursiveEquality(A other, B onBehalfOf) {
        if (onBehalfOf != null) {
            assert b != onBehalfOf;
            // we got here from within a B.equals(..) call, so we just need
            // to check that our B is the same as the one that called us.
        }
        // At this point, we got called from A.equals(Object). So,
        // need to recurse.
        else if (b == null) {
            if (other.b != null)
                return false;
        }
        // B has a similar structure. Call its recursive-aware equality,
        // passing in this for the onBehalfOf
        else if (!b.recursiveEquality(other.b, this))
            return false;

        // check bkey and return
    }
}

So, following A.equals : 因此,遵循A.equals

  1. A.equals calls `recursiveEquality(otherA, null) A.equals调用`recursiveEquality(otherA,null)
    1. if this.b != null , we end up in the third if-else block, which calls b.recursiveEquality(other.b, this) 如果this.b != null ,则结束于第三个if-else块,该块调用b.recursiveEquality(other.b, this)
      1. in B.recursiveEquality , we hit the first if-else block, which simply asserts that our A is the same one that was passed to us (ie, that the circular reference isn't broken) B.recursiveEquality ,我们到达了第一个 if-else块,该块简单地断言我们的A与传递给我们的那个相同(即,循环引用未中断)
      2. we finish B.recursiveEquality by checking aKey (depending on your invariants, you may want to assert something based on what happened in step 3). 我们通过检查aKey完成B.recursiveEquality (取决于您的不变量,您可能要根据步骤3中发生的事情来断言某些内容)。 B.recursiveEquality returns B.recursiveEquality回报
    2. we finish A.recursiveEquality by checking bKey , possibly with similar asserts 我们通过检查bKey完成A.recursiveEquality ,可能具有类似的断言
  2. A.equals returns the result of the recursive equality check A.equals返回递归相等检查的结果

First of all, are you sure you want to override Equals() and GetHashCode() ? 首先,您确定要覆盖Equals()GetHashCode()吗? 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 a getB field of type B and each B has a getA field of type A . 例如,假设每个A具有类型BgetB字段,每个B具有类型AgetA字段。 Let a1 and a2 be two A objects, have the same fields and the same getB (same as in "same memory address") b1 . 假设a1a2是两个A对象,具有相同的字段和相同的getB (与“相同的内存地址”相同) b1 Are a1 and a2 equal? a1a2相等吗? Suppose b1.getA is the same as a1 (same as in "same memory address") but not the same as a2 . 假设b1.getAa1相同(与“相同的内存地址”相同),但与a2 Do you still want to consider a1 and a2 equal? 您是否仍要考虑a1a2相等?

If not, don't override anything and use the default referential equality. 如果没有,则不要覆盖任何内容,并使用默认的引用相等性。

If yes, then here is a solution: Let A have a int GetCoreHashCode() function that does not depend on getB element, (but depend on other fields). 如果是,那么这里是一个解决方案:让A具有一个不依赖于getB元素(但依赖于其他字段)的int GetCoreHashCode()函数。 Let B have a int GetCoreHashCode() function that does not depend on getA element, (but depend on other fields). B具有一个不依赖于getA元素(但依赖于其他字段)的int GetCoreHashCode()函数。 Now let int GetHashCode() function of A depend upon this.GetCoreHashCode() and getB.GetCoreHashCode() and likewise for B , and you are done. 现在,让我们int GetHashCode()的函数A取决于this.GetCoreHashCode()getB.GetCoreHashCode()同样为B ,和你做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM