原:https://juejin.im/post/5ca1e8ade51d454e6a300048
前言
學過Java
的人都知道,Object
是所有類的父類。但是你有沒有這樣的疑問,我並沒有寫extends Object
,它是怎么默認繼承Object的呢?
那么今天我們就來看看像Java這種依賴於虛擬機的編程語言是怎樣實現默認繼承Object的,以及Java編譯器
和JVM
到底是如何做的?
繼承自Object驗證
首先我們來驗證一下Object是不是所有類的父類,隨便新建一個Java類,如下圖:
從上面的代碼可以看出,new MyClass()打點之后可以選擇調用的方法有很多,我們定義的MyClass類里面只有一個main方法,那這些方法哪來的,顯然是Object里聲明的,故MyClass類的父類就是Object,因此,在MyClass中可以使用Object類的public或protected資源。
另外,當A類繼承MyClass類時,通過打點也可以調到Object內的方法,這是繼承的傳遞,好比Object是MyClass的“父親”,MyClass是A類的“父親”,Object是A類的“爺爺”,間接的繼承了Object。
因此,Object是超類,是所有類的父類。
推測可能的原因
要了解Java類是如何默認繼承Object的?
的原因其實並不需要知道JVM的實現細節。只需了解一下對於這種虛擬機程序的基本原理即可。一般對於這種靠虛擬機運行的語言(如Java、C#等)會有兩種方法處理默認繼承問題。
編譯器處理
在編譯源代碼時,當一個類沒有顯式標明繼承的父類時,編譯器會為其指定一個默認的父類(一般為Object),而交給虛擬機處理這個類時,由於這個類已經有一個默認的父類了,因此,VM仍然會按照常規的方法像處理其他類一樣來處理這個類。對於這種情況,從編譯后的二進制角度來看,所有的類都會有一個父類(后面可以以此依據來驗證)。
JVM處理
編譯器仍然按照實際代碼進行編譯,並不會做額外的處理,即如果一個類沒有顯式地繼承於其他類時,編譯后的代碼仍然沒有父類。然后由虛擬機運行二進制代碼時,當遇到沒有父類的類時,就會自動將這個類看成是Object類的子類(一般這類語言的默認父類都是Object)。
驗證結論
從上面兩種情況可以看出,第1種情況是在編譯器上做的文章,也就是說,當沒有父類時,由編譯器在編譯時自動為其指定一個父類。第2種情況是在虛擬機上做文章,也就是這個默認的父類是由虛擬機來添加的。
那么Java是屬於哪一種情況呢?其實這個答案很好得出。只需要隨便找一個反編譯工具,將.class文件進行反編譯即可得知編譯器是如何編譯的。
就以上面代碼為例,如果是第1種情況,就算MyClass沒有父類,但由於編譯器已經為MyClass自動添加了一個Object父類,所以,在反編譯后得到的源代碼中的MyClass類將會繼承Object類的。如果不是這種情況,那么就是第2種情況。
那么實際情況是什么樣的呢?現在我們就將MyClass.class反編譯看看到底如何。
jd-gui反編:
使用JDK自帶的工具(javap)反編譯
CMD命令行下執行:javap MyClass>MyClass.txt
可以看出實際的反編譯后的文件中並沒有extends Object
,使用排除法,因此是第2情況。
這樣來推導出的結論是第2種情況,但事實真的如此嗎?為什么網上還有說反編譯后的是有extends Object
字樣?
JDK版本問題?
猜想是JDK版本的問題,於是把JDK版本切換到7,使用jd-gui和javap反編譯,接果和使用JDK8反編譯后的結果一樣,也都沒有extends Object
。
繼續換版本,昨晚在宿舍准備到Oracle官網下載JDK 6,但是死活下不來,今早到公司后第一件事就是下載,很順利,安裝后把JDK版本切換到JDK 6。
仍然在CMD窗口執行javap MyClass>MyClass.txt
,得到的TXT文件內容如下:
what?竟然有extends Object
,jd-gui反編譯后的依然沒有。
即,JDK 6之前使用javap反編譯后的MyClass類顯式的繼承Object,JDK 7以后沒有;jd-gui反編譯后的不管JDK版本如何始終沒有。我們以java自帶的工具為准。
總結
那么就是說JDK 6之前是編譯器
處理,JDK 7之后是虛擬機
處理。
但是仔細想想我們在編輯器
里(IDE)打點時就能列出Object類下的方法,此時還沒輪到編譯器和jvm,編輯器就已經知道MyClass類的父類是Object類了,這是因為編輯器為我們做了一些智能處理。
【end】
注意區分編輯器和編譯器