1. 程式人生 > >java中hashcode與equals詳解(集合中的用法)

java中hashcode與equals詳解(集合中的用法)

一:Java中的equals方法和hashCode方法是Object中的,所以每個物件都是有這兩個方法的,有時候我們需要實現特定需求,可能要重寫這兩個方法

equals()和hashCode()方法是用來在同一類中做比較用的,尤其是在容器裡如set存放同一類物件時用來判斷放入的物件是否重複。

      1)首先要明白一個問題:

      equals()相等的兩個物件,hashcode()一定相等,equals()不相等的兩個物件,卻並不能證明他們的hashcode()不相等。(可能由於雜湊碼在生成時產生衝突造成的)

反過來:hashcode()不等,一定能推出

equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

       在這裡hashCode就好比字典裡每個字的索引,equals()好比比較的是字典裡同一個字下的不同詞語。就好像在字典裡查“自”這個字下的兩個詞語“自己”、“自發”,如果用equals()判斷查詢的詞語相等那麼就是同一個詞語,比如equals()比較的兩個詞語都是“自己”,那麼此時hashCode()方法得到的值也肯定相等;如果用equals()方法比較的是“自己”和“自發”這兩個詞語,那麼得到結果是不想等,但是這兩個詞都屬於“自”這個字下的詞語所以在查索引時相同,即:hashCode()相同。如果用equals()比較的是“自己”和“他們”這兩個詞語的話那麼得到的結果也是不同的,此時hashCode() 得到也是不同的。

2)在object類中,hashcode()方法是本地方法,返回物件的32位jvm記憶體地址。而object類中的equals()方法比較的也是兩個物件的地址值,如果equals()相等,說明兩個物件地址值也相等,當然hashcode() 也就相等了;

3)同時hash演算法對於查詢元素提供了很高的效率

如果想查詢一個集合中是否包含有某個物件,大概的程式程式碼怎樣寫呢?
通常是逐一取出每個元素與要查詢的物件進行比較,當發現某個元素與要查詢的物件進行equals方法比較的結果相等時,則停止繼續查詢並返回肯定的資訊,否則,返回否定的資訊,如果一個集合中有很多個元素,比如有一萬個元素,並且沒有包含要查詢的物件時,則意味著你的程式需要從集合中取出一萬個元素進行逐一比較才能得到結論。
有人發明了一種雜湊演算法來提高從集合中查詢元素的效率,這種方式將集合分成若干個儲存區域,每個物件可以計算出一個雜湊碼,可以將雜湊碼分組(使用不同的hash函式來計算的),每組分別對應某個儲存區域,根據一個物件的雜湊嗎就可以確定該物件應該儲存在哪個區域HashSet就是採用雜湊演算法存取物件的集合,它內部採用對某個數字n進行取餘(這種的hash函式是最簡單的)的方式對雜湊碼進行分組和劃分物件的儲存區域;Object類中定義了一個hashCode()方法來返回每個Java物件的雜湊碼,當從HashSet集合中查詢某個物件時,Java系統首先呼叫物件的hashCode()方法獲得該物件的雜湊碼錶,然後根據雜湊嗎找到相應的儲存區域,最後取得該儲存區域內的每個元素與該物件進行equals方法比較

;這樣就不用遍歷集合中的所有元素就可以得到結論,可見,HashSet集合具有很好的物件檢索效能,但是,HashSet集合儲存物件的效率相對要低些,因為向HashSet集合中新增一個物件時,要先計算出物件的雜湊碼和根據這個雜湊碼確定物件在集合中的存放位置為了保證一個類的例項物件能在HashSet正常儲存,要求這個類的兩個例項物件用equals()方法比較的結果相等時,他們的雜湊碼也必須相等;也就是說,如果obj1.equals(obj2)的結果為true,那麼以下表達式的結果也要為true:obj1.hashCode() == obj2.hashCode();

換句話說:當我們重寫一個物件的equals方法,就必須重寫他的hashCode方法,不過不重寫他的hashCode方法的話,Object物件中的hashCode方法始終返回的是一個物件的hash地址,而這個地址是永遠不相等的。所以這時候即使是重寫了equals方法,也不會有特定的效果的,因為hashCode方法如果都不想等的話,就不會呼叫equals方法進行比較了,所以沒有意義了。

