代码之家  ›  专栏  ›  技术社区  ›  Matt Sheppard

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

  •  617
  • Matt Sheppard  · 技术社区  · 17 年前

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

    11 回复  |  直到 11 年前
        1
  •  1393
  •   community wiki 18 revs, 10 users 77% Antti Sykäri    10 年前

    该理论(适用于语言律师和数学爱好者):

    equals() ( javadoc )必须定义等价关系(必须 反身的 , 对称的 ,以及 及物的 ).此外,它必须 一致的 (如果对象没有被修改,那么它必须保持返回相同的值)。此外, o.equals(null) 必须始终返回false。

    hashCode() ( javadoc )也必须是 一致的 (如果对象在以下方面没有被修改 equals() ,它必须不断返回相同的值)。

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

    每当 a.equals(b) 那么 a.hashCode() 必须与 b.hashCode() .

    在实践中:

    如果你覆盖了一个,那么你应该覆盖另一个。

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

    使用优秀的助手课程 EqualsBuilder HashCodeBuilder Apache Commons Lang 图书馆。举个例子:

    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()在对象在集合中时永远不会改变。确保这一点的防弹方法是使您的密钥不可变, which has also other benefits .

        2
  •  284
  •   maaartinus    8 年前

    如果您不认为这已经变得异常复杂,那么如果您正在处理使用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通常使用getter来强制加载延迟加载的对象。这意味着 person.name null 如果 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() 散列值 getId() getName() (只是偏执狂) equals() 。如果存在一些“碰撞”的风险,那也没关系 hashCode() ,但永远不会好 equals() .

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

        3
  •  82
  •   BartoszKP    11 年前

    关于 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() .

        4
  •  43
  •   reevesy onejigtwojig    13 年前

    对于继承友好的实现,请查看Tal Cohen的解决方案, How Do I Correctly Implement the equals() Method?

    摘要:

    在他的书中 Effective Java Programming Language Guide (Addison-Wesley,2001),Joshua Bloch声称“根本不可能在保持相等契约的同时扩展一个可实例化类并添加一个方面。”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()必须跨继承层次结构工作,如果 Liskov Substitution Principle 就是要满足。

        5
  •  31
  •   Andy Thomas    11 年前

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

     //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());
        }
    
        6
  •  26
  •   Luna Kong    11 年前

    超类中有两个方法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

    玩得开心 @.@

        7
  •  18
  •   erickson    17 年前

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

    1. 使用 instanceof 操作员。
    2. 使用 this.getClass().equals(that.getClass()) .

    我在a中使用#1 final equals实现,或者在实现为equals规定算法的接口时(如 java.util 集合接口——正确的检查方式 (obj instanceof Set) 或者你正在实现的任何接口)。当等号可以被覆盖时,这通常是一个糟糕的选择,因为这会破坏对称性。

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

    如果你的班级也是 Comparable ,the 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;
      }
    
    }
    
        8
  •  15
  •   Johannes Schaub - litb    16 年前

    对于平等者,请查看 Secrets of Equals 靠近 Angelika Langer 我非常喜欢它。她也是一个很好的FAQ Generics in Java .查看她的其他文章 here (向下滚动到“Core Java”),她还继续介绍了第2部分和“混合类型比较”。祝你阅读愉快!

        9
  •  11
  •   user1431282 rohan kamat    11 年前

    equals()方法用于确定两个对象的相等性。

    因为int值10总是等于10。但是这个equals()方法是关于两个对象的相等。当我们说object时,它将具有属性。为了决定是否相等,我们考虑了这些属性。没有必要考虑所有属性来确定相等性,并且可以根据类定义和上下文来决定相等性。然后可以重写equals()方法。

    每当我们重写equals()方法时,我们都应该重写hashCode()方法。如果没有,会发生什么?如果我们在应用程序中使用哈希表,它将不会按预期运行。由于hashCode用于确定所存储值的相等性,因此它不会为键返回正确的对应值。

    给出的默认实现是Object类中的hashCode()方法,该方法使用对象的内部地址并将其转换为整数并返回。

    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
    
        10
  •  7
  •   Hermes    12 年前

    从逻辑上讲,我们有:

    a.getClass().equals(b.getClass()) && a.equals(b) a.hashCode() == b.hashCode()

    但是 反之亦然!

        11
  •  6
  •   Darren Greaves    17 年前

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

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

    推荐文章