访问子类对象实例变量

来源:互联网 发布:网络宣传好处 编辑:程序博客网 时间:2024/06/09 16:26

子类的方法可以访问父类的实例变量,这是因为子类继承父类就会获得父类的成员变量和方法;但父类的方法不能访问子类的实例变量,因为父类根本无从知道它将被哪个子类继承.它的子类将会增加怎样的成员变量.

但是,在极端的情况下,可能出现父类访问子类变量的情况.如下所示:

package com.lic.array;public class Demo14 {public static void main(String[] args) {// 创建Drived的构造器创建实例new Drived();}}class Base{// 定义一个名为i的实例变量private int i = 10;public Base(){this.display();}public void display(){System.out.println(i);}}// 继承Base的Drived子类class Drived extends Base{// 定义一个名为i的实例变量private int i = 20;// 构造器,将实例变量i初始化为2000public Drived(){i = 2000;}public void display(){System.out.println(i);}}
上面程序的main方法里只有一行代码:new Drived();.这行代码将会调用Drived里的构造器,由于Drived类继承了Base父类,而且Drived构造器里没有显式使用super来调用父类的构造器,因此系统将会自动调用Base类中无参数的构造器来执行初始化.

在Base类的无参数构造器中,只是简单地调用了this.display()方法来输出实例变量i的值,那么这个程序会输出多少呢,10?20?2000?运行该程序,会发现实际输出结果为0.也就是说,实例变量的i的值既不是10,也不是20,更不是2000,而是0,这看上去很奇怪.

接下来将详细介绍这个程序的运行过程,从内存分配的角度来分析程序的输出结果,从而更好地把握程序运行的真实过程.

当程序创建Drived对象时,系统开始为这个Drived对象分配内存空间.需要指出的是,这个Drived对象并不是只有一个i实例变量,它拥有两个i实例变量.

关于一个Java对象怎样拥有多个同名的实例变量,子类定义的成员变量并不能完全覆盖父类中成员变量的知识,后面再进行讲解.

为了解释这个程序,首先需要澄清一个概念:Java对象是由构造器创建的吗?很多书籍,资料中会说,是的.

但实际情况是:构造器只是负责对Java对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里值都默认是空值----对于基本类型的变量,默认的空值就是0或false;对于引用类型的变量,默认的空值就是null.

当程序调用new Drived();代码时,系统会先为Derived对象分配内存空间.此时系统内存需要为这个Drived对象分配两块内存,它们分别用于存放Drived对象的两个i实例变量,其中一个属于Base类定义的i实例变量,一个属于Drived类定义的i实例变量,此时这两个i实例变量的值都是0.

接下来程序在执行Drived类的构造器之前,首先会执行Base类的构造器.表面上看,Base类的构造器内只有一行代码this.display();,但由于Base类定义i实例变量时指定了初始值2,因此经过编译器处理后,该构造器应该包含如下两行代码.

i = 2;this.display();
因此,程序先将Base类中定义的i实例变量赋值为2,再调用this.display()方法.此处有一个关键:this代表谁?

回答这个问题之前,先进行一些简单的修改,将Base类的构造器改为如下形式:

public Base(){// 直接输出this.i;System.out.println(this.i);this.display();}
现在,Base构造器里表面上只有2行代码,实际上应该有3行代码,如下所示:

i = 10;System.out.println(this.i);this.display();
再次按运行该程序,将看到输出10,0.看到这样的结果,可能有人会更加混乱了:此时的this到底代表谁?

当this在构造器中时,this代表正在初始化的Java对象.此时的情况是:从源代码来看,此时的this位于Base()构造器内,但这些代码实际放在Drived()构造器内执行----是Drived()构造器隐式调用了Base()构造器的代码.由此可见,此时的this应该是Drived对象,而不是Base对象.

现在问题又出现了,既然this引用代表了Drived对象,那怎么直接输出this.i时会输出10呢?这是因为,这个this虽然代表Drived对象,但它却位于Base构造器中,它的编译时类型Base,而实际引用一个Drived对象.为了证实这一点,再次改写程序.

为Drived类增加一个简单的sub()方法,然后将Base构造器改为如下形式.

public Base(){//直接输出this.iSystem.out.println(this.i);this.display();// 输出this实际的类型,将看到输出DrivedSystem.out.println(this.getClass());// 因为this的编译类型时Base,所以依然不能调用sub()方法//this.sub();}
上面程序调用this.getClass()来获取this代表对象的类,将看到输出Drived类,这表明此时this引用代表的是Drived对象.但接下来,程序通过this调用sub()方法时,则无法通过编译,这就是因为this的编译时类型时Base的缘故.

当变量的编译时类型和运行时类型不同时,通过该变量访问它引用的对象的实例变量时,该实例变量的值由声明该变量的类型决定.但通过该变量调用它引用的对象的实例方法时,该方法行为将由它实际所引用的对象来决定.因此,当程序访问this.i时,将会访问Base类中定义的i实例变量,也就是将输出2;但执行this.display();代码时,则实际表现出Drived对象的行为,也就是输出Drived对象的i实例变量,即0.

我们再来把Drived的构造器改一下

public Drived(){this.display();i = 2000;}
输出结果又是什么呢?

100class com.lic.array.Drived20
这里实际上执行的代码是

int i;public Drived(){   super();   i = 20;   this.display();   i = 2000;}

结合前面的博文,知道实际执行的代码顺序是先定义实例变量,然后在构造器中按原来的代码顺序初始化赋值.然而super()和this()的调用又是构造器中的第一行代码,执行之前,i的值还是0,所以在Drived父类构造器中调用的display方法会输出0,在Drived的构造器中调用this.display()会输出20.

1 0