JAVA正確地自定義比較對象---如何重寫equals方法和hashCode方法


在實際應用中經常會比較兩個對象是否相等,比如下面的Address類,它有兩個屬性:String province 和 String city。

public class Address {
    private String province;
    private String city;
    public String getProvince() {
        return province;
    }
    public void setProvince(String province) {
        this.province = province;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    
    public Address() {}
    public Address(String province, String city) {this.province = province; this.city = city;}
    
}

 

在現實生活中我們認為若兩個 Address 的 province 和 city 屬性相同,則它們應該是同一個地址(省市都一樣,當然就是同一個地區啦)。但下面的代碼卻表明:address1 和 address2 是兩個“不同的地址”

 1 public class TestAddress {
 2     
 3     public static void main(String[] args) {
 4         Address address1 = new Address("廣東","廣州");
 5         Address address2 = new Address("廣東", "廣州");
 6         
 7         System.out.println(address1 == address2);//false
 8         System.out.println(address1.equals(address2));//false
 9         System.out.println(address1.hashCode() == address2.hashCode());//false
10     }
11 }

 

其原因是:在JAVA(編程語言)中比較對象 和 現實生活中 比較兩個對象是兩個不同的概念,前者暫且稱為“物理相等”,而后者是“邏輯相等”。

adress1==adress2 是根據兩個對象的內存地址是否相同進行比較的,第4、5行分別 new 了兩個對象,這兩個對象存儲在內存中不同的地址,當然不會相等了。

由於Address類並沒有重寫equals方法,那么address1.equals(address2) 執行的就是Object類的equals方法,而java.lang.Object類的equlas方法是這樣實現的:

    public boolean equals(Object obj) {
        return (this == obj);
    }

JDK中對該方法的注釋如下:也就是說:Object類的equals方法 是通過 == 來比較兩個對象是否相等的,也即根據 對象x引用 和 對象y 的引用是否指向內存中的同一個地址 來判斷 對象x 和 對象y 是否相等。

     * The {@code equals} method for class {@code Object} implements
     * the most discriminating possible equivalence relation on objects;
     * that is, for any non-null reference values {@code x} and
     * {@code y}, this method returns {@code true} if and only * if {@code x} and {@code y} refer to the same object
     * ({@code x == y} has the value {@code true}).

 

而按照現實思維,既然 adress1 和 address2 都代表廣東廣州,那么在程序中它們兩個對象比較就應該相等(邏輯上相等),也即address1.equals(address2)應該返回true才對。於是就需要覆蓋 Object 類中的 equals 方法 和 hashCode方法了。

而覆蓋 equals方法 和hashCode方法是需要技巧的。

①覆蓋了Object類的equals方法后,需要再覆蓋 Object類的hashCode方法。為什么呢?----不要違反“hashCode方法的 通過約定(general contract) ”

Object類的equals方法上的注釋如下:

   / * Note that it is generally necessary to override the {@code hashCode}
     * method whenever this method is overridden, so as to maintain the * general contract for the {@code hashCode} method, which states
     * that equal objects must have equal hash codes.
    */
    public boolean equals(Object obj) {
        return (this == obj);
    }

 

而這個“通用約定”就是:❶若兩個對象根據equals(Object)方法比較相等,那么調用這兩個對象中任意一個對象的hashCode方法 都必須 產生同樣的整數結果。

❷若兩個對象根據equals(Object)方法比較相等,那么調用這兩個對象中任意一個對象的hashCode方法 可以 產生相同的整數結果,但是最好 產生不同的整數結果,這樣可以提供散列表的性能(當要把Address類 作為 鍵 put 到HashMap中時,可以減少沖突,可參考這篇文章

 

那具體如何正確地覆蓋equals()呢?《Effective JAVA》里面給出了方法,套路是一樣的,其目標是保證:自反性、對稱性、一致性。總之,對於上面的Address類而言,可以這樣:

1     @Override
2     public boolean equals(Object obj) {
3         if(obj == this)
4             return true;
5         if(!(obj instanceof Address))
6             return false;
7         Address address = (Address)obj;
8         return address.getProvince().equals(province) && address.getCity().equals(city);
9     }

 

第8行從表明如果兩個Address的 province 和 city 相同,那這兩個Address就是相同的,這樣equals方法就會返回true了。(不考慮字符串大小寫問題)

覆蓋完了equals(),接下來就是 覆蓋hashCode()了。覆蓋hashCode()的目標是:

如果兩個對象 address1.equals(address2) 返回 false,那么 address1.hashCode() 最好 不等於 address2.hashCode()

當然,沒有超級完美的hashCode(),如果相等了,那么當 hashMap.put(address1,value1)  hashMap.put(address2,value2) 就會put到同一個 hashmap的同一個槽下了。

重寫了equals和hashCode的Address類如下:

public class Address {
    private String province;
    private String city;
    public String getProvince() {
        return province;
    }
    public void setProvince(String province) {
        this.province = province;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    
    public Address() {}
    public Address(String province, String city) {this.province = province; this.city = city;}
    
    @Override
    public boolean equals(Object obj) {
        if(obj == this)
            return true;
        if(!(obj instanceof Address))
            return false;
        Address address = (Address)obj;
        return address.getProvince().equals(province) && address.getCity().equals(city);
    }
    
    @Override
    public int hashCode() {
        int result = 17;
        result += 31 * province.hashCode();
        result += 31 * city.hashCode();
        return result;
    }
}

 

測試類如下:

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

public class TestAddress {
    
    public static void main(String[] args) {
        Address address1 = new Address("廣東","廣州");
        Address address2 = new Address("廣東", "廣州");
        
        System.out.println(address1 == address2);//false
        System.out.println(address1.equals(address2));//true
        System.out.println(address1.hashCode() == address2.hashCode());//true
        
        Address diff1 = new Address("四川","成都");
        Address diff2 = new Address("四川","綿陽");
        System.out.println(diff1 == diff2);//false
        System.out.println(diff1.equals(diff2));//false
        System.out.println(diff1.hashCode() == diff2.hashCode());//false
        
        Map<Address, Integer> hashMap = new HashMap<Address, Integer>();
        hashMap.put(address1, 1);
        hashMap.put(address2, 2);//address2的hashCode 和 address1 相同,因此 put 方法會覆蓋 address1 對應的 Value值1
        System.out.println(hashMap.get(address1));//2
        System.out.println(hashMap.get(address2));//2
        
        hashMap.put(diff1, 1);
        hashMap.put(diff2, 2);
        System.out.println(hashMap.get(diff1));//1
        System.out.println(hashMap.get(diff2));//2
    }
}

 

最后,其實Eclipse里面為我們提供了自動 生成 equals和hashCode的方法,可參考:JAVA中equals方法與hashCode方法學習。自動生成的方法如下:(還是自動生成的更專業呀。。。。)

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((city == null) ? 0 : city.hashCode());
        result = prime * result + ((province == null) ? 0 : province.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Address other = (Address) obj;
        if (city == null) {
            if (other.city != null)
                return false;
        } else if (!city.equals(other.city))
            return false;
        if (province == null) {
            if (other.province != null)
                return false;
        } else if (!province.equals(other.province))
            return false;
        return true;
    }

 

參考:《effective java》

http://www.cnblogs.com/hapjin/p/4582795.html

 

原文:http://www.cnblogs.com/hapjin/p/7327839.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM