最近重溫了《Thinking in Java》,發現了一個讓我為之興奮的知識漏洞,必須得分享一下。
上一篇的《Java類初始化的過程》的隨筆中,那個初始化順序并不完整。初始化的實際過程是:
然而,我們知道,當子類Sub繼承了父類Sup、并重寫了父類的方法draw()后,我們即使向上轉型為父類(即Sup sup=new Sub()),當我們調用sup.draw()方法的時候,它實際上調用的是Sub的draw方法。這里就有個坑了!
如果在父類的構造方法里調用draw()方法,從邏輯上,我們以為是調用父類的draw()方法,而實際上,即使在父類的構造器以內,Java編譯器讓它調用的還是子類的draw()方法。
我們用一個例子來展示一下:
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=5
看打印的第二行,邏輯上我們打印的本應該是Glyph.draw(),然而它被子類覆蓋了。而且,就算打印的是RoundGlyph.draw(),radius=0,那radius應該等于1才對??!不是的,按照類的初始化順序,先初始化的是父類Glyph的構造器,然后才輪到初始化RoundGlyph的成員變量radius。之所以radius=0,是因為Java類的加載機制的準備階段,即在用戶初始化變量之前,就已經為變量初始化為0了,關于Java虛擬機的類加載機制,可以看下《深入理解Java虛擬機》一書的第7章。
這種bug很難查找,但是又會破壞程序本身,讓我們忘bug興嘆。
所以我們在對構造器進行初始化的時候,要盡量簡單,盡量避免在構造方法內調用其他public的非構造方法(private方法可以調用,因為它不可被繼承)。
新聞熱點
疑難解答