如果一個類的hashCode()方法沒有遵循上述要求,那麼,當這個類的兩個例項物件用equals()方法比較的結果相等時,他們本來應該無法被同時儲存進set集合中,但是,如果將他們儲存進HashSet集合中時,由於他們的hashCode()方法的返回值不同(Object中的hashCode方法返回值是永遠不同的),第二個物件首先按照雜湊碼計算可能被放進與第一個物件不同的區域中,這樣,它就不可能與第一個物件進行equals方法比較了,也就可能被儲存進HashSet集合中了,Object類中的hashCode()方法不能滿足物件被存入到HashSet中的要求,因為它的返回值是通過物件的記憶體地址推算出來的,同一個物件在程式執行期間的任何時候返回的雜湊值都是始終不變的,所以,只要是兩個不同的例項物件,即使他們的equals方法比較結果相等,他們預設的hashCode方法的返回值是不同的。

下面來看一下一個具體的例子:

RectObject物件:

  1. package com.weijia.demo;  
  2. publicclass RectObject {  
  3.     publicint x;  
  4.     publicint y;  
  5.     public RectObject(int x,int y){  
  6.         this.x = x;  
  7.         this.y = y;  
  8.     }  
  9.     @Override
  10.     publicint hashCode(){  
  11.         finalint prime = 31;  
  12.         int result = 1;  
  13.         result = prime * result + x;  
  14.         result = prime * result + y;  
  15.         return result;  
  16.     }  
  17.     @Override
  18.     publicboolean equals(Object obj){  
  19.         if(this == obj)  
  20.             returntrue;  
  21.         if(obj == null)  
  22.             returnfalse;  
  23.         if(getClass() != obj.getClass())  
  24.             returnfalse;  
  25.         final RectObject other = (RectObject)obj;  
  26.         if(x != other.x){  
  27.             returnfalse;  
  28.         }  
  29.         if(y != other.y){  
  30.             returnfalse;  
  31.         }  
  32.         returntrue;  
  33.     }  
  34. }  
我們重寫了父類Object中的hashCode和equals方法,看到hashCode和equals方法中,如果兩個RectObject物件的x,y值相等的話他們的hashCode值是相等的,同時equals返回的是true;

下面是測試程式碼:

  1. package com.weijia.demo;  
  2. import java.util.HashSet;  
  3. publicclass Demo {  
  4.     publicstaticvoid main(String[] args){  
  5.         HashSet<RectObject> set = new HashSet<RectObject>();  
  6.         RectObject r1 = new RectObject(3,3);  
  7.         RectObject r2 = new RectObject(5,5);  
  8.         RectObject r3 = new RectObject(3,3);  
  9.         set.add(r1);  
  10.         set.add(r2);  
  11.         set.add(r3);  
  12.         set.add(r1);  
  13.         System.out.println("size:"+set.size());  
  14.     }  
  15. }  
我們向HashSet中存入到了四個物件,列印set集合的大小,結果是多少呢?

執行結果:size:2

為什麼會是2呢?這個很簡單了吧,因為我們重寫了RectObject類的hashCode方法,只要RectObject物件的x,y屬性值相等那麼他的hashCode值也是相等的,所以先比較hashCode的值,r1和r2物件的x,y屬性值不等,所以他們的hashCode不相同的,所以r2物件可以放進去,但是r3物件的x,y屬性值和r1物件的屬性值相同的,所以hashCode是相等的,這時候在比較r1和r3的equals方法,因為他麼兩的x,y值是相等的,所以r1,r3物件是相等的,所以r3不能放進去了,同樣最後再新增一個r1也是沒有沒有新增進去的,所以set集合中只有一個r1和r2這兩個物件

下面我們把RectObject物件中的hashCode方法註釋,即不重寫Object物件中的hashCode方法,在執行一下程式碼:

執行結果:size:3
這個結果也是很簡單的,首先判斷r1物件和r2物件的hashCode,因為Object中的hashCode方法返回的是物件本地記憶體地址的換算結果,不同的例項物件的hashCode是不相同的,同樣因為r3和r1的hashCode也是不相等的,但是r1==r1的,所以最後set集合中只有r1,r2,r3這三個物件,所以大小是3

下面我們把RectObject物件中的equals方法中的內容註釋,直接返回false,不註釋hashCode方法,執行一下程式碼:

執行結果:size:3

這個結果就有點意外了,我們來分析一下:

