Effective Java 第三版——24. 優先考慮靜態成員類

Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。ide

Effective Java, Third Edition

嵌套類(nested class)是在另外一個類中定義的類。 嵌套類應該只存在於其宿主類(enclosing class)中。 若是一個嵌套類在其餘一些狀況下是有用的,那麼它應該是一個頂級類。 有四種嵌套類:靜態成員類,非靜態成員類,匿名類和局部類。 除了第一種之外,剩下的三種都被稱爲內部類(inner class)。 這個條目告訴你何時使用哪一種類型的嵌套類以及爲何使用。學習

靜態成員類是最簡單的嵌套類。 最好把它看做是一個普通的類,剛好在另外一個類中聲明,而且能夠訪問全部宿主類的成員,甚至是那些被聲明爲私有類的成員。 靜態成員類是其宿主類的靜態成員,並遵循與其餘靜態成員相同的可訪問性規則。 若是它被聲明爲private,則只能在宿主類中訪問,等等。測試

靜態成員類的一個常見用途是做爲公共幫助類,僅在與其外部類一塊兒使用時纔有用。 例如,考慮一個描述計算器支持的操做的枚舉類型(條目 34)。 Operation枚舉應該是Calculator類的公共靜態成員類。 Calculator客戶端可使用Calculator.Operation.PLUSCalculator.Operation.MINUS等名稱來引用操做。翻譯

在語法上,靜態成員類和非靜態成員類之間的惟一區別是靜態成員類在其聲明中具備static修飾符。 儘管句法類似,但這兩種嵌套類是很是不一樣的。 非靜態成員類的每一個實例都隱含地與其包含的類的宿主實例相關聯。 在非靜態成員類的實例方法中,能夠調用宿主實例上的方法,或者使用限定的構造[JLS,15.8.4]得到對宿主實例的引用。 若是嵌套類的實例能夠與其宿主類的實例隔離存在,那麼嵌套類必須是靜態成員類:不可能在沒有宿主實例的狀況下建立非靜態成員類的實例。code

非靜態成員類實例和其宿主實例之間的關聯是在建立成員類實例時創建的,而且以後不能被修改。 一般狀況下,經過在宿主類的實例方法中調用非靜態成員類構造方法來自動創建關聯。 儘管不多有可能使用表達式enclosingInstance.new MemberClass(args)手動創建關聯。 正如你所預料的那樣,該關聯在非靜態成員類實例中佔用了空間,併爲其構建添加了時間開銷。對象

非靜態成員類的一個常見用法是定義一個Adapter [Gamma95],它容許將外部類的實例視爲某個不相關類的實例。 例如,Map接口的實現一般使用非靜態成員類來實現它們的集合視圖,這些視圖由Map的keySetentrySetvalues方法返回。 一樣,集合接口(如Set和List)的實現一般使用非靜態成員類來實現它們的迭代器:blog

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    @Override public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

若是你聲明瞭一個不須要訪問宿主實例的成員類,老是把static修飾符放在它的聲明中,使它成爲一個靜態成員類,而不是非靜態的成員類。 若是你忽略了這個修飾符,每一個實例都會有一個隱藏的外部引用給它的宿主實例。 如前所述,存儲這個引用須要佔用時間和空間。 更嚴重的是,而且會致使即便宿主類在知足垃圾回收的條件時卻仍然駐留在內存中(條目 7)。 由此產生的內存泄漏多是災難性的。 因爲引用是不可見的,因此一般難以檢測到。繼承

私有靜態成員類的常見用法是表示由它們的宿主類表示的對象的組件。 例如,考慮將鍵與值相關聯的Map實例。 許多Map實現對於映射中的每一個鍵值對都有一個內部的Entry對象。 當每一個entry都與Map關聯時,entry上的方法(getKeygetValuesetValue)不須要訪問Map。 所以,使用非靜態成員類來表示entry將是浪費的:私有靜態成員類是最好的。 若是意外地忽略了entry聲明中的static修飾符,Map仍然能夠工做,可是每一個entry都會包含對Map的引用,浪費空間和時間。接口

若是所討論的類是導出類的公共或受保護成員,則在靜態和非靜態成員類之間正確選擇是很是重要的。 在這種狀況下,成員類是導出的API元素,若是不違反向後兼容性,就不能在後續版本中從非靜態變爲靜態成員類。ip

正如你所指望的,一個匿名類沒有名字。 它不是其宿主類的成員。 它不是與其餘成員一塊兒聲明,而是在使用時同時聲明和實例化。 在表達式合法的代碼中,匿名類是容許的。 當且僅當它們出如今非靜態上下文中時,匿名類纔會封裝實例。 可是,即便它們出如今靜態上下文中,它們也不能有除常量型變量以外的任何靜態成員,這些常量型變量包括final的基本類型,或者初始化常量表達式的字符串屬性[JLS,4.12.4]。

匿名類的適用性有不少限制。 除了在聲明的時候以外,不能實例化它們。 你不能執行instanceof方法測試或者作任何其餘須要你命名的類。 不能聲明一個匿名類來實現多個接口,或者繼承一個類並同時實現一個接口。 匿名類的客戶端不能調用除父類型繼承的成員之外的任何成員。 由於匿名類在表達式中出現,因此它們必須保持短——約十行或更少——不然可讀性將受損。

在將lambda表達式添加到Java(第6章)以前,匿名類是建立小方法對象和處理對象的首選方法,但lambda表達式如今是首選(條目 42)。 匿名類的另外一個常見用途是實現靜態工廠方法(請參閱條目 20中的intArrayAsList)。

局部類是四種嵌套類中使用最少的。 一個局部類能夠在任何能夠聲明局部變量的地方聲明,並遵照相同的做用域規則。 局部類與其餘類型的嵌套類具備共同的屬性。 像成員類同樣,他們有名字,能夠重複使用。 就像匿名類同樣,只有在非靜態上下文中定義它們時,它們纔會包含實例,而且它們不能包含靜態成員。 像匿名類同樣,應該保持簡短,以避免損害可讀性。

回顧一下,有四種不一樣的嵌套類,每一個都有它的用途。 若是一個嵌套的類須要在一個方法以外可見,或者太長而不能很好地適應一個方法,使用一個成員類。 若是一個成員類的每一個實例都須要一個對其宿主實例的引用,使其成爲非靜態的; 不然,使其靜態。 假設這個類屬於一個方法內部,若是你只須要從一個地方建立實例,而且存在一個預置類型來講明這個類的特徵,那麼把它做爲一個匿名類; 不然,把它變成局部類。

相關文章
相關標籤/搜索