碼迷,mamicode.com
首頁 > 其他好文 > 詳細

JDK源碼閱讀(一):Object源碼分析

時間:2019-06-18 15:56:43      閱讀:22      評論:0      收藏:0      [點我收藏+]

標簽:obj   定位   操作   notify   rri   注釋   hashset   等價   閱讀   

最近經過某大佬的建議準備閱讀一下JDK的源碼來提升一下自己

所以開始寫JDK源碼分析的文章

閱讀JDK版本為1.8


  • 目錄
    • Object結構圖
    • 構造器
    • equals 方法
    • getClass 方法
    • hashCode 方法
    • toString 方法
    • finalize 方法
    • registerNatives 方法

1. Object結構圖

技術圖片

2. 類構造器

??類構造器是創建Java對象的方法之一。一般我們都使用new關鍵字來進行實例,還可以在構造器中進行相應的初始化操作。

??在一個Java類中必須存在一個構造器,如果沒有添加系統在編譯時會默認創建一個無參構造。

/*實例一個Object對象*/
Object obj = new Object()

3. equals 方法

??在面試中面試官經常會問 equals() 方法和 == 運算符的區別,== 運算符用于比較基本類型的值是否相同而 equals 用于比較兩個對象是否相等,那么有個問題來了,兩個對象怎么才算是相等的呢。
看object中的equals實現

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

在Object中equals和==是等價的。所以在Object中兩個對象的引用相同,那么一定就是相同的。在我們自定義對象的時候一定要重寫equals方法。我參考了以下網上的資料來分析一下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 是引用類型,比較時不能比較引用是否相等,重點是字符串的內容是否相等。所以 String 類定義兩個對象相等的標準是字符串內容都相同。

在Java規范中,對 equals 方法的使用必須遵循以下幾個原則:

  • 自反性:對于任何非空引用值 x,x.equals(x) 都應返回 true。
  • 對稱性:對于任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true。
  • 傳遞性:對于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 應返回 true。
  • 一致性:對于任何非空引用值 x 和 y,多次調用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改
  • 對于任何非空引用值 x,x.equals(null) 都應返回 false



下面定義一個類,在這個類中重寫equals方法 對象屬性相同則相等 否則不相等

public class Student {
    private String name;

    /**
     * 無參構造方法
     */
    public Student() {

    }

    /**
     * 無參構造方法
     */
    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) {
        //引用相同 兩個對象肯定是相同的
        if(this==obj){
            return true;
        }
        //對象等于空 或者不是Student 是不想等的
        if(obj==null || !(obj instanceof Student)){
            return false;
        }
        //轉為Student對象
        Student student = (Student)obj;
        //屬性相同 返回true
        return this.getName()==student.getName();
    }

}

然后創建一個測試類來進行測試:

        Student t1 = new Student("yes");
        Student t2 = new Student("slm");
        System.out.println("對象不同 屬性不同 ==  "+(t1==t2));
        System.out.println("對象不同 屬性不同 equals  "+(t1.equals(t2)));
        Student t3 = new Student("slm");
        System.out.println("對象不同 屬性相同"+(t2.equals(t3)));

輸出結果:

對象不同 屬性不同 == false

對象不同 屬性不同 equals false

對象不同 屬性相同true

現在可以看出 如果在這里不重寫equals方法的話永遠只會執行Object的equals也就是通過==對比對象引用地址是否相同。


下面再看一個例子,這個時候如果出現一個Student的子類我們在對比一下

/**
 * @Author: sunluomeng
 * @CreateTime: 2019-06-06 23:35
 * @Description:
 */
public class Language  extends Student{
    private String name;

    /**
     * 無參構造
     */
    public Language(){

    }