首先r1和r2的物件比較hashCode,不相等,所以r2放進set中,再來看一下r3,比較r1和r3的hashCode方法,是相等的,然後比較他們兩的equals方法,因為equals方法始終返回false,所以r1和r3也是不相等的,r3和r2就不用說了,他們兩的hashCode是不相等的,所以r3放進set中,再看r4,比較r1和r4發現hashCode是相等的,在比較equals方法,因為equals返回false,所以r1和r4不相等,同一r2和r4也是不相等的,r3和r4也是不相等的,所以r4可以放到set集合中,那麼結果應該是size:4,那為什麼會是3呢?

這時候我們就需要檢視HashSet的原始碼了,下面是HashSet中的add方法的原始碼:

  1. /** 
  2.      * Adds the specified element to this set if it is not already present. 
  3.      * More formally, adds the specified element <tt>e</tt> to this set if 
  4.      * this set contains no element <tt>e2</tt> such that 
  5.      * <tt>(e==null ? e2==null : e.equals(e2))</tt>. 
  6.      * If this set already contains the element, the call leaves the set 
  7.      * unchanged and returns <tt>false</tt>. 
  8.      * 
  9.      * @param e element to be added to this set 
  10.      * @return <tt>true</tt> if this set did not already contain the specified 
  11.      * element 
  12.      */
  13.     publicboolean add(E e) {  
  14.         return map.put(e, PRESENT)==null;  
  15.     }  

