最近重溫了《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方法能夠調用,由於它不可被繼承)。編譯器