作者有话说
大家好!仓鼠学习java已经很久啦,由于这本书的知识有点多,学习历史久远,所以仓鼠决定做一个回顾笔记。前面的章节就不做回顾了,本篇文章重点讨论其书第5章节:初始化与清理 所涉及到的对象初始化顺序问题。
1.初始化问题导读
我们知道,为了确保安全,在创建java对象时,系统会自动调用无参(默认)或有参的构造器来创建对象,构造器(方法)与类的名称相同。与此同时,每一个类中其对应的类似于int,double的属性也会被初始化(可以理解为赋值)。但是,你有没有想过,会不会发生一个属性还未初始化却已经被调用的情况呢?
2.代码示例
废话不多说,先上一个简单案例
//这是一个Glyph父类
class Glyph{
void draw() { print( "Glyph.draw()" ); }
Glyph(){
print( "Glyph() before draw()" );
draw();print( "Glyph() after draw()" );
}
}
//这是一个继承Glyph的RoundGlyph子类
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
print( "RoundGlyph.RoundGlypn, radius= " + radius);
}
void draw(){
print(""RoundGlyph.draw(), radius= " +radius);
}
}
//下面调用main函数
public static void main(String[] args){
new RoundGlyph(5);
}
程序输出结果:
Glyph() before draw()
RoundGlyph.draw , radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph() , radius = 5
根据输出的结果,我们能很容易看到方法调用顺序。首先,main函数新建了一个RoundGlyph对象,于是其构造方法开始调用,并且此时传入一个参数r=5,但是由于其继承自父类Glyph,所以系统会先进入其父类的构造函数。按照顺序,其父类构造函数依次按顺序向下调用,调用完毕后,返回子类构造函数,根据代码 “radius=r” 将5赋值给radius,再沿着顺序往下调用子类构造函数的内容。但是,注意看输出结果的第二行:radius = 0。首先我们可以知道这个输出是由Glyph(父类)在构造器里调用子类方法 draw()(因为此时这个同名方法已经被子类重载)得到的,但是它输出的值却是0,而这就是由于初始化顺序造成的问题。子类的参数在被调用之前,并没有被合理的初始化。3.学习总结
下面附一个初始化的初步总结:
1、在任何事情发生之前,将分配给对象的存储空间初始化为0;2、从根基类也就是父类开始递归,按照类中的声明顺序调用静态代码块、静态成员的初始化方法。3、从根基类开始,按照类中的声明顺序调用非静态代码块和非静态成员初始化方法,再调用构造函数,然后向子类递归初始化;需要注意的是final会造成一些特例,这个暂且不表。根据上面的例子,我们可以得出以下结论:在父类构造函数内部调用具有多态行为的函数将导致无法预测的结果,因为此时会调用子类重载(override)后的方法,而此时所有子类的非静态成员可能还没有初始化,不会得到我们想要的结果。
觉得有用的话关注微信号哦~ 微信号cangshuxuexi,更多学习干货等着你。