当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《在Java中覆盖equals和hashCode时应该考虑哪些问题?》经验,为你挑选了11个好方法。

什么问题/陷阱,必须重写时,必须考虑equalshashCode



1> Antti Kissan..:

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

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

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

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

无论何时a.equals(b),a.hashCode()必须与之相同b.hashCode().

在实践中:

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

使用您用于计算的同一组字段equals()进行计算hashCode().

使用优秀的辅助类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();
    }
}

还要记住:

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


您可以让Eclipse为您生成两种方法:Source> Generate hashCode()和equals().
Netbeans也是如此:http://developmentality.wordpress.com/2010/08/24/java-creating-correct-equals-and-hashcode-method/
关于appendSuper()的附加要点:当且仅当你想继承超类的相等行为时,你应该在hashCode()和equals()中使用它.例如,如果直接从Object派生,则没有意义,因为默认情况下所有对象都是不同的.
第一个空检查不是必需的,因为如果第一个操作数为空,则`instanceof`返回false(再次使用Effective Java).
@Darthenius Eclipse生成的equals使用getClass(),在某些情况下可能会导致问题(参见Effective Java item 8)
在Effective Java book中对这个问题进行了很好的讨论
Android Studio还可以为您生成equals()和hashCode().
当使用"instanceof"而不是"getClass()"时,并不总是保持对称性
为什么`instanceof`而不是`if((obj == null)||(obj.getClass()!= this.getClass()))`?instanceof可能会为父母和孩子带来问题.
@Darthenius:你的最有用!

2> Johannes Bro..:

如果您正在处理使用像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()迫使装载并返回"李四".根据我的经验,这种情况经常发生在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就会发生变化.如果对象在a中HashSet,你将"永远不会"再次找到它.

在我的Person例子中,我可能会使用getName()for hashCodegetId()plus getName()(仅用于偏执)equals().如果存在"碰撞"的风险,那就没关系hashCode(),但从来没有问题equals().

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


@Johannes Brodwall:我不明白"保存一个物体会改变它的状态"!`hashCode`必须返回`int`,那么你将如何使用`getName()`?你能为你的`hashCode`举个例子吗?

3> Ran Biron..:

有关的澄清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;

显然,这是错误的.

如果要确保对称条件.a = b如果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;

哪个会输出:

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

在哪里,如果a不是引用B,那么它可能是一个类的引用A(因为你扩展它),在这种情况下你super.equals() 可以调用.


它的问题在于它打破了传递性.如果你添加`B b2 = new B(1,99)`,那么`b.equals(a)== true`和`a.equals(b2)== true`但是`b.equals(b2)==返回FALSE.
@pihentagy - 当实现类没有覆盖equals方法时,我会得到一个stackoverflow.不好玩.
你可以用这种方式使equals对称(如果比较超类对象和子类对象,总是使用子类的等号)if(obj.getClass()!= this.getClass()&& obj.getClass().isInstance(this) )return obj.equals(this);
你不会得到一个stackoverflow.如果未覆盖equals方法,则将再次调用相同的代码,但递归的条件将始终为false!

4> Kevin Wong..:

有关继承友好的实现,请查看Tal Cohen的解决方案,如何正确实现equals()方法?

摘要:

在他的" 有效Java编程语言指南"(Addison-Wesley,2001)一书中,Joshua Bloch声称"在保留等同合同的同时,根本无法扩展可实例化的类并添加方面." 塔尔不同意.

他的解决方案是通过两种方式调用另一个非对称的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);
    }
}

请注意,如果要满足Liskov替换原则,则equals()必须在继承层次结构中起作用.


看看这里解释的canEqual方法 - 相同的原理使两个解决方案都有效,但是使用canEqual你不会比较相同的字段两次(上面,px == this.x将在两个方向上进行测试):http:/ /www.artima.com/lejava/articles/equality.html
无论如何,我认为这不是一个好主意.它使得Equals合同不必要地混淆 - 一个接受两个Point参数a和b的人必须意识到a.getX()== b.getX()和a.getY()== b.getY的可能性()可以为真,但a.equals(b)和b.equals(a)都是假的(如果只有一个是ColorPoint).

5> Eugene..:

仍然惊讶于没有人推荐番石榴库.

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


java.util.Objects.hash()和java.util.Objects.equals()是Java 7(2011年发布)的一部分,因此您不需要Guava.
`this.getDate()中的`this`意味着什么(除了杂乱)

6> Luna Kong..:

超类中有两种方法,如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> erickson..:

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

    使用instanceof运营商.

    使用this.getClass().equals(that.getClass()).

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

选项#2允许安全地扩展类,而不会覆盖等于或破坏对称性.

如果你的班级也是Comparable,那么equalscompareTo方法也应该是一致的.这是Comparable类中equals方法的模板:

final class MyClass implements Comparable
{

  …

  @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> Johannes Sch..:

对于平等的,考虑的Equals的秘密由安格兰格.我非常爱它.她也是关于Java中泛型的一个很棒的常见问题.在这里查看她的其他文章(向下滚动到"Core Java"),她还继续使用Part-2和"混合类型比较".玩得开心吧!



9> 小智..:

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

因为int值10总是等于10.但是这个equals()方法是关于两个对象的相等.当我们说对象时,它将具有属性.为了决定平等,考虑这些属性.没有必要考虑所有属性来确定相等性,并且可以决定类定义和上下文.然后可以覆盖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> Khaled.K..:

从逻辑上讲,我们有:

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

反之亦然!



11> Darren Greav..:

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

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


我认为这里的基本理论是区分对象的_attributes _,_aggregates_和_associatinos_._asssociations_不应该参与`equals()`.如果一个疯狂的科学家创造了我的副本,我们将是等同的.但我们不会有同一个父亲.
推荐阅读
爱唱歌的郭少文_
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有