假如这个世界上只剩下你一个人,当你正坐在屋子里的时候,这时突然响起了敲门声...
没有父类的Java Class是如何从Object继承的
查看( 124 ) /
评论( 0 )
本文为原创,如需转载,请注明作者和出处,谢谢!ITPUB个人空间#^W*G0U4x8`pY
d}
ITPUB个人空间d sl|nq&O:c
经常有Java初学者会问为什么一个没有父类的Java类会自动从java.lang.Object类继承。如下面是一个普通的Java类:
Tk2u8`#o0
6[`3EWtA i f2g0public class Test // 从Object类继承ITPUB个人空间yw~4PE"Tz n
{ITPUB个人空间\:UBH{
public static void main(String[] args)ITPUB个人空间 CGt"y%i'xG
{ITPUB个人空间0CU(o+L;aK!y8p
System.out.println(new Test().toString());
:o:C&I7]I9[V0 }ITPUB个人空间EbG/v,n
}ITPUB个人空间'zH;{ u.jw4V[
ITPUB个人空间:H RF vD#L
从上面的代码可以看出,实际上,Test类的父类就是Object,因此,在Test中可以使用Object类的public或protected资源,如toString方法。那么JVM到底是如何做的呢?ITPUB个人空间+m x| t3_%CC_R6{'l
了解这个原因其实并不需要知道JVM的实现细节。只要思考一下对于这种虚拟机程序的原理即可。一般对于这种靠虚拟机运行的语言(如Java、C#等)会有两种方法处理默认继承问题。ITPUB个人空间 ~$Xl\8l)C&Uhp
1. 在编译源代码时,当遇到没有父类的类时,编译器会将其指定一个默认的父类(一般为Object),而虚拟机在处理到这个类时,由于这个类已经有一个默认的父类了,因此,VM仍然会按着常规的方法来处理每一个类。对于这种情况,从编译后的二进制角度来看,所有的类都会有一个父类。ITPUB个人空间(d3N u)t]
2. 编译器仍然按着实际代码进行编译,并不会做额外的处理。如果一个类没有显式地继承于其他的类,编译后的代码仍然没有父类。然后由虚拟机运行二进制代码时,当遇到没有父类的类时,就会自动将这个类看成是Object类的子类(一般这类语言的默认父类都是Object)。
0a1tR6T!zd3Yv0ITPUB个人空间:Wq$L.P;yU
从上面两种情况可以看出,第1种情况是在编译器上做的文章,也就是说,当没有父类时,由编译器在编译时自动为其指定一个父类。第2种情况是在虚拟机上做文章,也就是这个默认的父类是由虚拟机来添加的。那么Java是属性哪一种情况呢?其实这个答案很好得出。只需要随便找一个反编译工具,并.class文件进行反编译即可得知编译器是如何编译的。就以上面代码为例,如果是第1种情况,就算Test没有父类,但由于编译器已经为Test自动添加了一个Object父类,因此,在反编译后得到的源代码中的Test类是从Object类继承的。如果没是这种情况,那么就是第2种情况。ITPUB个人空间M/z-KwpN
现在我们使用JDK带的反编译工具javap来反编译Test.class,先执行下面的命名:ITPUB个人空间)B.]d@%N
ITPUB个人空间mD&Ooh.b;?
javap Test > Test.txt
{c$h"?@$] Dk0
y%H7^naW0打开Test.txt文件后,会看到如下的代码:ITPUB个人空间 }J+U&_B2|Bg
ITPUB个人空间_*`mRz3Lt
public class Test extends java.lang.Object{
:Bsrsm"N8M'`W0 public Test();ITPUB个人空间[2C~0wK*IO
public static void main(java.lang.String[]);ITPUB个人空间5M0gb h@
}
0UW;?/PJ%|5dX0
D6R MD8?s)B`P!g0再使用下面的命名来得到bytecode代码:ITPUB个人空间Ot#TR%c8fk
(v$q|-h!J B)X0javap –c Test >Test1.txtITPUB个人空间 |tsuW
ITPUB个人空间!_ q3r}{Y
打开Test1.txt后,会看到如下的代码:ITPUB个人空间XY&xc#NQ"~k
ITPUB个人空间;d(_-k6~~wYX
public class Test extends java.lang.Object{
L V%d Hr0public Test();ITPUB个人空间1^LK E]vy3lLo0z
Code:
V*UW bK/_0 0: aload_0ITPUB个人空间)N*m7k8|CjWz
1: invokespecial #8; //Method java/lang/Object."<init>":()VITPUB个人空间:iE:E-p:O+l|[
4: returnITPUB个人空间5JN,S'zW
5n7c-S#H-wt0public static void main(java.lang.String[]);ITPUB个人空间5kL7FQ\-t
Code:ITPUB个人空间1YQ G:CY"R!?a
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
BR ?+|(O0 3: new #1; //class TestITPUB个人空间 M3gG.[ R.R
6: dup
@FA^,cg.KQ"Wh0 7: invokespecial #22; //Method "<init>":()VITPUB个人空间0S7M)ah$z{hs
10: invokevirtual #23; //Method java/lang/Object.toString:()Ljava/lang/String;ITPUB个人空间_#e?J$E:j m$C1C"U
13: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
V2Cz8{&m-XN h0 16: returnITPUB个人空间0pmUzJp*q
}ITPUB个人空间 alV,U3Ag s*t
W`}5O$Y}z0从上面两段代码可以看出,Test已经从Object继承了,因此,可以断定Java是属性第1种情况,也就是说由编译器为没有父类的类指定了Object作为其默认父类。如果读者还不确定,可以直接打开Test.class,看看里面有没有Object,图1是Test.class的十六进制代码:ITPUB个人空间Q m%^^#m%M:tYN/?
ITPUB个人空间ph#s^?E

