Effective Java 2.0_中文版__Item 1

文章作者:Tyan
博客:noahsnail.com

第二章

這章是關于創建和銷毀對象的:什么時候怎樣創建它們,什么時候怎樣避免創建它們,怎樣確保它們被及時的銷毀,怎么管理任何清理操作,清理操作必須在對象銷毀之前。

Item 1: 考慮用靜態工廠方法代替構造函數

一個類允許客戶獲得它本身的一個實例通常的方式是提供一個公有的構造函數。還有另一種技術應該成為每個程序員工具箱中的一部分。一個類可以提供一種公有的static factory methodstatic factory method是一種簡單的靜態方法,它會返回一個類的實例。這有一個來自Boolean(基本類型boolean的封裝類)的簡單例子。這個方法將一個布爾值轉成Boolean對象的引用:

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

注意靜態工廠方法與Design Patterns中的Factory Method是不同的。這個條目中描述的靜態工廠方法與設計模式中的工廠方法是不等價的。

一個類可以為它的客戶提供靜態工廠方法來代替構造函數,或者除了構造函數之外再提供一個靜態工廠方法。提供靜態工廠方法代替公有構造函數既有優點也有缺點。

與構造函數相比,靜態工廠方法的第一個優勢是它們有名字。如果構造函數的參數本身不能描述返回的對象,具有合適名字的靜態工廠是更容易使用的,并且產生的客戶端代碼更易讀。例如,構造函數BigInteger(int, int, Random)返回一個BigInteger,這個BigInteger可能是一個素數,使用名字為BigInteger.probablePrime的靜態工廠方法來表示會更好。(這個方法最終在1.4版本被引入。)

一個類只能有一個具有指定簽名的構造函數。程序員知道怎樣規避這個限制:通過提供兩個構造函數,它們僅在參數列表類型的順序上有所不同。這真的是一個壞主意。使用這種API的用戶永遠不能記住哪一個構造函數是哪一個,最后會無意中調用錯誤的構造函數。使用這些構造函數的人在讀代碼時如果沒有類的參考文檔將不知道代碼要做什么。

因為靜態工廠方法有名字,因此它們不會有上一段討論的那種限制。當一個類似乎需要多個具有相同簽名的構造函數時,用靜態工廠方法代替構造函數,通過仔細選擇工廠方法的名字來突出它們的不同。

與構造函數相比,靜態工廠方法的第二個優勢是當調用靜態工廠方法時不要求每次都創建一個新的對象。這允許不可變類(Item 15)使用預創建的實例,或緩存構建好的實例,通過重復分發它們避免創建不必要的重復對象。Boolean.valueOf(boolean)方法闡明了這個技術:它從未創建對象。這項技術與Flyweight模式類似[Gamma95, p. 195]。如果經常請求相同的對象,它能極大的提升性能,尤其是在創建對象的代價較昂貴時。

靜態工廠方法能從重復的調用中返回相同的對象,在任何時候都能使類嚴格控制存在的實例。這些類被稱為控制實例。編寫控制實例類是有一些原因的。實例控制允許一個類保證它是一個單例(Item 3)或不可實例化的(Item 4)。它也允許一個不變的類(Item 15)保證不存在兩個相等的實例:a.equals(b)當且僅當a==b。如果一個類保證了這一點,它的客戶端可以使用==操作符代替equals(Object)方法,這可能會導致性能的提升。Enum類型(Item 30)保證了這一點。

與構造函數相比,靜態工廠方法的第三個優勢是它們能返回它們的返回類型的任意子類型的對象。這樣在選擇返回對象的類時有了更大的靈活性。

靈活性的一個應用是API能返回對象而不必使它們的類變成公有的。通過這種方式中隱藏實現類會有一個更簡潔的API。這項技術適用于基于接口的框架(Item 18),接口為靜態工廠方法提供了自然的返回類型。接口不能有靜態方法,因此按慣例,命名為Type的接口的靜態工廠方法被放在一個命名為Types的不可實例化的類中(Item 4)。

例如,Java集合框架有三十二個集合接口的便利實現,提供了不可修改的集合,同步集合等等。幾乎所有的這些實現都是通過靜態工廠方法導出在一個不可實例化的類中(java.util.Collections)。返回對象的類都是非公有的。

集合框架API比它導出的三十二個分開的公有類更小,每一個便利實現對應一個類。它不僅僅是API的數量在減少,還是概念上意義上的減少。用戶知道返回的對象含有接口指定的精確API,因此不需要閱讀額外的實現類的文檔。此外,使用這樣的靜態工廠方法需要客戶端使用接口引用返回的對象而不是使用它的實現類,這通常是最佳的實踐(Item 52)。

不僅公有靜態工廠方法返回對象的類可以是非公有的,而且這個類還可以隨著調用靜態工廠時輸入的參數值的變化而變化。聲明的返回值類型的任何子類都是可以的。為了增強軟件的可維護性及性能,返回值對象的類也可以隨著發布版本的變化而變化。

在1.5版本中引入類java.util.EnumSet(Item 32),它沒有公有的構造函數,只有靜態工廠方法。根據枚舉類型的大小,靜態工廠方法返回兩個實現中的一個,枚舉類型的分類:如果枚舉類型中有六十四個元素或更少,與大多數枚舉類型一樣,靜態工廠返回一個RegularEnumSet實例,由單個的long支持;如果枚舉類型中有六十五個元素或更多,靜態工廠方法返回一個JumboEnumSet實例,由long[]支持。

