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

接口和抽象類是否繼承了Object

art 個數 也不會 interface ltr 常量 避免 接下來 tno

我們先看一下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());

接口和抽象類是否繼承了Object