溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何使用equals() ? ==? hashCode()?

發(fā)布時間:2021-10-13 15:34:30 來源:億速云 閱讀:110 作者:iii 欄目:編程語言

這篇文章主要講解了“如何使用equals() ? ==? hashCode()?”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何使用equals() ? ==? hashCode()?”吧!

目錄

  • Java中的數(shù)據(jù)類型

  • 什么時候用關(guān)系操作符==,什么時候用equals方法?

  • equals方法,為什么報了 空指針 java.lang.NullPointerException?

  • hashCode 方法有什么作用?hashCode 和 equals 的關(guān)系?

  • 為什么每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法?

數(shù)據(jù)類型

java中的數(shù)據(jù)類型,可分為兩類:

1.基本數(shù)據(jù)類型(原始數(shù)據(jù)類型)

byte,short,char,int,long,float,double,boolean  他們之間的比較,應(yīng)用雙等號(==),基礎(chǔ)數(shù)據(jù)類型比較的是他們的值。

2.引用類型(類、接口、數(shù)組)

當(dāng)他們用(==)進行比較的時候,比較的是他們在內(nèi)存中的存放地址,  對象是放在堆中的,棧中存放的是對象的引用(地址)。由此可見'=='在比較的對象是引用類型時,是對棧中的地址值進行比較的。

關(guān)系操作符 ==

