Java 的各種內部類、Lambda表達式
內部類
內部類是指在一個外部類的內部再定義一個類。內部類的出現,再次打破了Java單繼承的局限性。
內部類可以是靜態 static 的,也可用 public,default,protected 和 private 修飾。(而外部頂級類即類名和文件名相同的只能使用 public 和 default)。
注意:內部類是一個編譯時的概念,一旦編譯成功,就會成為完全不同的兩個類。
對於一個名為outer的外部類和其內部定義的名為inner的內部類。編譯完成後出現 outer.class 和outer$inner.class 兩類。所以內部類的成員變數/方法名是可以和外部類的相同的。
1 成員內部類
也就是普通的內部類,它在外部類裡面一層。
-
成員內部類可以訪問外部類的所有成員和方法;
-
外部類要訪問成員內部類的成員和方法需要通過實例對象來訪問;
-
成員內部類不能含有 static 的變數和方法。
想一想,非static的內部類,在外部類載入的時候,並不會載入它;可是你這個內部類卻擁有 static 的變數和方法,這是必須先載入的,就會和這個內部類的載入時機衝突
事實上,這種寫法編譯器就能提示錯誤:

具體的使用方法,參考如下程式碼及注釋:
public class Outer{
private int outi = 0;//外部類的成員
//外部類的方法
public void outPrint(){
System.out.println("out");
}
//成員內部類
class Inner{
int ini = outi + 1;//內部類可以訪問外部類的成員變數
public void inPrint(){
outPrint();//內部類可以訪問外部類的成員方法
System.out.println("in");
}
}
public static void main(String[] args) {
Outer outer = new Outer();
//這裡因為main是靜態方法,訪問非靜態的 Inner對象需要一個Outer對象
Inner inner = outer.new Inner();
inner.inPrint();//外部類訪問內部類的成員方法
System.out.println(inner.ini);//外部類訪問內部類的成員變數
}
}
2 靜態內部類
解決普通的成員內部類不能含有 static 成員的問題,就是將成員內部類聲明為 static 。
可以這麼說:靜態內部類只能訪問其外圍類的靜態成員,除此之外與非靜態內部類沒有任何區別。
靜態內部類相當於一個邏輯上可以獨立出去的類,不過放在了內部而已。
- 靜態內部類不依賴於外部類的載入。(根據是否使用去載入,相當於內外平行的關係。)
- 靜態內部類不能直接訪問外部類的非靜態成員。(因為外部類載入的時候非靜態成員是沒有載入的,除非實例化之後)
對比普通成員內部類的程式碼如下:
public class Outer{
private int outi = 0;//外部類的成員
//外部類的方法
public void outPrint(){
System.out.println("out");
}
//靜態成員內部類
static class Inner{
Outer outer = new Outer();
int ini = outer.outi+1;//只能通過實例
//int ini = outi + 1;//不能訪問外部類的普通成員變數
public void inPrint(){
outer.outPrint();//只能通過實例
//outPrint();//不能訪問外部類的普通成員方法
System.out.println("in");
}
}
public static void main(String[] args) {
Inner inner = new Inner();//可以直接new出來,因為相當於是兩個獨立的類
inner.inPrint();
System.out.println(inner.ini);
}
}
對於靜態內部類來說,我們的感受會更加明顯,既然兩個類都沒什麼關係了,為什麼還要放在內部作為一個內部類呢?
其實就是為了某一些層級關係,當 Inner 的使用範圍很小,適用於包含在 Outer 類當中,但又不依賴於外在的類,就這麼寫,同時還能夠讓程式碼的類間層級關係更加清晰,而不用重新放在另一個文件。
3 局部內部類
局部內部類,是指內部類定義在方法內部,或者某一個作用域里的類。
- 局部內部類只能在方法裡面實例化,外面就不行了;
- 局部內部類訪問外部方法的變數,這個變數必須有 final 修飾。
例如下面的程式碼:
class Outter{
public void outMethod(){
final int i=0;
class Inner{
//使用i
}
Inner in=new Inner();
}
}
很明顯,和普通的成員內部類是一樣的定義,但是這裡強調的特殊點:i 為什麼必須是 final 的呢?
原因是:
- 當 JVM 運行到需要創建 Inner 對象之後,Outter 類已經全部運行完畢,那麼垃圾回收機制是有可能釋放掉局部變數 i 的,而如果按照普通的成員內部類的定義,i 應該可以訪問才對,那這個問題需要一個約束,所以編譯器解決了這個問題,他會在 Inner 類中創建了一個 i 的備份;
- 那麼就會有新的問題,外部類的 i 變化的時候,內部類的 i 要一致才行;
- 因此不得不規定死這些局部域必須是常量,必須用 final 修飾,保證數據的一致。
4 匿名內部類
4.1 匿名內部類
當一個局部內部類沒有名字,這就是匿名內部類。
- 實現的方式是建立一個帶內容的外部類、或者介面的子類匿名對象。
- 和普通的局部內部類又不同,訪問外部方法的變數,這個變數可以不是final修飾。(jdk1.8之後)
比如我們常見的兩種
public static void main(String[] args) {
//第一種方式
new Thread(){
@Override
public void run() {
System.out.println("內部類輸出……");
}
}.start();
//第二種方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("內部類輸出……");
}
}).start();
}
- 第一種方式就是,建立了一個帶內容的外部類,重寫了它的方法,但是這個類我們沒有起名字,而是直接創建了一個實例;
- 第二種方式就是通過實現一個介面,實現對應的方法,同樣,這個實現類我們也沒有起名字,而是直接創建了一個實例。
正是因為上面所說的,匿名內部類可以訪問外部的變數,而且不用是 final ,同理也可以持有外部類的引用,這種情況都可能會導致記憶體泄漏問題。
外部類的生命周期到了,但是卻因為內部類持有者外部類的引用,所以導致外部類無法被回收,造成記憶體泄漏。
解決方案就是:
- 使用靜態內部類,並且不要持有外部類的引用,如果要調用外部類方法或使用外部類屬性,可以使用弱引用(前面說過,靜態內部類是和外部類平行的,弱引用會安全)。
4.2 匿名內部類和 lambda 表達式
從 java 8 開始,引入了 Lambda 表達式,將程式碼塊作為參數使用更簡潔的程式碼來創建只有一個抽象方法的介面(這種介面被稱為函數式介面)的實例。
仍然用上面的匿名內部類的程式碼作為示例:
public static void main(String[] args) {
new Thread(()-> System.out.println("內部類輸出")).start();
}
可以看出,lambda 表達式代替匿名內部類的時候,lambda 程式碼塊寫的是代替實現抽象類的方法體,總結一下lambda表達式的語法主要由三部分構成:
-
形參列表,如果只有一個參數可以省略括弧,當無參數類型時可以使用()或者obj來代替。
-
箭頭 ->
-
程式碼塊部分,如果程式碼只有一行則可以省略掉花括弧,不然使用花括弧將lambda表達式的程式碼部分標記出來。
寫法方面,我們再自己定義一個介面,然後寫一下 lambda 式有參數的情況:
//自定義介面
interface Origin{
int sum(int a, int b);//待實現方法,有參數
}
public class LambdaTest {
public static void main(String[] args) {
//寫法1:使用lambda表達式實現介面
Origin o = (int a, int b)-> {
return a+b;
};
//寫法2:省略參數類型
Origin o1 = (a, b)->{
return a+b;
};
//寫法3,省略花括弧(只適用於方法實現只有一行的情況)
Origin o2 = (a, b)-> a+b;
System.out.println(o.sum(100,100));
}
}
4.3 java 的四種引用類型
-
強引用:
最常見的普通對象引用,只要還有強引用指向一個對象,說明那個對象還活著,垃圾回收不會回收這種對象;
-
弱引用:
垃圾回收器一旦發現只具有弱引用的對象,不管當前記憶體空間是否足夠,都會回收他的記憶體。(即使弱引用被其他強引用引用,還是會被回收)
-
軟引用:
如果一個對象只具備軟引用,如果記憶體空間足夠,那麼 JVM 就不會 GC 它,如果記憶體空間不足了,就會GC該對象。
-
虛引用:
如果一個對象只具有虛引用,那麼它就和沒有任何引用一樣,隨時會被JVM當作垃圾進行GC。
不會被回收的情況?
強引用,只要還存在強引用就不會被回收。
關於強引用和弱引用帶來的問題, 最明顯的就在 ThreadLocal 的使用上。