java - hashcode用法 - 改寫hashcode




在Java中重寫equals和hashCode時應考慮哪些問題? (8)

重寫equalshashCode時必須考慮哪些問題/陷阱?


理論(針對語言律師和數學傾向):

equals()javadoc )必須定義一個等價關係(它必須是自反的對稱的和可傳遞的 )。 另外,它必須一致 (如果對像沒有被修改,那麼它必須保持返回相同的值)。 此外, o.equals(null)必須始終返回false。

hashCode()javadoc )也必須是一致的 (如果對像沒有用equals()修改,它必須保持返回相同的值)。

這兩種方法之間的關係是:

每當a.equals(b) ,則a.hashCode()必須與b.hashCode()相同。

在實踐中:

如果你重寫一個,那麼你應該重寫另一個。

使用您用來計算equals()來計算hashCode()的同一組字段。

使用來自Apache Commons Lang庫的優秀助手類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();
    }
}

還請記住:

在使用基於散列的CollectionMap例如HashSetLinkedHashSetHashMapHashtableWeakHashMap ,確保放入集合中的關鍵對象的hashCode()在對象位於集合中時永遠不會更改。 確保這一點的防彈方法是使您的密鑰不可變, 這也有其他好處


equals()方法用於確定兩個對象的相等性。

因為10的int值總是等於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

在檢查成員平等之前,有幾種方法可以檢查類的平等性,我認為這兩種方法在正確的情況下都很有用。

  1. 使用instanceof運算符。
  2. 使用this.getClass().equals(that.getClass())

我在final equals實現中使用#1,或者在實現一個規定equals的算法的接口時(比如java.util集合接口 - 使用(obj instanceof Set)或您正在實現的任何接口進行檢查的正確方法) 。 當等於可以被覆蓋時,通常是一個糟糕的選擇,因為它打破了對稱性。

選項#2允許類安全地擴展,而不會超越等號或破壞對稱性。

如果你的類也是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;
  }

}

對於平等,請看Angelika Langer 的“等於秘密” 。 我非常愛它。 她也是關於Java中泛型的一個很好的常見問題解答。 here查看她的其他文章(向下滾動到“Core Java”),在那裡她也繼續Part-2和“混合類型比較”。 玩得開心閱讀!


我發現的一個問題是兩個對象包含對彼此的引用(一個示例是在父級獲得所有孩子時使用方便方法的父母/子女關係)。
例如,在做Hibernate映射時,這些事情是相當普遍的。

如果在hashCode中包含關係的兩端,或者等於測試,則可能進入以Exception結尾的遞歸循環。
最簡單的解決方案是不在方法中包含getChildren集合。


有些問題值得注意,如果您正在處理使用像Hibernate這樣的Object-Relationship Mapper(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通常使用getters強制加載延遲加載的對象。 這意味著,即使person.getName()強制加載並返回“John Doe”,如果person是懶加載的, person.name將為null 。 根據我的經驗,這在hashCode()equals()經常出現。

如果您正在處理ORM,請務必始終使用getter,並且不要在hashCode()equals()使用字段引用。

保存對象將改變其狀態

持久對象通常使用一個id字段來保存該對象的關鍵字。 首次保存對象時,該字段將自動更新。 不要在hashCode()使用一個id字段。 但是你可以在equals()使用它。

我經常使用的模式是

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

但是:你不能在hashCode()包含getId() hashCode() 。 如果你這樣做,當一個對像被持久化時,它的hashCode改變。 如果對象位於HashSet ,您將“再也不會”找到它。

在我的Person例子中,我可能會使用getName()作為hashCode並使用getId()getName() (僅用於偏執狂)作為equals() 。 如果hashCode()存在一些“衝突”的風險,但對於equals()永遠不會有問題。

hashCode()應該使用equals()不變的屬性子集


超類中有兩個方法作為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

玩的開心! @。@


關於obj.getClass() != getClass()

這個語句是equals()繼承不友好的結果。 JLS(Java語言規範)指定如果A.equals(B) == trueB.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替換原則不僅在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;

哪個會輸出:

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

其中,如果a不是B的引用,那麼它可能是A類的引用(因為你擴展它),在這種情況下,你也可以調用super.equals()





hashcode