Java中父类构造器访问子类对象的实例变量

Java 1288℃

子类的方法可以访问父类的实例变量,这是因为子类继承父类就会获得父类的成员变量和方法;但父类的方法不能访问子类的实例变量,因为父类根本无从知道它将被哪个子类继承,它的子类将会增加怎样的成员变量。 但是,在极端的情况下,可能出现父类访问子类变量的情况。请看下面的示例程序。

class Base  
{ 
      //定义一个名为i的实例变量 
      private int i = 2; 
      public Base()  
      { 
            this.display(); 
      } 
      public void display()  
      { 
            System.out.println(i); 
      } 
} 
//继承 Base 的 Derived 子类 
class Derived extends Base  
{ 
      //定义一个名为i的实例变量 
      private int i = 22; 
      //构造器,将实例变量i初始化为 222 
      public Derived()  
      { 
            i = 222;                         //② 
      } 
      public void display() 
      { 
            System.out.println(i); 
      } 
      public static void main(String[] args)  
      { 
            //创建 Derived 的构造器创建实例 
        new Derived();                       //① 
      } 
}

上面程序的main方法里只有一行代码:new Derived();。这行代码将会调用Derived里的构造器,由于Derived 类继承了Base 父类,而且Derived 构造器里没有显式使用super 来调用父类的构造器,因此系统将会自动调用Base 类中无参数的构造器来执行初始化。

在 Base 类的无参数构造器中,只是简单地调用了 this.display()方法来输出实例变量 i 的值,那么这个程序将会输出多少呢?2?22?222?。运行该程序,会发现实际输出结果为0,这看上去很奇怪。接下来将详细介绍这个程序的运行过程,从内存分配的角度来分析程序的输出结果,从而更好地把握程序运行的真实过程。

当程序在①行代码处创建Derived对象时,系统开始为这个Derived对象分配内存空间。需要指出的是,这个Derived对象并不是只有一个 i实例变量,它将拥有两个 i实例变量。为了解释这个程序,首先需要澄清一个概念:Java 对象是由构造器创建的吗?很多书籍、资料中会说:是的。 但实际情况是:构造器只是负责对Java对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里值都默认是空值—对于基本类型的变量,默认的空值就是 0 或 false;对于引用类型的变量,默认的空值就是 null。

当程序调用①行代码时,系统会先为 Derived 对象分配内存空间。此时系统内存需要为这个Derived对象分配两块内存,它们分别用于存放Derived对象的两个 i实例变量,其中一个属于Base类定义的 i实例变量,一个属于Derived类定义的 i实例变量,此时这两个 i实例变量的值都是0。接下来程序在执行Derived类的构造器之前,首先会执行Base类的构造器。表面上看,Base 类的构造器内只有一行代码this.display();,但由于Base类定义i实例变量时指定了初始值2,因此经过编译器处理后,该构造器应该包含如下两行代码。

i = 2; 
this.display();

因此,程序先将Base 类中定义的 i实例变量赋值为2,再调用 this.display()方法。此处有一个关键:this 代表谁?回答这个问题之前,先进行一些简单的修改,将Base 类的构造器改为如下形式。

public Base()  
{ 
      i = 2; 
	  //直接输出 this.i 
      System.out.println(this.i); 
      this.display(); 
}

再次运行该程序,将看到输出2、0。看到这样的结果,可能有人会更加混乱了:此时的this 到底代表谁?当 this 在构造器中时,this 代表正在初始化的Java对象。此时的情况是:从源代码来看,此时的 this 位于 Base()构造器内,但这些代码实际放在Derived()构造器内执行—是Derived()构造器隐式调用了Base()构造器的代码。由此可见,此时的this 应该是Derived对象,而不是Base对象。

现在问题又出现了,既然 this 引用代表了Derived 对象,那怎么直接输出 this.i 时会输出2 呢?这是因为,这个 this 虽然代表Derived对象,但它却位于 Base 构造器中,它的编译时类型是Base,而它实际引用一个Derived对象。为了证实这一点,再次改写程序。 为Derived 类增加一个简单的 sub()方法,然后将Base 构造器改为如下形式。

public Base()  
{ 
      //直接输出 this.i 
      System.out.println(this.i); 
      this.display(); 
      //输出 this 实际的类型,将看到输出Derived 
      System.out.println(this.getClass()); 
      //因为 this 的编译类型是 Base,所以依然不能调用sub()方法, 
      //this.sub(); 
}

上面程序调用 this.getClass()来获取this 代表对象的类,将看到输出Derived 类,这表明此时 this 引用代表的是Derived对象。但接下来,程序通过 this 调用 sub()方法时,则无法通过编译,这就是因为 this 的编译时类型是Base 的缘故。

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

转载请注明:零五宝典 » Java中父类构造器访问子类对象的实例变量