多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > 對象相等性——如何給自定義對象添加equals和hashCode方法

對象相等性——如何給自定義對象添加equals和hashCode方法

來源:程序員人生   發布時間:2016-07-11 09:08:04 閱讀次數:3045次

譯自 http://www.javaworld.com/article/2072762/java-app-dev/object-equality.html

每一個Java對象都從java.lang.Object繼承了1些方法:

Creational methods
Object()Default no-argument constructor
clone()Returns a new instance of the class
**Equality methods**
equals(Object)Returns true if this instance is equals to the argument
hashCode()Returns a hash code based on the instance data
**Synchronizing methods**
notify()Sends a signal to a waiting thread (on the current instance)
notifyAll()Sends a signal to all waiting threads (on the current instance)
wait()Forces the current thread to wait for a signal (on the current instance)
**Other methods**
toString()Returns a string representation of the object
finalize()Perform garbage-collection duties
getClass()Returns the Class object associated with the instance

這些方法都提供了默許實現,其中除notify(), notifyAll(), wait()3個方法是final的,沒法被子類重寫,其他方法都可以被重寫。這篇文章將討論如何重寫equals()hashCode()方法。

equals()和hashCode()方法做甚么?

equals()方法的目的是判斷參數對象和當前實例是不是相等。實際上,java.util包中的所有集合類都使用了該方法,還有其他很多較為底層的庫(如RMI,JDBC,等等)都隱式地依賴于該方法的正確性。如果兩個對象被認為是相等的,那末該方法返回tree,否則返回false。哪些內容相等才被認為是兩個對象相等由每一個類自己定義。

由于計算對象是不是相等是1件很耗時的事,Java提供了1種快速判斷兩個對象是不是相等的方法,即hashCode()。該方法根據對象的內部數據結構生成1個小的數值,被稱為哈希碼(hash code),如果兩個對象具有不同的哈希碼,那末他們不可能相等。(比如字典里的兩個英文單詞,如果它們都以A開頭,那末它們有可能相等,如果1個以A開頭,1個以B開頭,那末它們不可能相等。)

計算哈希碼的目的是哈希要比計算全部對象的相等性快。HashMap就使用了哈希碼來盡量地避免計算對象的相等性,HashMapList快的1個緣由就是,List需要搜索全部數據結構判斷對象是不是存在,而HashMap只需搜索那些具有相同哈希值的對象。

切記,1個類只重寫equals()方法而不重寫hashCode()方法是毛病的。在繼承體系中,只需父類提供1個hashCode()方法便可,后面會詳細討論。

實現equals()方法

方法簽名必須為

