首頁  >  文章  >  Java  >  詳解Java中的 ==, equals 與 hashCode的區別與聯繫的範例程式碼

詳解Java中的 ==, equals 與 hashCode的區別與聯繫的範例程式碼

黄舟
黄舟原創
2017-03-14 11:25:011394瀏覽


一、概述​​

1、概念

  • #== :運算符產生的是一個boolean結果,它計算的是運算元的值之間的關係

  • # equals  : Object實例方法,比較兩個物件content


  • #是否相同

    hashCode : Object 的

    native方法

    , 取得對象的

    雜湊值

    ,用於確定該物件在雜湊表中的

    索引位置,它實際上是int型別整數二、關係運算子==1、運算元的值


基本
    資料型別
  • 變數
    在Java中有八種基本資料型別:  浮點型

    float
  • (4 byte), double(8 byte)

  整數型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)


  • 字元型: char(2 byte)  布林型: boolean(JVM規範沒有明確規定其所佔的空間大小,僅規定其只能夠取字面值”true”和”false”)   對於這八種基本資料類型的變量,變數直接儲存的是「值」。因此,在使用關係運算子 == 來進行比較時,比較的就是「值」本身。 要注意的是,浮點型和整數型都是有符號類型的(最高位元只用來表示正負,不參與計算【以byte 為例,其範圍為-2^7 ~ 2^7 - 1,-0即-128】),而char是無符號類型的(所有位元均參與計算,所以char類型取值範圍為0~2^16-1)





引用

類型變數
在Java中,


引用類型的變數儲存的並不是「值」本身,而是與其關聯的物件在記憶體中的位址。例如下面這行程式碼,

    String str1;
  這句話宣告了一個引用型別的變量,此時它並沒有和任何物件關聯。

  而透過

new 來產生一個對象,並將這個物件和str1進行綁定:

str1= new String("hello");

  那麼str1 就指向了這個對象,此時

引用變數str1中儲存的是它指向的物件在記憶體中的儲存位址,並不是「值」本身,也就是說並不是直接儲存的字串」hello」

。這裡面的引用和 C/C++ 中的指標很類似。 #########2、小結###### 因此,對於關係運算子==:############若運算元的型別是##### #基本資料型別######,則該關係運算子判斷的是左右兩邊運算元的#######值######是否相等########### #若運算元的型別是######引用資料型別######,則該關係運算子判斷的是左右兩邊運算元的######記憶體位址#######是否相同。 ######也就是說,若此時傳回true,則該運算元作用的一定是同一個物件。 #####################三、equals方法######1、來源###  equals方法是基底類別Object中的實例方法,因此對所有###繼承###於Object的類別都會有該方法。 ###   ###  在Object 中的宣告:###
    public boolean equals(Object obj) {}
######2、equals方法的作用### ###初衷:### ###判斷兩個物件的### content ###是否相同######### 為了更直覺地理解equals方法的作用,我們先看###Object類別###中equals方法的實作。 ###
  public boolean equals(Object obj) {    return (this == obj);
  }
###  很顯然,###在Object類別中,equals方法是用來比較兩個物件的參考是否相等,也就是是否指向同一個物件。 ######

  但我们都知道,下面代码输出为 true:

public class Main {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");

        System.out.println(str1.equals(str2));
    }
}

原来是 String 类重写了 equals 方法:

public boolean equals(Object anObject) {   // 方法签名与 Object类 中的一致
    if (this == anObject) {     // 先判断引用是否相同(是否为同一对象),
        return true;
    }    if (anObject instanceof String) {   // 再判断类型是否一致,
        // 最后判断内容是否一致.
        String anotherString = (String)anObject;        
        int n = count;        
        if (n == anotherString.count) {        
        char v1[] = value;        
        char v2[] = anotherString.value;        
        int i = offset;        
        int j = anotherString.offset;       
         while (n-- != 0) {            
         if (v1[i++] != v2[j++])            
         return false;
        }        return true;
        }
    }    return false;
}

即对于诸如“字符串比较时用的什么方法,内部实现如何?”之类问题的回答即为:

使用equals方法,内部实现分为三个步骤:

  • 比较引用是否相同(是否为同一对象),

  • 判断类型是否一致(是否为同一类型),

  • 最后 比较内容是否一致

Java 中所有内置的类的 equals 方法的实现步骤均是如此,特别是诸如 Integer,Double 等包装器类。


3、equals 重写原则

