為什么android API 中有很多對象的創(chuàng)建都是使用new關鍵字

為什么android API 中有很多對象的創(chuàng)建都是使用new關鍵字?
比起工廠方法、builder模式,java 中不提倡直接使用構造方法創(chuàng)建對象(new),為什么android API 中還是有很多對象的創(chuàng)建都使用構造方法 ?

這只是個草稿

首先,謝邀。
其次,是怎么找到我知乎賬號的,我隱藏的這么深(臉紅了)
最后,加入了自己的總結概括,讓然也可以當成讀書筆記來看。

我會很認真,很認真地回答問題噠,畢竟這是第一次回答專業(yè)相關的提問 : )

最近在溫習《Effective Java》這本書,真的是每一次都有新的收獲和認識。從第二章《創(chuàng)建和銷毀對象》開始,就涉及了“靜態(tài)工廠方法”,“構造器創(chuàng)建對象”等概念,篇幅不長,但實用性極強,且概括性極強,可謂句句精辟。

那么回到問題本身,其實在Java中,并不是不提倡直接使用構造函數來創(chuàng)建對象,而是在某些情況下,很難區(qū)分究竟調用哪個構造函數來初始化對象,或者說當函數簽名類似時,一不小心就使用了錯誤的構造函數,從而埋下難以發(fā)現的隱患,最后付出程序崩潰的代價,等等一系列“眼一花,手一滑”所導致的后果,或多或少給人們帶來“使用new關鍵字直接創(chuàng)建對象不靠譜”的錯覺,其實這種結論有些片面了,為什么呢?因為所有的用例都有一個場景約束,一旦脫離適用場景,強制使用總是很牽強的。OK,讓我們來再來細致的了解一下,或者說回顧一下。

考慮使用靜態(tài)工廠方法代理構造函數

假設你已經知道了這里的“靜態(tài)工廠”與設計模式中的“工廠模式”是兩碼事。

靜態(tài)工廠方法可以有突出的名稱

我們不能通過給類的構造函數定義特殊的名稱來創(chuàng)建具備指定初始化功能的對象,也就是說我們必須通過參數列表來找到合適的構造函數,即便文檔健全但仍很煩人,而且一旦使用了錯誤的構造函數,假如編譯期不報錯,一旦運行時奔潰,那就說明我們已經離錯誤發(fā)生的地方很遠了,而且錯誤的對象已經被創(chuàng)建了,不過謝天謝地,它崩潰了,如果不崩潰,我們將更難找到問題所在。所以,這個時候我們就需要使用“靜態(tài)工廠方法”了,因為有突出的名稱,因此它很直觀,易讀,能夠幫助我們避免這種低級錯誤的發(fā)生。當然,它的適用場景是存在多個構造函數,如果你只有一個構造函數,且希望被繼承,則完全可以使用new來創(chuàng)建對象。

靜態(tài)工廠方法可以使用對象池,避免對象的重復創(chuàng)建

反正這也應該是細節(jié)隱藏的,因此我們可以在“靜態(tài)工廠方法”的背景下,在類的內部維護一個對象緩存池。這使得不可變類可以使用預先構件好的實例,或者將構建好的實例緩存起來,重復利用,從而避免創(chuàng)建不必要的對象。

可以像Boolean.valueOf(boolean)那樣,使用預先創(chuàng)建好的實例。


  public static final Boolean TRUE = new Boolean(true);
  public static final Boolean FALSE = new Boolean(false);

  public static Boolean valueOf(boolean b) {
      return (b ? TRUE : FALSE);
    }

它從不創(chuàng)建新的對象,而且Boolean自身的不變性,因此能夠很好的使用預先創(chuàng)建好的實例。

或者像Parcel.obtain()那樣,在類的內部維護一個數組結構的緩存池


private static final int POOL_SIZE = 6;
private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];

/**
 * Retrieve a new Parcel object from the pool.
 */
public static Parcel obtain() {
    final Parcel[] pool = sOwnedPool;
    synchronized (pool) {
        Parcel p;
        for (int i=0; i<POOL_SIZE; i++) {
            p = pool[i];
            if (p != null) {
                pool[i] = null;
                if (DEBUG_RECYCLE) {
                    p.mStack = new RuntimeException();
                }
                return p;
            }
        }
    }
    return new Parcel(0);
}

也可以像Message.obtain()那樣,使用一個鏈表結構的緩存池


private static Message sPool;

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

需要注意的是,為這些對象添加一個正確的回收邏輯。

在這些場景下,我們能夠輕松的控制究竟使用緩存實例,還是創(chuàng)建新的對象,或者設計成單例,它完全是可控的,屬于“實例受控類”的范疇。相反地,如果你在設計類的時候考慮到,既不需要緩存,也不可能成為單例,那么你同樣可以,以直接new的方式來創(chuàng)建對象。

使用靜態(tài)工廠方法可以返回“原返回”類型的任何子類型

這樣,我們在選擇返回對象的類時就有了更大的靈活性。

這種靈活性的一種場景是,API可以返回對象,同時又不會使對象的所對應的類變成共有的。以這種方式隱藏實現類會使API變得非常簡潔。如Collections.unmodifiableList(list)


public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
    }

