在Java中覆盖equals和hashCode时应该考虑哪些问题?

2022-08-31 04:14:05

覆盖和时必须考虑哪些问题/陷阱?equalshashCode


答案 1

理论(对于语言律师和数学倾向者):

equals() (javadoc)必须定义等价关系(它必须是自反的对称的和传递的)。此外,它必须是一致的(如果对象未被修改,那么它必须继续返回相同的值)。此外,必须始终返回 false。o.equals(null)

hashCode() (javadoc)也必须是一致的(如果对象未在 方面进行修改,则必须继续返回相同的值)。equals()

这两种方法之间的关系是:

只要 a.等于 (b),则 a.hashCode() 必须与 b.hashCode() 相同

在实践中:

如果覆盖一个,则应覆盖另一个。

使用用于计算的同一组字段来计算 。equals()hashCode()

使用 Apache Commons Lang 库中优秀的帮助器类 EqualsBuilderHashCodeBuilder。例如:

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();
    }
}

还要记住:

使用基于哈希的集合映射(如 HashSetLinkedHashSetHashMapHashtableWeakHashMap)时,请确保放入集合中的键对象的 hashCode() 在对象位于集合中时永远不会更改。确保这一点的防弹方法是使您的密钥不可变,这还有其他好处


答案 2

如果您正在处理使用对象关系映射器(ORM)(如Hibernate)持久化的类,那么有一些问题值得注意,如果您不认为这已经非常复杂了!

延迟加载的对象是子类

如果您的对象是使用 ORM 持久化的,那么在许多情况下,您将处理动态代理,以避免过早地从数据存储中加载对象。这些代理作为您自己的类的子类实现。这意味着 将返回 。例如:this.getClass() == o.getClass()false

Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy

如果你正在处理一个ORM,使用o instanceof Person是唯一可以正确操作的东西。

延迟加载的对象具有空字段

ORM 通常使用 getter 来强制加载延迟加载的对象。这意味着这将是如果是延迟加载,即使强制加载并返回“John Doe”。根据我的经验,这在 和 中更频繁地出现。person.namenullpersonperson.getName()hashCode()equals()

如果您正在处理ORM,请确保始终使用getters,并且永远不要在hashCode()equals()中字段引用。

保存对象将更改其状态

持久性对象通常使用字段来保存对象的键。首次保存对象时,此字段将自动更新。不要在 中使用 id 字段。但是您可以在.idhashCode()equals()

我经常使用的一种模式是

if (this.getId() == null) {
    return this == other;
}
else {
    return this.getId().equals(other.getId());
}

但是:您不能包含在 .如果这样做,当对象持久化时,其将发生变化。如果对象在 中,您将“永远不会”再次找到它。getId()hashCode()hashCodeHashSet

在我的例子中,我可能会使用for和plus(仅用于偏执狂)作为。如果有一些“碰撞”的风险是可以的,但对于 .PersongetName()hashCodegetId()getName()equals()hashCode()equals()

hashCode() 应使用 equals() 中的属性的不可更改子集


推荐