简体   繁体   English

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

[英]What issues should be considered when overriding equals and hashCode in Java?

What issues / pitfalls must be considered when overriding equals and hashCode ?覆盖equalshashCode时必须考虑哪些问题/陷阱?

The theory (for the language lawyers and the mathematically inclined):理论(适用于语言律师和数学爱好者):

equals() ( javadoc ) must define an equivalence relation (it must be reflexive , symmetric , and transitive ). equals() ( javadoc ) 必须定义一个等价关系(它必须是自反的对称的传递的)。 In addition, it must be consistent (if the objects are not modified, then it must keep returning the same value).另外,它必须是一致的(如果对象没有被修改,那么它必须保持返回相同的值)。 Furthermore, o.equals(null) must always return false.此外, o.equals(null)必须始终返回 false。

hashCode() ( javadoc ) must also be consistent (if the object is not modified in terms of equals() , it must keep returning the same value). hashCode() ( javadoc ) 也必须一致(如果对象没有根据equals()修改,它必须保持返回相同的值)。

The relation between the two methods is:这两种方法的关系是:

Whenever a.equals(b) , then a.hashCode() must be same as b.hashCode() .每当a.equals(b) ,那么a.hashCode()必须与b.hashCode()相同。

In practice:在实践中:

If you override one, then you should override the other.如果您覆盖一个,那么您应该覆盖另一个。

Use the same set of fields that you use to compute equals() to compute hashCode() .使用用于计算equals()的相同字段集来计算hashCode()

Use the excellent helper classes EqualsBuilder and HashCodeBuilder from the Apache Commons Lang library.使用Apache Commons Lang库中优秀的辅助类EqualsBuilderHashCodeBuilder An example:一个例子:

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

Also remember:还要记住:

When using a hash-based Collection or Map such as HashSet , LinkedHashSet , HashMap , Hashtable , or WeakHashMap , make sure that the hashCode() of the key objects that you put into the collection never changes while the object is in the collection.使用基于散列的集合映射(例如HashSetLinkedHashSetHashMapHashtableWeakHashMap )时,请确保放入集合的关键对象的 hashCode() 在对象在集合中时不会更改。 The bulletproof way to ensure this is to make your keys immutable, which has also other benefits .确保这一点的万无一失的方法是使您的密钥不可变,这还有其他好处

There are some issues worth noticing if you're dealing with classes that are persisted using an Object-Relationship Mapper (ORM) like Hibernate, if you didn't think this was unreasonably complicated already!如果您正在处理使用像 Hibernate 这样的对象关系映射器 (ORM) 持久化的类,如果您认为这已经不合理地复杂了,那么有一些问题值得注意!

Lazy loaded objects are subclasses延迟加载的对象是子类

If your objects are persisted using an ORM, in many cases you will be dealing with dynamic proxies to avoid loading object too early from the data store.如果您的对象使用 ORM 持久化,在许多情况下您将处理动态代理以避免从数据存储中过早加载对象。 These proxies are implemented as subclasses of your own class.这些代理作为您自己类的子类实现。 This means that this.getClass() == o.getClass() will return false .这意味着this.getClass() == o.getClass()将返回false For example:例如:

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

If you're dealing with an ORM, using o instanceof Person is the only thing that will behave correctly.如果您正在处理 ORM,那么使用o instanceof Person是唯一可以正确运行的方法。

Lazy loaded objects have null-fields延迟加载的对象具有空字段

ORMs usually use the getters to force loading of lazy loaded objects. ORM 通常使用 getter 来强制加载延迟加载的对象。 This means that person.name will be null if person is lazy loaded, even if person.getName() forces loading and returns "John Doe".这意味着如果person延迟加载,则person.name将为null ,即使person.getName()强制加载并返回“John Doe”。 In my experience, this crops up more often in hashCode() and equals() .根据我的经验,这在hashCode()equals()出现得更频繁。

If you're dealing with an ORM, make sure to always use getters, and never field references in hashCode() and equals() .如果您正在处理 ORM,请确保始终使用 getter,并且从不使用hashCode()equals()字段引用。

Saving an object will change its state保存一个对象会改变它的状态

Persistent objects often use a id field to hold the key of the object.持久化对象通常使用一个id字段来保存对象的键。 This field will be automatically updated when an object is first saved.首次保存对象时,此字段将自动更新。 Don't use an id field in hashCode() .不要在hashCode()使用 id 字段。 But you can use it in equals() .但是您可以在equals()使用它。

