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


当前回答

对于平等的人,可以看看Angelika Langer的《平等的秘密》。我非常喜欢它。她还提供了关于Java泛型的常见问题解答。在这里查看她的其他文章(向下滚动到“核心Java”),在那里她还继续介绍了第2部分和“混合类型比较”。祝你阅读愉快!

其他回答

对于继承友好的实现,请查看Tal Cohen的解决方案,如何正确地实现equals()方法?

简介:

在《有效Java编程语言指南》(Addison-Wesley, 2001)一书中,Joshua Bloch声称“根本没有办法扩展可实例化类并在保留等于契约的同时添加一个方面。”塔尔不同意。

他的解决方案是通过调用另一个非对称的blindlyEquals()来实现equals()。blindlyEquals()被子类覆盖,equals()被继承,并且永远不会被覆盖。

例子:

class Point {
    private int x;
    private int y;
    protected boolean blindlyEquals(Object o) {
        if (!(o instanceof Point))
            return false;
        Point p = (Point)o;
        return (p.x == this.x && p.y == this.y);
    }
    public boolean equals(Object o) {
        return (this.blindlyEquals(o) && o.blindlyEquals(this));
    }
}

class ColorPoint extends Point {
    private Color c;
    protected boolean blindlyEquals(Object o) {
        if (!(o instanceof ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint)o;
        return (super.blindlyEquals(cp) && 
        cp.color == this.color);
    }
}

注意,如果要满足利斯科夫替换原则,equals()必须跨继承层次结构工作。

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

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

对于平等的人,可以看看Angelika Langer的《平等的秘密》。我非常喜欢它。她还提供了关于Java泛型的常见问题解答。在这里查看她的其他文章(向下滚动到“核心Java”),在那里她还继续介绍了第2部分和“混合类型比较”。祝你阅读愉快!

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

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

如果您正在使用对象关系映射器(Object-Relationship Mapper, 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强制加载惰性加载的对象。这意味着如果person被惰性加载,person.name将为空,即使person. getname()强制加载并返回“John Doe”。根据我的经验,这种情况在hashCode()和equals()中更常见。

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

保存一个对象会改变它的状态

持久对象通常使用id字段保存对象的键。当对象第一次保存时,该字段将自动更新。不要在hashCode()中使用id字段。但是你可以在equals()中使用它。

我经常使用的一个模式是

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

但是:你不能在hashCode()中包含getId()。如果这样做,当对象被持久化时,它的hashCode将发生变化。如果对象在HashSet中,您将“永远”找不到它。

在我的Person示例中,我可能会使用getName()来表示hashCode, getId()加上getName()(只是为了偏执)来表示equals()。对于hashCode()来说,如果存在一些“冲突”风险是可以的,但对于equals()来说就绝对不行。

hashCode()应该使用equals()中不变的属性子集