java中包含的關(guān)系操作符有小于(<)、大于(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。

==和!=適用所有對象,但是這兩個操作符通常在比較對象的時候會出問題:

在這里==和!=比較的是對象的引用。盡管對象的內(nèi)容相同,但是對象的引用卻是不同的,說以n1==n2是false。

Integer n1 = new Integer(47);         Integer n2 = new Integer(47);                  System.out.println(n1 == n2);  //false         System.out.println(n1 != n2);  //true

在這里 == 比較的是基本數(shù)據(jù)類型, 那么他會比較數(shù)值是否相等.所以此時 n1 == n2輸出 true.

int n1 = 100;        int n2 = 100;         System.out.println(n1 == n2);  //true        System.out.println(n1 != n2);  //false

equals方法

默認(rèn)情況,對象的equals方法 調(diào)用的是Object類中equals方法.源碼如下:

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

注意這里相當(dāng)于還是用的==,在這里比較的是引用對象,所以是比較地址(是不是同一個對象)

第二種情況,重寫了對象的equals方法. 例如String對象.源碼如下:

public boolean equals(Object anObject) {         if (this == anObject) {             return true;//如果是同一個對象直接返回         }         if (anObject instanceof String) {//是String對象開始判斷內(nèi)容.             String anotherString = (String)anObject;             int n = value.length;             if (n == anotherString.value.length) {                 char v1[] = value;                 char v2[] = anotherString.value;                 int i = 0;                 while (n-- != 0) {//逐個字符比較,若有不相等字符,返回false                     if (v1[i] != v2[i])                         return false;                     i++;                 }                 return true;             }         }         return false;     }

此時,equals方法的重寫實現(xiàn)不盡相同,但是重寫后一般都是通過對象的內(nèi)容是否相等 來判斷  對象是否相等,對于大多數(shù)Java類庫都實現(xiàn)了equals()方法,以便用來比較對象的內(nèi)容,而非比較對象的引用

避免equals方法,報空指針

避免equals方法報空指針,先告訴大家,答案是使用Objects.equals(a,b),在JDK7添加了一個Objects工具類,它提供了一些方法來操作對象,它由一些靜態(tài)的實用方法組成,這些方法是null-save(空指針安全的)或null-tolerant(容忍空指針的),用于計算對象的hashcode、返回對象的字符串表示形式、比較兩個對象。

在默認(rèn)情況下,對象的equals方法沒有重寫 調(diào)用的是Object類中equals方法

那么我們來寫個報錯的例子:

A a = null;//假設(shè)我接收到config對象,我并不知道是否為空,就進行比較         boolean r = a.equals(new B());         System.out.println(r); //輸出 java.lang.NullPointerException

此時由于我們的疏忽,接收到參數(shù)后,并沒有對參數(shù)進行校驗,導(dǎo)致調(diào)用equals方法報出空指針.

//其它的例子有: null.equals("java寶典");  //NullPointerException  "java寶典".equals(null);  //false 只有equals左邊的對象不為Null時,才有結(jié)果  null.equals(null);  //NullPointerException

使用Objects.equals(a,b),左右 兩邊都為Null也不會報空指針

Objects.equals(null,"java寶典");  //false  Objects.equals("java寶典",null);  //false  Objects.equals(null,null);  //true

看一下Objects.equals方法的源碼,它是容忍空指針的

public static boolean equals(Object a, Object b) {        return (a == b) || (a != null && a.equals(b));    }

hashCode() 方法

哈希(Hash)實際上是  人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也稱為散列算法,是指將數(shù)據(jù)依特定算法直接指定到一個地址上,通俗理解就是一種從任何一種數(shù)據(jù)中創(chuàng)建小的數(shù)字“指紋”的方法。

在java中,默認(rèn)情況下,對象沒有重寫hashCode()方法.使用的是Object類中的.

public native int hashCode(); //它是一個native方法.

Object 類定義的 hashCode 方法會針對不同的對象返回不同的整數(shù)。(這是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實現(xiàn)的)

例子:

Config config1 = new Config();        Config config2 = new Config();         System.out.println(config1.hashCode());  //1128032093         System.out.println(config2.hashCode());  //1066516207         System.out.println(config1.equals(config2));  //false

hashCode 和 equals 的關(guān)系

二者均是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 這個方法是給用戶調(diào)用的,而 hashcode 方法一般用戶不會去調(diào)用 ;

  • 原則 5  :當(dāng)一個對象類型作為集合對象的元素時,那么這個對象應(yīng)該擁有自己的equals()和hashCode()設(shè)計,而且要遵守前面所說的幾個原則。

在 Java 應(yīng)用程序執(zhí)行期間,在對同一對象多次調(diào)用 hashCode 方法時,必須一致地返回相同的整數(shù),前提是將對象進行 equals  比較時所用的信息沒有被修改。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無需保持一致。

如果根據(jù) equals(Object) 方法,兩個對象是相等的,那么對這兩個對象中的每個對象調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。

如果根據(jù) equals(java.lang.Object) 方法,兩個對象不相等,那么對這兩個對象中的任一對象上調(diào)用 hashCode 方法 不要求  一定生成不同的整數(shù)結(jié)果。但是,程序員應(yīng)該意識到,為不相等的對象生成不同整數(shù)結(jié)果可以提高哈希表的性能。

為什么每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法?

在每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法。如果不這樣做的話,就會違反Object.hashCode的通用約定,從而導(dǎo)致該類無法結(jié)合所有基于散列的集合一起正常運作

上面我們介紹了hashCode是什么,進一步了解 hashCode 的應(yīng)用,我們必須先要了解Java中的容器,因為 HashCode  只是在需要用到哈希算法的數(shù)據(jù)結(jié)構(gòu)中才有用,比如 HashSet, HashMap ..

我們以hashMap為例:

HashMap是由數(shù)組和鏈表組成的存儲數(shù)據(jù)的結(jié)構(gòu)。確定一個數(shù)據(jù)存儲在數(shù)組中的哪個位置 就是通過hashCode方法進行計算出存儲在哪個位置,  產(chǎn)生沖突的話就會調(diào)用equals方法進行比對,  如果不同,那么就將其加入鏈表尾部,如果相同就替換原數(shù)據(jù)。計算位置當(dāng)然不是上面簡單的一個hashCode方法就計算出來,中間還有一些其他的步驟,這里可以簡單的認(rèn)為是hashCode確定了位置,代碼如下:

public V put(K key, V value) {      // 如果哈希表沒有初始化就進行初始化      if (table == EMPTY_TABLE) {          // 初始化哈希表          inflateTable(threshold);      }         // 當(dāng)key為null時,調(diào)用putForNullKey方法,保存null于table的第一個位置中,這是HashMap允許為null的原因      if (key == null) {          return putForNullKey(value);      }         // 計算key的hash值      int hash = hash(key);      // 根據(jù)key的hash值和數(shù)組的長度定位到entry數(shù)組的指定槽位      int i = indexFor(hash, table.length);      // 獲取存放位置上的entry,如果該entry不為空,則遍歷該entry所在的鏈表      for (Entry<K, V> e = table[i]; e != null; e = e.next) {          Object k;          // 通過key的hashCode和equals方法判斷,key是否存在, 如果存在則用新的value取代舊的value,并返回舊的value          if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {              V oldValue = e.value;              e.value = value;              e.recordAccess(this);              return oldValue;          }      }         // 修改次數(shù)增加1      modCount++;      // 如果找不到鏈表 或者 遍歷完鏈表后,發(fā)現(xiàn)key不存在,則創(chuàng)建一個新的Entry,并添加到HashMap中      addEntry(hash, key, value, i);      return null;  }

在上面的方法中,調(diào)用了一個方法可以看到,數(shù)組的下標(biāo)是根據(jù)傳入的元素hashCode方法的返回值再和特定的值異或決定的:

static int indexFor(int h, int length) {         // 對hash值和length-1進行與運算來計算索引         return h & (length - 1);     }

再回到我們的問題:為什么每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法?

如果你重寫了equals,  而hashCode的實現(xiàn)不重寫,那么類的hashcode方法就是Object默認(rèn)的hashcode方法,由于默認(rèn)的hashcode方法是根據(jù)對象的內(nèi)存地址經(jīng)哈希算法得來的一個值,那么很可能某兩個對象明明是“相等”,而hashCode卻不一樣。

這樣,當(dāng)你用其中的一個作為鍵保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一個作為鍵值去查找他們的時候  ,則根本找不到。導(dǎo)致HashSet、HashMap不能正常的運作.

比如:有個A類重寫了equals方法,但是沒有重寫hashCode方法,對象a1和對象a2使用equals方法相等,按照上面的hashcode的用法,那么他們兩個的hashcode肯定相等,但是這里由于沒重寫hashcode方法,他們兩個hashcode并不一樣,所以,我們在重寫了equals方法后,盡量也重寫了hashcode方法,通過一定的算法,使他們在equals相等時,也會有相同的hashcode值。

總結(jié)

== 在比較基本數(shù)據(jù)類型時,比較的是值

== 在比較引用數(shù)據(jù)類型時,比較的是對象的引用地址

對象的equals方法,在不重寫的情況下,使用的是==,比較的是對象的引用地址

對象的equals方法,在重寫以后,用于比較對象的內(nèi)容是否相等,實現(xiàn)可以使用IDE生成或者自定義實現(xiàn).(例如,String類對equals方法的重寫就是逐個比較字符)

不重寫的情況下,對象的equals方法  調(diào)用的是Object類中equals方法,在條件左邊為Null時會報空指針,使用Objects.equals(a,b)可以避免空指針

hashcode是系統(tǒng)用來快速檢索對象而使用的

重寫了equals方法后,也要重寫了hashcode方法,否則會導(dǎo)致HashSet、HashMap等依賴hashCode的容器不能正常的運作

感謝各位的閱讀,以上就是“如何使用equals() ? ==? hashCode()?”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對如何使用equals() ? ==? hashCode()?這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI