【专题讨论】【继承】父类构造器中调用了被子类重写的方法

53 阅读5分钟

父类构造器中调用了被子类重写的方法(重写的方法中处理了父子类中定义的同名实例变量)

专题讨论代码演练

========

1.1 父类构造器中调用了被子类重写的方法


public class ExtendTest {

public static void main(String[] args) {

Base base = new Base();//Base info override method

}

}

class Super{

Super(){

info();

}

void info(){

System.out.println("Super info method");

}

}

class Base extends Super{

void info(){

System.out.println("Base info override method");

}

}

测试代码后输出Base info override method

可以得出结论:如果在父类构造器中调用被子类重写的方法,则父类构造器调用的实际是子类重写后的方法。

这有点像多态,我们知道多态发生的条件是:子类重写了父类方法,父类引用指向子类对象,父类引用调用重写方法总表现为子类重写后的方法的行为。

但是这里没有显式的父类引用指向子类对象?我们需要知道多态的本质是什么?

多态的本质是:编译时类型和运行时类型不一致。

而多态的结果是什么?

对于方法而言,总是体现调用对象的运行时类型的类的行为。所以多态的表现还是看运行时类型的行为。

而在继承关系中,子类构造器总是会调用父类构造器。那它调用父类构造器干嘛呢?

子类构造器中使用super(实参列表)是为了(复用父类构造器初始化逻辑 )来初始化(继承自父类的实例变量)。

注意:此处操作的东西是继承自父类的实例变量。那你觉得这个实例变量到底是属于父类,还是子类?

略微思索一下就能得出,属于子类。而只有子类实例才能操作子类实例变量。所以super调用父类构造器的运行时类型是子类本身。

如果看到着你还不理解的话:可以用另一个简单的说明:子类构造器通过super()调用父类构造器时,会有父类对象吗?这个答案应该很清晰,没有。不会产生父类对象。在子类构造器中自始至终都只有一个对象:那就是子类对象。

所以父类构造器中调用重写方法的运行时类型还是子类,所以调用的是子类重写后的方法。

1.2 父类构造器中调用了被子类重写的方法(重写的方法中包含对子类实例变量的处理)


public class ExtendTest {

public static void main(String[] args) {

Base base = new Base();

System.out.println(base.i);//1

}

}

class Super{

Super(){

addI();

}

void addI(){}

}

class Base extends Super{

int i;

void addI(){

i++;

}

}

通过上一个讨论方向,我们得出,如果在父类构造器中调用被子类重写的方法,则父类构造器调用的实际是子类重写后的方法。

但是此时该重写方法中还操作了子类独有的实例变量。

我们知道子类构造器中会调用父类构造器,调用父类构造器的原因:将子类对象中继承自父类的实例变量初始化。、

但是此时实例变量i不是子类对象继承自父类的实例变量。那么父类构造器是不是会抛异常,i变量不存在呢?

不会。

为什么呢?我们能知道子类继承父类后,创建子类对象,该对象内存中会有两个区域,我们姑且叫他this区和super区。其中this存储子类独有的实例变量,super区存储继承自父类的实例变量。

当在一个方法中访问某变量时,系统会先去方法的局部变量中找,(如果上一步找不到)然后去调用该方法的对象的this区找,(如果上一步找不到)然后去调用该方法的对象的super区找。

所以父类构造器调用addI,而addI方法中操作了i实例变量,则直接先去对象的this区找,发现this区有i变量,所以就对它直接操作了。

注意啊:这个代码有的人测试出来输出0,那是因为你的代码可能抄错了,你将Base类中的 实例变量定义从 int i; 改成了 int i = 0;这样改了之后输出0,是因为创建对象时,实例变量初始化顺序导致的,特别时发生父子类关系时,更为复杂,这里我直接给出实例变量初始化顺序结论:

0.创建子类对象,为对象在堆中开辟空间,并为对象的实例变量(包含子类独有实例变量,和继承自父类的实例变量)执行默认初始化

1.父类构造器执行

1.1 执行父类实例变量声明时初始化或实例初始化块初始化(具体看二者定义先后顺序,先定义的先执行)

1.2 执行父类构造器体初始化逻辑

2.子类构造器执行

2.1 执行子类实例变量声明时初始化或实例初始化块初始化(具体看二者定义先后顺序,先定义的先执行)

2.2 执行子类构造器体初始化逻辑

所以根据这个顺序,我们再来看下int i = 0;之后的输出为什么是0?

在0步中,Base.i的值为0

在1.2步中,Base.i的值为1,

在2.1步中,Base.i的值又被声明时初始化改为0

下面时int i = 0;的完整代码,有兴趣的同学可以debug调试一下

public class ExtendTest {

public static void main(String[] args) {

Base base = new Base();

System.out.println(base.i);//0

}

}

class Super{

Super(){

addI();

}

void addI(){}

}

class Base extends Super{

int i = 0;

void addI(){

i++;

}

}

1.3 父类构造器中调用了被子类重写的方法(重写的方法中处理了父子类中定义的同名实例变量)


public class ExtendTest {

public static void main(String[] args) {

Base base = new Base();

System.out.println(base.i);//1

System.out.println(base.getSuperI());//0

}

}

class Super{

int i;

Super(){

addI();

}

void addI(){}

}

class Base extends Super{

int i;

void addI(){

i++;

}

int getSuperI(){

return super.i;

}

}

这个讨论方向和上一个讨论方向的原理一样,就不再赘述了。

结果输出是:

1

0

衍生题  


如果这题能弄明白,我估计这个专题差不多就到位了。

public class NewInstanceProcess {

public static void main(String[] args) {

new Baser();

}

}

class Superr {

int i = getI();

int getI() {

i++;

return i;

}

Superr(){

}

}

class Baser extends Superr {

int i = getI();