JDK源碼學習系列——java.lang.Object類

Java源碼學習筆記–Object類

歡迎👏👏👏關注我的微信公衆號:人人都是Java程序員,公衆號不定期更新。

本源碼的版本如下:.
java version “1.8.0_201”.
Java™ SE Runtime Environment (build 1.8.0_201-b09).
Java HotSpot™ 64-Bit Server VM (build 25.201-b09, mixed mode).

方法摘要

public final native Class<?> getClass();    //返回當前對象的運行時類對象
public native int hashCode();	//返回當前對象的哈希值
public boolean equals(Object obj);	//判斷兩個對象是否相等
protected native Object clone() throws CloneNotSupportedException;	//創建並返回當前對象的一份拷貝
public String toString();	//類返回對象的字符串表示形式
public final native void notify();	//喚醒正在此對象監視器上等待的單個線程
public final native void notifyAll();	//喚醒正在此對象監視器上等待的所有線程
public final native void wait(long timeout) throws InterruptedException; 	//使當前線程進入等待狀態
public final void wait(long timeout, int nanos) throws InterruptedException;
public final void wait() throws InterruptedException;
protected void finalize() throws Throwable { };//該方法的作用是實例被垃圾回收器回收的時候觸發的操作 (最後的掙扎)

getClass()方法

public final native Class<?> getClass();

  返回當前對象的運行時類對象。類對象由static synchronized關鍵字鎖定來保證唯一。
  需要注意的是,雖然Object類中getClass() 方法聲明是:public final native Class<?> getClass();返回的是一個 Class<?>,但是如下是能通過編譯的: Class<? extends String> c = "".getClass();也就是說類型爲T的變量getClass方法的返回值類型其實是Class<? extends T>而非getClass方法聲明中的Class<?>。

hashCode()方法

public native int hashCode()

  返回當前對象的哈希值,哈希值主要用在HashMap等需要哈希值的地方。

哈希值應當滿足:

  • 在程序執行期間,只要不改變產生哈希值的字段,那麼無論對象調用多少次該方法,返回結果一定相同。如果改變了產生哈希值的字段,那麼返回結果可能不同,會造成內存泄露(因爲該對象無法找到)。
  • 通過equals調用返回true的兩個對象的哈希值一定相同。
  • 通過equasl返回false的兩個對象的哈希值並不是是完全不同的。也就是說它們有可能相同。但考慮到哈希表的性能,最好保證它們不同。

爲什麼要設計這個方法?

  我們知道在Java 中有幾種集合類,比如 List,Set,Map。List集合存放的元素是有序可重複的,Set 存放的元素則是無序不可重複的,而 Map 集合存放的是鍵值對。
  判斷一個元素是否相等可以通過 equals 方法。但每增加一個元素,我們就需要通過 equals 方法判斷集合中的每一個元素是否重複。如果集合中有一百萬個元素了,但我們新加入一個元素時,那就需要進行一百萬次equals方法的調用,這顯然效率很低。
  於是,Java 的集合設計者就採用了哈希表來實現。這樣一來,當集合要添加新的元素時,先調用這個元素的 hashCode 方法,就一下子能定位到它應該放置的物理位置上。

  • 如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較了;
  • 如果這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了;不相同的話,也就是發生了Hash key相同導致衝突的情況,那麼就在這個Hash key的地方產生一個鏈表,將所有產生相同HashCode的對象放到這個單鏈表上去,串在一起(很少出現)。這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。

equals(Object obj)方法

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

  Object類的默認實現是比較兩個對象的內存地址是否相等。

面試高頻題:==和equals 區別?

  ==運算符用於比較基本類型的值是否相同,或者比較兩個對象的引用是否相等(也就是比較兩個對象指向的內存地址是否相同)。而 equals 用於比較兩個對象是否相等(包含內存中保存兩個相等的數據,兩個對象分別指向這兩個內存地址的情形)。

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方法,通常必須重寫hashCode方法。通過重寫 equals 方法,我們自定義兩個對象相等的標尺爲對象的屬性都相等,則對象相等,否則不相等。如果不重寫 equals 方法,那麼始終是調用 Object 類的equals 方法,也就是用 == 比較兩個對象在棧內存中的引用地址是否相等。

重寫equals方法的注意點:

