概述
如果你看過一些JDK和框架源碼的話,就經常會發現一般在類的定義中,都會再定義一些其他的類,這些類也同樣會被編譯成字節碼文件,這樣的類就被叫做內部類,按照一般的分法,大致可以分爲以下四類:
- 成員內部類
- 局部內部類
- 匿名內部類
- 靜態內部類
接下來會針對這四種內部類進行詳細講解,旨在解釋這些類的特點和應用場景,如果你懶得看的話,可以直接翻到最底下看總結
我們預先定義好一個類,接來下的所有操作都會在這個類中執行:
class Demo {
public int pubVal = 1;
private int priVal = 1;
public static void staticMethod() {}
public void publicMethod() {}
private void privateMethod() {}
}
成員內部類
概念
從名字就可以看出,這種內部類是作爲類的成員而存在的,其定義位於類的內部,可以類比爲成員變量來理解
實現
class Demo {
// ...
class InternalClass {
}
// ...
}
這樣就是一個成員內部類了
約束
首先是類定義的約束,實際上,成員內部類的類定義沒有任何約束(不涉及static,因爲這屬於靜態內部類的範疇),不僅可以將內部類聲明爲public、private的,甚至可以將其聲明爲一個abstract抽象類和interface接口,以及繼承外部類也是允許的,其定義是十分寬鬆的
接着是內部屬性和方法,這裏就有一些約束了:
- 不能使用static來修飾任何成員內部類中的屬性和方法,但可以使用private、final等其餘任意修飾符
- 可以用static final來修飾成員
- 可以允許與外部類字段和方法重名
也就是說,成員內部類不能存在靜態屬性和方法,這麼做也是符合成員變量的含義
最後是訪問上的約束,這裏十分重要:
- 成員內部類也存在this指針,但是這個指針指向的是自己的引用,如果想訪問外部類,需要使用外部類名.this這個指針
- 可以通過外部類名.靜態字段來訪問外部類的靜態屬性或方法字段
這裏的限制就和成員方法是類似的,在理解時可以進行類比
使用
先來說聲明,以本文中程序爲例,我們可以通過下面的方式來聲明一個內部類:
Demo.InternalClass demo
注意,如果你將這個內部類聲明爲private的,外部類依然可以訪問,但是外部類之外的其他類是無法訪問到的
接着就是通過new來創建,我們在外部類的任何非靜態方法中,都可以通過new來進行創建,具體格式如下:
Demo.InternalClass demo = new Demo.InternalClass();
同樣地,我們可以在外部類中,通過內部類的實例來訪問其內部的私有變量和方法
局部內部類
我們剛花了大力氣來講解成員內部類,而局部內部類和成員內部類十分類似,可能一些相同的地方我就一筆帶過了
概念
局部內部類位於外部類成員方法的內部,可以類比局部變量
實現
public void publicMethod() {
class InternalClass {
}
}
約束
這裏的約束基本和成員內部類類似,我就單獨說一些不同的地方:
- 其類上不允許有任何修飾符,但是可以使用abstract將類聲明爲抽象類
- 不允許將局部內部類聲明爲接口
- 不允許使用static來聲明成員變量和方法
- 可以將局部內部類聲明在靜態方法中
- 任意兩個方法中的局部內部類可以重名
其餘的基本和成員內部類類似,把局部內部類當作成員內部類的局部變量版本就好理解了,比如也擁有外部類的指針,使用方法和成員內部類一致
使用
既然是局部內部類,就只能在聲明類的方法處來使用,聲明和使用方式如下:
public void publicMethod() {
class InternalClass {
}
InternalClass test = new InternalClass();
}
同樣地,我們依然可以無條件訪問內部類中定義的私有屬性
匿名內部類
這種內部類應該是我們使用的最多的一種,有時候甚至我們已經使用過了卻沒有發現
概念
匿名內部類沒有類的聲明,會隱式地繼承一個類或實現一個接口
實現
概念比較抽象,我們直接看是如何定義的,這裏我們有一個接受一個對象參數的方法:
private void privateMethod() {
new Demo() {
public int newVal = 20;
@Override
public void publicMethod() {
super.publicMethod();
}
};
}
這裏的Demo可以換成任意一個類或者接口,你會發現這個類沒有名字,所以被叫做匿名內部類
如果Demo是一個普通類,則匿名內部類相當於這個類的子類;如果Demo是一個接口或者抽象類,則這個匿名內部類相當於接口或抽象類的實現
約束
想要理解匿名內部類的約束,就需要將整個匿名內部類不要當成一塊程序邏輯來看,而應該當成一個對象來處理,整塊匿名內部類完全可以當成一個對象,可以調用對象的方法、屬性等等
其主要的約束有以下這些:
- 不能使用static來修飾方法和屬性,但是可以有static final的屬性
- 可以使用this指針來訪問本身定義的變量和繼承得到的變量,也可以使用外部類名.this指針來訪問外部類中的所有屬性
- 無法在類上進行任何修飾,因爲沒有class定義符和類名
- 其中定義的私有字段對外是完全可見的
使用
這裏就不進行過多講解了,完全和普通對象的用法一致,這裏就舉兩個簡單的例子:
int val = new Demo() {
private int newVal = 20;
@Override
public void publicMethod() {
super.publicMethod();
}
}.newVal;
Demo demo = new Demo() {
private int newVal = 20;
@Override
public void publicMethod() {
super.publicMethod();
}
};
靜態內部類
概念
靜態內部類相當於static修飾的成員內部類,可以當作靜態變量來理解
實現
class Demo {
// ...
static class InternalClass {
}
// ...
}
約束
這裏的約束就和之前的有很大不同了,如下:
- 可以使用任意的修飾符來修飾類、方法和屬性
- 可以訪問外部類的靜態方法和屬性
沒錯,廣義上,靜態內部類根本沒有約束
使用
使用有以下兩種情況:
在外部類的方法中使用時,通過如下語句來創建內部類對象:
InternalClass test = new InternalClass();
在外部類之外的其他類可以通過下面的語句來創建內部類對象:
Demo.InternalClass test = new Demo.InternalClass();
總結
最後,爲大家總結了一張表,基本上內部類的知識點都在這裏了:
成員內部類 | 局部內部類 | 匿名內部類 | 靜態內部類 | |
---|---|---|---|---|
類比 | 成員變量 | 局部變量 | 子類或接口的實現類 | 靜態變量 |
聲明位置 | 類的內部 | 作用域內部 | 任意對象可能出現的位置 | 與成員內部類一致 |
類修飾符 | 除static外任意 | 只允許用abstract修飾 | 無法修飾 | 任意的修飾符均可 |
變量和方法修飾符 | 除static外任意(但是可以用static final修飾變量) | 與成員內部類一致 | 與成員內部類一致 | 任意修飾符均可 |
外部類的訪問權限 | 均可 | 均可 | 均可 | 均可 |
聲明位置 | 只能在外部類的成員方法中,和外部類之外的其餘類中聲明(如果在外部類之外,要看類訪問控制是否允許) | 只能在作用域中聲明 | 無需聲明 | 任意位置均可 (如果在外部類之外,要看類訪問控制是否允許) |
創建位置 | 只能在外部類的成員方法中創建 | 只能在作用域中創建 | 立即創建 | 任意位置均可 (如果在外部類之外,要看類訪問控制是否允許) |
是否允許有內部類的內部類 | 可以,但是不允許有靜態內部類 | 與成員內部類一致 | 與成員內部類一致 | 可以 |