JDK1.8 --- Object類的HashCode、equals、clone方法解析

一、public native int hashCode()

???????返回當前對象運行時的hash碼。(在jdk源碼中的解釋是用于支持散列表數據結構,因為散列表在進行數據存儲時依賴hash碼決定數據存儲的位置(邏輯位置)。在程序運行中,無論什么情況下,相同的對象對應的hash碼一定是相同的。但是不同的對象有可能會返回相同的hash碼。那么其實也代表如果兩個對象的hash碼不一致,這兩個對象一定是不同的。)
這里我們做一下延伸:

為什么需要Hash碼?
???????Hash碼的作用:文本校驗,數字簽名。

hash碼到底是什么?(這里我們引用百度百科的內容)
???????Hash譯作“散列”,就是把任意長度的輸入(又叫做預映射pre-image)通過散列算法變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小于輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。

以上有一些概念需要一一作出解釋:
常見的散列算法:

  1. 余數法:先估計整個哈希表中的表項目數目大小。然后用這個估計值作為除數去除每個原始值,得到商和余數。用余數作為哈希值。因為這種方法產生沖突的可能性相當大,因此任何搜索算法都應該能夠判斷沖突是否發(fā)生并提出取代算法。
  2. 折疊法:這種方法是針對原始值為數字時使用,將原始值分為若干部分,然后將各部分疊加,得到的最后四個數字(或者取其他位數的數字都可以)來作為哈希值。
  3. 基數轉換法:當原始值是數字時,可以將原始值的數制基數轉為一個不同的數字。例如,可以將十進制的原始值轉為十六進制的哈希值。為了使哈希值的長度相同,可以省略高位數字。
  4. 數據重排法:這種方法只是簡單的將原始值中的數據打亂排序。比如可以將第三位到第六位的數字逆序排列,然后利用重排后的數字作為哈希值。

Java中Hashcode方法的規(guī)定:

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

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

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

Hotspot中hashcode方法的實現:


圖片 1.png

???????hash方法的實現是先獲取該對象的標記字對象,然后對該標記字對象的的地址做位移和邏輯與操作,以結果作為hashcode(其中,mark_bits方法在globalDefinitions.hpp),之所以做移位操作是因為hashcode在標記字中只占用了部分位(32位機器上是占用25位,64位機器上占用31)
???????從實現來看,我們試圖解釋hashcode的一個現象,為什么不同的對象可能返回相同的hashcode方法?
因為盡管虛擬機在運行過程中,不同的對象的地址一定是不同的,但是由于hashcode需要固定25位或者31位,那么就導致真正的hashcode值需要在對象地址上做一定的操作。從而將一個大范圍區(qū)間的值映射到一個小范圍區(qū)間的值(hashcode的計算過程),這樣的操作必定會導致一部分數據的計算結果會重復。所以說這就是不同的對象可能返回相同hash值的原因。

二、 public boolean equals(Object obj)

圖片 1.png

在java中equals的作用是,判斷兩個對象是不是相等的。
???????從jdk的實現可以看出:equals其實就是在比較兩個對象的地址是否相等,所以這個方法不存在hashcode()的問題,因為對象在內存中的地址是唯一的。
重寫equals()方法就必須重寫hashCode()方法的原因。
???????假設兩個對象,重寫了其equals方法,其相等條件是屬性相等,就返回true。如果不重寫hashcode方法,其返回的依然是兩個對象的內存地址值,必然不相等。這就出現了equals方法相等,但是hashcode不相等的情況。這不符合hashcode的規(guī)則。在集合框架中,這種情況會導致的嚴重的問題,具體的問題在hashMap中會具體分析。

三、 protected native Object clone()

本地方法表
static JNINativeMethod methods[] = {
???????{"hashCode", "()I", (void *)&JVM_IHashCode},
???????{"wait", "(J)V", (void *)&JVM_MonitorWait},
???????{"notify", "()V", (void *)&JVM_MonitorNotify},
???????{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
???????{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone}
};
???????由本地方法表知道clone方法對應的本地函數為JVM_Clone,clone方法主要實現對象的克隆功能,根據該對象生成一個相同的新對象(我們常見的類的對象的屬性如果是原始類型則會克隆值,但如果是對象則會克隆對象的地址)。Java的類要實現克隆則需要實現Cloneable接口,if (!klass->is_cloneable())這里會校驗是否有實現該接口。然后判斷是否是數組分兩種情況分配內存空間,新對象為new_obj,接著對new_obj進行copy及C++層數據結構的設置。最后再轉成jobject類型方便轉成Java層的Object類型。
源碼:


圖片 1.png

看到jvm源碼之后我們來分析一下:
???????底層在實現clone()方法其實非常簡單,一.首先確認當前對象是否實現了cloneable接口(這是一個標記接口),二.分配一個和當前對象一樣大小的空間,三.將原對象堆中的區(qū)域以字節(jié)的方式復制到新對象中。
???????然而對象在堆中的存儲,對象屬性其實存儲的只是引用地址,那么使用clone()方法的時候就是我們所說淺拷貝,只是值拷貝??截惖慕涌谑强截惖膶ο蠛驮瓕ο蟮膶ο髮傩灾赶蛲粋€地址。對這個屬性做改動時,會互相影響。

那么引出一個問題,我們怎么進行深拷貝呢?
???????解決的方法是調用對象屬性的clone()方法,需要所有的對象屬性都重寫clone()方法。然后再調用上層對象的clone()方法時,再里面調用屬性的對象的clone()方法,實現深拷貝。

還有一個值得思考的問題,String對象如何進行拷貝的?
???????String對象本身是個final類,所以在對它做拷貝的時候注定是值拷貝,拷貝之后的String和原來的String指向同一個區(qū)域(常量池),然后String有一個基本特性是它的不可變性,如果改變就新增,這樣的話雖然是淺拷貝,但變相的實現了深拷貝的效果。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,886評論 0 11
  • Java中的equals方法和hashCode方法是Object中的,所以每個對象都是有這兩個方法的,有時候我們需...
    差不多先生_tl閱讀 1,476評論 2 6
  • “有狐綏綏,在彼其梁!”這是方遠第一次看見綏綏時腦海中閃現出的詞。 沒錯,當時聽到這個名字的時候...
    方一落閱讀 307評論 0 1
  • 各位家長: 望子成龍、望女成鳳,一定是您最殷切期望!可是,如何讓孩子成龍、成鳳,這也許是困攏每個家長的一道...
    徐鼎文閱讀 388評論 0 0
  • 如果我很喜歡一個男生,他也愿意接受我做女朋友,但他心里裝著一個別的女生,我還愿意和他在一起嗎? 關...
    李嚕嚕小姐閱讀 1,216評論 0 4

友情鏈接更多精彩內容