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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27581/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 07:20:09  来源:igfitidea点击:

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

javaoverridingequalshashcode

提问by Matt Sheppard

What issues / pitfalls must be considered when overriding equalsand hashCode?

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

采纳答案by Antti Kissaniemi

The theory (for the language lawyers and the mathematically inclined):

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

equals()(javadoc) must define an equivalence relation (it must be reflexive, symmetric, and transitive). 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.

equals()( javadoc) 必须定义一个等价关系(它必须是自反的对称的传递的)。另外,它必须是一致的(如果对象没有被修改,那么它必须保持返回相同的值)。此外,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 relationbetween 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 EqualsBuilderand HashCodeBuilderfrom the Apache Commons Langlibrary. An example:

使用优秀的辅助类EqualsBuilderHashCodeBuilder阿帕奇共享郎库。一个例子:

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 Collectionor Mapsuch 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. The bulletproof way to ensure this is to make your keys immutable, which has also other benefits.

当使用基于散列的集合映射(例如HashSetLinkedHashSetHashMapHashtableWeakHashMap )时,请确保放入集合的关键对象的 hashCode() 在对象在集合中时永远不会更改。确保这一点的万无一失的方法是使您的密钥不可变,这还有其他好处

回答by Ran Biron

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

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

This statement is the result of equals()being inheritance unfriendly. The JLS (Java language specification) specifies that if A.equals(B) == truethen B.equals(A)must also return true. If you omit that statement inheriting classes that override equals()(and change its behavior) will break this specification.

此语句是equals()继承不友好的结果。JLS(Java 语言规范)指定 if A.equals(B) == truethenB.equals(A)也必须返回true. 如果您省略该语句,继承覆盖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 Binstance, but check after for Ainstance:

如果要保证对称条件。a=b if b=a 和 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;

Which will output:

这将输出:

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

Where, if ais 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()

回答by erickson

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 instanceofoperator.
  2. Use this.getClass().equals(that.getClass()).
  1. 使用instanceof运算符。
  2. 使用this.getClass().equals(that.getClass()).

I use #1 in a finalequals implementation, or when implementing an interface that prescribes an algorithm for equals (like the java.utilcollection interfaces—the right way to check with with (obj instanceof Set)or whatever interface you're implementing). It's generally a bad choice when equals can be overridden because that breaks the symmetry property.

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

Option #2 allows the class to be safely extended without overriding equals or breaking symmetry.

选项#2 允许在不覆盖等号或破坏对称性的情况下安全地扩展类。

If your class is also Comparable, the equalsand compareTomethods should be consistent too. Here's a template for the equals method in a Comparableclass:

如果您的类也是Comparable,则equalscompareTo方法也应该保持一致。这是一个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;
  }

}

回答by Darren Greaves

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.
The simplest solution is to not include the getChildren collection in the methods.

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

回答by Kevin Wong

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." Tal disagrees.

在他的《Effective Java Programming Language Guide》(Addison-Wesley,2001 年)一书中,Joshua Bloch 声称“根本没有办法在保留 equals 约定的同时扩展可实例化的类并添加方面。” 塔尔不同意。

His solution is to implement equals() by calling another nonsymmetric blindlyEquals() both ways. blindlyEquals() is overridden by subclasses, equals() is inherited, and never overridden.

他的解决方案是通过双向调用另一个非对称的blinlyEquals()来实现equals()。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 Principleis to be satisfied.

请注意,如果要满足Liskov 替换原则,equals() 必须跨继承层次工作。

回答by Johannes Brodwall

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. These proxies are implemented as subclasses of your own class. This means thatthis.getClass() == o.getClass()will return false. For example:

如果您的对象使用 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

If you're dealing with an ORM, using o instanceof Personis the only thing that will behave correctly.

如果您正在处理 ORM,则 usingo instanceof Person是唯一可以正确运行的方法。

Lazy loaded objects have null-fields

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

ORMs usually use the getters to force loading of lazy loaded objects. This means that person.namewill be nullif personis lazy loaded, even if person.getName()forces loading and returns "John Doe". In my experience, this crops up more often in hashCode()and equals().

ORM 通常使用 getter 来强制加载延迟加载的对象。这意味着,person.name将是null,如果person是懒加载,即使person.getName()迫使装载并返回“李四”。根据我的经验,这在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 idfield to hold the key of the object. This field will be automatically updated when an object is first saved. Don't use an id field in hashCode(). But you can use it in equals().

持久化对象通常使用一个id字段来保存对象的键。首次保存对象时,此字段将自动更新。不要在hashCode(). 但是你可以在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(). If you do, when an object is persisted, its hashCodechanges. If the object is in a HashSet, you'll "never" find it again.

但是:您不能包含getId()hashCode(). 如果这样做,当一个对象被持久化时,它会hashCode发生变化。如果对象在 a 中HashSet,您将“永远”找不到它。

In my Personexample, I probably would use getName()for hashCodeand getId()plus getName()(just for paranoia) for equals(). It's okay if there are some risk of "collisions" for hashCode(), but never okay for equals().

在我的Person示例中,我可能会使用getName()forhashCodegetId()plus getName()(仅用于偏执狂) for equals()。如果有“冲突”为一定的危险性也没关系hashCode(),但从来没有好equals()

hashCode()should use the non-changing subset of properties from equals()

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

回答by Johannes Schaub - litb

For equals, look into Secrets of Equalsby Angelika Langer. I love it very much. She's also a great FAQ about Generics in Java. View her other articles here(scroll down to "Core Java"), where she also goes on with Part-2 and "mixed type comparison". Have fun reading them!

对于平等的,考虑的Equals的秘密安格兰格。我非常爱它。她也是关于Java 泛型的一个很好的常见问题解答。在此处查看她的其他文章(向下滚动到“Core Java”),在那里她还继续讨论第 2 部分和“混合类型比较”。祝您阅读愉快!

回答by Eugene

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

回答by Khaled.K

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 notvice-versa!

反之亦然!

回答by rohan kamat

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. 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.

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

we should always override hashCode() method whenever we override equals() method. 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.

每当我们覆盖 equals() 方法时,我们都应该始终覆盖 hashCode() 方法。如果没有,会发生什么?如果我们在应用程序中使用哈希表,它将不会按预期运行。由于 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