現有的兩個實現類對于客戶端是不可見的。如果RegularEnumSet對于較少數量的枚舉類型沒有提供性能優勢,那么在將來的版本中將其移除不會任何影響。同樣地,如果新的EnumSet實現在性能上更有優勢,在將來的版本中添加EnumSet的第三或第四個實現也不會有任何影響。客戶端不知道也不關心它們從工廠方法中得到的對象所屬的類;它們只關心它是EnumSet的某個子類。

在編寫靜態工廠方法所屬的類時,靜態工廠方法返回的對象所屬的類可以不必存在。這種靈活的靜態工廠方法形成了服務提供者框架的基礎,例如Java數據庫鏈接API(JDBC)。服務提供者框架是一個系統:多個服務提供者實現一個服務,系統為客戶端提供服務的多個實現,使客戶端與服務實現解耦。

服務提供者框架有三個基本的組件:服務接口,提供者實現;提供者注冊API,系統用來注冊實現,使客戶端能訪問它們;服務訪問API,客戶端用來得到服務實例。服務訪問API通常允許但不要求客戶端指定一些選擇提供者的規則。在沒有指定的情況下,API返回一個默認的實現實例。服務訪問API是"靈活的靜態工廠",其形成了服務提供者框架的基礎。

服務提供者框架的第四個可選組件是服務提供者接口,服務提供者通過實現這個接口來創建服務實現的實例。在沒有服務提供者接口的情況下,服務實現通過類名進行注冊,通過反射來進行實例化(Item 53)。在JDBC的案例中,Connection是服務接口,DriverManager.registerDriver是提供者注冊API,DriverManager.getConnection服務訪問API,Driver是服務提供者接口。

服務提供者框架模式有許多變種。例如,服務訪問API通過使用適配器模式[Gamma95, p. 139],能返回比提供者需要的更更豐富的服務接口。下面是服務提供者接口的一個簡單實現和默認的提供者:

    // Service provider framework sketch

    // Service interface
    public interface Service {
        ... // Service-specific methods go here
    }

    // Service provider interface
    public interface Provider {
        Service newService();
    }

    // Noninstantiable class for service registration and access
    public class Services {
        private Services() { }  // Prevents instantiation (Item 4)

        // Maps service names to services
        private static final Map<String, Provider> providers =
            new ConcurrentHashMap<String, Provider>();

        public static final String DEFAULT_PROVIDER_NAME = "<def>";

        // Provider registration API
        public static void registerDefaultProvider(Provider p) {
            registerProvider(DEFAULT_PROVIDER_NAME, p);
        }

        public static void registerProvider(String name, Provider p){
            providers.put(name, p);
        }

        // Service access API
        public static Service newInstance() {
            return newInstance(DEFAULT_PROVIDER_NAME);
        }
        
        public static Service newInstance(String name) {
            Provider p = providers.get(name);
            if (p == null)
                throw new IllegalArgumentException(
                    "No provider registered with name: " + name);
        return p.newService();
        }
    }

靜態工廠方法的第四個優勢是它們降低了創建參數化類型實例的冗長性。遺憾的是,當你調用參數化類的構造函數時,你必須指定類型參數,即使它們在上下文中是非常明顯的。這通常需要你緊接著提供兩次類型參數:

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

隨著類型參數長度和復雜性的增加,這個冗長的說明很快就讓人變得很痛苦。但是使用靜態工廠的話,編譯器可以為你找出類型參數。這被稱為類型推導。例如,假設HashMap由這個靜態工廠提供:

    public static <K, V> HashMap<K, V> newInstance() {
        return new HashMap<K, V>();
    }

你可以將上面冗長的聲明用下面簡潔的形式去替換:

    Map<String, List<String>> m = HashMap.newInstance();

某一天,Java語言可能在構造函數調用上也有與方法調用類似的類型推導,但到發行版本1.6為止,它一直沒有。

遺憾的是,但到發行版本1.6為止,標準集合實現例如HashMap沒有工廠方法,但你可以把這些方法放到你自己的工具類力。更重要的是,你可以在你自己的參數化類里提供這樣的靜態工廠。

只提供靜態工廠方法的缺點是沒有公有或保護構造函數的類不能進行子類化。公有靜態工廠返回的非公有類同樣如此。例如,不可能子類化集合框架中的這些便利實現類。可以說這是因禍得福,因為它鼓勵程序員使用組合來代替繼承(Item 16)。

靜態工廠方法的第二個缺點是它們不能很容易的與其它靜態方法進行區分。它們不能像構造函數那樣在API文檔中明確標識出來,因此很難弄明白怎樣實例化一個提供靜態工廠方法代替構造函數的類。Javadoc工具可能某一天會關注靜態工廠方法。同時,你可以通過在類中或接口注釋中注意靜態工廠和遵循通用命名約定來減少這個劣勢。下面是靜態工廠方法的一些常用命名:

  • valueOf — 不嚴格地說,返回一個與它的參數值相同的一個實例。這種靜態工廠是有效的類型轉換方法。

  • ofvalueOf的一種簡潔替代方法,通過EnumSet(Item 32)得到普及。

  • getInstance — 返回一個通過參數描述的實例,但不能說是相同的值。在單例情況下,getInstance沒有參數并且返回唯一的一個實例。

  • newInstance — 除了newInstance保證每個返回的實例都是與其它的實例不同之外,其它的類似于getInstance

  • getType — 類似于getInstance,當靜態工廠方法在不同的類中時使用。Type表示靜態工廠方法返回的對象類型。

  • newType — 類似于newInstance,當靜態工廠方法在不同的類中時使用。Type表示靜態工廠方法返回的對象類型。

總之,靜態工廠方法和公有構造函數都有它們的作用,理解它們的相對優勢是值得的。靜態工廠經常是更合適的,因此要避免習慣性的提供公有構造函數而不首先考慮靜態工廠。

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

推薦閱讀更多精彩內容