這裡我們可以看到其實HashSet是基於HashMap實現的,我們在點選HashMap的put方法,原始碼如下:

  1. /** 
  2.      * Associates the specified value with the specified key in this map. 
  3.      * If the map previously contained a mapping for the key, the old 
  4.      * value is replaced. 
  5.      * 
  6.      * @param key key with which the specified value is to be associated 
  7.      * @param value value to be associated with the specified key 
  8.      * @return the previous value associated with <tt>key</tt>, or 
  9.      *         <tt>null</tt> if there was no mapping for <tt>key</tt>. 
  10.      *         (A <tt>null</tt> return can also indicate that the map 
  11.      *         previously associated <tt>null</tt> with <tt>key</tt>.) 
  12.      */
  13.     public V put(K key, V value) {  
  14.         if (key == null)  
  15.             return putForNullKey(value);  
  16.         int hash = hash(key);  
  17.         

    相關推薦

    javahashcodeequals集合用法

    一:Java中的equals方法和hashCode方法是Object中的,所以每個物件都是有這兩個方法的,有時候我們需要實現特定需求,可能要重寫這兩個方法 equals()和hashCode()方法是用來在同一類中做比較用的,尤其是在容器裡如set存放同一類物件

    C語言二維陣列名陣列地址、首行地址、首行首元素地址關係區別初學者必須掌握

    C語言作為很多大學理工科都會學習的語言,作為一種程式設計入門語言。但是相對於其他高階程式語言來說相對是比較難,尤其是指針,不知道有多少莘莘學子都是因為它,從C語言入門到放棄。想當年,筆者在大一學習C語言

    KEIL啟動文件匯編語言

    環境 變量 .html keil ans 參數形式 ref 線下 flash 原文轉自:http://www.cnblogs.com/mddblog/p/4920063.html 閱讀目錄 概述 1.堆棧空間定義 2.存放中斷向量表 3. 復位中斷函數(Reset_Ha

    html——floatclear深度好文

    教程開始:        首先要知道,div是塊級元素,在頁面中獨佔一行,自上而下排列,也就是傳說中的流。如下圖:        可以看出,即使div1的寬度很小,頁面中一行可以容下d

    DFSBFS很不錯的

    有需要關注微信公眾號:演算法那些事兒 --------------------- 咱們由BFS開始: 首先,看下演算法導論一書關於 此BFS 廣度優先搜尋演算法的概述。 演算法導論第二版,中譯本,第324頁。 廣度優先搜尋(BFS) 在Prime最小生成樹演算法,

    string裡的IndexOf、LastIndexOf、Substring的意義和用法

    今天遇到擷取字串的問題,在網上查了IndexOf、LastIndexOf、Substring這三種擷取字串的使用總結如下:   String.IndexOf   String.IndexOf 方法 (Char, Int32, Int32) 報告指定字元在此例項中的第

    在MFC繪製地圖以及地理座標螢幕座標轉換附工程原始碼

    在MFC中繪圖時,因為都是以畫素為單位的,所以我們只能以整數作為引數。如果我們想要把一幅地圖資料繪製在MFC視窗中,地圖的座標資料肯定的含有小數位的,這樣我們就不能直接在MFC中繪製地圖了,這就涉及到地理座標向螢幕座標的轉換。 1、地理座標轉螢幕座標 首先我們看一張圖(圖

    hashcode==equals

    ArrayList有序,HashSet不允許重複資料 HashCode方法的作用:把集合分成若干個區域,根據不同的值放入不同的區域中,查詢時,算出hashcode值          找到指定的區域。(必須集合是hash演算法,hashcode值才有價值) 記憶體洩露:物件

    Java 異常的捕獲處理

    上一篇Java 異常的捕獲與處理詳解(一)講了異常的產生、處理以及處理流程,接下來講其他內容。 一、throws關鍵字 thrwos關鍵字主要是在方法定義上使用的,表示的是此方法之中不進行異常的處理,而交給被呼叫處處理。 例如: class MyMath

    Java代理模式實現原理

    關於Java中的代理,我們首先需要了解的是一種常用的設計模式——代理模式,而對於代理,可根據代理類建立的時間點,分為靜態代理和動態代理。今天我們先來了解一下Java中的靜態代理。 1 代理模式 代理模式是一種常用的設計模式,百度百科中對其定義為:為其他物件提供一個代理以控制對某個物件的訪問。

    Java網路程式設計NIO4:淺析NIO包的Buffer、Channel 和 Selector

    Java NIO:Buffer、Channel 和 Selector轉自https://www.javadoop.com/post/nio-and-aio本文將介紹 Java NIO 中三大元件 Buffer、Channel、Selector 的使用。本來要一起介紹非阻塞 I

    JavahashCodeequals方法的約定及重寫原則

    Java中Set的contains()方法 —— hashCode與equals方法的約定及重寫原則翻譯人員: 鐵錨翻譯時間: 2013年11月5日原文連結: Java hashCode() and equals() Contract for the contains(Obj

    Java 異常的捕獲處理

    一、異常的產生 異常是程式之中導致程式中斷的一種指令流,異常一旦出現並且沒有進行合理處理的話,那麼程式就將中斷執行。 下面,通過兩個程式來進行異常產生問題的對比。 (1)不產生異常的程式: public class Test { public s

    Spring核心思想,IoCDI如果還不明白,放棄java

    1.IoC是什麼? IoC(Inversion of Control)控制反轉,IoC是一種新的Java程式設計模式,目前很多輕量級容器都在廣泛使用的模式。 2.IoC解決了什麼問題? 在IoC出現以前,元件之間的協調關係是由程式內部程式碼來控制的,或者說,以前我們使用New關鍵字來實現兩組

    JAVA線程池原理1

    err 最大 RKE private queue 分享 ren ++ ant 線程池的優點 1、線程是稀缺資源,使用線程池可以減少創建和銷毀線程的次數,每個工作線程都可以重復使用。 2、可以根據系統的承受能力,調整線程池中工作線程的數量,防止因為消耗過多內存導致服務器崩潰。

    Java開源生鮮電商平臺-Java後端生成Token架構設計(源碼可下載

    red 基於 一次 frame service state dha 概述 class Java開源生鮮電商平臺-Java後端生成Token架構與設計詳解(源碼可下載) 目的:Java開源生鮮電商平臺-Java後端生成Token目的是為了用於校驗客戶端,防止重復提交. 技

    VS2010 Chart控件Chart控件在ASP.NET網站的應用示例C#語言

    [1] 設置 cti write conf int 應用程序 itl config 步驟如下: 1、 Chart控件(一)Chart控件在ASP.NET網站中的應用示例詳解(C#語言)" title="VS2010 Chart控件(一)Chart控件在ASP.NET網站中的

    eclipsesvn圖標

    tin note 引用 pda call use ips linked Opens - 已忽略版本控制的文件。可以通過Window → Preferences → Team → Ignored Resources.來忽略文件。A file ignored by versio

    JAVA:Excel匯入匯出3--匯出

    Excel匯出 一、設定查詢條件 注意:無法通過Ajax下載 jsp程式碼 <form class="col-sm-2" action="/manage/order/download" method="post" onsubmit="checkForm()"

    JAVA:Excel匯入匯出2--匯入

    1. 瀏覽資料夾,選擇需要上傳的檔案 程式碼 jsp <li class="col-sm-1"> <span>上&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&a