public boolean equals(Object obj)
`</pre>

注意:任何類的`equals()`方法的參數都必須是`Object`類型,否則該方法就不是重寫,而是重載了,當判斷兩個對象是不是相等時就會調用`java.lang.Object`類的默許的`equals()`方法,而非你定義的。

Javadoc中描寫`equals()`方法必須滿足:
  • 自反性(Reflexive),1個對象必須和本身相等,即`a.equals(a);
  • 對稱性(Symmetric),即如果a.equals(b),那末b.equals(a)
  • 傳遞性(Transitive),即如果a.equals(b),并且b.equals(c),那末a.equals(c)
  • 非null,1個對象任什么時候候都不能等于null,即a.equals(null)永久返回false。

    根據上面規則,很容易寫出1個equals()方法的實現,只需要比較以下內容:

    1. 如果參數是this,返回true;(自反性)
    2. 如果參數是null,返回false;(非null)
    3. 如果參數類型和當前對象類型不同,返回false;(對稱性)
    4. 所有非static和非transient域都是相等的。(對稱性,傳遞性)

    為什么不需要比較static域和transient域?由于static數據是屬于類的而非對象實例,所有對象實例同享static數據。transient關鍵字的目的是對象在序列化時不讓某些域寫入(如為安全起見,用戶的銀行卡號和密碼等信息,不希望在網絡操作中被傳輸,那末對這些變量加上transient關鍵字后就不會被持久化 [詳見]),如果這些域用于測試對象是不是相等,那末同1個對象在序列化之前和以后就會不等。

    以2維平面坐標中的點Point為例,我們實現1個簡單的equals()方法:

    `public class Point {
        private static double version = 1.0;
        private transient double distance;
        private int x, y;
    
    
    public boolean equals(Object other) {
        if (other == this) return true;
        if (other == null) return false;
        if (getClass() != other.getClass()) return false;
        Point point = (Point)other;
        return (x == point.x &amp;&amp; y = point.y);
    }
    

    }
    `

    注意:這里使用getClass()來比較兩個對象是不是屬于同1個類型,而非instancof,后面我們會討論為什么不用instanceof

    比較援用類型

    如果1個對象里含有援用類型,那末如何比較兩個對象的援用是不是相等?答案是根據以下規則:

    1. 如果this的援用變量為null,那末other相應的援用變量也必須為null
    2. 如果this的援用變量不為null,那末它必須和other相應的援用變量equals()

    以下面的Person類,含有兩個援用類型的變量name和birth:

    `public class Person {
        private String name;
        private Date birth;
    
    
    public boolean equals(Object other) {
        if (other == this) return true;
        if (other == null) return false;
        if (getClass() != other.getClass()) return false;
        Person person = (Person)other;
        return (name == person.name || (name != null &amp;&amp; name.equals(person.name)))
        &amp;&amp; (birth == person.birth || (birth != null &amp;&amp; birth.equals(person.birth)));
    }
    

    }
    `

    注意:name == person.name檢查了兩個援用都為null的情況和兩個援用指向同1個對象的情況。在調用name.equals(person.name)方法前1定要先檢查name != null,否則將會拋出’NullPointerException’。

    實現hashCode()方法

    哈希碼(hash code)就是根據實例數據計算出來的1個int值,如果兩個實例被認為是equals,那末它們必須具有相同的hash code。因此,hash code**只能**根據equals()方法中所比較的變量域來計算,但其實不1定需要使用所有的域。

    2維平面上的點PointhashCode()實現以下:

    `public class Point {
        private int x, y;
    
    
    public boolean equals(Object other) {
        // see above
    }
    
    publice int hashCode() {
        return x;
    }
    

    }
    `

    上面的hashCode()實現時正確的(雖然不是最優的),由于它依賴于equals()方法的比較域x

    固然,我們期望不同對象的hash code越分散越好。上面的實現中,所有具有相同x坐標的點都具有相同的hash code,我們可以通過以下方法改進:

  • 使用多個變量的乘積;

  • 使用位運算異或^
  • 給整數變量乘上1個質數。

    `public class Point { 
    private int x, y;

    public boolean equals(Object other) {
        // see above
    }
    
    publice int hashCode() {
        return 31*x ^ 37*y;
    }
    

    }
    `

    但是,為了使hash code的計算速度快,1般不使用乘法。

    帶援用類型的hashCode()方法

    如果1個類包括了援用變量,那末就能夠使用援用變量的hashCode()方法。但是,跟equals()方法1樣,1定要注意援用是不是為null,否則可能拋出NullPointerException異常。對null的情況,可以返回1個固定值(這個固定值應當是正整數,由于在1些hash map的實現中這些值可能有特殊的意義)。

    `public class Person {
        private String name;
        private Date birht;
    
    
    public boolean equals(Object other) {
        // see above
    }
    
    public int hashCode() {
        return (name == null ? 17 : name.hashCode()) 
            ^ (birht == null ? 31 : birth.hashCode());
    }
    

    }
    `

    equals()和hashCode()方法的默許實現

    這兩個方法的默許實現只適用于簡單的情況。equals方法只有在與其本身比較時才返回truehashCode()方法依賴于單1對象的哈希(unique instance hash,如對象在內存中的地址或不同虛擬機有不同的實現)。

    由于hashCode()依賴于對象的唯1性(identity),所以只重寫equals()方法而不重寫hashCode()方法是毛病的。否則,兩個對象多是相等的,卻有不同的哈希值。如果實在沒有適合的哈希值計算方法,那末可以返回1個常數(比如7),這樣也比使用默許的實現好,雖然這樣會致使Map退化成List

    高級策略

    equals()hashCode()必須都盡量地快,由于它們常常被重復地調用。

    equals()方法的某些部份要比其他部份快,所以可以先比較快的部份。比如,基本數據類型的比較要快于援用類型的equals(),那末可以先比較基本數據類型。一樣地,如果兩個對象類型不同,那末就沒必要比較任何數據域了。

    只讀對象

    如果1個對象是只讀的(immutable),那末可以提早計算出它的哈希值。當這樣的對象被創建時,所有的值都會通過構造函數傳入,這時候就能夠計算出它的哈希值。

    `public class Point {
        private final int x, y;
        private final int hashCode;
    
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
        this.hashCode = 31*x ^ 37*y;
    }
    
    public boolean equals(Object other) {
        // see above
    }
    
    public int hashCode() {
        return hashCode;
    }
    

    }
    `

    注意,上面代碼中的變量x和y都是final的,保證以后不會被改變,所以可以在創建對象時就計算出哈希值。

    為什么不用instanceof

    在Joshua Bloch非常著名的《Effective Java》1書中,他推薦使用instanceof來測試以決定對象的類型,表面上看這是1個很好的想法,實際上這有1個重大缺點,由于instanceof不是對稱的。

    下面是Bloch推薦的做法:

    `public class BadPoint {
        private int x, y;
    
    
    public boolean equals(Object other) {
        if (other == this) return true;
        if (!(other instanceof BadPoint)) return false; // Bad!!!
        BadPoint point = (BadPoint)other;
        return (x == point.x &amp;&amp; y == point.y);
    }
    
    public int hashCode() {
        return x + y;
    }
    

    }
    `

    由于這個代碼更短,而且是《Effective Java》1書所推薦,所以已根深蒂固于很多Java程序員的編碼中。這同樣成為本書最具爭議的內容之1。

    使用instanceof的最大問題是它不具有對稱性,當使用繼承時這個問題將會體現出來:

    `public class BadPoint3D extends BadPoint {
        private int z;
    
    
    public boolean equals(Object other) {
        if (!super.equals(other)) return false;
        if (!(other instanceof BadPoint3D)) return false; // Bad!!!
        BadPoint3D point = (BadPoint3D)other;
        return (z == point.z);
    }
    

    }
    `

    當1個BadPoint對象和1個BadPoint3D對象比較時就會出現問題,badPoint instanceof badPoint3D == false,而point3D instanceof badPoint == true

    完全的寫法

    Point

    `public class Point {
        private static double version = 1.0;
        private transient double distance;
    
    
    private String name;
    private int x, y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public Point(String name, int x, int y) {
        this(x, y);
        this.name = name;
    }
    
    public boolean equals(Object other) {
        if (other == this) return true;
        if (other == null) return false;
        if (getClass() != other.getClass()) return false;
        Point point = (Point)other;
        return (x == point.x &amp;&amp; y == point.y 
            &amp;&amp; (name = point.name || (name != null &amp;&amp; name.equals(point.name))));
    }
    
    public int hashCode() {
        return x ^ y;
    }
    

    }
    `

    Point3D

    `public class Point3D {
        private int z;
    
    
    public Point3D(int x, int y, int z) {
        super(x, y);
        this.z = z;
    }
    
    public Point3D(String name, int x, int y, int z) {
        super(name, x, y);
        this.z = z;
    }
    
    public boolean equals(Object other) {
        if (!super.equals(other)) return false;
        Point3D point = (Point3D)other;
        return (z == point.z);
    }
    
    public int hashCode() {
        return super.hashCode() ^ z;
    }
    

    }

更深的學習可以參看 如何在Java中避免equals方法的隱藏圈套 | 酷 殼 - CoolShell.cn

說明:本文譯自javaword.com,如果侵權,請聯系站長刪除。同時,如果轉載本文,請注明本文鏈接。

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 伊人福利网 | 久久国产精品一区 | 欧美性大交 | 欧美午夜理伦三级在线观看 | 五月激情五月婷婷 | yellow中文字幕视频在线 | 亚洲欧洲一区 | 自拍视频一区 | 免费观看无遮挡www的视频 | 久久婷婷人人澡人人爱91 | 手机在线看福利 | 亚洲十八精品网站 | 久久厕所精品国产精品亚洲 | 九色地址 | 国产精品成人免费综合 | 国语精品视频在线观看不卡 | 欧美男人天堂 | 亚洲欧美日韩国产综合 | 中文字幕伦伦精品 | 亚洲精品资源在线 | 国产一级视频久久 | 亚洲国产综合精品中文第一区 | 影院福利 | 日本久本草精品 | 网站国产| 美女网站在线观看 | 美国黄色一级毛片 | 一区二区三区精品国产 | 日韩在线专区 | 亚洲国产成人久久一区久久 | 手机看片国产高清 | 日本动漫片b站免费观看 | 久久亚洲精中文字幕冲田杏梨 | 国产极品嫩模在线观看91精品 | 欧美成人看片一区二区三区 | 国产偷啪视频一区 | 日本中文字幕在线观看视频 | 日韩亚洲一区中文字幕在线 | 欧洲美女a视频一级毛片 | 亚洲国产色综合有声小说 | 亚洲第一影院 |