Java中的equals()和hashCode()方法

    在Java語言中,一般不需要自定義equals()和hashCode()這兩個方法,當需要對對象的內容進行比較的時候,才需要這樣兩個方法。例如,需要把對象放入HashSet或者把對象作爲key放入HashMap、 Hashtable時,如果不想把具有相同內容的兩個對象作爲兩個對象來看待,就需要重定義這兩個方法。這兩個方法的使用是緊密配合的,要是設計了其中一個,就要設計另外一個。

    equals():
    用於兩個對象的比較,在Object類中已經實現了這個方法,是對對象內部地址的比較,即如果兩個對象的內部地址是一樣的則是相等的。如果要按照對象內容的進行比較,就需要重載這兩個方法。Java語言對equals()的要求如下,這些要求是必須遵循的:
  • 對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。
  • 反射性:x.equals(x)必須返回是“true”。
  • 類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true”。
  • 一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是“true”。
    任何情況下,x.equals(null)永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”。
    hashCode():
    這個方法返回的是一個用來進行hash操作的整型數,可用於在Collection對象中定位特定對象的位置。Object中默認的實現是把對象內部地址轉化爲整數作爲hashCode。
    hashCode()的返回值和equals()的關係如下:
  • 如果x.equals(y)返回“true”,那麼x和y的hashCode()必須相等。
  • 如果x.equals(y)返回“false”,那麼x和y的hashCode()有可能相等,也有可能不等。
    這裏有必要說明一下HashMap的原理。爲了優化查找對象的性能,在HashMap中按照鍵值對象的hash值放了若干個箱子,當有一個鍵值對象加入進來時,調用鍵值對象的hashCode()方法,根據計算出的hash值把對象放入對應的箱子。當對鍵值對象進行查找時,首先計算對象的hash值,找到對應的箱子,然後調用equals()與箱子中的對象逐個比較,直到找出相等的對象或者遍歷了一遍。
    如果x.equals(y)返回“true”,而x.hashCode() != y.hashCode()會有什麼後果呢?假設把x、y方法HashMap,即hashMap.put(x, xValue)和hashMap.put(y, yValue),在hashMap中會存在兩個元素x、y,而不是一個。原因很好理解,因爲二者的hashCode不同,在hashMap中會放入不用的箱子,從而會被認爲是兩個對象。
    使用hashCode的目的是爲了把對象散列在不同的地方,從而提高檢索對象的性能,所以編寫一個好的hashCode算法相當重要。在Effective Java 2 edition 中給出了一個簡單的hashCode算法:
    1、保存一些常量非0值,例如17,在一個int的result變量中。這個值可以是任意的,但是最好不是0,會增加衝突的可能。
    2、對象每一個重要的域f(就是equals方法使用到那些field)做以下操作:
        a)對域f計算一個int的哈希碼c
           i.如果field是boolean型的,計算(f ? 1 : 0)
           ii.如果field是byte,char,int之類的,計算 (int)f
           iii.如果field是long, 計算 (int) (f^(f>>32))
           iv.如果field是float,計算 Float.floatToIntBits(f)
           v.如果field是double,計算Double.doubleToLongBits(f),然後使用2.a.iii的方法計算hash code
           vi.如果field是一個對象的引用,並且這個類的此域比較的equals方法使用了遞歸的調用equals,直接遞歸的調用hashCode方法。如果一個更復雜的比較是必須的,對此域計算”canonical representation(規範的表示)”並且在”規範的表示”上調用hashCode(invoke hashCode on the canonical representation)。如果此field爲null,返回0。
           vii.如果field時array,將數組中的每個元素作爲一個單獨的field,用上述規則來計算,並使用2.b中的方式合併這些值。如果每個元素都是significant,你可以使用1.5版本添加的Arrays.hasCode方法。
         b)合併2.a中哈希碼c的值到result使用下面的方法:
                   result=31*result+c;
    3、返回result
    4、當你完成hashCode函數之後,自我檢驗一下相等的實例是否有相等的hash code。編寫單元測試去檢驗你所想的結果。如果相等實例有不同的hash code,找出原因並解決。
    下面給出一個例子,利用上面講述的東西應該可以解釋是什麼原因。代碼如下:
package learning.collection;

import java.util.HashMap;
import java.util.Map;

/**
 * 測試equals()、hashCode()方法對對象在Collection中的影響。
 * 
 */
public class EqualsAndHashCode
{

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        Point p1 = new Point(1,2);
        Point p2 = new Point(1,2);
        System.out.println("p1.equals(p2):" + p1.equals(p2));
        System.out.println("p1.hashCode():" + p1.hashCode());
        System.out.println("p2.hashCode():" + p2.hashCode());
        Map<Point, String> map1 = new HashMap<Point, String>();
        map1.put(p1, "point 1");
        map1.put(p2, "point 2");
        System.out.println("map1.size():" + map1.size());
        
        BadPoint bp1 = new BadPoint(1,2);
        BadPoint bp2 = new BadPoint(1,2);
        System.out.println("bp1.equals(bp2):" + bp1.equals(bp2));
        System.out.println("bp1.hashCode():" + bp1.hashCode());
        System.out.println("bp2.hashCode():" + bp2.hashCode());
        Map<BadPoint, String> map2 = new HashMap<BadPoint, String>();
        map2.put(bp1, "point 1");
        map2.put(bp2, "point 2");
        System.out.println("map2.size():" + map2.size());
        
        GoodPoint gp1 = new GoodPoint(1,2);
        GoodPoint gp2 = new GoodPoint(1,2);
        System.out.println("gp1.equals(gp2):" + gp1.equals(gp2));
        System.out.println("gp1.hashCode():" + gp1.hashCode());
        System.out.println("gp2.hashCode():" + gp2.hashCode());
        Map<GoodPoint, String> map3 = new HashMap<GoodPoint, String>();
        map3.put(gp1, "point 1");
        map3.put(gp2, "point 2");
        System.out.println("map3.size():" + map3.size());
    }

}

/**
 * 沒有重載equals()、hashCode()方法。
 */
class Point 
{
    private int x;
    private int y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    
    public int getX()
    {
        return this.x;
    }
    
    public int getY()
    {
        return this.y;
    }
}

/**
 * 重載了equals()方法,導致equals()返回true,而hashCode不同。
 * 這是一個不好的設計(甚至是錯誤的)。
 */
class BadPoint
{
    private int x;
    private int y;
    public BadPoint(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    
    public int getX()
    {
        return this.x;
    }
    
    public int getY()
    {
        return this.y;
    }
    
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null) return false;
        if (!(obj instanceof BadPoint)) return false;
        BadPoint objPoint = (BadPoint) obj;
        if (this.x == objPoint.x && this.y == objPoint.y) return true;
        return false;
    }
}

/**
 * 重載了equals()、hashCode()方法。
 */
class GoodPoint
{
    private int x;
    private int y;
    public GoodPoint(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    
    public int getX()
    {
        return this.x;
    }
    
    public int getY()
    {
        return this.y;
    }
    
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null) return false;
        if (!(obj instanceof GoodPoint)) return false;
        GoodPoint objPoint = (GoodPoint) obj;
        if (this.x == objPoint.x && this.y == objPoint.y) return true;
        return false;
    }
    
    @Override
    public int hashCode()
    {
        int result = 17;
        result = 31 * result + this.x;
        result = 31 * result + this.y;
        return result;
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章