1. 程式人生 > >Java原始碼解析(1) —— Object

Java原始碼解析(1) —— Object

Java基類Object

  java.lang.Object,Java所有類的父類,在你編寫一個類的時候,若無指定父類(沒有顯式extends一個父類)編譯器(一般編譯器完成該步驟)會預設的新增Object為該類的父類(可以將該類反編譯看其位元組碼,不過貌似Java7自帶的反編譯javap現在看不到了)。
  再說的詳細點:假如類A,沒有顯式繼承其他類,編譯器會預設新增Object為其父類;若有,那麼那個顯式父類呢?要麼是沒有顯式繼承,那麼Object是這個父類的父類,那肯定也是類A的父類,如果有,以此類推,所以,Object是Java所有類的祖先類(父類)。

宣告

  1.本系列是JDK1.7(oracle)的原始碼分析,若和你檢視到的原始碼有差異,請對比JDK版本。
  2.本系列是本人對Java原始碼的解析,但由於本人水平有限,勢必不能做到完全解讀,甚至只能說通過搜尋閱讀學習,做一些表面的解析,有不足之處,望指教,望諒解。

Object原始碼

public class Object {
    //本地方法,C/C++在DLL中實現,通過JNI呼叫
    private static native void registerNatives();
    //類初始化呼叫此方法
    static {
        registerNatives();
    }
    //返回此Object的執行時類(每個類的Class類物件)
    public final native Class<?> getClass();
    //獲得該物件的hash值
    public native int hashCode
(); //對比兩物件的記憶體地址,如果不重寫,equals方法比較的是物件地址 public boolean equals(Object obj) { return (this == obj); } //本地clone方法,用於物件的賦值 protected native Object clone() throws CloneNotSupportedException; //返回物件的的字串表示,預設是:類名[email protected]+hash值 public String toString() { return
getClass().getName() + "@" + Integer.toHexString(hashCode()); //notify()/notifyAll()/wait()以及wait兩個過載方法都是執行緒同步相關方法 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 >= 500000 || (nanos != 0 && timeout == 0)) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); } //物件被回收時呼叫,不管如何,一個物件只調用一次 protected void finalize() throws Throwable { }

概述

