【Thinking in Java】編寫構造器時應注意:儘可能避免調用其餘非private方法

  最近重溫了《Thinking in Java》,發現了一個讓我爲之興奮的知識漏洞,必須得分享一下。html

  上一篇的《Java類初始化的過程》的隨筆中,那個初始化順序並不完整。初始化的實際過程是:ide

    1.   在其餘任何事物發生以前,將分配給對象的存儲空間初始化成二進制的0;
    2.   如上一篇的《Java類初始化的過程》那樣:父類的static成員變量和方法-->該類的static變量和方法-->開始實例化-->父類的普通成員變量和方法-->父類的構造方法-->該類的普通成員變量和方法-->該類的構造方法-->實例化結束

  然而,咱們知道,當子類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方法能夠調用,由於它不可被繼承)。編譯器

相關文章
相關標籤/搜索