重寫時用 instanceof 關鍵字是做不到對稱性的要求的。什麼時候使用 instanceof 運算符,什麼時候使用 getClass() 有如下建議:

  • 如果子類能夠擁有自己的相等概念,則對稱性需求將強制採用 getClass 進行檢測。
  • 如果有超類決定相等的概念,那麼就可以使用 instanceof 進行檢測,這樣可以在不同的子類的對象之間進行相等的比較。
    代碼模板如下:
@Override
    public boolean equals(Object otherObject) {
        //1、判斷比較的兩個對象引用是否相等,如果引用相等那麼表示是同一個對象,那麼當然相等
        if(this == otherObject){
            return true;
        }
        //2、如果 otherObject 爲 null,直接返回false,表示不相等
        if(otherObject == null ){//對象爲空或者不是Person類的實例
            return false;
        }
        //3、比較 this 和 otherObject 是否是同一個類(注意下面兩個只能使用一種)
        //3.1:如果 equals 的語義在每個子類中所有改變,就使用 getClass 檢測
        if(this.getClass() != otherObject.getClass()){
            return false;
        }
        //3.2:如果所有的子類都有統一的定義,那麼使用 instanceof 檢測
        if(!(otherObject instanceof Person)){
            return false;
        }
        //4、將 otherObject 轉換成對應的類類型變量
        Person other = (Person) otherObject;
        //5、最後對對象的屬性進行比較。使用 == 比較基本類型,使用 equals 比較對象。如果都相等則返回true,否則返回false
        //   使用 Objects 工具類的 equals 方法防止比較的兩個對象有一個爲 null而報錯,因爲 null.equals() 是會拋異常的
        return Objects.equals(this.pname,other.pname) && this.page == other.page;
        //6、注意如果是在子類中定義equals,則要包含 super.equals(other)
        //return super.equals(other) && Objects.equals(this.pname,other.pname) && this.page == other.page;

    }

clone()方法

protected native Object clone() throws CloneNotSupportedException;

  創建並返回當前對象的一份淺拷貝(僅拷貝數值類型數據和引用對象地址值),使用時往往需要重寫爲public形式。需要注意的是,要求被克隆的對象所屬的類實現Cloneable接口,否則拋出CloneNotSupportedException。

淺拷貝與深拷貝

  在淺拷貝中,如果原型對象的成員變量是值類型,將複製一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址複製一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。簡單來說,在淺克隆中,當對象被複制時只複製它本身和其中包含的值類型的成員變量,而引用類型的成員對象並沒有複製。在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆。

  在深拷貝中,無論原型對象的成員變量是值類型還是引用類型,都將複製一份給克隆對象,深拷貝將原型對象的所有引用對象也複製一份給克隆對象。簡單來說,在深克隆中,除了對象本身被複制外,對象所包含的所有成員變量也將複製。

  在Java語言中,如果需要實現深拷貝,可以通過覆蓋Object類的clone()方法實現,也可以通過序列化(Serialization)等方式來實現。如果引用類型裏面還包含很多引用類型,或者內層引用類型的類裏面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現對象的深拷貝。

  序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在於內存中。通過序列化實現的拷貝不僅可以複製對象本身,而且可以複製其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流裏將其讀出來,可以實現深拷貝。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。

  Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單,它們都是空接口,這種空接口也稱爲標識接口,標識接口中沒有任何方法的定義,其作用是告訴JRE這些接口的實現類是否具有某個功能,如是否支持克隆、是否支持序列化等。

toString() 方法

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

  Object對象的默認實現是輸出類的名字@實例的哈希碼的16進制。通常,toString方法返回一個“以文本形式表示”此對象的字符串。 結果應該是簡潔易懂的表示形式,便於人們閱讀。 建議所有子類都重寫此方法。

notify()、notifyAll()方法

public final native void notify();
public final native void notifyAll();

  notify()方法喚醒正在此對象監視器上等待的單個線程。如果有任何線程正在等待這個對象,則選擇其中一個線程被喚醒。這個選擇是任意的,由JVM來決定。線程通過調用一個wait方法來等待對象的監視器。此方法應僅由此對象監視器的所有者所在的線程調用。線程通過以下三種方式之一成爲對象監視器的所有者:

  • 通過執行該對象的同步實例方法。
  • 通過執行在對象上同步的同步語句的主體。
  • 對於類對象,通過執行該類的同步靜態方法。
    如果當前線程不是該對象監視器的所有者則拋出IllegalMonitorStateException。

