我们有一个文件 child.java
class Child
{
public static void main(String[] args)
{
System.out.println(new Child().toString());
}
}
从上面的代码我们可以看出,其实Child类的父类是Object,所以我们可以在Child中使用Object类的public或者protected的资源,比如toString方法。那么Java编译器和JVM是怎么做的呢?
知道这个原因其实并不需要知道JVM的实现细节。想想这个虚拟机程序的原理。一般来说,对于运行在虚拟机(如Java)上的这类语言,有两种方法可以处理默认继承问题。
在编译源码阶段,如果遇到一个没有父类的类,编译器会给它分配一个默认的父类(通常是Object),如果虚拟机处理这个类,因为这个类已经有了默认父类,因此 VM 仍将以通常的方式处理每个类。在这种情况下,从编译二进制的角度来看,所有的类都会有一个父类。
-
编译器仍使用实际代码进行编译,不进行额外处理。如果一个类没有显式继承自其他类,并且编译后的代码仍然没有父类。那么在虚拟机运行二进制代码时,如果遇到没有父类的类,则自动将该类视为Object类的子类(一般默认父类为Object)。
李>
从以上两种情况可以看出,第一种情况是在编译器上做的一篇文章,即在没有父类的情况下,编译器在编译时会自动为其分配一个父类。第二种情况是在虚拟机上做文章,即默认父类由虚拟机添加。
那么 Java 的情况是怎样的呢?事实上,我们可以通过使用 javap 得到这个答案。随便找个反编译工具,反编译.class文件看看编译器是怎么编译的。以上面的代码为例。如果是第一种情况,即使 Child 没有父类,因为编译器已经自动给 Child 添加了 Object 父类,反编译后得到的源码中的 Child 类是继承自 Object 类的。如果不是这种情况,那就是第二种情况。
首先,将child.java编译成Child.class
%javac child.java
现在我们用JDK的反编译工具javap来反编译Child.class,首先执行如下命令:
% javap -c Child
在命令之后,我们的字节码以某种可读的形式出现,我们可以在其中识别我们的方法、整数、命令和字符串
class Child {
Child();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #3 // class Child
6: dup
7: invokespecial #4 // Method "<init>":()V
10: invokevirtual #5 // Method java/lang/Object.toString:()Ljava/lang/String;
13: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return
}
从上面这段代码可以看出,Test已经继承自Object,所以可以断定Java是属性的第一种情况,即编译器指定Object为其默认父类没有父类的类。