一直有一个困惑,继承机制中,子类会继承父类中的所有的成员,那么子类继承父类中的这些成员后,是如何存储它们的呢?是在一个与子类不同的堆地址空间存储?还是父类中的这些成员与子类中特有的成员存储在同一个地址空间呢?下面通过以下测试代码,我们可以获得答案:
除了私有成员,这句话到底怎么理解呢?我们一起来看看下面的代码吧。
//定义了一个父类Father
class Father {
private String name;
public Father(String name){
this.name = name;
System.out.println("father address:"+this.hashCode());
}
}//编写son子类
public class son extends Father{
private String name;
public son(String name){
super(name);
this.name = name;
System.out.println(this.hashCode() == super.hashCode());
}
public static void main(String[] args) {
Father son = new son("son");
System.out.println("son address:"+son.hashCode());
}
}
测试结果如下:
father address:366712642
true
son address:366712642
程序分析:
上述测试代码中,在main方法中当创建一个子类对象son的语句 Father son = new son("son");被执行时,内存中会完成以下工作:
1:先将硬盘上指定位置的son.class文件加载进内存。
2:执行main方法时,在栈内存(栈内存中存储的是方法的局部变量)中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量son。
3:当执行到new语句时,JVM会在堆内存中开辟一个实体空间,分配了一个内存首地址值。
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化(对属性按照默认初始值进行初始化)。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。(此时才是调用构造函数,执行构造函数内的初始化语句)
8:将首地址赋值给son ,son变量就引用了该实体。(指向了该对象)
在该例子中的语句 Father son = new son("son"),按照上述步骤当程序执行到第七步,栈中的名为son的引用变量指向了存储son对象的堆地址空间,同时调用son对象的构造函数,执行构造函数内的第一条语句super(name)时,会相应调用父类中的构造函数,执行父类中的构造函数内的语句,输出父类对象的内存存储的HashCode。注意在父类的构造方法被调用时并没有在堆空间重新分配一个地址空间来存储父类对象的属性,而是将父类对象构造方法中的变量的值直接存储在栈中son变量指向的内存空间中。当super(name)语句(即父类中带有一个String类型参数的构造方法)执行完毕,JVM继续执行 this.name = name; System.out.println(this.hashCode() == super.hashCode());输出结果显示this代表的本类类型的对象引用的哈希地址和子类所属父类中的内存空间引用的hash地址是相等的。代表子类和父类在同一个堆的内存空间中,该内存空间既包含了子类的所有成员,也包含了父类的所有成员(包括私有的成员都存储在此空间,只是这些父类的私有成员对子类对象是不可见的)。对此,我们也能解释清楚为什么两个不同对象的hashCode可以相等。