  因為Object是Java所有類的祖先類,所以Java所有類都有Object中的方法,在看這些方法的時候要聯絡這些方法不是針對Objec一個類,而是所有類。
  既然是所有類共有,設計的時候肯定想的是所有類的共性,比如:equals方法就是用來比較任意兩個相同型別物件是否相等的,toString是用來將任意物件轉換成String,方便列印檢視。當然,以上方法的實現都是預設的,想要實現自己的邏輯需要在自己類中覆蓋重寫。
  以上的native方法,在Oracle的jdk是看不到的,但在OpenJDK或其他開源JDK是可以找到對應的C/C++程式碼的。

原始碼詳解

1.構造方法
  原始碼中並沒有Object的構造方法,但是,同樣的,編譯器在編譯期間會給Object(事實上,所有的Java類,只要類中沒有構造方法,編譯器都會預設的給一個空構造方法,若已有構造方法,則不會新增)一個預設的空的構造方法:

public Object(){}

2.registerNatives
  帶有native修飾的都是本地方法,所謂的本地方法是不通過Java語言實現的方法,但可以通過JNI,像呼叫Java方法一樣呼叫這些方法。詳細的可以搜尋檢視JNI。
  這個方法的作用是對Object以下幾個本地方法(hashCode/clone/notify等)進行註冊(可以理解為,這個方法是告訴JVM這幾個本地方法的實現對映),每一個有本地方法的都會有這個方法,但其內容不一樣(因為註冊的方法不一樣嘛)。
3.getClass
  每一個類在被載入的時候,都會生成一個Class類例項,而這個方法就可以在執行時期獲得物件(這裡的物件是堆裡的那個物件,也就是獲得的是動態型別的那個類)的Class物件,Class物件主要用於反射。

class A{}
class B extends A{}
class C extends B{}
A a = new C();//物件new C()的靜態型別是A,動態型別是C
B b = (B)a;//引用b指向的還是new C(),動態型別還是C
C c = (C)b;
System.out.println(a.getClass().getName());
System.out.println(b.getClass().getName());
System.out.println(c.getClass().getName());
//列印結果均是:com.xxx.test.C
//物件的動態型別是不會變的,即new後面那個型別(構造物件的那個型別),但是靜態類
//型是由指向它的引用決定的,事實上可以這樣理解物件只有動態型別,引用型別才是靜態型別
//以上說的物件指的是堆裡物件,而不是泛指Object o = new Object()中的o
//不明白靜態型別,動態型別的可以自行百度

4.hashCode
  獲得該物件的hash值,Java虛擬機器規範並沒有規定這個方法的具體實現,只是規定了同一個物件兩次呼叫(任何條件情形下)這個方法返回的int值要想等(但並沒有規定兩個不同物件hash值一定不相同),具體實現由各個JVM廠商自己實現,所以返回的值意義並不一定(這裡特指Object的hashCode方法),有可能返回的是物件的記憶體地址,也有可能是某個特定計算公式計算出來的值。
5.equals
  原則上或則說語義上,設計目的上,equals的作用意義,是用來比較兩個物件是否相等,這裡是我們通常理解的相等:即兩個物件其內容是否相等,而不是程式上來看,兩個物件是否是同一個物件,即比較其記憶體地址;如果想比較兩個物件是否是同一個物件(這裡是說兩個引用是否指向同一個物件),直接用==比較即可(==比較的就是物件的記憶體地址)。但這裡重要的是,對於Object來說,它並不能知道子類是如何判斷他們的兩個例項是如何equals的,所以,預設的equals實現,比較的是兩物件記憶體地址,即,若子類不重寫equals方法,其作用等同於==。

//如何重寫equals方法實現判斷內容相等?
//關鍵點取決於你的邏輯,你想讓兩個物件在什麼時候相等,你邏輯上就怎麼寫
class A {
    public int a;
    public String b;
    public D d;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;//如果指向同一個物件,當然equals
        //如果o為null或兩個物件型別都不相同,當然不equals
        if (o == null || getClass() != o.getClass()) return false;
        //動態型別相同,強制轉換
        A a1 = (A) o;
        /*下面是自己的邏輯,判斷兩個物件是否相同,邏輯1 Begin*/
        if (a != a1.a) return false;
        if (b != null ? !b.equals(a1.b) : a1.b != null) return false;
        return d != null ? d.equals(a1.d) : a1.d == null;
        //全部欄位相同,則equals。如果物件越複雜,想要實現全部欄位相同,也就越複雜
        /* 邏輯1 End */
        /* 邏輯2 begin*/
        //只要欄位a相同,就認為兩個物件equals
        if(a == a1.a) return true;
        /* 邏輯2 end*/
    }
    @Override
    public int hashCode() {
        int result = a;
        result = 31 * result + (b != null ? b.hashCode() : 0);
        result = 31 * result + (d != null ? d.hashCode() : 0);
        return result;
    }
}
class D{
    public int a;
}

  網上說的,重寫equals方法,必重寫hashCode,其實不然,若確定所有地方都沒有用到類似Map的地方,就不必重寫hashCode,因為Map的諸多方法是有用到hashCode方法判斷兩物件是否相等,而若你僅僅是自己用來判斷兩個物件是否equals,也就不必重寫hashCode(當然,還要確定其他地方不會用到hashCode的地方,比如,以後用,別人用等,不過一般的,推薦重寫hashCode方法,這樣保證任何地方都不會因此出錯)。
  若hash值不相等,則兩個物件肯定不等(不equals);
  若hash值相等,兩個物件不一定相等(不一定equals)。
  equals相等,hash值肯定想等,也就是說,hash值相等時equals相等的必要條件。
  hashCode方法一般用來判斷兩個物件equals前置條件,用來排除,這樣做的原因是,hashCode方法速度快,不相等的可快速否決掉,若hash相同,則再呼叫equals判斷。
6.clone
  克隆物件,克隆一個與原先物件所有欄位值相等的物件,從而獲得一個新的物件,需要注意的是:

  1. 想要使用這個方法,物件型別必須實現Cloneable介面,否則會報錯,原因是Object的clone方法有對物件型別驗證,如沒實現則報錯拋異常;
  2. clone方法返回的是一個新的物件,這個物件的建立不是通過new(除非你像下面那樣不通過Object的clone方法重寫)指令,而是JVM通過其他指令建立的;
  3. clone有深度clone和淺clone,這主要是針對類中間具有引用型別而言劃分的,詳情可參看:Java clone深度解析。
class A{}
A a = new A();
a.clone();//報錯,即拋CloneNotSupportedException異常
class A implements Cloneable{}//這樣才不會
//但,若你重寫clone方法,並且在這個方法中沒有呼叫父clone(也就是Object)方法
class A{
    @Override
    public Object clone() throws CloneNotSupportedException{
        return new A();
    }
}
a.clone();//這個時候呼叫clone方法即使沒有實現Cloneable方法也不會報錯
//說白了,你要理解為什麼呼叫clone方法要實現Cloneable的原因,而不是僅僅是記住
//當你理解了,你就能熟練掌握這些規則,而不是記住他們

