一、public native int hashCode()
???????返回當(dāng)前對象運行時的hash碼咧党。(在jdk源碼中的解釋是用于支持散列表數(shù)據(jù)結(jié)構(gòu)秘蛔,因為散列表在進行數(shù)據(jù)存儲時依賴hash碼決定數(shù)據(jù)存儲的位置(邏輯位置)。在程序運行中傍衡,無論什么情況下深员,相同的對象對應(yīng)的hash碼一定是相同的。但是不同的對象有可能會返回相同的hash碼蛙埂。那么其實也代表如果兩個對象的hash碼不一致倦畅,這兩個對象一定是不同的。)
這里我們做一下延伸:
為什么需要Hash碼绣的?
???????Hash碼的作用:文本校驗叠赐,數(shù)字簽名。
hash碼到底是什么屡江?(這里我們引用百度百科的內(nèi)容)
???????Hash譯作“散列”芭概,就是把任意長度的輸入(又叫做預(yù)映射pre-image)通過散列算法變換成固定長度的輸出,該輸出就是散列值惩嘉。這種轉(zhuǎn)換是一種壓縮映射罢洲,也就是,散列值的空間通常遠(yuǎn)小于輸入的空間文黎,不同的輸入可能會散列成相同的輸出惹苗,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數(shù)耸峭。
以上有一些概念需要一一作出解釋:
常見的散列算法:
- 余數(shù)法:先估計整個哈希表中的表項目數(shù)目大小桩蓉。然后用這個估計值作為除數(shù)去除每個原始值,得到商和余數(shù)劳闹。用余數(shù)作為哈希值院究。因為這種方法產(chǎn)生沖突的可能性相當(dāng)大,因此任何搜索算法都應(yīng)該能夠判斷沖突是否發(fā)生并提出取代算法本涕。
- 折疊法:這種方法是針對原始值為數(shù)字時使用儡首,將原始值分為若干部分,然后將各部分疊加偏友,得到的最后四個數(shù)字(或者取其他位數(shù)的數(shù)字都可以)來作為哈希值蔬胯。
- 基數(shù)轉(zhuǎn)換法:當(dāng)原始值是數(shù)字時,可以將原始值的數(shù)制基數(shù)轉(zhuǎn)為一個不同的數(shù)字位他。例如氛濒,可以將十進制的原始值轉(zhuǎn)為十六進制的哈希值产场。為了使哈希值的長度相同,可以省略高位數(shù)字舞竿。
- 數(shù)據(jù)重排法:這種方法只是簡單的將原始值中的數(shù)據(jù)打亂排序京景。比如可以將第三位到第六位的數(shù)字逆序排列,然后利用重排后的數(shù)字作為哈希值骗奖。
Java中Hashcode方法的規(guī)定:
在 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é)果可以提高哈希表的性能亡问。
Hotspot中hashcode方法的實現(xiàn):
???????hash方法的實現(xiàn)是先獲取該對象的標(biāo)記字對象官紫,然后對該標(biāo)記字對象的的地址做位移和邏輯與操作,以結(jié)果作為hashcode(其中玛界,mark_bits方法在globalDefinitions.hpp)万矾,之所以做移位操作是因為hashcode在標(biāo)記字中只占用了部分位(32位機器上是占用25位悼吱,64位機器上占用31)
???????從實現(xiàn)來看慎框,我們試圖解釋hashcode的一個現(xiàn)象,為什么不同的對象可能返回相同的hashcode方法后添?
因為盡管虛擬機在運行過程中笨枯,不同的對象的地址一定是不同的,但是由于hashcode需要固定25位或者31位遇西,那么就導(dǎo)致真正的hashcode值需要在對象地址上做一定的操作馅精。從而將一個大范圍區(qū)間的值映射到一個小范圍區(qū)間的值(hashcode的計算過程),這樣的操作必定會導(dǎo)致一部分?jǐn)?shù)據(jù)的計算結(jié)果會重復(fù)粱檀。所以說這就是不同的對象可能返回相同hash值的原因洲敢。
二、 public boolean equals(Object obj)
在java中equals的作用是茄蚯,判斷兩個對象是不是相等的压彭。
???????從jdk的實現(xiàn)可以看出:equals其實就是在比較兩個對象的地址是否相等睦优,所以這個方法不存在hashcode()的問題,因為對象在內(nèi)存中的地址是唯一的壮不。
重寫equals()方法就必須重寫hashCode()方法的原因汗盘。
???????假設(shè)兩個對象,重寫了其equals方法询一,其相等條件是屬性相等隐孽,就返回true。如果不重寫hashcode方法健蕊,其返回的依然是兩個對象的內(nèi)存地址值菱阵,必然不相等。這就出現(xiàn)了equals方法相等绊诲,但是hashcode不相等的情況送粱。這不符合hashcode的規(guī)則。在集合框架中掂之,這種情況會導(dǎo)致的嚴(yán)重的問題抗俄,具體的問題在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方法對應(yīng)的本地函數(shù)為JVM_Clone动雹,clone方法主要實現(xiàn)對象的克隆功能,根據(jù)該對象生成一個相同的新對象(我們常見的類的對象的屬性如果是原始類型則會克隆值跟压,但如果是對象則會克隆對象的地址)胰蝠。Java的類要實現(xiàn)克隆則需要實現(xiàn)Cloneable接口,if (!klass->is_cloneable())這里會校驗是否有實現(xiàn)該接口震蒋。然后判斷是否是數(shù)組分兩種情況分配內(nèi)存空間茸塞,新對象為new_obj,接著對new_obj進行copy及C++層數(shù)據(jù)結(jié)構(gòu)的設(shè)置查剖。最后再轉(zhuǎn)成jobject類型方便轉(zhuǎn)成Java層的Object類型钾虐。
源碼:
看到j(luò)vm源碼之后我們來分析一下:
???????底層在實現(xiàn)clone()方法其實非常簡單,一.首先確認(rèn)當(dāng)前對象是否實現(xiàn)了cloneable接口(這是一個標(biāo)記接口)笋庄,二.分配一個和當(dāng)前對象一樣大小的空間效扫,三.將原對象堆中的區(qū)域以字節(jié)的方式復(fù)制到新對象中。
???????然而對象在堆中的存儲直砂,對象屬性其實存儲的只是引用地址菌仁,那么使用clone()方法的時候就是我們所說淺拷貝,只是值拷貝静暂〖们穑拷貝的接口是拷貝的對象和原對象的對象屬性指向同一個地址。對這個屬性做改動時洽蛀,會互相影響摹迷。
那么引出一個問題弯院,我們怎么進行深拷貝呢?
???????解決的方法是調(diào)用對象屬性的clone()方法泪掀,需要所有的對象屬性都重寫clone()方法听绳。然后再調(diào)用上層對象的clone()方法時,再里面調(diào)用屬性的對象的clone()方法异赫,實現(xiàn)深拷貝椅挣。
還有一個值得思考的問題,String對象如何進行拷貝的塔拳?
???????String對象本身是個final類鼠证,所以在對它做拷貝的時候注定是值拷貝,拷貝之后的String和原來的String指向同一個區(qū)域(常量池)靠抑,然后String有一個基本特性是它的不可變性量九,如果改變就新增,這樣的話雖然是淺拷貝颂碧,但變相的實現(xiàn)了深拷貝的效果荠列。