[TOC]
*****
# 17.1 內部類概述
Java語言中允許在一個類(或方法、代碼塊)的內部定義另一個類,后者稱為“內部類”,封裝它的類稱為“外部類”。內部類與外部類之間存在邏輯上的隸屬關系,內部類一般只用在封裝它的外部類或代碼塊中使用。
## 17.1.1 內部類的作用
內部類的作用如下:
1. 封裝。將不想公開的實現細節封裝到一個內部類中,內部類可以聲明為私有的,只能在所在外部類中訪問。
2. 提供命名空間。靜態內部類和外部類能夠提供有別于包的命名空間。
3. 便于訪問外部類成員。內部類能夠很方便訪問所在外部類的成員,包括私有成員也能訪問。
## 17.1.2 內部類的分類
內部類的分類如圖17-1所示,按照內部類在定義的時候是否給它一個類名,可以分為:有名內部類和匿名內部類。有名內部類又按照作用域不同可以分為:局部內部類和成員內部類,成員內部類又分為:實例內部類和靜態內部類。

# 17.2 成員內部類
成員內部類類似于外部類的成員變量,在外邊類的內部,且方法體和代碼塊之外定義的內部類。
## 17.2.1 實例內部類
實例內部類與實例成員類似,可以聲明為公有級別、私有級別、默認級別或保護級別,即4種訪問級別都可以,而外部類只能聲明為公有或默認級別。
**實例內部類示例代碼如下:**
```
//Outer.java文件
package no_17;
//外部類
public class Outer {
// 外部類成員變量
private int x = 10;
// 外部類方法
private void print() {
System.out.println("調用外部方法...");
}
// 測試調用內部類
public void test() {
Inner inner = new Inner();
inner.display();
}
// 內部類
class Inner {
// 內部類成員變量
private int x = 5;
// 內部類方法
void display() {
// 訪問外部類的成員變量x
System.out.println("外部類成員變量 x = " + Outer.this.x);
// 訪問內部類的成員變量x
System.out.println("內部類成員變量 x = " + this.x);
System.out.println("內部類成員變量 x = " + x);
// 調用外部類的成員方法
Outer.this.print();
print(); //8
}
}
}
```
如果內部類和外部類它們的成員命名沒有沖突情況下,在引用外部類成員時可以不用加“外部類名.this”,如代碼第8行的print()方法只有外部類中定義,所以可以省略Outer.this。
**測試內部HelloWorld代碼如下:**
```
//Outer.java文件
package no_17;
public class HelloWorld {
public static void main(String[] args) {
// 通過外部類訪問內部類
Outer outer = new Outer();
outer.test();
System.out.println("-------直接訪問內部類------");
// 直接訪問內部類
Outer.Inner inner = outer.new Inner(); //2
inner.display(); //3
}
}
```
通常情況下,使用實例成員內部類不是給外部類之外調用使用的,而是給外部類自己使用的。但是一定要在外部類的之外訪問內部類,Java語言也是支持的,見代碼第②行內部類的類型表示“外部類.內部類”,實例化過程是先實例化外部類,再實例化內部類,outer對象是外部類實例,outer.new Inner()表達式實例化內部類對象。 另外,HelloWorld與內部類Inner在同一個包中,內部類Inner和它的方法display()訪問級別都是默認的,它們對于在同一包中HelloWorld是可見的。
> **提示** 內部類編譯成功后生成的字節碼文件是“外部類$內部類.class”。
## 17.2.2 靜態內部類
靜態內部類與靜態成員類似,在聲明的時候使用關鍵字static修飾。靜態內部類只能訪問外部類靜態成員,所以靜態內部類使用的場景不多。但可以提供有別于包的命名空間。
**示例代碼如下:**
```
//Outer.java文件
package no_17;
//外部類
public class View {
// 外部類實例變量
private int x = 20;
// 外部類靜態變量
private static int staticX = 10;
// 靜態內部類
static class Button {
// 內部類方法
void onClick() {
//訪問外部類的靜態成員
System.out.println(staticX);
//不能訪問外部類的非靜態成員
// System.out.println(x); //編譯錯誤
}
}
}
```
上述代碼第③行定義了靜態內部類Button,在靜態內部類中可以訪問外部類的靜態成員,見代碼第⑤行。但是不能訪問非靜態成員,見代碼第⑥行試圖訪問外部類的x實例變量,會發生編譯錯**誤。
測試內部HelloWorld代碼如下:**
```
//HelloWorld.java文件
package com.a51work6;
public class HelloWorld {
public static void main(String[] args) {
// 直接訪問內部類
View.Button button = new View.Button();
button.onClick();
}
}
```
從代碼View.Button button = new View.Button()可見,在聲明靜態內部時采用“內部類.靜態內部類”形式,實例化也是如此形式。
> **提示** 如果不看代碼或文檔,View.Button形式看起來像是View包中的Button類,事實上它是View類中靜態內部類Button。View.Button形式客觀上能夠提供有別于包的命名空間,View相關的類集中管理起來,View.Button可以防止命名沖突。
# 17.3 局部內部類
局部內部類就是在方法體或代碼塊中定義的內部類,局部內部類的作用域僅限于方法體或代碼塊中。
局部內部類訪問級別只能是默認的,不能使用public、private和protected修飾。局部內部類也不能是靜態,即不能使用static修飾。局部內部類可以訪問外部類所有成員。
**示例代碼如下:**
```
~~~
//Outer.java文件
package com.a51work6;
//外部類
public class Outer {
// 外部類成員變量
private int value = 10;
// 外部類方法
public void add(final int x, int y) { //1
//局部變量
int z = 100;
// 定義內部類
class Inner { //2
// 內部類方法
void display() {
int sum = x + z + value; //3
System.out.println("sum = " + sum);
}
}
// Inner inner = new Inner();
// inner.display();
//聲明匿名對象
new Inner().display(); //4
}
}
~~~
```
上述代碼在add方法中定義了局部內部類,見代碼第②行,在內部類中代碼第③行訪問了外部類成員變量value、方法參數x和方法局部變量z,其中方法參數應該聲明為final的,見代碼第①行x參數是final的。
> **提示** 代碼第④行new Inner().display()是實例化Inner對象后馬上調用它的方法,沒有為Inner對象分配一個引用變量名,這種寫法稱為“匿名對象”。匿名對象適合只運行一次情況下。匿名對象寫法使代碼變得簡潔,但是給初學者閱讀代碼帶來了難度。
**測試內部HelloWorld代碼如下:**
```
//HelloWorld.java文件
package com.a51work6;
public class HelloWorld {
public static void main(String[] args) {
Outer outer = new Outer();
outer.add(100, 300);
}
}
~~~
```
# 17.4 匿名內部類
匿名內部類是沒有名字的內部類,本質上是沒有名的局部內部類,具有局部內部類所有特征。例如:可以訪問外部類所有成員。如果匿名內部類在方法中定義,它所訪問的參數需要聲明為final的。
**下面通過示例介紹一下匿名內部類。有如下一個View類:**
~~~
//View.java文件
package com.a51work6;
//外部類
public class View {
public void handler(OnClickListener listener) { ①
listener.onClick();
}
}
~~~
代碼第①行中handler方法需要一個實現OnClickListener接口的參數。OnClickListener接口代碼如下:
~~~
//OnClickListener.java文件
package com.a51work6;
public interface OnClickListener {
void onClick();
}
~~~
接口中只有一個onClick()方法。使用匿名內部類的示例代碼如下:
~~~
//HelloWorld.java文件
package com.a51work6;
public class HelloWorld {
public static void main(String[] args) {
View v = new View(); //1
// 方法參數是匿名內部類
v.handler(new OnClickListener() { //2
@Override
public void onClick() {
System.out.println("實現接口的匿名內部類...");
}
});
//繼承類的匿名內部類
Figure f = new Figure() { //3
@Override
public void onDraw() {
System.out.println("繼承類的匿名內部類...");
}
};
//具體類作為內部類
Person person = new Person("Tony", 18) { //4
@Override
public String toString() {
return "匿名內部類.實現 "
+ " Person[name=" + name
+ ", age=" + age + "]";
}
};
//打印過程自動調用person的 toString()方法
System.out.println(person);
}
}
~~~
在HelloWorld的main方法中,代碼第①行是實例化View對象,代碼第②行是調用它的handler方法,該方法需要一個實現OnClickListener接口的參數,new OnClickListener() {… }表達式是實際參數,它就是匿名內部類。表達式中OnClickListener是要實現的接口或要繼承的類,new是為匿名內部類創建對象,()是調用構造方法,{… }是類體部分。
代碼第③行是在賦值時候使用匿名內部類,其中Figure是14.1.2節使用過的抽象類。匿名內部類實現了它的抽象方法onDraw()。
代碼第④行也是在賦值時候使用匿名內部類,其中Person是13.4.2節使用過的具體類,匿名內部類覆蓋了toString()方法。它有個兩個參數的構造方法,匿名內部類使用了這個構造方法。
匿名內部類通常用來實現接口或抽象類的,很少覆蓋具體類。