1. 程式人生 > >介面和抽象類是否繼承了Object

介面和抽象類是否繼承了Object

我們先看一下Java的幫助文件對於Object的描述:

Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class.

Object 類是類層次結構的根類。每個類都使用 Object 作為超類。所有物件(包括陣列)都實現這個類的方法。

  注意:描述是Every class(所有的類)。有這句話可以猜想一下,抽象類是繼承了Object。

  對於繼承,我們知道C++語言支援多繼承,Java語言只支援單繼承。那麼Java語言為什麼不支援多繼承呢?我們先看一看多繼承中最典型的鑽石問題(菱型缺陷),如下圖(圖片來源於https://www.cnblogs.com/sddai/p/6516668.html):

  其中A、B、C、D是四個類,B繼承A,C也繼承A,D又同時繼承了B和C。如果B和C都有test方法,看如下程式碼

D d = new D();
d.test();
  第一句中當new D(); 的時候會不會呼叫兩次A的建構函式?

  第二句中呼叫的是B裡面的test方法還是C裡面的test方法?

  為了避免以上的問題,Java採用了折衷的方法,只允許單繼承,但可以實現多個介面。所以我們可以以java語言是單繼承這個前提,來推導一下介面和抽象類是否繼承Object。如下:

  對於抽象類而言:一個普通類肯定是繼承了Object,如果一個抽象類再繼承這個普通類,這個時候抽象類肯定也是繼承了Object的。而對於沒有繼承任何類的抽象類而言,如果它沒有繼承Object,那麼當一個普通類繼承這個抽象類的時候,這個普通類也肯定沒有繼承Object,悖論。所以抽象類肯定是繼承了Object。

  對於介面而言呢:如果介面繼承了Object類。那麼當一個類實現多個介面的時候,那不就相當於繼承了多遍Object?又變成了多繼承?這個問題先放一放。

  到目前為止,以上的言論還都處於猜想階段,現在我們就來深入一點,找一下確鑿的“證據”。我們都知道Java原始檔會先編譯成class檔案,然後再被jvm執行。那麼如果我們能夠知道父類在class檔案中是怎麼儲存的,然後看一下介面編譯成的class檔案,不就知道介面是否繼承Object了嗎?以下內容涉及位元組碼,來源於《深入理解Java虛擬機器》第二版的6.3節(核心是6.3.4節)。

  Java檔案編譯而成的class檔案是二進位制檔案,沒有任何分隔符,所以無論是順序還是數量都是被嚴格規定的。

  class檔案開始的4個位元組是 CAFEBABE,表示這是一個能被虛擬機器接受的class檔案;緊跟著4個位元組表示class檔案的版本號;緊接著後面是常量池,前兩個位元組是常量中的常量數量,後面是常量池的內容;常量池後面的2個位元組代表訪問標誌,比如是否public、介面、註解、列舉等;緊接著2個位元組代表類的索引;類索引後面兩個位元組代表父類索引;父類索引後面是介面索引集合,前兩個位元組代表集合的大小,後面跟具體的介面索引。如下圖所示:

注:

  1. 由於常量池中常量的數量是用兩個位元組儲存的,也就是說單個class檔案中的常量池中常量的個數不會超過2個位元組。

  2. “索引” 是指在常量池中的第幾項常量(從1開始),佔兩個位元組(和常量池中的常量數量佔用空間一樣)。比如類索引為5,表示類的全類名在常量池中的第5個常量處。

  3. 父類索引只使用了兩個位元組,這也說明了在class檔案中父類最多存在一個(除了Object類的父類索引為0外,其他都有值)。

  可見,我們只需找出常量池的結尾,即可找出父類索引,從而確定一個類的父類是誰?jdk中有一個javap的命令(javap -v xxx)。可以檢視一個類的常量池,從而檢視常量池中最後一個常量的值,然後再根據class檔案找出對應的值,即可確定常量池的末尾。

例:TestJ1.java 如下:

public class TestJ1 {
}
  使用UltraEdit開啟TestJ1.class檔案,使用命令列輸入命令:“javap -v TestJ1”。如下圖所示:

  由圖中可知常量池最後一個常量為”java/lang/Object” (Constant pool 為常量池),在class檔案中對應的位置為0x0069~0X0078。所以訪問標誌的位置為0x0079~0x007a,值為:0x0021;同理類索引的值為:0x0002;父類索引值為:0x0003;介面索引集合長度為:0x0000(該類沒有實現介面)。

  類索引為:0x0002,換算成10進位制是2,找常量池中為#02(#02 表示常量池中的第二項常量)的值,為 #11,再找#11,為Test1(此處為類的全類名。由於TestJ1類沒有包,所以是類名。格式如java/lang/Object)。同理父類為:0x0003 --> #3 --> #12 --> java/lang/Object。所以TestJ1繼承Object類。

接下來我們寫一個最簡單的介面如下:

public interface InterSuper1 {

}
class檔案和常量池如下:

  由上圖可以看出在class檔案中InterSuper1介面的父類識別符號指向的也是Object類。不止如此,如果一個介面有父介面。那麼此介面的父類識別符號指向的也是Object類。可以說對於class檔案而言所有介面的父類都是Object(同理也可證明Object類也是所有抽象類的父類)。

  現在我們再回過頭看一看上面遺留的問題:如果介面繼承了Object類。那麼當一個類實現多個介面的時候,那不就相當於繼承了多遍Object?又變成了多繼承?首先不會繼承多遍Object,因為在class檔案而言,只能儲存一個父類。這個類還是直接或者間接的繼承Object。也是單繼承,由於介面不能例項化,所以也不會出現上面的菱形缺陷。

  至於網上流傳的Java 的標準——“Java Language Specification”中的9.2節,如下(來源於http://www.cnblogs.com/softnovo/articles/4546418.html):

  我的理解是:首先這段話沒有明確說明介面不繼承Object;其次它是出自於java語言規範中,所以它的目的是讓人們更加容易使用Java,所以故意省略了這個細節也是有可能的;再者如果介面繼承Object,上面的觀點也能說得通。

  還有一個是如下程式碼,為什麼不輸出Object中的方法?這個我也無法解釋。

複製程式碼
public interface SuperInter {
public void test();
public String getString();
}

public static void main(String[] args) {

Method[] methods = SuperInter.class.getMethods();

for (Method method : methods) {
       System.out.println(method.getName());