接口和抽象類是否繼承了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