Effective Java 3rd 條目24 靜態成員類優於非靜態
巢狀類(nested class)是一個定義在另外一個類內部的類。巢狀類應該僅僅是為了服務外部類而存在。如果內嵌類在其他某些情形下有用,那麼他應該是一個頂層類。有四種巢狀類:靜態成員類(static member class)、非靜態成員類(nonstatic member class)、匿名類(anonymous class)和本地類(local class)。只有第一種是被認為是內部類。這個條目告訴你宣告時候使用哪種巢狀類以及為什麼。
靜態成員類是巢狀類的最簡單的種類。它最好被認為是一個普通類,它只是恰好宣告在另外一個類的內部,而且可以訪問外部類的所有成員,即使這些成員是宣告為私有的。靜態成員類是它的外部類的靜態成員,而且就像其他靜態成員一樣遵從同樣的訪問規則。如果它是宣告為私有的,那麼他僅僅可以在外部類內部訪問,諸如此類。
靜態內部類的一個通常使用是作為一個公開協助類,僅僅是和它的外部類聯合使用。例如,考慮一個列舉,它描述由計算器支援的操作 (條目34)。Operation列舉應該是Calculator類的公開靜態成員。於是Calculator的客戶端應該引用一些操作,這些操作使用像Calculator.Operation.PLUS和Calculator.Operation.MINUS這樣的名字。
在語句構成上,靜態和非靜態成員類的唯一區別在於,靜態成員類在它們的宣告中有修飾符static。儘管語法上的相似性,但是這兩種巢狀類是非常不同的。非靜態成員類的每個例項與包含類的外部例項相關聯。在非靜態成員類的例項方法裡面,你可以呼叫外部例項(enclosing instance)的方法,或者使用限定的(qualified)this結構體獲得外部例項的引用 [JLS, 15.8.4]。如果巢狀類的例項的存在脫離它的外部類的例項,那麼巢狀類必須是一個靜態成員類:沒有外部例項,建立一個非靜態成員類是不可能的。
當成員類例項建立時,非靜態成員類例項和它的外部例項的聯絡建立,而且在這之後不能改變。通常,這個聯絡是從外部類的例項方法內部,通過呼叫一個非靜態成員類構造子自動建立的。手動使用enclosingInstance.new MemberClass(args)建立這個聯絡,也是可能的,雖然非常少見。就像你所預料的,這個聯絡在非靜態成員類例項中佔用了空間,而且它的構造過程增添了時間。
非靜態成員類的一個通常使用是定義一個Adapter[Gamma95],它允許外部類的例項被看成是某個不相關類的例項。例如,Map介面的實現通常使用非靜態成員類實現他們的集合檢視(collection view),它們是由Map’s keySet、entrySet和values返回。相似地,集合介面,比如Set和List,它們的實現通常使用非靜態成員類來實現它們的迭代器:
// 非靜態成員類的典型使用
public class MySet<E> extends AbstractSet<E> {
... // 這個類的大部分省略
@Override public Iterator<E> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<E> {
...
}
}
如果你定義一個成員類,它不需要訪問外部例項,那麼永遠把static修飾符放在它的宣告中,讓它成為一個靜態而不是非靜態成員類。如果你省略這個修飾符,那麼每個例項將有一個它的外部例項的額外隱含引用。就像前面提到的,儲存這個引用耗費時間和空間。更為嚴重的是,它可能導致外部例項留存,原本它是適合垃圾收集(條目7)。最終的記憶體洩漏可能是災難性的。它通常很難監測到,因為這個引用是不可見的。
私有靜態成員類的一個通常使用是代表物件的元件,這個物件由它們外部類表示。例如,考慮Map例項,它把鍵和值聯絡起來。許多Map例項,對於對映中每個鍵值對,有一個內部Entry例項。雖然每個entry和一個對映相聯絡,但是entry的方法(getKey、getValue和setValue)不需要訪問對映。所以,使用非靜態成員類來表示entry是很浪費的:私有靜態成員類是最好的。如果你在entry宣告中不慎忽略了static修飾符,這個對映仍然是工作的,但是每個entry將包含一個多餘的對對映的引用,這就浪費了空間和時間。
如果正在討論的類是一個匯出類的公開或者受保護成員,在一個靜態和非靜態成員類之間正確地選擇是非常重要的。在這種情況下,成員類是一個匯出API元素,而且在後續釋出中不能把它從非靜態改變為靜態成員類,而不會違反向後相容性。
就像你所預料的,匿名類沒有名字。它不是它的外部類的一個成員。不是和其他成員一起宣告,它是在使用時同時宣告和例項化的。程式碼中一個表示式合法的任何地方,匿名類也是允許的。當且僅當它們在非靜態環境中發生,匿名類有外部類。但是即使它們在靜態環境中發生,它們也不能夠有任何靜態成員,除了常數變數(constant variable),它是初始化為常數表示式的final原始或者字串域,[JLS, 4.12.4]。
匿名類的應用有許多限制。除非在它們被宣告時,你不能夠例項化它們。你不能進行instanceof測試,或者做需要你命名這個類的任何其他事情。你不能宣告一個匿名類來實現多個介面,或者同時擴充套件一個類和實現一個介面。使用匿名類的客戶端不能夠呼叫任何成員,除了從它的超類繼承而來的那些成員。因為匿名類發生在表示式之中,它們應該保持簡短(大約十行或者更少),否則可讀性將會受損。
在Java新增lambda(第6章)之前,匿名類是隨手建立小函式物件(function object)和處理物件(process object)的優選方式,而且,但是lambda現在是更優的(條目42)。匿名類的另外一個通常使用是靜態工廠方法的實現(參考條目20中intArrayAsList)。
本地類是四種內嵌類中最少使用。一個本地類實際上可以在一個本地變數可以宣告的任何地方宣告,而且遵從相同的作用域規則。本地類和其他型別的巢狀類有許多相同的特質。像成員類,它們有名字,而且可以重複使用。像匿名類,只有當它們在非靜態情形中定義時,它們有外部例項,而且它們不能保護靜態成員。而且像匿名類,為了不傷害可讀性,它們應該保持簡短。
簡要概括,有四種不同巢狀類,而且每種有它的位置。如果一個巢狀類需要在單個方法的外部可見,或者太長了而不適合在一個方法內部,那麼使用成員類。如果成員類的每個例項需要它外部例項的引用,那麼把它變成非靜態;否則,把它變成靜態。假設類屬於一個方法的內部,如果你需要僅僅從一個位置建立例項,而且有描述這個類的特性的已存類,那麼把它變成匿名類;否則,把它變成本地類。
相關文章
- Java的靜態成員類Java
- c#物件導向- 靜態成員和非靜態成員的區別C#物件
- C++類靜態成員C++
- 靜態資料成員和靜態成員函式函式
- c++類的靜態成員C++
- C++:類的靜態成員C++
- C++ 類的靜態成員C++
- 類內的靜態成員函式函式
- 靜態內部類和非靜態內部類區別
- C++類中的常成員和靜態成員C++
- net 靜態方法與非靜態方法
- Java中靜態跟非靜態的區別總結Java
- 注意!非靜態內部類和非靜態方法的匿名類的this$0屬性
- c# 用反射獲得靜態類成員C#反射
- Python的靜態方法和類成員方法Python
- Effective Java - 靜態方法與構造器Java
- 靜態變數和非靜態變數變數
- oop類的繼承與類靜態成員學習OOP繼承
- C# 靜態成員與例項成員C#
- java靜態內部類Java
- c++中的靜態成員C++
- C++ 靜態資料成員C++
- java之內部類(InnerClass)----非靜態內部類、靜態內部類、區域性內部類、匿名內部類Java
- C++類的靜態成員變數初始化C++變數
- TypeScript 中 class 的例項成員與靜態成員TypeScript
- C#快速入門教程(3)——類的靜態成員和例項成員C#
- 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。函式原型物件
- Java靜態代理Java
- C# 靜態類C#
- java 非靜態內部類與外部類引用之間的關係Java
- 基於NACOS和JAVA反射機制動態更新JAVA靜態常量非@Value註解Java反射
- 獨一無二----靜態成員變數 (轉)變數
- PHP類的靜態(static)方法和靜態(static)變數PHP變數
- JAVA 靜態代理 & 動態代理Java
- Java | 靜態巢狀類(Static Nested Class)Java巢狀
- Java靜態代理模式Java模式
- Java靜態匯入Java
- TypeScript 類靜態屬性TypeScript