什麼
定義在一個類内部的類,稱為内部類(累不累),如下:
public class A {
private int c = 1;
public class C {
public void test() {
System.out.println("c:" + c);
}
}
}
C稱為A的内部類,簡稱内部類
A稱為C的外部類,簡稱外部類
而且内部類能通路外部類的成員(靜态成員、執行個體成員),當然有一些限制,限制如下
4種聲明方式
按照内部類的聲明方式,分為4種内部類:
-
靜态内部類
像類的靜态成員一樣聲明的類,就稱呼為“靜态内部類”
public class A { private static String b = "b"; private int c = 1; // B是A的靜态内部類 public static class B { public void test() { System.out.println(b); } } }
靜态内部類,隻能通路外部類的靜态成員(方法和變量),并且可以像類的成員一樣使用修飾符(public/protected/private);
建立靜态内部類對象的方式:
;A.B b = new A.B()
-
成員内部類
新類的執行個體成員(未加static修飾)聲明的類,稱為“成員内部類”
public class A { private static String b = "b"; private int c = 1; // C是A的成員内部類 public class C { public void test() { System.out.println(c); System.out.println(b); } } }
成員内部類,通路外部類的一切(靜态,還是執行個體),就像成員方法一樣,并且可以像類的成員一樣使用修飾符(public/protected/private)
建立成員内部類對象的方式:
A a = new A(); A.C c = a.new C();
-
方法内部類
在一個代碼塊聲明的類稱為方法内部類,代碼塊包括(方法内、靜态代碼塊内、執行個體代碼塊内)
public class A { private static String b; private int c; // 成員方法 public void test() { final int d = 1; // 方法内部類 class D { public void test() { // 通路靜态變量 System.out.println(b); // 通路執行個體變量 System.out.println(c); // 通路方法final類型的局部變量 System.out.println(d); } } } }
方法内部類,和它所在的方法(代碼塊),具有相同的通路能力,如果上面代碼是在static方法中聲明的,那麼内部類D不能通路c變量。
jdk1.8 方法内部類,能夠通路非final類型的局部變量,本質相當有在内部類D内儲存了副本
-
匿名内部類
匿名内部類也就是沒有名字的内部類
正因為沒有名字,是以匿名内部類隻能使用一次,它通常用來簡化代碼編寫
但使用匿名内部類還有個前提條件:必須繼承一個父類或實作一個接口
内部類的本質
内部類的文法頗為奇怪,我們來看看如下代碼,編譯後的位元組碼檔案!
public class A {
private static String b = "b";
private int c = 1;
// 靜态内部類
public static class B {
public void b() {
System.out.println(b);
}
}
// 成員内部類
class C {
public void c() {
System.out.println(c);
}
}
}

- A.java檔案被編譯成了多個class檔案
- A類對應A.class
- B類對應A$B.class
- C類對應A$C.class
内部類會被編譯成單獨的class檔案,那意味JVM解釋執行class檔案時類“B”和類A是獨立的,由此可以見内部類也是一種文法糖!
對于JVM來說,類A的private b和c 成員,怎麼能分别被類B和類C通路到的了!
用javap指令反編譯類A.class來看看:
秘密就來自,編譯器為外部類生的兩個靜态通路方法,
Stinrg access$000()
傳回b變量的值,
int access$100(A a)
傳回a對象的c成員變量值;
而在靜态内部類B中,編譯器将通路靜态變量b的地方替換為如上方法:
// 靜态内部類
public static class A$B {
public void b() {
System.out.println(A.access$000());
}
}
在成員内部類C中,原理也是如此,不過增加了更多的東西,反編譯A$C.class:
- 新增了成員字段
final A $this
- 構造方法添加形參 `A$C(A obj);
- 通路外部類成員變量的地方會被替換成:
System.out.println(A.access$100($this));
你一定會好奇成員構造方法中的外部類對象的參數從哪裡傳入的!看看我們是怎麼聲明内部類的對象的
A a = new A();
A.C c = a.new C();
将會被編譯器替換成:
A a = new A();
A$C c = new A$C(a);
内部類的使用時機
兩個類之間緊密聯系時,可以使用内部類:
- 當一個類需要通路另外一個類的許多屬性時,内部類可以簡化通路代碼
- 實作更好封裝性,比如:B 類僅僅被A類通路時,可以将B類作為A的私有内部類
- 使代碼更簡潔,匿名内部類