    /**
     * 有參構造
     * @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) {
        //引用相同 兩個對象肯定是相同的
        if(this==obj){
            return true;
        }
        //對象等于空 或者不是Student 是不想等的
        if(obj==null || !(obj instanceof Language)){
            return false;
        }
        //轉為Student對象
        Language language = (Language)obj;
        //屬性相同 返回true
        return this.getName()==language.getName();
    }

}

這個時候我們的新創建的Language類繼承Student然后創建兩個對象去做比較

技術圖片
輸出結果:

父類對比子類 屬性相同---true

子類對比父類 屬性相同---false

可以看出父類去對比子類既 student.equals(language) 結果為true 而子類去對比父類 既 language.equals(student) 返回false

這樣的話就違反了問哦們上面說到的對稱性

對于任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true

如果y是Student x 是Language
那么現在就是 y.equals(x) 等于true 反過來x.equals(y)也應該返回true,但是現在為什么會返回false呢?

先來看一下代碼

技術圖片
我們在判斷的時候使用了instanceof關鍵字來判斷運行的時候是否是指定的類型

java 中的instanceof 運算符是用來在運行時指出對象是否是特定類的一個實例。instanceof通過返回一個布爾值來指出,這個對象是否是這個特定類或者是它的子類的一個實例。

這樣的話也就是說 Language是Student的子類 在用instanceof判斷的時候是返回true,而Language雖然是繼承Student 但是使用instanceof判斷的時候會發現 Language和Student的類型不同 然后Student也不是Language的子類所以會返回false。


而解決的辦法就是

技術圖片

然后我們在運行一下剛剛的代碼

輸出結果:

父類對比子類 屬性相同---false

子類對比父類 屬性相同---false

完美解決,滿足對稱性

注意:使用getClass是要根據情況而定,使用getClass 不符合多態的定義

那什么時候使用instanceof,什么時候使用getClass呢?

  • 如果子類能夠擁有自己的相等概念,則對稱性需求將強制采用 getClass 進行檢測。
  • 如果有超類決定相等的概念,那么就可以使用 instanceof 進行檢測,這樣可以在不同的子類的對象之間進行相等的比較。

還有就是一定要注意無論何時重寫此方法,通常都必須重寫hashCode方法,以維護hashCode方法的一般約定,該方法聲明相等對象必須具有相同的哈希代碼。

4.getClass 方法

我們首先看一下getClass在Object中的實現。

技術圖片
我們看到getClass被native標識,這代表這是調用本地方法實現

關于native更多請百度。native是由操作系統幫我們實現

文檔說明的是調用getClass返回一個運行時的類。什么意思呢 我們看下面的代碼實現。

技術圖片
打印結果:

技術圖片

可以看出getClass是返回一個運行時的對象。class是返回編譯的類對象

可以看到getClass方法被final修飾,說明此方法不能被重寫。

5.hashCode

先看一下hashCode在Object中的實現:

技術圖片

hashCode也是一個被native修飾的本地方法

注釋說明的是返回該對象的哈希值。那么它有什么作用呢?

主要是保證基于散列的集合,如HashSet、HashMap以及HashTable等,在插入元素時保證元素不可重復,同時為了提高元素的插入刪除便利效率而設計;主要是為了查找的便捷性而存在。

就比如使用Set進行舉例子。

Set集合是不可重復的,如果每次添加數據都使用equals去做對比的話,插入十萬條數據就要對比十萬次效率是非常慢的。


所以在添加數據的時候使用了哈希表,哈希算法也稱之為散列算法,當添加一個值的時候先算出它的哈希值根據算出的哈希值將數據插入指定位置。這樣的話就避免了一直調用equals造成的效率隱患。同時有以下條件:

  • 如果位置為空則直接添加
  • 如果位置不為空,判斷兩個元素是否相同如果相同則不存儲。

還有一種情況是兩個元素不相同,但是hashCode相同,這就是哈希碰撞。

如果發生了hash key相同的情況就在相同的元素創建一個鏈表。把所有相同的元素存放在鏈表中。

技術圖片
可以看出T1的哈希和T2相同,但是元素不同,所以現在會形成一個鏈來存儲。

6.toString

先看toString的實現

技術圖片
可以看出toString是返回的類名加16進制無符號整數形式返回此哈希碼的字符串表示形式。

運行輸出結果:

技術圖片
直接輸出對象和使用toString是一樣的

技術圖片
如果想要toString輸出屬性內容則需要重寫toString方法

7.finalize

源碼中實現方法:

技術圖片
finalize用戶垃圾回收是由JVM調用。

8.registerNatives

源碼實現:

技術圖片
上面說到native是調用本地實現方法,而registerNatives則是對本地方法注冊,裝載本地庫。在Object初始化時執行。

還有notify()/notifyAll()/wait()等寫到多線程的時候在做分析

最后

小弟不才,如有錯誤請指出。喜歡請關注,慢慢更新JDK源碼閱讀筆記

** 小弟公眾號,亂敲代碼。歡迎點贊,關注**
技術圖片
---

JDK源碼閱讀(一):Object源碼分析

標簽:obj   定位   操作   notify   rri   注釋   hashset   等價   閱讀   

原文地址:https://www.cnblogs.com/Scramblecode/p/11045443.html

(0)
(0)
   
舉報
評論 一句話評論(0
0條  
登錄后才能評論!
? 2014 mamicode.com 版權所有 京ICP備13008772號-2
迷上了代碼!
25选5历史开奖结果百度