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

在Java中重写equals和hashCode时应该考虑哪些问题?,java,overriding,equals,hashcode,Java,Overriding,Equals,Hashcode,当覆盖equals和hashCode时,必须考虑哪些问题/陷阱?该理论(适用于语言律师和有数学倾向的人): equals()()必须定义一个等价关系(它必须是自反的、对称的和传递的)。此外,它必须是一致的(如果对象没有被修改,那么它必须继续返回相同的值)。此外,o.equals(null)必须始终返回false hashCode()()也必须是一致的(如果对象没有按照equals()进行修改,它必须不断返回相同的值) 这两种方法之间的关系为: 只要a.equals(b),那么a.hashCode

当覆盖
equals
hashCode
时,必须考虑哪些问题/陷阱?

该理论(适用于语言律师和有数学倾向的人):
equals()
()必须定义一个等价关系(它必须是自反的、对称的和传递的)。此外,它必须是一致的(如果对象没有被修改,那么它必须继续返回相同的值)。此外,
o.equals(null)
必须始终返回false

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

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

只要
a.equals(b)
,那么
a.hashCode()
必须与
b.hashCode()相同

实际上: 如果覆盖一个,则应覆盖另一个

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

使用库中的优秀助手类和。例如:

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();
    }
}
还要记住:
使用基于散列的或(如、或)时,请确保放入集合中的键对象的hashCode()在对象位于集合中时不会更改。确保这一点的防弹方法是使您的密钥不可变。

关于
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).等于(new A(1))
同样,
new B(1,1).等于(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()

在检查成员相等之前检查类相等有几种方法,我认为这两种方法在合适的情况下都很有用

  • 使用
    instanceof
    运算符
  • 使用
    this.getClass().equals(that.getClass())
  • 我在
    final
    equals实现中使用#1,或者在实现规定equals算法的接口时使用#1(如
    java.util
    集合接口,这是检查
    (obj instanceof Set)
    或任何您正在实现的接口的正确方法)。如果可以重写equals,则通常是一个错误的选择,因为这会破坏对称性属性

    选项#2允许在不重写equals或破坏对称性的情况下安全地扩展类

    如果您的类也是
    可比的
    ,那么
    equals
    compareTo
    方法也应该是一致的。下面是一个
    可比
    类中equals方法的模板:

    final class MyClass implements Comparable<MyClass>
    {
    
      …
    
      @Override
      public boolean equals(Object obj)
      {
        /* If compareTo and equals aren't final, we should check with getClass instead. */
        if (!(obj instanceof MyClass)) 
          return false;
        return compareTo((MyClass) obj) == 0;
      }
    
    }
    
    final类MyClass实现了可比较的
    {
    …
    @凌驾
    公共布尔等于(对象obj)
    {
    /*如果compareTo和equals不是最终的,我们应该改为使用getClass*/
    如果(!(MyClass的obj实例))
    返回false;
    返回compareTo((MyClass)obj)==0;
    }
    }
    
    我发现的一个问题是,两个对象包含彼此的引用(一个示例是父/子关系,父对象上有一个方便的方法来获取所有子对象)。
    例如,在进行Hibernate映射时,这类事情非常常见

    如果在hashCode或equals测试中包含关系的两端,则可能进入以StackOverflowException结尾的递归循环。

    最简单的解决方案是在方法中不包含getChildren集合。

    要获得继承友好的实现,请查看Tal Cohen的解决方案

    总结:

    在他的书(Addison Wesley,2001)中,Joshua Bloch声称“在保持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);
        }
    }
    

    请注意,如果要满足以下条件,equals()必须跨继承层次结构工作。

    如果您使用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
    是唯一能够正确操作的方法

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

    orm通常使用gett
    if (this.getId() == null) {
        return this == other;
    }
    else {
        return this.getId().equals(other.getId());
    }
    
     //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());
        }
    
    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
    
    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
    }