这两段程序出自李刚《疯狂JAVA突破程序员基本功的16课》中的两段小代码,非常简单,但初学者很难掌握,考虑到内存分析,以及java虚拟机执行机制
代码一:
class Base {
int count = 2;
}
class Mid extends Base {
int count = 20;
}
public class Sub extends Mid {
int count = 200;
public static void main(String args[]) {
Sub s = new Sub();
Mid s2 = s;
Base s3 = s;
System.out.println(s.count);
System.out.println(s2.count);
System.out.println(s3.count);
}
}
上面程序中定义了3个带有父子关系的类,Base派生了Mid,Mid派生了Sub,而且这三个类中都定义了名为count的实例变量,程序创建了一个Sub对象,并将这个Sub对象向上转型,s2,s3同时指向了s对象,那么是不是输出200,200,200??
显然猜错了,程序将会输出200,20,2。这意味着,s,s2,s3这3个变量所指向的java对象拥有三个count实例变量,也就是说需要3块内存存储它们,Sub对象不仅存储了它自身的count实例变量,还需要存储从Mid,Base两个父类那里继承过来的count实例变量,但这3个count实例变量在底层室是有区别的,程序通过Base型变量来访问对象的count值时将输出2,通过Mid型的变量来访问该对象的count实例变量时,将输出20;当直接在sub类中访问实例变量count时,程序显然会输出200,既访问到sub类中定义的实例变量count。为了在sub类中访问Mid类定义的count实例变量,可以在count实例变量之前增加super关键字作为限定。
下面给出内存分析图:
代码二:
class A {
private int i = 2;
public A() {
this.display();
}
public void display() {
System.out.println(i);
}
}
class B extends A {
private int i=22;
public B() {
i = 222;
}
public void display() {
System.out.println(i);
}
}
public class BaseTest {
public static void main(String args[]) {
new B();
}
}
看到这段代码,你会想到输出的代码是2?22?还是222?
结果是0,出乎意料吧,这看上去很奇怪!
接下来将详细介绍这个程序的运行过程,从内存分配的角度来分析程序输出的结果,从而更好的把握程序运行的真实过程,
new B()的时候系统开始为这个对象分配内存空间,需要指出的是这个对象并不是只有一个i实力变量,他将拥有两个i实例变量。
注意:关于一个java对象怎样拥有多个同名的实例变量,子类定义的成员变量并不能完全覆盖父类中成员变量!!!
为了解释这个程序,首先需要澄清一个概念:java对象都是由构造器创建的么?很多书籍资料中会说:是的!!!
但实际情况是:构造器只是负责对java对象实例变量执行初始化(也就是赋初始值)!!!
在执行构造器代码之前该对象所占的内存已经被分配下来,这些内存里的值都是默认是空的值----对于基本类型的变量,默认控制是0或false;
对于引用类型的变量,默认空值就是null;
new B()的时候,系统会为B对象分配内存空间,此时系统内存需要为这个B对象分配两块内存,它们分别用于存放B对象的两个i实例变量,其中一个属于A类定义的i实例变量,一个属于B类定义的i实例变量。此时这两个i实例变量的值都为0。
接下来程序在执行B类构造器之前首先会执行A类构造器,表面上看A类的构造器内只有一行代码this.display();但是由于A类定义i实例变量时制定了初值2,因此经过编辑器处理后该构造器应该包含两行代码。
i=2;
this.display();
因此A类中定义的i实例变量赋值为2;再调用this.display()方法;此时有一个关键:this代表谁???
回答这个问题之前,先进行一些简单的修改,将Base类的构造器改为如下形式,
public A() {
// 直接输出this.i;
System.out.println(this.i);
this.display();
}
现在A构造器表里有2行代码,实际上应该有3行代码
i=2;
System.out.println(this.i);
this.display();
再次运行,结果是2,0;看到这样的结果,可能会有人更加混乱了,此时的this到底代表谁?
笔者在《疯狂java讲义》中已经指出:当this在构造器当中时,this代表正在初始化的java对象,此时的情况是:从源代码来看,此时的this位于B构造器中,但这些代码实际上放在A构造器内执行,是B构造器隐式的调用了A构造器的代码,由此可见,这里的this应该是B对象,而不是A对象.
现在的问题又出现了,既然this代表了B对象,那么直接输出this.i时会输出2呢?这是因为,这个this虽然代表B对象,但它却位于A构造器中,它的编译时类型是A,而它实际上引用了一个B对象。为了证实这一点,再次改写程序。
public A() {
i = 2;
//直接输出this.i
System.out.println(this.i);
this.display();
//输出this实际的类型,将看到是B
System.out.println(this.getClass());
//因为this的编译类型是A,所以不能调用sub();
//this.sub();
}
看到java的博大精深了吧!!!