在Java中重写equals和hashCode时应考虑哪些问题?

重写equalshashCode时必须考虑哪些问题/陷阱?


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

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

hashCode() (javadoc)也必须是一致的(如果对象没有用equals()修改,它必须保持返回相同的值)。

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

每当a.equals(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();
    }
}

还请记住:

在使用基于散列的Collection或Map(例如HashSet,LinkedHashSet,HashMap,Hashtable或WeakHashMap)时,确保放入集合中的关键对象的hashCode()在对象位于集合中时永远不会更改。 确保这一点的防弹方法是使您的密钥不可变,这也有其他好处。


有些问题值得注意,如果您正在处理使用像Hibernate这样的Object-Relationship Mapper(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是唯一能正确表现的东西。

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

ORM通常使用getters强制加载延迟加载的对象。 这意味着,即使person.getName()强制加载并返回“John Doe”,如果person是懒加载的, person.name将为null 。 根据我的经验,这在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() 。 如果你这样做,当一个对象被持久化时,它的hashCode改变。 如果对象位于HashSet ,您将“再也不会”找到它。

在我的Person例子中,我可能会使用getName()作为hashCodegetId()加上getName() (仅仅用于偏执狂)为equals() 。 如果hashCode()存在一些“冲突”的风险,但对于equals()永远不会有问题。

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


关于obj.getClass() != getClass()

这个语句是equals()继承不友好的结果。 JLS(Java语言规范)指定如果A.equals(B) == trueB.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;

显然,这是错误的。

如果你想确保对称条件。 a = b如果b = a并且Liskov替换原则不仅在B实例的情况下调用super.equals(other) ,而且在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()

链接地址: http://www.djcxy.com/p/13275.html

上一篇: What issues should be considered when overriding equals and hashCode in Java?

下一篇: Difference between a deprecated and a legacy API?