wait()方法

public final native void wait(long timeout) throws InterruptedException;

  此方法導致當前線程(稱爲T)將自己放入此對象的等待集中,然後放棄此對象上的任何和所有同步聲明。線程T在線程調度時被禁用,並處於休眠狀態,直到發生以下四種情況之一:

  • 其他一些線程調用此對象的notify方法,而線程T恰好被任意選擇爲要喚醒的線程。
  • 其他一些線程調用此對象的notifyAll方法。
  • 其他一些線程中斷線程T。
  • 指定的實際時間已經過了,或多或少。但是,如果超時爲零,則不考慮實時,線程只是等待,直到得到通知。

  然後從該對象的等待集中刪除線程T,並重新啓用線程調度。然後它以通常的方式與其他線程競爭對象上的同步權;一旦它獲得了對對象的控制,它對對象的所有同步聲明就會恢復到原來的狀態——也就是說,恢復到調用wait方法時的狀態。然後線程T從wait方法的調用返回。因此,從wait方法返回時,對象和線程T的同步狀態與調用wait方法時的同步狀態完全相同。
  線程也可以在沒有通知、中斷或超時的情況下喚醒,這就是所謂的僞喚醒。雖然這種情況在實踐中很少發生,但是應用程序必須通過測試導致線程被喚醒的條件來防範這種情況,如果條件不滿足,則繼續等待。換句話說,等待應該總是在循環中發生,就像這個:

synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
}

  如果設置時間小於零,會拋出IllegalArgumentException錯誤;如果當前線程沒有獲得對象監視器則拋出IllegalMonitorStateException錯誤;如果任何線程在當前線程等待通知之前或在當前線程等待通知期間中斷當前線程,則拋出InterruptedException 。當拋出此異常時,當前線程的中斷狀態將被清除。
  以下兩個方法和上面大致相同,就不做過多介紹了:

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

  跟之前的兩個wait方法一樣,只不過該方法一直等待,沒有超時時間這個概念。wait方法和notify方法會一起使用的,wait方法阻塞當前線程,notify方法喚醒當前線程

 public final void wait() throws InterruptedException {
        wait(0);
    }

finalize()方法

protected void finalize() throws Throwable { }

  類對象的finalize方法不執行任何特殊操作;它只是正常返回。對象的子類可以覆蓋此定義。子類重寫finalize方法以處置系統資源或執行其他清理。
  Java編程語言並不保證哪個線程將爲任何給定對象調用finalize方法。但是,可以保證調用finalize的線程在調用finalize時不會持有任何用戶可見的同步鎖。如果finalize方法拋出未捕獲的異常,則忽略該異常並終止該對象的終結。
  對象的finalize方法被調用後,沒有采取進一步的行動(進一步的行動指的是將某個引用指向該對象),直到Java虛擬機再次確定,不再有任何方法可以訪問這個對象的線程還沒有死,包括其他對象或類的可能的行動準備完成,此時對象可能被丟棄。
  對於任何給定對象,Java虛擬機都不會多次調用finalize方法,只有一次機會。
  finalize方法拋出的任何異常都會導致此對象的終止,但在其他情況下會被忽略。
  finalizer 方法的調用時機由 sun 和 JVM 開發商共同決定:確定不再有任何未死亡的線程通過任何方法調用來訪問或使用該對象時。(即確定對象的任何方法都不(再)會被調用時,調用其 finalize 方法)

整體代碼結構

/*
 * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.lang;

/**
 *類Object是等級最高的類,是所有類的父類。包括數組在內的所有對象都繼承了該類。
 */
public class Object {

    private static native void registerNatives();
    static {
        registerNatives();
    }
    
    public final native Class<?> getClass();
    
    public native int hashCode();
    
    public boolean equals(Object obj) {
        return (this == obj);
    }
    
    protected native Object clone() throws CloneNotSupportedException;
    
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    public final native void notify();
    
    public final native void notifyAll();

    public final native void wait(long timeout) throws InterruptedException;

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

    public final void wait() throws InterruptedException {
        wait(0);
    }

    protected void finalize() throws Throwable { }
}

參考文檔

1、JDK1.8源碼(一)——java.lang.Object類
2、Class Object
3、java.lang.Object學習
4、Java提高篇——對象克隆(複製)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章