A pattern I often use is我经常使用的一种模式是

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

But: you cannot include getId() in hashCode() .但是:您不能在hashCode()包含getId() hashCode() If you do, when an object is persisted, its hashCode changes.如果这样做,当一个对象被持久化时,它的hashCode改变。 If the object is in a HashSet , you'll "never" find it again.如果对象在HashSet ,您将“永远”找不到它。

In my Person example, I probably would use getName() for hashCode and getId() plus getName() (just for paranoia) for equals() .在我的Person示例中,我可能会使用getName()作为hashCodegetId()加上getName() (仅用于偏执)作为equals() It's okay if there are some risk of "collisions" for hashCode() , but never okay for equals() .如果hashCode()有一些“冲突”的风险是可以的,但对于equals()永远没有问题。

hashCode() should use the non-changing subset of properties from equals() hashCode()应该使用equals()不变的属性子集

A clarification about the obj.getClass() != getClass() .关于obj.getClass() != getClass()

This statement is the result of equals() being inheritance unfriendly.此语句是equals()继承不友好的结果。 The JLS (Java language specification) specifies that if A.equals(B) == true then B.equals(A) must also return true . JLS(Java 语言规范)指定如果A.equals(B) == trueB.equals(A)也必须返回true If you omit that statement inheriting classes that override equals() (and change its behavior) will break this specification.如果您忽略继承覆盖equals() (并更改其行为)的语句将破坏此规范。

Consider the following example of what happens when the statement is omitted:考虑以下省略语句时发生的情况的示例:

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

Doing new A(1).equals(new A(1)) Also, new B(1,1).equals(new B(1,1)) result give out true, as it should.执行new A(1).equals(new A(1))此外, new B(1,1).equals(new B(1,1))结果应该是正确的。

This looks all very good, but look what happens if we try to use both classes:这看起来都很好,但是看看如果我们尝试使用这两个类会发生什么:

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

Obviously, this is wrong.显然,这是错误的。

If you want to ensure the symmetric condition.如果要保证对称条件。 a=b if b=a and the Liskov substitution principle call super.equals(other) not only in the case of B instance, but check after for A instance: 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;

Which will output:这将输出:

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

Where, if a is not a reference of B , then it might be a be a reference of class A (because you extend it), in this case you call super.equals() too .其中,如果a不的参考B ,那么它可能是一个是类的引用A (因为你扩展它),在这种情况下,你叫super.equals()

For an inheritance-friendly implementation, check out Tal Cohen's solution, How Do I Correctly Implement the equals() Method?对于继承友好的实现,请查看 Tal Cohen 的解决方案, 如何正确实现 equals() 方法?

Summary:概括:

In his book Effective Java Programming Language Guide (Addison-Wesley, 2001), Joshua Bloch claims that "There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract."在他的《 Effective Java Programming Language Guide》 (Addison-Wesley,2001 年)一书中,Joshua Bloch 声称“根本没有办法在保留 equals 约定的同时扩展可实例化的类并添加方面。” Tal disagrees.塔尔不同意。

His solution is to implement equals() by calling another nonsymmetric blindlyEquals() both ways.他的解决方案是通过双向调用另一个非对称的blinlyEquals()来实现equals()。 blindlyEquals() is overridden by subclasses, equals() is inherited, and never overridden. BlindlyEquals() 被子类覆盖,equals() 是继承的,永远不会被覆盖。

Example:例子:

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

Note that equals() must work across inheritance hierarchies if the Liskov Substitution Principle is to be satisfied.请注意,如果要满足Liskov 替换原则,equals() 必须跨继承层次工作。

Still amazed that none recommended the guava library for this.仍然感到惊讶的是,没有人为此推荐番石榴库。

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

There are two methods in super class as java.lang.Object.超类中有两个方法java.lang.Object。 We need to override them to custom object.我们需要将它们覆盖为自定义对象。

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

Equal objects must produce the same hash code as long as they are equal, however unequal objects need not produce distinct hash codes.相等的对象只要相等就必须产生相同的散列码,但不相等的对象不需要产生不同的散列码。

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
}

If you want get more, please check this link as http://www.javaranch.com/journal/2002/10/equalhash.html如果您想获得更多,请检查此链接为http://www.javaranch.com/journal/2002/10/equalhash.html

This is another example, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html这是另一个例子, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html

