问答中心分类: JAVA在Java中重写equals和hashCode时应该考虑哪些问题?
0
Antti Sykäri 提问 3月 前

这个问题的答案是社区努力. 编辑现有答案以改进此帖子。它目前不接受新的答案或互动。

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

10 Answers
0
Johannes Brodwall 回答 3月 前

如果您使用Hibernate之类的对象关系映射器(ORM)来处理持久化的类,那么有一些问题值得注意,如果您不认为这已经非常复杂的话!
延迟加载的对象是子类
如果您的对象是使用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是唯一能正确操作的东西。
延迟加载的对象具有空字段
ORMs通常使用getter来强制加载延迟加载的对象。这意味着person.namenull如果person延迟加载,即使person.getName()强制加载并返回“John Doe”。根据我的经验,这种情况在hashCode()equals().
如果您处理的是ORM,请确保始终使用getter,并且永远不要在中使用字段引用hashCode()equals().
保存对象将更改其状态
持久化对象通常使用id字段以保持对象的键。首次保存对象时,此字段将自动更新。不要在中使用id字段hashCode(). 但你可以在equals().
我经常使用的模式是

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

但是:您不能包括getId()在里面hashCode(). 如果这样做,当对象被持久化时,其hashCode变化。如果对象位于HashSet,您将“永远”找不到它。
在我的Person例如,我可能会使用getName()对于hashCodegetId()getName()(只为偏执狂)为了equals(). 如果有“碰撞”的风险hashCode(),但对equals().
hashCode()应使用equals()

jimmybondy 回复 3月 前

@约翰内斯·布罗德沃尔:我不明白Saving an object will change it's state!hashCode必须返回int,那么您将如何使用getName()? 你能给我举个例子吗hashCode

mateusz.fiolka 回复 3月 前

@jimmybondy:getName将返回一个字符串对象,该对象还包含一个可以使用的哈希代码

0
Ran Biron 回答 3月 前

关于obj.getClass() != getClass().
此声明是equals()继承遗产不友好。JLS(Java语言规范)规定,如果A.equals(B) == true然后B.equals(A)还必须返回true. 如果省略继承重写的类的语句equals()(并更改其行为)将违反此规范。
考虑以下示例,说明省略该语句时会发生什么情况:

class A {
      int field1;

      A(int field1) {
        this.field1 = field1;
      }

      public boolean equals(Object other) {
        return (other != null && other instanceof A && ((A) other).field1 == field1);
      }
    }

    class B extends A {
        int field2;

        B(int field1, int field2) {
            super(field1);
            this.field2 = field2;
        }

        public boolean equals(Object other) {
            return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
        }
    }

正在执行new A(1).equals(new A(1))而且new B(1,1).equals(new B(1,1))结果应该是真的。
这看起来都很好,但看看如果我们尝试使用这两个类会发生什么:

A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;

显然,这是错误的。
如果要确保对称条件。如果b=a,则a=b和Liskov替换原则调用super.equals(other)不仅是在B实例,但在之后检查A实例:

if (other instanceof B )
   return (other != null && ((B)other).field2 == field2 && super.equals(other)); 
if (other instanceof A) return super.equals(other); 
   else return false;

这将输出:

a.equals(b) == true;
b.equals(a) == true;

其中,如果a不是的引用B,则它可能是类的引用A(因为您扩展了它),在这种情况下,您调用super.equals() .

pihentagy 回复 3月 前

如果(obj.getClass()!=这getClass()&&对象。getClass()。isInstance(this))返回对象。等于(本);

Ran Biron 回复 3月 前

@pihentagy—然后,当实现类不重写equals方法时,我会得到一个stackoverflow。不好玩。

Jacob Raihle 回复 3月 前

你不会得到堆栈溢出。如果没有重写equals方法,您将再次调用相同的代码,但递归的条件将始终为false!

supercat 回复 3月 前

