目前,見到的類、接口和枚舉類型都定義為頂層類型。也就是說,都是包的直接成員,獨立于其他類型。不過,類型還可以嵌套在其他類型中定義。這種類型是嵌套類型(nested type),一般稱為“內(nèi)部類”,是 Java 語言的一個強大功能。
嵌套類型的使用目的,都和封裝有關(guān)。
內(nèi)部類只是 Java 編譯器的概念,對于Java虛擬機而言,它是不知道內(nèi)部類這回事的,每個內(nèi)部類最后都會被編譯為一個獨立的類,生成一個獨立的字節(jié)碼文件。也就是說,每個內(nèi)部類其實都可以被替換為一個獨立的類。當(dāng)然,這是單純就技術(shù)實現(xiàn)而言。
內(nèi)部類可以方便地訪問外部類的私有變量,可以聲明為 private 從而實現(xiàn)對外完全隱藏,相關(guān)代碼寫在一起,寫法也更為簡潔,這些都是內(nèi)部類的好處。
嵌套類型也可以理解為通過某種方式和其他類型綁定在一起的類型,不作為完全獨立的實體真實存在。類型能通過四種不同的方式嵌套在其他類型中。
提示 內(nèi)部類編譯成功后生成的字節(jié)碼文件是“外部類$內(nèi)部類.class”。
1. 靜態(tài)成員類型 / 靜態(tài)內(nèi)嵌類
靜態(tài)成員類型是定義為其他類型靜態(tài)成員的類型。嵌套的接口、枚舉和注解始終都是靜態(tài)成員類型(就算不使用 static 關(guān)鍵字也是)。
2. 非靜態(tài)成員類/成員內(nèi)部類
“非靜態(tài)成員類型”就是沒使用 static 聲明的成員類型。只有類才能作為非靜態(tài)成員類型。
3. 局部類/方法內(nèi)部類
局部類是在 Java 代碼塊中定義的類,只在這個塊中可見。接口、枚舉和注解不能定義為局部類型。
4. 匿名局部類/匿名內(nèi)部類
匿名類也是一種局部類,但對 Java 語言來說沒有有意義的名稱。因此沒有名字。接口、枚舉和注解不能定義為匿名類型。
“嵌套類型”這個術(shù)語雖然正確且準(zhǔn)確,但開發(fā)者并沒有普遍使用,大多數(shù) Java 程序員使用的是一個意義模糊的術(shù)語——“內(nèi)部類”。根據(jù)語境的不同,這個術(shù)語可以指代非靜態(tài)成員類、局部類或匿名類,但不能指代靜態(tài)成員類型,因此使用“內(nèi)部類”這個術(shù)語時無法區(qū)分指代的是哪種嵌套類型。雖然表示各種嵌套類型的術(shù)語并不總是那么明確,但幸運的是,從語境中一般都能確定應(yīng)該使用哪種句法。
靜態(tài)成員類型
語法上,靜態(tài)內(nèi)部類除了位置放在其他類內(nèi)部外,它與一個獨立的類差別不大,可以有靜態(tài)變量、靜態(tài)方法、成員方法、成員變量、構(gòu)造方法等。