^!S4S|/f!^ ] Ie0javaclass01.jpg
#Sr0jU4nK8B0ITPUB个人空间*l.C.Bp7f6mb X^o2]
图1
F FDYI$s3g\0
W;VB] Ex0大家可以看到,Java编译器已经为Test指定了一个默认的Object类作为其父类。目前大多数基于虚拟器的语言都是采用的第1种方法来处理默认父类的,如下面的C#代码:
uFH X&q(wq0ITPUB个人空间.|#}c(\0X M?F4`
using System;ITPUB个人空间M rD#oMU&L$r
ITPUB个人空间2}qW!I Lvs
namespace ConsoleApplication1ITPUB个人空间+s6u(Au!U4P5jE
{ITPUB个人空间4b5]`'C&uPd4Y
class TestITPUB个人空间pEG#y3Z2i&U
{ITPUB个人空间qGSm!k
static void Main(string[] args)ITPUB个人空间F4@(h F qex+o
{ITPUB个人空间%P5ss4{+L(s J8F
Console.WriteLine(new Test().ToString());ITPUB个人空间o@5n g Qij+\-W
}
i\`` cn{3Xz0}ITPUB个人空间Wp9M kXzF
}ITPUB个人空间&}*R-JdkW
ITPUB个人空间E7} |q1K
使用ildasm.exe将上面的代码反编译后,产生的MSIL代码如下:
t;LBH,e6Xr0@0ITPUB个人空间ZE K PZMO
.class private auto ansi beforefieldinit ConsoleApplication1.Test
],_:k`%Av\Z0 extends [mscorlib]System.ObjectITPUB个人空间p _5ig%sD*c
{ITPUB个人空间{8rl4~#l;I;|;I1u9Hc
} // end of class ConsoleApplication1.TestITPUB个人空间bE/B1a)\
ITPUB个人空间d sl|nq&O:c
经常有Java初学者会问为什么一个没有父类的Java类会自动从java.lang.Object类继承。如下面是一个普通的Java类:
Tk2u8`#o0
6[`3EWtA i f2g0public class Test // 从Object类继承ITPUB个人空间yw~4PE"Tz n
{ITPUB个人空间\:UBH{
public static void main(String[] args)ITPUB个人空间 CGt"y%i'xG
{ITPUB个人空间0CU(o+L;aK!y8p
System.out.println(new Test().toString());
:o:C&I7]I9[V0 }ITPUB个人空间EbG/v,n
}ITPUB个人空间'zH;{ u.jw4V[
ITPUB个人空间:H RF vD#L
从上面的代码可以看出,实际上,Test类的父类就是Object,因此,在Test中可以使用Object类的public或protected资源,如toString方法。那么JVM到底是如何做的呢?ITPUB个人空间+m x| t3_%CC_R6{'l
了解这个原因其实并不需要知道JVM的实现细节。只要思考一下对于这种虚拟机程序的原理即可。一般对于这种靠虚拟机运行的语言(如Java、C#等)会有两种方法处理默认继承问题。ITPUB个人空间 ~$Xl\8l)C&Uhp
1. 在编译源代码时,当遇到没有父类的类时,编译器会将其指定一个默认的父类(一般为Object),而虚拟机在处理到这个类时,由于这个类已经有一个默认的父类了,因此,VM仍然会按着常规的方法来处理每一个类。对于这种情况,从编译后的二进制角度来看,所有的类都会有一个父类。ITPUB个人空间(d3N u)t]
2. 编译器仍然按着实际代码进行编译,并不会做额外的处理。如果一个类没有显式地继承于其他的类,编译后的代码仍然没有父类。然后由虚拟机运行二进制代码时,当遇到没有父类的类时,就会自动将这个类看成是Object类的子类(一般这类语言的默认父类都是Object)。
0a1tR6T!zd3Yv0ITPUB个人空间:Wq$L.P;yU
从上面两种情况可以看出,第1种情况是在编译器上做的文章,也就是说,当没有父类时,由编译器在编译时自动为其指定一个父类。第2种情况是在虚拟机上做文章,也就是这个默认的父类是由虚拟机来添加的。那么Java是属性哪一种情况呢?其实这个答案很好得出。只需要随便找一个反编译工具,并.class文件进行反编译即可得知编译器是如何编译的。就以上面代码为例,如果是第1种情况,就算Test没有父类,但由于编译器已经为Test自动添加了一个Object父类,因此,在反编译后得到的源代码中的Test类是从Object类继承的。如果没是这种情况,那么就是第2种情况。ITPUB个人空间M/z-KwpN
现在我们使用JDK带的反编译工具javap来反编译Test.class,先执行下面的命名:ITPUB个人空间)B.]d@%N
ITPUB个人空间mD&Ooh.b;?
javap Test > Test.txt
{c$h"?@$] Dk0
y%H7^naW0打开Test.txt文件后,会看到如下的代码:ITPUB个人空间 }J+U&_B2|Bg
ITPUB个人空间_*`mRz3Lt
public class Test extends java.lang.Object{
:Bsrsm"N8M'`W0 public Test();ITPUB个人空间[2C~0wK*IO
public static void main(java.lang.String[]);ITPUB个人空间5M0gb h@
}
0UW;?/PJ%|5dX0
D6R MD8?s)B`P!g0再使用下面的命名来得到bytecode代码:ITPUB个人空间Ot#TR%c8fk
(v$q|-h!J B)X0javap –c Test >Test1.txtITPUB个人空间 |tsuW
ITPUB个人空间!_ q3r}{Y
打开Test1.txt后,会看到如下的代码:ITPUB个人空间XY&xc#NQ"~k
ITPUB个人空间;d(_-k6~~wYX
public class Test extends java.lang.Object{
L V%d Hr0public Test();ITPUB个人空间1^LK E]vy3lLo0z
Code:
V*UW bK/_0 0: aload_0ITPUB个人空间)N*m7k8|CjWz
1: invokespecial #8; //Method java/lang/Object."<init>":()VITPUB个人空间:iE:E-p:O+l|[
4: returnITPUB个人空间5JN,S'zW
5n7c-S#H-wt0public static void main(java.lang.String[]);ITPUB个人空间5kL7FQ\-t
Code:ITPUB个人空间1YQ G:CY"R!?a
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
BR ?+|(O0 3: new #1; //class TestITPUB个人空间 M3gG.[ R.R
6: dup
@FA^,cg.KQ"Wh0 7: invokespecial #22; //Method "<init>":()VITPUB个人空间0S7M)ah$z{hs
10: invokevirtual #23; //Method java/lang/Object.toString:()Ljava/lang/String;ITPUB个人空间_#e?J$E:j m$C1C"U
13: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
V2Cz8{&m-XN h0 16: returnITPUB个人空间0pmUzJp*q
}ITPUB个人空间 alV,U3Ag s*t
W`}5O$Y}z0从上面两段代码可以看出,Test已经从Object继承了,因此,可以断定Java是属性第1种情况,也就是说由编译器为没有父类的类指定了Object作为其默认父类。如果读者还不确定,可以直接打开Test.class,看看里面有没有Object,图1是Test.class的十六进制代码:ITPUB个人空间Q m%^^#m%M:tYN/?
ITPUB个人空间ph#s^?E

