前言
这是本系列的第三篇文章:
[短文速读-2] 重载/重写,动/静态分派?
[短文速读-1] a=a+b和a+=b的区别
本篇文章讨论一个很简单的问题:实例化子类是否会实例化父类。
本篇篇幅不长,适合碎片化时间阅读。文章会从字节码和dump内存结构来解答这个问题。
出场角色
小A:刚踏入Java编程之路…
MDove:一个快吃不上饭的Android开发…
正题
引子
小A:MDove,我最近在思考一个问题:实例化子类,会实例化父类么?
MDove:这是一个有趣的问题。那我也反问你一个问题:抽象类可以被实例化么?
小A:虽然我很菜,但你请不要羞辱我!抽象类不能被实例化这是常识啊。
MDove:既然你知道这个,那我再问你:子类继承了抽象类。此时,实例化子类,那么父类作为抽象类你觉得能被实例化么?
小A:不能吧...
MDove:没错,就是如此啊。那你为什么还会有疑问呢?
疑惑点
小A:因为我在学习多态的时候,发现new子类,父类的构造方法也会执行,就像这样:
public static void main(String[] args) {
SunClass sunClass1 = new SunClass();
}
public class SunClass extends SuperClass {
public String mSunName;
public SunClass() {
mSunName = "Sun Name";
System.out.println(mSunName);
}
}
public class SuperClass {
public String mSuperName;
public SuperClass() {
mSuperName = "Super Name";
System.out.println(mSuperName);
}
}
小A:既然父类的构造方法都被执行了,那岂不也会实例化?
大错特错,父类不会实例化
MDove:有这样的疑问,属实不怪你。首先指出你一个错误:构造方法被执行不代表实例化这个类。为什么构造方法会被执行?那是因为要初始化父类变量。
MDove:我们来看一下这个demo的字节码:
MDove:看到红线的指令了吧?子类的构造方法里,调用了父类的构造方法。子类需要继承父类的public/protect类型的变量和方法。有继承,那么势必要调用构造方法对其进行内存分配。
小A:那如果这么说的话?如果父类是一个空实现,是不是就不用调用父类的构造方法了?
MDove:不会的,编译期会帮我们隐式的调用。
MDove:我最开始也有这个疑惑,既然父类都没有对变量进行赋值,为啥还这么费劲的调用它的构造方法?没错,我们的父类可能不需要初始化变量,但是!!它的父类不一定不需要啊!!别忘了,我们所有的类都隐式的继承了Object,你告诉我,怎么可以做到跳过调用父类的构造方法,而直接调用Object的构造方法?很显然,不好实现,因此逐级调用父类构造方法是一个合适的解决方案!
小A:这么一说还真是这回事。
从内存分配角度看构造方法
MDove:为了避免文字的苍白。这里我使用一些工具,让你看一看对象的内存分配。
这里使用的工具是AndroidStudio3.0,因为我是一名Android开发,而且AS3.0在内存查看方面做的很出色,所以就用这个工具来展开这个内容。
MDove:我简单新建了一个工程,很简单的一个操作:
findViewById(R.id.btn_ok).setOnClickListener(new View.OnClickListener() {
` @Override
public void onClick(View v) {
new SunClass();
}
});
MDove:接下来让我们在Android Profiler里边看一些我们的内存分配情况,这里我Dump了new子类的那个过程:
MDove:看到了吧?我们的包下面只有这么3个主要内存对象。一个是我们MainActivity这个无需多言。MainActivity$1是我们的Listener。而SunClass也就是我们new的子类。很明显,没有父类!那让我们来看一看SunClass的内存都分配了些啥:
MDove:看没看到父类的变量:mSuperName!而且在SunClass的对象里。清晰了吧?父类构造方法的执行,是为了给mSuperName这个变量进行赋值,而不是为了实例化父类。
小A:那如果我不在构造方法里边给变量赋值咋办?比如这样:
public class TestClass {
public String name = "TestClass";
public TestClass() {
}
}
MDove:你口中所谓的不在构造方法里边赋值,只是java层面的而已。我们看一看字节码就清楚了:
MDove:看到了吧?我们的java文件在被编译成class文件时。我们的编译器会把对应成员变量初始化操作的字节码写到构造方法里边,就酱~
小A:那我学习道路常遇到的一些尝试,比如:1、如果父类没有显示声明一个无参构造方法,编译器会隐式的增加一个无参构造方法。2、如果我们不显式的调用父类的构造方法,编译器会隐式的帮我们调用。是不是也是通过你说的这种形式去做?
MDove:没错,就酱~
小A:看样子,学习的道路上不能想当然,有些内容还要多思考,多实践才多~