最近經(jīng)過某大佬的建議準(zhǔn)備閱讀一下JDK的源碼來提升一下自己
所以開始寫JDK源碼分析的文章
閱讀JDK版本為1.8
- 目錄
- Object結(jié)構(gòu)圖
- 構(gòu)造器
- equals 方法
- getClass 方法
- hashCode 方法
- toString 方法
- finalize 方法
- registerNatives 方法
1. Object結(jié)構(gòu)圖
2. 類構(gòu)造器
??類構(gòu)造器是創(chuàng)建Java對(duì)象的方法之一。一般我們都使用new關(guān)鍵字來進(jìn)行實(shí)例,還可以在構(gòu)造器中進(jìn)行相應(yīng)的初始化操作。
??在一個(gè)Java類中必須存在一個(gè)構(gòu)造器,如果沒有添加系統(tǒng)在編譯時(shí)會(huì)默認(rèn)創(chuàng)建一個(gè)無參構(gòu)造。
/*實(shí)例一個(gè)Object對(duì)象*/
Object obj = new Object()
3. equals 方法
??在面試中面試官經(jīng)常會(huì)問 equals() 方法和 == 運(yùn)算符的區(qū)別,== 運(yùn)算符用于比較基本類型的值是否相同而 equals 用于比較兩個(gè)對(duì)象是否相等,那么有個(gè)問題來了,兩個(gè)對(duì)象怎么才算是相等的呢。
看object中的equals實(shí)現(xiàn)
public boolean equals(Object obj) {
return (this == obj);
}
在Object中equals和==是等價(jià)的。所以在Object中兩個(gè)對(duì)象的引用相同,那么一定就是相同的。在我們自定義對(duì)象的時(shí)候一定要重寫equals方法。我參考了以下網(wǎng)上的資料來分析一下String中重寫的 equals方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
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) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String 是引用類型,比較時(shí)不能比較引用是否相等,重點(diǎn)是字符串的內(nèi)容是否相等。所以 String 類定義兩個(gè)對(duì)象相等的標(biāo)準(zhǔn)是字符串內(nèi)容都相同。
在Java規(guī)范中,對(duì) equals 方法的使用必須遵循以下幾個(gè)原則:
- 自反性:對(duì)于任何非空引用值 x,x.equals(x) 都應(yīng)返回 true。
- 對(duì)稱性:對(duì)于任何非空引用值 x 和 y,當(dāng)且僅當(dāng) y.equals(x) 返回 true 時(shí),x.equals(y) 才應(yīng)返回 true。
- 傳遞性:對(duì)于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 應(yīng)返回 true。
- 一致性:對(duì)于任何非空引用值 x 和 y,多次調(diào)用 x.equals(y) 始終返回 true 或始終返回 false,前提是對(duì)象上 equals 比較中所用的信息沒有被修改
- 對(duì)于任何非空引用值 x,x.equals(null) 都應(yīng)返回 false
下面定義一個(gè)類,在這個(gè)類中重寫equals方法 對(duì)象屬性相同則相等 否則不相等
public class Student {
private String name;
/**
* 無參構(gòu)造方法
*/
public Student() {
}
/**
* 無參構(gòu)造方法
*/
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
//引用相同 兩個(gè)對(duì)象肯定是相同的
if(this==obj){
return true;
}
//對(duì)象等于空 或者不是Student 是不想等的
if(obj==null || !(obj instanceof Student)){
return false;
}
//轉(zhuǎn)為Student對(duì)象
Student student = (Student)obj;
//屬性相同 返回true
return this.getName()==student.getName();
}
}
然后創(chuàng)建一個(gè)測(cè)試類來進(jìn)行測(cè)試:
Student t1 = new Student("yes");
Student t2 = new Student("slm");
System.out.println("對(duì)象不同 屬性不同 == "+(t1==t2));
System.out.println("對(duì)象不同 屬性不同 equals "+(t1.equals(t2)));
Student t3 = new Student("slm");
System.out.println("對(duì)象不同 屬性相同"+(t2.equals(t3)));
輸出結(jié)果:
對(duì)象不同 屬性不同 == false
對(duì)象不同 屬性不同 equals false
對(duì)象不同 屬性相同true
現(xiàn)在可以看出 如果在這里不重寫equals方法的話永遠(yuǎn)只會(huì)執(zhí)行Object的equals也就是通過==對(duì)比對(duì)象引用地址是否相同。
下面再看一個(gè)例子,這個(gè)時(shí)候如果出現(xiàn)一個(gè)Student的子類我們?cè)趯?duì)比一下
/**
* @Author: sunluomeng
* @CreateTime: 2019-06-06 23:35
* @Description:
*/
public class Language extends Student{
private String name;
/**
* 無參構(gòu)造
*/
public Language(){
}
/**
* 有參構(gòu)造
* @param name
*/
public Language(String name){
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
//引用相同 兩個(gè)對(duì)象肯定是相同的
if(this==obj){
return true;
}
//對(duì)象等于空 或者不是Student 是不想等的
if(obj==null || !(obj instanceof Language)){
return false;
}
//轉(zhuǎn)為Student對(duì)象
Language language = (Language)obj;
//屬性相同 返回true
return this.getName()==language.getName();
}
}
這個(gè)時(shí)候我們的新創(chuàng)建的Language類繼承Student然后創(chuàng)建兩個(gè)對(duì)象去做比較
輸出結(jié)果:
父類對(duì)比子類 屬性相同---true
子類對(duì)比父類 屬性相同---false
可以看出父類去對(duì)比子類既 student.equals(language) 結(jié)果為true 而子類去對(duì)比父類 既 language.equals(student) 返回false
這樣的話就違反了問哦們上面說到的對(duì)稱性
對(duì)于任何非空引用值 x 和 y,當(dāng)且僅當(dāng) y.equals(x) 返回 true 時(shí),x.equals(y) 才應(yīng)返回 true
如果y是Student x 是Language
那么現(xiàn)在就是 y.equals(x) 等于true 反過來x.equals(y)也應(yīng)該返回true,但是現(xiàn)在為什么會(huì)返回false呢?
先來看一下代碼
我們?cè)谂袛嗟臅r(shí)候使用了instanceof關(guān)鍵字來判斷運(yùn)行的時(shí)候是否是指定的類型
java 中的instanceof 運(yùn)算符是用來在運(yùn)行時(shí)指出對(duì)象是否是特定類的一個(gè)實(shí)例。instanceof通過返回一個(gè)布爾值來指出,這個(gè)對(duì)象是否是這個(gè)特定類或者是它的子類的一個(gè)實(shí)例。
這樣的話也就是說 Language是Student的子類 在用instanceof判斷的時(shí)候是返回true,而Language雖然是繼承Student 但是使用instanceof判斷的時(shí)候會(huì)發(fā)現(xiàn) Language和Student的類型不同 然后Student也不是Language的子類所以會(huì)返回false。
而解決的辦法就是
然后我們?cè)谶\(yùn)行一下剛剛的代碼
輸出結(jié)果:
父類對(duì)比子類 屬性相同---false
子類對(duì)比父類 屬性相同---false
完美解決,滿足對(duì)稱性
注意:使用getClass是要根據(jù)情況而定,使用getClass 不符合多態(tài)的定義
那什么時(shí)候使用instanceof,什么時(shí)候使用getClass呢?
- 如果子類能夠擁有自己的相等概念,則對(duì)稱性需求將強(qiáng)制采用 getClass 進(jìn)行檢測(cè)。
- 如果有超類決定相等的概念,那么就可以使用 instanceof 進(jìn)行檢測(cè),這樣可以在不同的子類的對(duì)象之間進(jìn)行相等的比較。
還有就是一定要注意無論何時(shí)重寫此方法,通常都必須重寫hashCode方法,以維護(hù)hashCode方法的一般約定,該方法聲明相等對(duì)象必須具有相同的哈希代碼。
4.getClass 方法
我們首先看一下getClass在Object中的實(shí)現(xiàn)。
我們看到getClass被native標(biāo)識(shí),這代表這是調(diào)用本地方法實(shí)現(xiàn)
關(guān)于native更多請(qǐng)百度。native是由操作系統(tǒng)幫我們實(shí)現(xiàn)
文檔說明的是調(diào)用getClass返回一個(gè)運(yùn)行時(shí)的類。什么意思呢 我們看下面的代碼實(shí)現(xiàn)。
打印結(jié)果:
可以看出getClass是返回一個(gè)運(yùn)行時(shí)的對(duì)象。class是返回編譯的類對(duì)象
可以看到getClass方法被final修飾,說明此方法不能被重寫。
5.hashCode
先看一下hashCode在Object中的實(shí)現(xiàn):
hashCode也是一個(gè)被native修飾的本地方法
注釋說明的是返回該對(duì)象的哈希值。那么它有什么作用呢?
主要是保證基于散列的集合,如HashSet、HashMap以及HashTable等,在插入元素時(shí)保證元素不可重復(fù),同時(shí)為了提高元素的插入刪除便利效率而設(shè)計(jì);主要是為了查找的便捷性而存在。
就比如使用Set進(jìn)行舉例子。
Set集合是不可重復(fù)的,如果每次添加數(shù)據(jù)都使用equals去做對(duì)比的話,插入十萬條數(shù)據(jù)就要對(duì)比十萬次效率是非常慢的。
所以在添加數(shù)據(jù)的時(shí)候使用了哈希表,哈希算法也稱之為散列算法,當(dāng)添加一個(gè)值的時(shí)候先算出它的哈希值根據(jù)算出的哈希值將數(shù)據(jù)插入指定位置。這樣的話就避免了一直調(diào)用equals造成的效率隱患。同時(shí)有以下條件:
- 如果位置為空則直接添加
- 如果位置不為空,判斷兩個(gè)元素是否相同如果相同則不存儲(chǔ)。
還有一種情況是兩個(gè)元素不相同,但是hashCode相同,這就是哈希碰撞。
如果發(fā)生了hash key相同的情況就在相同的元素創(chuàng)建一個(gè)鏈表。把所有相同的元素存放在鏈表中。
可以看出T1的哈希和T2相同,但是元素不同,所以現(xiàn)在會(huì)形成一個(gè)鏈來存儲(chǔ)。
6.toString
先看toString的實(shí)現(xiàn)
可以看出toString是返回的類名加16進(jìn)制無符號(hào)整數(shù)形式返回此哈希碼的字符串表示形式。
運(yùn)行輸出結(jié)果:
直接輸出對(duì)象和使用toString是一樣的
如果想要toString輸出屬性內(nèi)容則需要重寫toString方法
7.finalize
源碼中實(shí)現(xiàn)方法:
finalize用戶垃圾回收是由JVM調(diào)用。
8.registerNatives
源碼實(shí)現(xiàn):
上面說到native是調(diào)用本地實(shí)現(xiàn)方法,而registerNatives則是對(duì)本地方法注冊(cè),裝載本地庫。在Object初始化時(shí)執(zhí)行。
還有notify()/notifyAll()/wait()等寫到多線程的時(shí)候在做分析
最后
小弟不才,如有錯(cuò)誤請(qǐng)指出。喜歡請(qǐng)關(guān)注,慢慢更新JDK源碼閱讀筆記
小弟公眾號(hào),亂敲代碼。歡迎點(diǎn)贊,關(guān)注