天天看點

Effective Java學習筆記 4 通過私有構造器增強不可執行個體化的能力

有些類不希望被執行個體化,比如一些工具類(隻包含static方法和域),執行個體化沒有任何意義。為了防止這樣的類被執行個體化,不寫構造函數是沒有用的,因為預設的無參構造器(編譯器自動生成)可以用。

同時,把類寫成抽象類不可行。原因:

1. 繼承之後可以執行個體化;
    2. 更糟糕的是,還有可能讓使用者覺得這是為了繼承設計的。
           

解決方案:

加上私有構造器,并在方法體内部抛出異常(不是必須但是有效);最好再加上注釋,表明這個類不應該被執行個體化。

于是我就特意檢視Arrays的源碼發現:

// Suppresses default constructor, ensuring non-instantiability.
    private Arrays() {}
           

還有Math:

/**
     * Don't let anyone instantiate this class.
     */
    private Math() {}
           

當然,這樣的副作用就是無法繼承,因為要生成子類父類必須先構造,而這樣的類的構造器無法調用。

你以為這樣就安全了嗎?

太天真了…

我從Thinking in Java中看到Bruce Eckel在類型資訊這一章最後一節強行反射調用了内部類方法,匿名内部類方法,private方法。而想要調用private方法的關鍵就是:

setAccessible(true);

是以最後實作的關鍵代碼就是(異常已經在main中直接抛出):

//擷取構造函數,這裡隻有一個無參構造器
    Constructor con=Math.class.getDeclaredConstructor();
    //沒有這句話會抛出IllegalAccessException
    con.setAccessible(true);
    //構造和強轉
    Math math=(Math) con.newInstance();
    //驗證
    System.out.println(math.PI);
           
輸出結果是:3.141592653589793

當然,在Math類有構造器裡抛出異常的話…像這樣:

/**
     * Don't let anyone instantiate this class.
     */
    private Math() {throw new AssertionError()}
           

依然沒有用,相應的代碼隻需要把構造部分try catch不做任何處理就行。

//擷取構造函數,這裡隻有一個無參構造器
    Constructor con=Math.class.getDeclaredConstructor();
    //沒有這句話會抛出IllegalAccessException
    con.setAccessible(true);
    //構造和強轉
    try{
        Math math=(Math) con.newInstance();
        System.out.println(math.PI);
    }catch(Exception e){
        //do nothing 
    }
           

……

當然,如果你都花這麼大力氣去構造了,還有什麼理由要阻止你呢?畢竟在懸崖邊建立護欄隻是為了防止意外,你非要跳下去也許是為了飛起來吧。

:-P