最近重温了《Thinking in Java》,发现了一个让我为之兴奋的知识漏洞,必须得分享一下。html
上一篇的《Java类初始化的过程》的随笔中,那个初始化顺序并不完整。初始化的实际过程是:ide
然而,咱们知道,当子类Sub继承了父类Sup、并重写了父类的方法draw()后,咱们即便向上转型为父类(即Sup sup=new Sub()),当咱们调用sup.draw()方法的时候,它实际上调用的是Sub的draw方法。这里就有个坑了!spa
若是在父类的构造方法里调用draw()方法,从逻辑上,咱们觉得是调用父类的draw()方法,而实际上,即便在父类的构造器之内,Java编译器让它调用的仍是子类的draw()方法。code
咱们用一个例子来展现一下:htm
public class Glyph { Glyph(){ System.out.println("Glyph before draw()"); draw(); //逻辑上本应该调用本类的draw(),然而结果不是 System.out.println("Glyph after draw()"); } void draw(){ System.out.println("Glyph.draw()"); } } public class RoundGlyph extends Glyph{ private int radius=1; public RoundGlyph(int r) { radius=r; System.out.println("RoundGlyph.RoundGlyph(),radius="+radius); } @Override void draw() { System.out.println("RoundGlyph.draw(),radius="+radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } }
打印结果是: 对象
Glyph before draw()
RoundGlyph.draw(),radius=0
Glyph after draw()
RoundGlyph.RoundGlyph(),radius=5blog
看打印的第二行,逻辑上咱们打印的本应该是Glyph.draw(),然而它被子类覆盖了。并且,就算打印的是RoundGlyph.draw(),radius=0,那radius应该等于1才对啊!不是的,按照类的初始化顺序,先初始化的是父类Glyph的构造器,而后才轮到初始化RoundGlyph的成员变量radius。之因此radius=0,是由于Java类的加载机制的准备阶段,即在用户初始化变量以前,就已经为变量初始化为0了,关于Java虚拟机的类加载机制,能够看下《深刻理解Java虚拟机》一书的第7章。继承
这种bug很难查找,可是又会破坏程序自己,让咱们忘bug兴叹。get
因此咱们在对构造器进行初始化的时候,要尽可能简单,尽可能避免在构造方法内调用其余public的非构造方法(private方法能够调用,由于它不可被继承)。编译器