我們也可以看一些在 Java API 中使用靜態(tài)內(nèi)部類的例子:
? Integer 類內(nèi)部有一個私有靜態(tài)內(nèi)部類 IntegerCache,用于支持整數(shù)的自動裝箱。
? 表示鏈表的 LinkedList 類內(nèi)部有一個私有靜態(tài)內(nèi)部類 Node,表示鏈表中的每個節(jié)點。
? Character 類內(nèi)部有一個 public 靜態(tài)內(nèi)部類 UnicodeBlock,用于表示一個 Unicode block。
非靜態(tài)成員類
與靜態(tài)內(nèi)部類不同,除了靜態(tài)變量和方法,成員內(nèi)部類還可以直接訪問外部類的實例變量和方法,如 innerMethod 直接訪問外部類私有實例變量a。成員內(nèi)部類還可以通過“外部類.this.xxx”的方式引用外部類的實例變量和方法,如 Outer.this. action(),這種寫法一般在重名的情況下使用,如果沒有重名,那么“外部類.this. ”是多余的。
創(chuàng)建內(nèi)部類對象的語法是“外部類對象.new 內(nèi)部類()
public static void main(String arg[]) {
OuterOne.InnerOne i = new OuterOne().new InnerOne();
i.innerMethod();
}
成員內(nèi)部類有哪些應(yīng)用場景呢?如果內(nèi)部類與外部類關(guān)系密切,需要訪問外部類的實例變量或方法,則可以考慮定義為成員內(nèi)部類。外部類的一些方法的返回值可能是某個接口,為了返回這個接口,外部類方法可能使用內(nèi)部類實現(xiàn)這個接口,這個內(nèi)部類可以被設(shè)為 private,對外完全隱藏。
比如,在 Java API 的類LinkedList中,它的兩個方法 listIterator 和 descendingIterator 的返回值都是接口 Iterator,調(diào)用者可以通過 Iterator 接口對鏈表遍歷,listIterator 和 descend-ingIterator 內(nèi)部分別使用了成員內(nèi)部類 ListItr 和 DescendingIterator,這兩個內(nèi)部類都實現(xiàn)了接口 Iterator。
方法局部類
局部類在一個 Java 代碼塊中聲明,不是類的成員。只有類才能局部定義,接口、枚舉類型和注解類型都必須是頂層類型或靜態(tài)成員類型。局部類往往在方法中定義,但也可以在類的靜態(tài)初始化程序或?qū)嵗跏蓟绦蛑卸x。
因為所有 Java 代碼塊都在類中,所以局部類都嵌套在外層類中。因此,局部類和成員類有很多共同的特性。局部類往往更適合看成完全不同的嵌套類型。
- 局部類的特性
局部類有如下兩個有趣的特性:
和成員類一樣,局部類和外層實例關(guān)聯(lián),而且能訪問外層類的任何成員,包括私有成員;
除了能訪問外層類定義的字段之外,局部類還能訪問局部方法的作用域中聲明為 final 的任何局部變量、方法參數(shù)和異常參數(shù)。 - 局部類的限制
- 局部類的名稱只存在于定義它的塊中,在塊的外部不能使用。(但是要注意,在類的作用域中創(chuàng)建的局部類實例,在這個作用域之外仍能使用。稍后本節(jié)會詳細說明這種情況。)
- 局部類不能聲明為 public、protected、private 或 static。
- 與成員類的原因一樣,局部類不能包含靜態(tài)字段、方法或類。唯一的例外是同時使用 static 和 final 聲明的常量。
- 接口、枚舉類型和注解類型不能局部定義。
- 局部類和成員類一樣,不能與任何外層類同名。
- 前面說過,局部類能使用同一個作用域中的局部變量、方法參數(shù)和異常參數(shù),但這些變量或參數(shù)必須聲明為 final。這是因為,局部類實例的生命周期可能比定義它的方法的執(zhí)行時間長很多。
局部類用到的每個局部變量都有一個私有內(nèi)部副本(這些副本由 javac 自動生成)。只有把局部變量聲明為 final 才能保證局部變量和私有副本始終保持一致。
這一點從下述代碼中可以看出:
public class Weird {
// 靜態(tài)成員接口,下面會用到
public static interface IntHolder { public int getValue(); }
public static void main(String[] args) {
IntHolder[] holders = new IntHolder[10];
for(int i = 0; i < 10; i++) {
final int fi = i;
// 局部類
class MyIntHolder implements IntHolder {
// 使用前面定義的final變量
public int getValue() { return fi; }
}
holders[i] = new MyIntHolder();
}
// 局部類不在作用域中了,因此不能使用
// 但是在數(shù)組中保存有這個類的 10 個有效實例
// 局部變量fi現(xiàn)在已經(jīng)不在作用域中了
// 但仍然在那10個對象 getValue()方法的作用域中
// 因此,可以在每個對象上調(diào)用getValue()方法,打印fi的值
// 下述代碼打印數(shù)字 0 到 9
for(int i = 0; i < 10; i++) {
System.out.println(holders[i].getValue());
}
}
}
方法內(nèi)部類可以用成員內(nèi)部類代替,至于方法參數(shù),也可以作為參數(shù)傳遞給成員內(nèi)部類。不過,如果類只在某個方法內(nèi)被使用,使用方法內(nèi)部類,可以實現(xiàn)更好的封裝。
匿名局部類 / 匿名內(nèi)部類
匿名內(nèi)部類只能被使用一次,用來創(chuàng)建一個對象。它沒有名字,沒有構(gòu)造方法,但可以根據(jù)參數(shù)列表,調(diào)用對應(yīng)的父類構(gòu)造方法。它可以定義實例變量和方法,可以有初始化代碼塊,初始化代碼塊可以起到構(gòu)造方法的作用,只是構(gòu)造方法可以有多個,而初始化代碼塊只能有一份。因為沒有構(gòu)造方法,它自己無法接受參數(shù),如果必須要參數(shù),則應(yīng)該使用其他內(nèi)部類。與方法內(nèi)部類一樣,匿名內(nèi)部類也可以訪問外部類的所有變量和方法,可以訪問方法中的 final 參數(shù)和局部變量。

匿名內(nèi)部類能做的,方法內(nèi)部類都能做。但如果對象只會創(chuàng)建一次,且不需要構(gòu)造方法來接受參數(shù),則可以使用匿名內(nèi)部類,這樣代碼書寫上更為簡潔。
總結(jié)
內(nèi)部類本質(zhì)上都會被轉(zhuǎn)換為獨立的類,但一般而言,它們可以實現(xiàn)更好的封裝,代碼實現(xiàn)上也更為簡潔。
參考
丁振凡編著,《Java 語言程序設(shè)計(第2版)》華東交大版,2014.9
Java語言程序設(shè)計_中國大學(xué)MOOC(慕課)
https://www.icourse163.org/learn/ECJTU-1206089803?tid=1451269475#/learn/content?type=detail&id=1218983526&cid=1227151005Java 編程的邏輯-微信讀書
https://weread.qq.com/web/reader/b51320f05e159eb51b29226kc81322c012c81e728d9d180