^!S4S|/f!^ ] Ie0javaclass01.jpg
#Sr0jU4nK8B0ITPUB个人空间*l.C.Bp7f6mb X^o2]
图1
F FDYI$s3g\0
W;VB] Ex0大家可以看到,Java编译器已经为Test指定了一个默认的Object类作为其父类。目前大多数基于虚拟器的语言都是采用的第1种方法来处理默认父类的,如下面的C#代码:
uFH X&q(wq0ITPUB个人空间.|#}c(\0X M?F4`
using System;ITPUB个人空间M rD#oMU&L$r
ITPUB个人空间2}qW!I Lvs
namespace ConsoleApplication1ITPUB个人空间+s6u(Au!U4P5jE
{ITPUB个人空间4b5]`'C&uPd4Y
class TestITPUB个人空间pEG#y3Z2i&U
{ITPUB个人空间qGSm!k
static void Main(string[] args)ITPUB个人空间F4@(h F qex+o
{ITPUB个人空间%P5ss4{+L(s J8F
Console.WriteLine(new Test().ToString());ITPUB个人空间o@5n g Qij+\-W
}
i\`` cn{3Xz0}ITPUB个人空间Wp9M kXzF
}ITPUB个人空间&}*R-JdkW
ITPUB个人空间E7} |q1K
使用ildasm.exe将上面的代码反编译后,产生的MSIL代码如下:
t;LBH,e6Xr0@0ITPUB个人空间ZE K PZMO
.class private auto ansi beforefieldinit ConsoleApplication1.Test
],_:k`%Av\Z0 extends [mscorlib]System.ObjectITPUB个人空间p _5ig%sD*c
{ITPUB个人空间{8rl4~#l;I;|;I1u9Hc
} // end of class ConsoleApplication1.TestITPUB个人空间bE/B1a)\