7.toString
  toString這個方法算是Object比較常用的方法了,它的意義是提供將類的欄位以String形式格式化輸出這一功能,當然,同樣的,Object不可能知道子類的欄位資訊,所以,預設toString輸出的是:全路徑類名[email protected]+hash值。
  若你想要輸出類的欄位資訊,需要重寫toString方法,將該類欄位資訊以你自己的格式輸出。
8.notify/notifyAll/wait
  這三個方法適用於執行緒同步,這裡只簡單介紹其作用,詳細請參考:notify/notifyAll/wait。
  notify:隨機喚醒等待(wait)佇列中一個物件,使其需要該物件的執行緒繼續執行;
  notifyAll:喚醒佇列中所有物件
  wait:該物件陷入等待狀態,需要該物件的執行緒將不能再繼續執行,直到該物件由其他執行緒呼叫notify/notifyAll方法喚醒。
9.finalize
  在物件被GC(垃圾回收,詳情可參考:Java GC概述)之前被呼叫(JVM主動呼叫),你可以重寫這個方法,然後在這個物件回收之前做某些動作,這個方法對於這個物件來說只能呼叫一次,為什麼會這麼說呢?物件都回收了,沒了,難道不是當然只能呼叫一次?不是這樣的,若你理解了Java GC原理便知道,若當你在finalize方法中,將這個物件重新賦予了強引用,GC這個物件將失敗,這個物件將繼續存活,而下次這個物件又成為可回收物件了,GC回收這個物件的時候,這個物件的finalize方法將不會再執行。
  另外,需要區分的是:
  finalize不是C/C++中的解構函式,更不是釋放記憶體的方法,它只是提供了在回收一個物件之前做某些操作,如果你熟悉C ,那你知道C 允許你為一個類定義一個撤消函式(destructor ),它在物件正好出作用域之前被呼叫。Java不支援這個想法也不提供撤消函式。finalize() 方法只和撤消函式的功能接近。當你對Java 有豐富經驗時,你將看到因為Java使用垃圾回收子系統,幾乎沒有必要使用撤消函式。
  而且,在設計之初,這個方法就是為了相容C/C++程式設計師習慣(對的,貌似就是這樣),後來設計者也說,這是個失敗的設計,所以,可以的話,在實踐中忘掉這個方法吧。

class D{
    public static D d111;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        d111 = this;//這個時候該物件第一次回收將失敗,而以後將不會在執行該方法
        System.out.println("finalize a = " + this.a);
    }
}
D d = new D();
d = null;
//程式結束
//這個時候,雖然程式結束了,new D()物件也是可回收物件了,但是並不會執行
//finzlize,因為對於JVM來說GC的觸發條件是記憶體不足,所以不會執行GC也就不會呼叫
//finzlize方法

相關推薦

Java原始碼解析(1) —— Object

