JAVA内存分析中的两道简单小题

来源:互联网 发布:mysql连接池配置文件 编辑:程序博客网 时间:2024/05/20 04:11

这两段程序出自李刚《疯狂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.iSystem.out.println(this.i);this.display();//输出this实际的类型,将看到是BSystem.out.println(this.getClass());//因为this的编译类型是A,所以不能调用sub();//this.sub();}

看到java的博大精深了吧!!!

0 0
原创粉丝点击