public class Father { private String name; public Father() { System.out.println("调用了Father的构造器-------this指针的值为:" + this);// ③ System.out.println("调用了Father的构造器-------this.getClass()的值为:" + this.getClass());// ④ this.name = "父亲";// ⑤ } //省略get/set方法…… } |
public class Children extends Father { private int age; public Children() { super();// ② System.out.println("调用了Children的构造器-------this指针的值为:" + this);// ⑥ this.age = 1;//⑦ } //省略get/set方法…… } |
public class Test { public static void main(String[] args) { // 创建Children的构造器创建实例 Children children = new Children();// ① System.out.println("name:"+children.getName());//⑧ System.out.println("age:"+children.getAge());//⑨ } } |
准备知识:
1、首先需要澄清一个概念:java对象是由构造器创建的吗?
很多书籍、资料中会说,是的。但是实际情况是:构造器只是负责对java对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里值都默认是空值——对于基本类型的变量,默认的空值就是0或false,对于引用类型的变量默认的空值就是null。
2、在处理java类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定。必须明确,运行时(动态)绑定针对的范畴只是对象的方法。(方法的话只有static和final(所有private默认是final的)是静态绑定的.)
3、当创建任何java对象时,程序总会先一次调用每个类非静态初始化块、父类构造器(总是从Object开始)执行初始化,最后才调用本类的非静态初始化块、构造器执行初始化。
此时当程序执行到①时,系统会先为父类中的私有属性在堆内存开辟空间
注意:这里并不会实例化父类对象,仅仅是为父类中的属性在堆中开辟了一段内存空间。
然后在为子类Children在堆内存空间,此时调用父类的构造方法(不写super();的话会隐式的调用),此时通过准备知识1中我们已经知道构造方法仅仅只是负责对java对象实例变量执行初始化,而不会实例化父类。
该结论可以通过Father类的构造器中输出的this和this.getClass()结果得到佐证。
输出结果如下:
通过Father类构造器中的输入我们发现,Father类构造器中的this指向的实际是子类对象的实例。这里说明子类实例化的确不会创建父类的对象,因为如果创建了父类对象的话,那么父类对象构造器中的this指向的地址肯定就不可能是子类对象的实例了。
这时我们有可能产生一个疑惑,那就是:既然不创建父类对象的实例,那么子类中是怎么拥有(这个说法可能不够严谨,但为了方便理解不做其他讨论)父类中的属性的?
此时我们需要结合准备知识2来说明了,在编译时java类的成员变量采用静态绑定,即成员变量被关联到了类上。此时虽然父类构造器中的this指向的是子类对象的实例,但是虚拟机在子类对象中找不到name属性的时候,就到包含该构造方法中的类去找静态绑定相应的name属性的,并为其赋值为“父亲”。所以⑧中输出的结果为“父亲”。
上面是我对子类对象创建过程中是否实例化父类对象的理解,如果有什么不对的地方欢迎斧正!