Java基類Object   java.lang.Object,Java所有類的父類,在你編寫一個類的時候,若無指定父類(沒有顯式extends一個父類)編譯器(一般編譯器完成該步驟)會預設的新增Object為該類的父類(可以將該類反編譯看其位元組碼,不過貌似

JDK核心JAVA原始碼解析1

想寫這個系列很久了,對自己也是個總結與提高。原來在學JAVA時,那些JAVA入門書籍會告訴你一些規律還有法則,但是用的時候我們一般很難想起來,因為我們用的少並且不知道為什麼。知其所以然方能印象深刻並學以致用。 首先我們從所有類的父類Object開始: 1

Java原始碼解析(2) —— Class(1)

Class —— 反射基石   Java基本類之一,反射機制的基礎。其意義為:類的抽象,即對“類”做描述:比如類有修飾、欄位、方法等屬性,有獲得該類的所有方法、所有公有方法等方法。同時,Class也是Java型別中最重要的一種,表示原始型別(引用型別)及基本型

BItCoin原始碼解析(1)——Base58編碼

看了https://blog.csdn.net/pure_lady/article/category/5858993/2好久,決定寫下開篇。 比特幣加密演算法一共有兩類:非對稱加密演算法(橢圓曲線加密演算法)和雜湊演算法(SHA256,RIMPED160演算法)。比特幣私鑰(private ke

11 java原始碼解析-Thread(草稿)

1類的宣告 public class Thread implements Runnable 實現了Runnable介面 在程式開發中只要是多執行緒肯定永遠以實現Runnable介面為主。 1.1Runnable 說明 public interface Run

Java原始碼解析系列(二)ArrayList原始碼解析

備註:以下都是基於JDK8 原始碼分析 ArrayList簡介        ArrayList 是一個數組佇列,相當於 動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Clonea

Java原始碼解析ArrayList

本文基於jdk1.8來分析ArrayList的原始碼 首先是主要的成員變數。 /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /

java原始碼解析--Map

Map集合 An object that maps keys to values. A map cannot contain duplicate keys; each key can map to

java原始碼解析S

Set A collection that contains no duplicate elements. More formally, sets contain no pair of elemen

Java原始碼解析CopyOnWriteArrayList

本文基於jdk1.8進行分析。 ArrayList和HashMap是我們經常使用的集合,它們不是執行緒安全的。我們一般都知道HashMap的執行緒安全版本為ConcurrentHashMap,那麼ArrayList有沒有類似的執行緒安全的版本呢?還真有,它就是CopyOnWriteArrayLi

Java原始碼解析阻塞佇列ArrayBlockingQueue功能簡介

本文基於jdk1.8進行分析。 阻塞佇列是java開發時常用的一個數據結構。首先看一下阻塞佇列的作用是什麼。阻塞佇列的作用,從原始碼中類的註釋中來了解,是最清晰準確的。如下圖。 ArrayBlockingQueue是一個用陣列實現的有界阻塞佇列。提供FIFO的功能。佇列頭上的元素是在佇列中呆

Java原始碼解析之可重入鎖ReentrantLock(二)

上文接Java原始碼解析之可重入鎖ReentrantLock(一)。 接下來是tryLock方法。程式碼如下。從註釋中我們可以理解到,只有當呼叫tryLock時鎖沒有被別的執行緒佔用,tryLock才會獲取鎖。如果鎖沒有被另一個執行緒佔用,那麼就獲取鎖,並立刻返回true,並把鎖計數設定為1.

Java原始碼解析之可重入鎖ReentrantLock(一)

本文基於jdk1.8進行分析。 ReentrantLock是一個可重入鎖,在ConcurrentHashMap中使用了ReentrantLock。 首先看一下原始碼中對ReentrantLock的介紹。如下圖。ReentrantLock是一個可重入的排他鎖,它和synchronized的方法

Java原始碼解析LinkedList

本文基於jdk1.8進行分析。 LinkedList和ArrayList都是常用的java集合。ArrayList是陣列,Linkedlist是連結串列,是雙向連結串列。它的節點的資料結構如下。 private static class Node<E> {

Google guava cache原始碼解析1--構建快取器(2)

此文已由作者趙計剛授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 CacheBuilder-->maximumSize(long size)     /**      * 

Google guava cache原始碼解析1--構建快取器(3)

此文已由作者趙計剛授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 下面介紹在LocalCache(CacheBuilder, CacheLoader)中呼叫的一些方法: CacheBuilder-->getConcurrencyLevel()

Java原始碼解析HashMap的tableSizeFor函式

aka,HashMap的容量大小必須為2的指數,即16,32,64,128這樣的值。那麼,在建構函式中,如果呼叫者指定了HashMap的初始大小不是2的指數,那麼,HashMap的tableSizeFor函式,會計算一個大於或等於給定引數的2的指數的值。先來看一下tableSizeFor函式的原始碼

Java原始碼解析HashMap成員變數

本文基於jdk1.8進行分析。 關於HashMap的簡介,可以參考這篇文章https://blog.csdn.net/li_canhui/article/details/85076521。 首先看一下HashMap的一些靜態常量。第一個是DEFAULT_INITIAL_CAPACITY,預設

Java原始碼解析HashMap簡介

本文基於jdk1.8進行分析。 HashMap是java開發中可以說必然會用到的一個集合。本文就HashMap的原始碼實現進行分析。 首先看一下原始碼中類的javadoc註釋對HashMap的解釋。如下圖。HashMap是對Map介面的基於hash表的實現。這個實現提供了map的所有可選操作

Tensorflow原始碼解析1 -- 核心架構和原始碼結構

1 主流深度學習框架對比 當今的軟體開發基本都是分層化和模組化的,應用層開發會基於框架層。比如開發Linux Driver會基於Linux kernel,開發Android app會基於Android Framework。深度學習也不例外,框架層為上層模型開發提