@pihentagy:如果有两个不同的派生类,这是如何表现的?如果aThingWithOptionSetA可以等于Thing前提是所有额外选项都有默认值,对于ThingWithOptionSetB,则应该可以ThingWithOptionSetA比较等于ThingWithOptionSetB只有当两个对象的所有非基础属性都与它们的默认值匹配时,我看不出如何进行测试。

nickgrim 回复 3月 前

问题是它破坏了及物性。如果您添加B b2 = new B(1,99)然后b.equals(a) == truea.equals(b2) == true但是b.equals(b2) == false.

0
Kevin Wong 回答 3月 前

对于继承友好的实现,请查看Tal Cohen的解决方案,如何正确实现equals()方法?
总结:
在他的书中高效Java编程语言指南(AddisonWesley,2001),JoshuaBloch声称“在保持equals契约的同时,根本没有办法扩展一个实例化类并添加一个方面。”Tal不同意。
他的解决方案是通过双向调用另一个非对称的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);
    }
}

请注意,如果Liskov替换原则就是要满足。

Blaisorblade 回复 3月 前

看看这里介绍的canEqual方法-相同的原理使两种解决方案都能工作,但使用canEqual,您不会对相同的字段进行两次比较(如上所述,p.x==此.x将在两个方向上进行测试):阿蒂玛。com/lejava/articles/equality。html

Kevin 回复 3月 前

无论如何,我认为这不是一个好主意。它使Equals契约变得不必要的混乱——使用两个点参数a和b的人必须意识到a.getX()=b.getX()和a.getY()=b.getY()可能为真,但a.Equals(b)和b.Equals(a)都为假(如果只有一个是色点)。

Aleksandr Dubinsky 回复 3月 前

基本上,这就像if (this.getClass() != o.getClass()) return false,但很灵活,因为它只在派生类费心修改equals时返回false。是这样吗?

0
Eugene 回答 3月 前

仍然感到惊讶的是,没有人为此推荐番石榴图书馆。

//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());
    }
herman 回复 3月 前

Java语言util。物体。hash()和java。util。物体。equals()是Java 7(于2011年发布)的一部分,因此您不需要番石榴。

herman 回复 3月 前

当然,但您应该避免这种情况,因为Oracle不再为Java 6提供公共更新(自2013年2月以来一直如此)。

Steve Kuo 回复 3月 前

你的this在里面this.getDate()没有任何意义(除了杂乱无章)

Amos M. Carpenter 回复 3月 前

“not instanceof”表达式需要额外的括号:if (!(otherObject instanceof DateAndPattern)) {. 同意hernan和Steve Kuo的观点(尽管这是个人喜好的问题),但仍然是+1。

0
Luna Kong 回答 3月 前

超类中有两种方法,如java。lang.Object。我们需要将它们重写为自定义对象。

public boolean equals(Object obj)
public int hashCode()

只要相等,相等的对象必须生成相同的哈希代码,但是不相等的对象不需要生成不同的哈希代码。

public class Test
{
    private int num;
    private String data;
    public boolean equals(Object obj)
    {
        if(this == obj)
            return true;
        if((obj == null) || (obj.getClass() != this.getClass()))
            return false;
        // object must be Test at this point
        Test test = (Test)obj;
        return num == test.num &&
        (data == test.data || (data != null && data.equals(test.data)));
    }

    public int hashCode()
    {
        int hash = 7;
        hash = 31 * hash + num;
        hash = 31 * hash + (null == data ? 0 : data.hashCode());
        return hash;
    }

    // other methods
}

如果您想获得更多信息,请查看以下链接http://www.javaranch.com/journal/2002/10/equalhash.html
这是另一个例子,http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
玩得开心!@@

Sam 回复 3月 前

抱歉,我不理解关于hashCode方法的这句话:如果它使用的变量多于equals(),则是不合法的。但如果我用更多的变量编码,我的代码就会编译。为什么不合法?