内存中子类实例

来源:互联网 发布:iphone6网络很慢 编辑:程序博客网 时间:2024/05/16 06:55

前面已经介绍:当通过引用变量来访问它所引用对象的实例变量时,该实例变量的值取决于声明该变量时所用的类型.

现在的问题是:Drived对象在内存中到底如何存储?很明显它有两个不同的count实例变量,这意味着必须用两块内存保存它们.

package com.lic.array;public class Demo18 {public static void main(String[] args) {// 创建一个sub对象Sub_18 s = new Sub_18();// 将Sub对象向上转型后赋为Mmid,Base类型的变量Mid_18 s2m = s;Base_18 s2b = s;// 分别通过3个变量来访问count实例变量System.out.println(s.count);System.out.println(s2m.count);System.out.println(s2b.count);}}class Base_18{int count = 2;}class Mid_18 extends Base_18{int count = 20;}class Sub_18 extends Mid_18{int count = 200;}
上述程序会输出
200202
这意味着s,s2m,s2b这3个变量所引用Java对象拥有3个count实例变量,也就是说需要3块内存存储他们.

对于s,s2m,s2b这3个变量所引用的Java对象在内存中的分配,如图2.10所示.

从图2.10可以看出,这个Sub对象不仅存储了它自身的count实例变量,还需要存储从Mid,Base两个父类那里继承到的count实例变量.但这3个count实例变量在底层是有区别的,程序通过Base型变量来访问该对象的count实例变量时,将输出2;通过Mid型的变量来访问该对象的count实例变量时,将输出20.

当直接在Sub类中访问实例变量i时,程序显然会输出200,即访问到Sub类中定义的实例变量i.为了在Ssub类中访问Mid类定义的count实例变量,可以在count实例变量之前增加super关键字作为限定.例如,在Ssub类中增加如下方法.

public void accessMid(){System.out.println(super.count);}
上面的accessMid()方法就可以访问到父类中定义的count实例变量.那么此处的super代表什么?绝大部分Java图书中都会说:super代表父类的默认实例.这个说法含糊而笼统,如果super代表父类的默认实例,那么这个默认实例在哪里呢?

对于前面介绍的Base,Mid,Sub这3个具有父子关系的Java类,当创建一个Sub对象之后,该对象在内存中有图2.10所示的存储.而且,Sub类实际上只定义了一个count实例变量,另外两个count实例变量是Mid,Base所定义的,因此尝试按图2.11的方式来看Sub对象.

如图2.11所示的内存分配是事实,也就是内存中保存3个Java对象--Sub对象,Mid对象和Base对象.3个对象各具有一个count实例变量,而Sub类中的super正是引用该ub对象关联的Mid对象,因此通过super来访问count实例变量时输出20.

实际上会发现系统中只有一个Sub对象,而且这个Sub对象持有3个count实例变量,因为通过s,s2m,s2b变量访问count实例变量时,可以分别输出200,20,2这3个值.

系统内存中并不存在Mid和Base两个对象,程序内存中只有一个Sub对象,只是这个Sub对象中不仅保存了在Sub类中定义的所有实例变量,还保存了它的所有父类所定义的全部实例变量.

那super关键字的作用到底是什么?

package com.lic.array;public class Demo19 {public static void main(String[] args) {// 创建一个Apple对象Apple_19 a = new Apple_19();// 调用getSuper()方法获取Apple对象关联的super引用Fruit_19 f = a.getSuper();// 判断a和f的关系System.out.println("a和f所引用的对象是否相同:"+(a==f));System.out.println("访问a所引用对象的color实例变量:"+a.color);System.out.println("访问f所引用对象的color实例变量"+f.color);// 分别通过a,f两个变量来调用info方法a.info();f.info();// 调用AccessSuperInfo来调用父类的info()方法a.AccessSuperInfo();}}class Fruit_19{String color = "未确定颜色";// 定义一个方法,该方法返回调用该方法的实例public Fruit_19 getThis(){return this;}public void info(){System.out.println("Fruit方法");}}class Apple_19 extends Fruit_19{// 重写父类的方法@Overridepublic void info(){System.out.println("Apple方法");}// 通过super调用父类的info()方法public void AccessSuperInfo(){super.info();}// 尝试返回super关键字代表的内容public Fruit_19 getSuper(){return super.getThis();}String color = "红色";}
上述程序中父类Fruit类定义了一个getThis()方法,该方法直接返回调用该方法的对象;接着子类Apple类又定义了一个getSuper()方法,该方法返回super.getThis().程序试图通过这种方式达到一种效果:当一个Apple对象调用getSuper()方法时,该方法返回该Apple对象所关联的父类对象.
Java程序允许某个方法通过return this; 返回调用该方法的Java对象,但不允许直接return super;,甚至不允许直接将super当成一个引用变量使用.

上面程序的输出结果如下

从上图可以看出:通过Apple对象的getSuper()方法所返回的实际上是该Apple对象本身,只是它的声明类型是Fruit,因此通过f变量访问color实例变量时,该实例变量的值由Fruit类决定;但通过f变量调用info()方法时,该方法的行为由f变量实际所引用的Java对象决定,因此程序输出"Apple方法".

当程序在Apple类的AccessSuperInfo()方法使用super.作为限定调用info()方法,该info()才真正表现出Fruit类的行为.

通过上面的分析可疑看出:super关键字本身并没有引用任何对象,它甚至不能被当成一个真正的引用变量使用.主要有如下两个原因:

1.子类方法不能直接使用return super; 但使用return this;返回调用该方法的对象是允许得到;

2.程序不允许直接把super当成变量使用,例如,试图判断super和a变量是否引用同一个Java对象---super == a; 但这条语句将引起编译错误.

至此,对父,子对象在内存中存储有了准确的结论:当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为其父类中定义的所有实例变量分配内存,即使子类定义了与父类中同名实例变量.也就是说,当系统创建一个Java对象时候,如果该Java类有两个父类(一个直接父类A,一个间接父类B),假设A类中定义了2个实例变量,B类中定义了3个实例变量,当前类中定义了2个实例变量,那这个Java对象将会保存2+3+2个实例变量.

如果在子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量.注意不是完全覆盖,因此系统为创建子类对象时,依然会为父类中定义的,被隐藏变量分配内存空间.

为了在子类方法中访问父类中定义的,被隐藏的实例变量,或者为了在子类方法中调用父类中定义的,被覆盖(Override)方法,可以通过super,作为限定来修饰这些实例变量和实例方法.

因为子类中定义与父类中同名的实例变量并不会完全覆盖父类中定义的实例变量,它只是简单地隐藏了父类中的实例变量.所以会出现如下特殊的情形.

package com.lic.array;public class Demo20 {public static void main(String[] args) {Drived_20 d = new Drived_20();// 程序不可访问d的私有变量:tag,所以下面语句将引起编译错误// System.out.println(d.tag);// 将Drived_20显式的向上转型为Parent_20后,即可访问tag实例变量// 程序将输出:疯狂Java讲义.System.out.println(((Parent_20)d).tag);}}class Parent_20{public String tag = "疯狂Java讲义";}class Drived_20 extends Parent_20{// 定义一个私有的tag实例变量来隐藏父类的tag实例变量private String tag = "轻量级Java EE企业应用实战";}
上面成程序为父类Parent定义了一个tag实例变量,子类中定义的这个实例变量将会隐藏父类中定义的tag实例变量.

程序的入口main方法中先创建了一个Drived对象,这个Drived对象将会保存两个tag实例变量,一个是在Parent类中定义的tag实例变量,一个是在Drived类中定义的tag实例变量.

接着,程序将Drived对象赋给d变量,当程序试图通过d来访问tag实例变量时,程序将提示访问权限不允许,这是因为访问哪个实例变量由声明该变量的类型决定,所以系统将会试图访问Drived类中定义的tag实例变量;后面一个输出语句先将d变量强制向上转型为Parent类型,再通过它来访问tag实例变量是允许的,因为此时系统将会访问Parent中定义的tag实例变量,也就是输出"疯狂Java讲义".


1 0
原创粉丝点击