Have Fun!玩得开心! @.@ @.@

There are a couple of ways to do your check for class equality before checking member equality, and I think both are useful in the right circumstances.在检查成员相等性之前,有几种方法可以检查类相等性,我认为这两种方法在正确的情况下都很有用。

  1. Use the instanceof operator.使用instanceof运算符。
  2. Use this.getClass().equals(that.getClass()) .使用this.getClass().equals(that.getClass())

I use #1 in a final equals implementation, or when implementing an interface that prescribes an algorithm for equals (like the java.util collection interfaces—the right way to check with with (obj instanceof Set) or whatever interface you're implementing).我在final equals 实现中使用 #1,或者在实现为 equals 规定算法的接口时(如java.util集合接口——使用(obj instanceof Set)或您正在实现的任何接口进行检查的正确方法) . It's generally a bad choice when equals can be overridden because that breaks the symmetry property.当 equals 可以被覆盖时,这通常是一个糟糕的选择,因为这会破坏对称性。

Option #2 allows the class to be safely extended without overriding equals or breaking symmetry.选项#2 允许在不覆盖等号或破坏对称性的情况下安全地扩展类。

If your class is also Comparable , the equals and compareTo methods should be consistent too.如果您的类也是Comparable ,则equalscompareTo方法也应该保持一致。 Here's a template for the equals method in a Comparable class:这是Comparable类中 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;
  }

}

For equals, look into Secrets of Equals by Angelika Langer .对于平等,请查看Angelika Langer 的Secrets of Equals I love it very much.我非常爱它。 She's also a great FAQ about Generics in Java .她也是关于Java 泛型的一个很好的常见问题解答。 View her other articles here (scroll down to "Core Java"), where she also goes on with Part-2 and "mixed type comparison".在此处查看她的其他文章(向下滚动到“Core Java”),在那里她还继续讨论第 2 部分和“混合类型比较”。 Have fun reading them!祝您阅读愉快!

equals() method is used to determine the equality of two objects. equals() 方法用于确定两个对象的相等性。

as int value of 10 is always equal to 10. But this equals() method is about equality of two objects.因为 int 值 10 总是等于 10。但是这个 equals() 方法是关于两个对象的相等性。 When we say object, it will have properties.当我们说对象时,它将具有属性。 To decide about equality those properties are considered.为了确定相等性,要考虑这些属性。 It is not necessary that all properties must be taken into account to determine the equality and with respect to the class definition and context it can be decided.不必考虑所有属性来确定相等性,并且可以根据类定义和上下文来决定。 Then the equals() method can be overridden.然后可以覆盖 equals() 方法。

we should always override hashCode() method whenever we override equals() method.每当我们覆盖 equals() 方法时,我们都应该始终覆盖 hashCode() 方法。 If not, what will happen?如果不是,会发生什么? If we use hashtables in our application, it will not behave as expected.如果我们在应用程序中使用哈希表,它将不会按预期运行。 As the hashCode is used in determining the equality of values stored, it will not return the right corresponding value for a key.由于 hashCode 用于确定存储的值的相等性,因此它不会为键返回正确的对应值。

Default implementation given is hashCode() method in Object class uses the internal address of the object and converts it into integer and returns it.给出的默认实现是 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;

  }
}

Example Code Output:示例代码输出:

bengalTiger1 and bengalTiger2: true 
bengalTiger1 and siberianTiger: false 
bengalTiger1 hashCode: 1398212510 
bengalTiger2 hashCode: 1398212510 
siberianTiger hashCode: –1227465966

Logically we have:逻辑上我们有:

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

But not vice-versa!反之则不然!

One gotcha I have found is where two objects contain references to each other (one example being a parent/child relationship with a convenience method on the parent to get all children).我发现的一个问题是两个对象包含彼此的引用(一个例子是父/子关系,父/子关系使用父级上的便捷方法来获取所有子级)。
These sorts of things are fairly common when doing Hibernate mappings for example.例如,在进行 Hibernate 映射时,这类事情相当普遍。

If you include both ends of the relationship in your hashCode or equals tests it's possible to get into a recursive loop which ends in a StackOverflowException.如果在 hashCode 或 equals 测试中包含关系的两端,则可能会进入以 StackOverflowException 结尾的递归循环。
The simplest solution is to not include the getChildren collection in the methods.最简单的解决方案是不在方法中包含 getChildren 集合。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM