重写equals和hashCode时必须考虑哪些问题/陷阱?


当前回答

理论(适用于语言律师和数学爱好者):

Equals () (javadoc)必须定义等价关系(它必须是自反的、对称的和可传递的)。此外,它必须是一致的(如果对象没有被修改,那么它必须一直返回相同的值)。此外,o.equals(null)必须总是返回false。

hashCode() (javadoc)也必须是一致的(如果对象没有按照equals()进行修改,它必须一直返回相同的值)。

这两种方法的关系是:

当a. = (b)时,则a.hashCode()必须与b. hashcode()相同。

在实践中:

如果你覆盖了一个,那么你也应该覆盖另一个。

使用用于计算equals()的相同字段集来计算hashCode()。

使用Apache Commons Lang库中的优秀辅助类EqualsBuilder和HashCodeBuilder。一个例子:

public class Person {
    private String name;
    private int age;
    // ...

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
            // if deriving: appendSuper(super.hashCode()).
            append(name).
            append(age).
            toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
       if (!(obj instanceof Person))
            return false;
        if (obj == this)
            return true;

        Person rhs = (Person) obj;
        return new EqualsBuilder().
            // if deriving: appendSuper(super.equals(obj)).
            append(name, rhs.name).
            append(age, rhs.age).
            isEquals();
    }
}

还记得:

当使用基于哈希的集合或映射(如HashSet、LinkedHashSet、HashMap、Hashtable或WeakHashMap)时,确保你放入集合中的关键对象的hashCode()在对象在集合中时永远不会改变。确保这一点的防弹方法是使您的密钥不可变,这还有其他好处。

其他回答

仍然令人惊讶的是,没有人推荐番石榴库。

 //Sample taken from a current working project of mine just to illustrate the idea

    @Override
    public int hashCode(){
        return Objects.hashCode(this.getDate(), this.datePattern);
    }

    @Override
    public boolean equals(Object obj){
        if ( ! obj instanceof DateAndPattern ) {
            return false;
        }
        return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
                && Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
    }

Equals()方法用于确定两个对象是否相等。

因为10的int值总是等于10。但是这个equals()方法是关于两个对象是否相等的。当我们说对象时,它会有属性。要决定是否相等,就要考虑这些性质。没有必要考虑所有属性来确定相等性,可以根据类定义和上下文来确定相等性。然后equals()方法可以被重写。

无论何时重写equals()方法,我们都应该重写hashCode()方法。如果不是,会发生什么?如果我们在应用程序中使用哈希表,它将不能像预期的那样运行。由于hashCode用于确定存储的值是否相等,因此它不会为键返回正确的对应值。

给出的默认实现是对象类中的hashCode()方法,该方法使用对象的内部地址并将其转换为整数并返回。

public class Tiger {
  private String color;
  private String stripePattern;
  private int height;

  @Override
  public boolean equals(Object object) {
    boolean result = false;
    if (object == null || object.getClass() != getClass()) {
      result = false;
    } else {
      Tiger tiger = (Tiger) object;
      if (this.color == tiger.getColor()
          && this.stripePattern == tiger.getStripePattern()) {
        result = true;
      }
    }
    return result;
  }

  // just omitted null checks
  @Override
  public int hashCode() {
    int hash = 3;
    hash = 7 * hash + this.color.hashCode();
    hash = 7 * hash + this.stripePattern.hashCode();
    return hash;
  }

  public static void main(String args[]) {
    Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
    Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
    Tiger siberianTiger = new Tiger("White", "Sparse", 4);
    System.out.println("bengalTiger1 and bengalTiger2: "
        + bengalTiger1.equals(bengalTiger2));
    System.out.println("bengalTiger1 and siberianTiger: "
        + bengalTiger1.equals(siberianTiger));

    System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
    System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
    System.out.println("siberianTiger hashCode: "
        + siberianTiger.hashCode());
  }

  public String getColor() {
    return color;
  }

  public String getStripePattern() {
    return stripePattern;
  }

  public Tiger(String color, String stripePattern, int height) {
    this.color = color;
    this.stripePattern = stripePattern;
    this.height = height;

  }
}

示例代码输出:

bengalTiger1 and bengalTiger2: true 
bengalTiger1 and siberianTiger: false 
bengalTiger1 hashCode: 1398212510 
bengalTiger2 hashCode: 1398212510 
siberianTiger hashCode: –1227465966

我发现的一个问题是两个对象包含彼此的引用(一个例子是父/子关系,在父对象上有一个方便的方法来获取所有的子对象)。 例如,在执行Hibernate映射时,这类事情相当常见。

如果在hashCode或equals测试中包含关系的两端,则有可能进入以StackOverflowException结束的递归循环。 最简单的解决方案是在方法中不包括getChildren集合。

逻辑上我们有:

a.getClass().equals(b.getClass()) && a.equals(b) ⇒ a.hashCode() == b.hashCode()

但反之亦然!

理论(适用于语言律师和数学爱好者):

Equals () (javadoc)必须定义等价关系(它必须是自反的、对称的和可传递的)。此外,它必须是一致的(如果对象没有被修改,那么它必须一直返回相同的值)。此外,o.equals(null)必须总是返回false。

hashCode() (javadoc)也必须是一致的(如果对象没有按照equals()进行修改,它必须一直返回相同的值)。

这两种方法的关系是:

当a. = (b)时,则a.hashCode()必须与b. hashcode()相同。

在实践中:

如果你覆盖了一个,那么你也应该覆盖另一个。

使用用于计算equals()的相同字段集来计算hashCode()。

使用Apache Commons Lang库中的优秀辅助类EqualsBuilder和HashCodeBuilder。一个例子:

public class Person {
    private String name;
    private int age;
    // ...

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
            // if deriving: appendSuper(super.hashCode()).
            append(name).
            append(age).
            toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
       if (!(obj instanceof Person))
            return false;
        if (obj == this)
            return true;

        Person rhs = (Person) obj;
        return new EqualsBuilder().
            // if deriving: appendSuper(super.equals(obj)).
            append(name, rhs.name).
            append(age, rhs.age).
            isEquals();
    }
}

还记得:

当使用基于哈希的集合或映射(如HashSet、LinkedHashSet、HashMap、Hashtable或WeakHashMap)时,确保你放入集合中的关键对象的hashCode()在对象在集合中时永远不会改变。确保这一点的防弹方法是使您的密钥不可变,这还有其他好处。