static class UnmodifiableRandomAccessList<E> extends UnmodifiableList<E>
                                              implements RandomAccess{

  UnmodifiableRandomAccessList(List<? extends E> list) {
      super(list);
  }
    ...
}

static class UnmodifiableList<E> extends UnmodifiableCollection<E>
                                  implements List<E> {

   final List<? extends E> list;

   UnmodifiableList(List<? extends E> list) {
       super(list);
       this.list = list;
   }
    ...
}

就像描述中的一樣,由于訪問域的限制,我們“永遠”無法在Collections類的外部直接初始化UnmodifiableRandomAccessListUnmodifiableList實例。

不過這也有個限制,我們只能通過接口"List"來引用被返回的對象,而不是通過它的實現類來引用,值得一提的是,通過接口或者抽象來引用被返回的對象,理應成為一種良好的習慣。

靜態(tài)工廠方法在創(chuàng)建參數化類型實例的時候,它們使代碼變得更加簡潔。

在調用參數化構造器時,即使類型參數很明顯,也必須指明。這通常需要連續(xù)兩次提供類型參數


  Map<String, List<String>> map = new HashMap<String, List<String>>();

  /*使用靜態(tài)工廠方法,編譯器會通過“類型推導”,找到正確的類型參數*/
  Map<String, List<String>> map1 = newInstance();
  public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
  }

不過現在編譯器或者說IDE已經足夠智能,上面第一個例子完全允許寫成:

Map<String, List<String>> map = new HashMap<>();

不必連續(xù)兩次提供類型參數。

上面提到的大都是使用“靜態(tài)工廠方法”相較于其他(創(chuàng)建對象方式)的優(yōu)勢,那么我們再來看看它有什么限制。

靜態(tài)工廠方法,類如果不含共有的或者受保護的構造器,就不能子類化

因為子類需要在構造函數中隱式調用父類的無參構造函數或者顯式調用有參構造函數,這和把類修飾成final所表達的效果一致。而一旦類中存在公有構造函數,也就是說客戶端可直接通過構造函數創(chuàng)建對象,也就弱化了靜態(tài)工廠方法約束性。

靜態(tài)工廠方法,它和其他靜態(tài)方法實際上沒有任何區(qū)別

一旦考慮使用“靜態(tài)工廠方法”,就必須考慮簡單,直觀,完善的命名,這的確是個頭疼的事 : (

遇到多個構造器參數時考慮使用構建器

其實,靜態(tài)工廠方法和構造函數都有局限性:“他們都不能很好的擴展到大量的可選參數”

在《Effective Java》舉了這樣一個經典的例子:

考慮用一個類表示包裝食品外面顯示的營養(yǎng)成分標簽。這些標簽中有幾個域是必需的:每份含量,每罐的含量以及每份的卡路里,還有超過20個可選域:總脂肪量、飽和脂肪量、轉化脂肪、膽固醇,鈉等等。

如果這種情況下依然堅持使用構造函數或者靜態(tài)工廠方法,那么要編寫很多重疊構造函數,而且對于那么多的可選域而言,這些重疊函數簡直就是噩夢!

避免代碼難寫,難看,難以閱讀,有兩種辦法可以解決。

JavaBeans模式

使用JavaBeans模式,把必需域作為構造函數的參數,可選域則通過setter方法注入。

我們都知道JavaBeans模式自身存在著嚴重的缺陷。因為構造過程可能被分到幾個調用中,在構造過程中JavaBean可能處于不一致狀態(tài)。類無法通過檢驗構造參數的有效性來保證一致性。而試圖使用處于不一致狀態(tài)的對象,將會導致失敗,這種失敗與包含錯誤代碼大相徑庭,因此調試起來十分困難。與此相關的另一點不足在于,JavaBeans模式阻止了了把類做成不可變的可能,這就需要程序員付出額外的努力來確保它的線程安全。

Builder模式

幸運地是,Builder模式既能保證像重疊模式那樣的安全性,也能保證JavaBeans模式那么好的可讀性。而且也能夠對參數進行及時的校驗,一旦傳入無效參數或者違反約束條件就應該立即拋出IllegalStateException異常,而不是等著build的調用,從而創(chuàng)建錯誤的對象。

那么我們真的需要把創(chuàng)建對象的方式更改為Builder嗎?
答案是,否定的。

我們可以在可選域多樣化的條件下,考慮使用這種模式,而且我們應該注意:不要過度設計API。

其實看完這些總結和經驗,我想你心里一定有明確的答案了,那就讓我們再來一句總結:
如果你的類足夠簡單,那么完全可以使用new來直接創(chuàng)建!切記過猶不及的API設計

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,714評論 18 399
  • 第5章 引用類型(返回首頁) 本章內容 使用對象 創(chuàng)建并操作數組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,261評論 0 4
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,186評論 30 471
  • 目錄 第二章 創(chuàng)建和銷毀對象 1 考慮用靜態(tài)工廠方法替代構造器 對于代碼來說, 清晰和簡潔是最重要的. 代碼應該被...
    高廣超閱讀 1,458評論 0 12
  • 今天開始,要進入爸爸全陪階段,我要發(fā)奮加班了。 第一天還是回家旁觀指導。真是好爸爸,對孩子一百二十個放心,把書丟給...
    Hisi閱讀 151評論 0 0