經常有Java初學者會問爲什麼一個沒有父類的Java類會自動從java.lang.Object類繼承。如下面是一個普通的Java類:
{
public static void main(String[] args)
{
System.out.println(new Test().toString());
}
}
從上面的代碼可以看出,實際上,Test類的父類就是Object,因此,在Test中可以使用Object類的public或protected資源,如toString方法。那麼Java編譯器和JVM到底是如何做的呢?
瞭解這個原因其實並不需要知道JVM的實現細節。只要思考一下對於這種虛擬機程序的原理即可。一般對於這種靠虛擬機運行的語言(如Java、C#等)會有兩種方法處理默認繼承問題。
1. 在編譯源代碼時,當遇到沒有父類的類時,編譯器會將其指定一個默認的父類(一般爲Object),而虛擬機在處理到這個類時,由於這個類已經有一個默認的父類了,因此,VM仍然會按着常規的方法來處理每一個類。對於這種情況,從編譯後的二進制角度來看,所有的類都會有一個父類。
2. 編譯器仍然按着實際代碼進行編譯,並不會做額外的處理。如果一個類沒有顯式地繼承於其他的類,編譯後的代碼仍然沒有父類。然後由虛擬機運行二進制代碼時,當遇到沒有父類的類時,就會自動將這個類看成是Object類的子類(一般這類語言的默認父類都是Object)。
從上面兩種情況可以看出,第1種情況是在編譯器上做的文章,也就是說,當沒有父類時,由編譯器在編譯時自動爲其指定一個父類。第2種情況是在虛擬機上做文章,也就是這個默認的父類是由虛擬機來添加的。那麼Java是屬性哪一種情況呢?其實這個答案很好得出。只需要隨便找一個反編譯工具,並.class文件進行反編譯即可得知編譯器是如何編譯的。就以上面代碼爲例,如果是第1種情況,就算Test沒有父類,但由於編譯器已經爲Test自動添加了一個Object父類,因此,在反編譯後得到的源代碼中的Test類是從Object類繼承的。如果沒是這種情況,那麼就是第2種情況。
現在我們使用JDK帶的反編譯工具javap來反編譯Test.class,先執行下面的命令:
javap Test > Test.txt
打開Test.txt文件後,會看到如下的代碼:
public Test();
public static void main(java.lang.String[]);
}
再使用下面的命令來得到bytecode代碼:
javap -c Test >Test1.txt
打開Test1.txt後,會看到如下的代碼:
public Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: new #1; //class Test
6: dup
7: invokespecial #22; //Method "<init>":()V
10: invokevirtual #23; //Method java/lang/Object.toString:()Ljava/lang/String;
13: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return
}
從上面兩段代碼可以看出,Test已經從Object繼承了,因此,可以斷定Java是屬性第1種情況,也就是說由編譯器爲沒有父類的類指定了Object作爲其默認父類。如果讀者還不確定,可以直接打開Test.class,看看裏面有沒有Object,圖1是Test.class的十六進制代碼:
圖1
大家可以看到,Java編譯器已經爲Test指定了一個默認的Object類作爲其父類。目前大多數基於虛擬器的語言都是採用的第1種方法來處理默認父類的,如下面的C#代碼:
namespace ConsoleApplication1
{
class Test
{
static void Main(string[] args)
{
Console.WriteLine(new Test().ToString());
}
}
}
使用ildasm.exe將上面的代碼反編譯後,得到的MSIL代碼如下:
extends [mscorlib]System.Object
{
} // end of class ConsoleApplication1.Test
從上面的代碼可以清楚地看到,Test類已經有一個System.Object作爲父類了。