嵌套類(nested class)是一個定義在另外一個類內部的類叹话。嵌套類應該僅僅是為了服務外部類而存在凌停。如果內嵌類在其他某些情形下有用,那么他應該是一個頂層類圈盔。有四種嵌套類:靜態(tài)成員類(static member class)豹芯、非靜態(tài)成員類(nonstatic member class)、匿名類(anonymous class)和本地類(local class)驱敲。只有第一種是被認為是內部類铁蹈。這個條目告訴你聲明時候使用哪種嵌套類以及為什么。
靜態(tài)成員類是嵌套類的最簡單的種類众眨。它最好被認為是一個普通類木缝,它只是恰好聲明在另外一個類的內部便锨,而且可以訪問外部類的所有成員,即使這些成員是聲明為私有的我碟。靜態(tài)成員類是它的外部類的靜態(tài)成員放案,而且就像其他靜態(tài)成員一樣遵從同樣的訪問規(guī)則。如果它是聲明為私有的矫俺,那么他僅僅可以在外部類內部訪問吱殉,諸如此類。
靜態(tài)內部類的一個通常使用是作為一個公開協助類厘托,僅僅是和它的外部類聯合使用友雳。例如,考慮一個枚舉铅匹,它描述由計算器支持的操作 (條目34)押赊。Operation枚舉應該是Calculator類的公開靜態(tài)成員。于是Calculator的客戶端應該引用一些操作包斑,這些操作使用像Calculator.Operation.PLUS和Calculator.Operation.MINUS這樣的名字流礁。
在語句構成上,靜態(tài)和非靜態(tài)成員類的唯一區(qū)別在于罗丰,靜態(tài)成員類在它們的聲明中有修飾符static神帅。盡管語法上的相似性,但是這兩種嵌套類是非常不同的萌抵。非靜態(tài)成員類的每個實例與包含類的外部實例相關聯找御。在非靜態(tài)成員類的實例方法里面,你可以調用外部實例(enclosing instance)的方法绍填,或者使用限定的(qualified)this結構體獲得外部實例的引用 [JLS, 15.8.4]霎桅。如果嵌套類的實例的存在脫離它的外部類的實例,那么嵌套類必須是一個靜態(tài)成員類:沒有外部實例讨永,創(chuàng)建一個非靜態(tài)成員類是不可能的滔驶。
當成員類實例創(chuàng)建時,非靜態(tài)成員類實例和它的外部實例的聯系建立住闯,而且在這之后不能改變。通常澳淑,這個聯系是從外部類的實例方法內部比原,通過調用一個非靜態(tài)成員類構造子自動建立的。手動使用enclosingInstance.new MemberClass(args)建立這個聯系杠巡,也是可能的量窘,雖然非常少見。就像你所預料的氢拥,這個聯系在非靜態(tài)成員類實例中占用了空間蚌铜,而且它的構造過程增添了時間锨侯。
非靜態(tài)成員類的一個通常使用是定義一個Adapter[Gamma95],它允許外部類的實例被看成是某個不相關類的實例冬殃。例如囚痴,Map接口的實現通常使用非靜態(tài)成員類實現他們的集合視圖(collection view),它們是由Map’s keySet审葬、entrySet和values返回深滚。相似地,集合接口涣觉,比如Set和List痴荐,它們的實現通常使用非靜態(tài)成員類來實現它們的迭代器:
// 非靜態(tài)成員類的典型使用
public class MySet<E> extends AbstractSet<E> {
... // 這個類的大部分省略
@Override public Iterator<E> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<E> {
...
}
}
如果你定義一個成員類,它不需要訪問外部實例官册,那么永遠把static修飾符放在它的聲明中生兆,讓它成為一個靜態(tài)而不是非靜態(tài)成員類。如果你省略這個修飾符膝宁,那么每個實例將有一個它的外部實例的額外隱含引用鸦难。就像前面提到的,存儲這個引用耗費時間和空間昆汹。更為嚴重的是明刷,它可能導致外部實例留存,原本它是適合垃圾收集(條目7)满粗。最終的內存泄漏可能是災難性的辈末。它通常很難監(jiān)測到,因為這個引用是不可見的映皆。
私有靜態(tài)成員類的一個通常使用是代表對象的組件挤聘,這個對象由它們外部類表示。例如捅彻,考慮Map實例组去,它把鍵和值聯系起來。許多Map實例步淹,對于映射中每個鍵值對从隆,有一個內部Entry實例。雖然每個entry和一個映射相聯系缭裆,但是entry的方法(getKey键闺、getValue和setValue)不需要訪問映射。所以澈驼,使用非靜態(tài)成員類來表示entry是很浪費的:私有靜態(tài)成員類是最好的辛燥。如果你在entry聲明中不慎忽略了static修飾符,這個映射仍然是工作的,但是每個entry將包含一個多余的對映射的引用挎塌,這就浪費了空間和時間徘六。
如果正在討論的類是一個導出類的公開或者受保護成員,在一個靜態(tài)和非靜態(tài)成員類之間正確地選擇是非常重要的榴都。在這種情況下待锈,成員類是一個導出API元素,而且在后續(xù)發(fā)布中不能把它從非靜態(tài)改變?yōu)殪o態(tài)成員類缭贡,而不會違反向后兼容性炉擅。
就像你所預料的,匿名類沒有名字阳惹。它不是它的外部類的一個成員谍失。不是和其他成員一起聲明,它是在使用時同時聲明和實例化的莹汤。代碼中一個表達式合法的任何地方快鱼,匿名類也是允許的。當且僅當它們在非靜態(tài)環(huán)境中發(fā)生纲岭,匿名類有外部類抹竹。但是即使它們在靜態(tài)環(huán)境中發(fā)生,它們也不能夠有任何靜態(tài)成員止潮,除了常數變量(constant variable)窃判,它是初始化為常數表達式的final原始或者字符串域,[JLS, 4.12.4]喇闸。
匿名類的應用有許多限制袄琳。除非在它們被聲明時,你不能夠實例化它們燃乍。你不能進行instanceof測試唆樊,或者做需要你命名這個類的任何其他事情。你不能聲明一個匿名類來實現多個接口刻蟹,或者同時擴展一個類和實現一個接口逗旁。使用匿名類的客戶端不能夠調用任何成員,除了從它的超類繼承而來的那些成員舆瘪。因為匿名類發(fā)生在表達式之中片效,它們應該保持簡短(大約十行或者更少),否則可讀性將會受損英古。
在Java添加lambda(第6章)之前淀衣,匿名類是隨手創(chuàng)建小函數對象(function object)和處理對象(process object)的優(yōu)選方式,而且哺呜,但是lambda現在是更優(yōu)的(條目42)舌缤。匿名類的另外一個通常使用是靜態(tài)工廠方法的實現(參考條目20中intArrayAsList)。
本地類是四種內嵌類中最少使用某残。一個本地類實際上可以在一個本地變量可以聲明的任何地方聲明国撵,而且遵從相同的作用域規(guī)則。本地類和其他類型的嵌套類有許多相同的特質玻墅。像成員類介牙,它們有名字,而且可以重復使用澳厢。像匿名類环础,只有當它們在非靜態(tài)情形中定義時,它們有外部實例剩拢,而且它們不能保護靜態(tài)成員线得。而且像匿名類,為了不傷害可讀性徐伐,它們應該保持簡短贯钩。
簡要概括,有四種不同嵌套類办素,而且每種有它的位置角雷。如果一個嵌套類需要在單個方法的外部可見,或者太長了而不適合在一個方法內部性穿,那么使用成員類勺三。如果成員類的每個實例需要它外部實例的引用,那么把它變成非靜態(tài)需曾;否則吗坚,把它變成靜態(tài)。假設類屬于一個方法的內部胯舷,如果你需要僅僅從一個位置創(chuàng)建實例刻蚯,而且有描述這個類的特性的已存類,那么把它變成匿名類桑嘶;否則炊汹,把它變成本地類。