对象内容的比较才是设计equals()的真正目的,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(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

  • 任何情况下,x.equals(null)【应使用关系比较符 ==】,永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”


4、小结
 因此,对于 equals 方法:

  • 本意比较两个对象的 content 是否相同

  • 必要的时候,我们需要重写该方法,避免违背本意,且要遵循上述原则


四、hashCode 方法

1、hashCode 的来源
  hashCode 方法是基类Object中的 实例native方法,因此对所有继承于Object的类都会有该方法。
  
  在 Object类 中的声明(native方法暗示这些方法是有实现体的,但并不提供实现体,因为其实现体是由非java语言在外面实现的):

     public native int hashCode();

2、哈希相关概念
 我们首先来了解一下哈希表:

  • 概念 : Hash 就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出(int),该输出就是散列值。这种转换是一种  压缩映射,也就是说,散列值的空间通常远小于输入的空间。不同的输入可能会散列成相同的输出,从而不可能从散列值来唯一的确定输入值。简单的说,就是一种将任意长度的消息压缩到某一固定长度的消息摘要函数

  • 应用–数据结构 : 数组的特点是:寻址容易,插入和删除困难; 而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入和删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为 “链表的数组”,如图:

             詳解Java中的 ==, equals 與 hashCode的區別與聯繫的範例程式碼
                               图1 哈希表示例

     左邊很明顯是個數組,數組的每個成員都是一個鍊錶。此資料結構所容納的所有元素均包含一個指針,用於元素間的連結。我們根據元素的自身特徵把元素分配到不同的鍊錶中去,也是根據這些特徵,找到正確的鍊錶,再從鍊錶中找出這個元素。 其中,將根據元素特徵計算元素數組下標的方法就是雜湊法。

  • 拉鍊法的適用範圍 : 快速查找,刪除的基本資料結構,通常需要總資料量可以放入記憶體。

  • 重點:  
    hash函數選擇,針對字串,整數,排列,具體對應的hash方法;
    碰撞處理,一種是open hashing,也稱為拉鍊法,另一種是closed hashing,也稱為開地址法,opened addressing。


3、hashCode 簡述
 在 Java 中,由 Object 類別定義的 hashCode 方法會針對不同的物件傳回不同的整數。 (這是透過將該物件的內部位址轉換成一個整數來實現的,但是 JavaTM 程式語言不需要這種實作技巧)。

 hashCode 的常規協定是:

  • 在Java 應用程式執行期間​​,在對相同物件多次調用hashCode 方法時,必須一致地傳回相同的整數,前提是將物件進行equals 比較時所使用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。

  • 如果根據 equals(Object) 方法,兩個物件是相等的,那麼對這兩個物件中的每個物件呼叫 hashCode 方法都必須產生相同的整數結果。

  • 如果根據equals(java.lang.Object) 方法,兩個物件不相等,那麼對這兩個物件中的任一物件上呼叫hashCode方法 不要求一定會產生不同的整數結果。但是,程式設計師應該意識到,為不相等的物件產生不同整數結果可以提高雜湊表的效能。
     
      要想進一步了解hashCode 的作用,我們必須先要了解Java中的容器,因為HashCode 只是在需要用到哈希演算法的資料結構中才有用,例如HashSet, HashMap 和Hashtable

      Java中的集合(Collection)有三類,一類是List,一類是Queue,再有一類就是Set。 前兩個集合內的元素是有序的,元素可以重複;最後一個集合內的元素無序,但元素不可重複。

      那麼, 這裡就有一個比較嚴重的問題:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢? 這就是 Object.equals 方法了。但是,如果每增加一個元素就檢查一次,那麼當元素很多時,後面加入集合中的元素比較的次數就非常多了。 也就是說,如果集合中現在已經有1000個元素,那麼第1001個元素加入集合時,它就要呼叫1000次equals方法。這顯然會大大降低效率。於是,Java採用了哈希表的原理。 這樣,我們對每個要存入集合的元素使用雜湊演算法算出一個值,然後根據該值計算出元素應該在數組的位置。所以,當集合要加入新的元素時,可分為兩個步驟:
         

  • 先呼叫這個元素的hashCode 方法,然後根據所所得的值計算出元素應該在數組的位置。如果這個位置上沒有元素,那麼直接將它儲存在這個位置上;

  • #如果這個位置上已經有元素了,那麼呼叫它的equals方法與新元素進行比較:相同的話就不存了,否則,將其存在這個位置對應的鍊錶中(Java 中HashSet, HashMap 和Hashtable的實作總會元素放到鍊錶的錶頭) 。


4、equals 與hashCode

 前提: 談到hashCode就不得不說equals方法,二者均是Object類別裡的方法。由於Object類別是所有類別的基類,所以在所有類別中都可以重寫這兩個方法。

  • 原則1 : 如果x.equals(y) 回傳“true”,那麼x 和y 的hashCode() 必須相等;

  • #原則2 : 如果x.equals(y) 回傳“false”,那麼x 和y 的hashCode() 有可能相等,也有可能不等;

  • 原则 3 : 如果 x 和 y 的 hashCode() 不相等,那么 x.equals(y) 一定返回 “false” ;

  • 原则 4 : 一般来讲,equals 这个方法是给用户调用的,而 hashcode 方法一般用户不会去调用 ;

  • 原则 5 : 当一个对象类型作为集合对象的元素时,那么这个对象应该拥有自己的equals()和hashCode()设计,而且要遵守前面所说的几个原则。


5、实现例证

 hashCode()在object类中定义如下:

public native int hashCode();

 说明是一个本地方法,它的实现是根据本地机器相关的。

 String 类是这样重写它的:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence{
    /** The value is used for character storage. */
    private final char value[];     //成员变量1

    /** The offset is the first index of the storage that is used. */
    private final int offset;      //成员变量2

    /** The count is the number of characters in the String. */
    private final int count;       //成员变量3

   /** Cache the hash code for the string */
    private int hash; // Default to 0    //非成员变量

    public int hashCode() {    int h = hash;        int len = count;         //用到成员变量3
    if (h == 0 && len > 0) {        int off = offset;         //用到成员变量2
        char val[] = value;       //用到成员变量1
            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];       //递推公式
            }
            hash = h;
        }        return h;
    }
}

对程序的解释:h =  s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],由此可以看出,对象的hash地址不一定是实际的内存地址。


五、小结

  • hashcode是系统用来快速检索对象而使用

  • equals方法本意是用来判断引用的对象是否一致

  • 重写equals方法和hashcode方法时,equals方法中用到的成员变量也必定会在hashcode方法中用到,只不过前者作为比较项,后者作为生成摘要的信息项,本质上所用到的数据是一样的,从而保证二者的一致性


以上是詳解Java中的 ==, equals 與 hashCode的區別與聯繫的範例程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn