Java-匿名內部類
我們先分析匿名內部類的結構,然后逐一解釋,最后以下羅列的問題都會在下面的內容中一一得到解答 :
匿名內部類到底是什么?
我們為什么要學習匿名內部類 ?
匿名內部類都有怎樣的作用 ?
匿名內部類應用的場景又有哪些 ?
匿名內部類是否有缺陷?
讓我們帶著這些問題來學習Java中的匿名內部類吧 !
結構決定性質
結構
- 匿名內部類基本語法 :
new 父類構造器(參數) / 實現接口() {// 類的主體部分
};
-
解釋匿名內部類語法中所有的概念:
-
new 父類構造器(參數)
表示匿名內部類是某個類的子類實例。 -
實現接口()
表示匿名內部類是某個接口的實現實例。 -
{ ... }
內部是匿名內部類的主體部分,包含類的字段、方法等定義。
-
性質
匿名內部類(Anonymous Inner Class)是一種在聲明和創建對象的同時定義類的方式,它沒有顯式的類名。通過 匿名內部類
看這幾個字的字面意思我們都知道這是個沒有名字的類,即 非具名類 . 以下是匿名內部類的具備的一些性質 :
- 可以實現接口或繼承類: 匿名內部類可以實現接口或繼承某個類,從而提供具體的實現。
- 沒有顯式的類名: 匿名內部類沒有顯式的類名,因為它是一種臨時的、一次性的實現。
- 一次性使用: 通常用于臨時的、一次性的場景,不需要復用。因為匿名內部類沒有類名,所以無法在其他地方重復使用。
- 可以訪問外部類的成員: 匿名內部類可以訪問外部類的成員,包括成員變量和方法。對于外部類的局部變量,有一些規則,比如必須是
final
或者事實上是final
的。 - 可以包含字段和方法: 在匿名內部類的主體部分,可以包含字段(成員變量)和方法的定義。
- 不可以包含靜態成員: 匿名內部類不能包含靜態成員,包括靜態方法和靜態變量。
我們來一個一個的解釋,同時我也會拿具體的代碼演示.
可以實現接口或繼承類
- 匿名內部類可以用來實現接口或者繼承類,這也是匿名內部類最常用的一個用法,我們在實際開發中,如果需要實現(重寫)某個接口(類)的方法,而且這個方法只是臨時使用,不需要復用,而且會出現很多這樣的場景,我們肯定是不想一一都去額外的封裝一個類去實現接口或者繼承類然后再創建類的實例,取調用我們需要的方法,這個時候我們就 匿名內部類 就排上用場啦,我們可以通過匿名內部類去臨時創建一個接口的實例(接口不能實例化,這里只是形象比喻一下)或者創建一個臨時的子類重寫了父類方法的實例. 總結一下 : 當匿名內部類需要實現接口或者繼承類時,它可以直接在創建對象的地方定義類的主體,而不需要顯式地聲明一個具名的類。
package src.demo;//定義一個接口
interface Chef{void cook();
}class Sever{public void shangCai(){System.out.println("服務員上菜");}
}public class Demo01 {public static void main(String[] args) {//使用匿名內部類來實現接口Chef chef1 = new Chef() {@Overridepublic void cook() {System.out.println("炒蛋炒飯");}};//調用我們使用匿名內部類實現接口實例中的抽象方法chef1.cook();//使用匿名內部類重寫父類方法Sever sever = new Sever() {@Overridepublic void shangCai() {System.out.println("服務員上菜->蛋炒飯");}};//調用我們使用匿名內部類重寫父類的方法sever.shangCai();}
}
通過上述的代碼,我演示了兩種使用匿名內部類的情況:
-
實現接口: 通過匿名內部類實現了
Chef
接口,提供了cook
方法的具體實現。Chef chef1 = new Chef() {@Overridepublic void cook() {System.out.println("炒蛋炒飯");} };
這部分代碼演示了匿名內部類用于實現接口的情況,通過創建一個實現了
Chef
接口的匿名內部類的實例,重寫了接口中的抽象方法cook
。 -
重寫父類方法: 通過匿名內部類重寫了
Sever
類的shangCai
方法。Sever sever = new Sever() {@Overridepublic void shangCai() {System.out.println("服務員上菜->蛋炒飯");} };
這部分代碼演示了匿名內部類用于重寫父類方法的情況,通過創建一個繼承自
Sever
類的匿名內部類的實例,重寫了Sever
類的方法shangCai
。
通過這兩種使用情況,你展示了匿名內部類在實現接口和重寫父類方法時的便利性。匿名內部類可以直接在創建對象的地方提供類的定義,避免了顯式地聲明一個具名的類,尤其適用于一次性的實現或重寫。
沒有顯式的類名
沒有顯式的類名,這意味這我們使用匿名內部類創建出來的實例是無法通過類名去訪問的,如果我們需要實現函數回調的話,我們就可以使用父類引用或者接口引用去接收. 所以,我們可以知道 **匿名內部類是必須基于已存在的類或者接口,因為它的實質是在這個基礎上創建一個新的匿名子類或者實現一個匿名實例,**這樣做的優勢就是我們省略了顯式的創建一個具名子類的步驟,這對于我們一些簡單的或者一次性的任務是非常方便的,因我們我們可以在需要的地方直接實現類的功能,而無需為此專門定義一個新的類,這使得代碼可以更為緊湊和直觀.
一次性使用
匿名內部類具備這個性質的理由就是 : 因為匿名內部類沒有類名,所以無法在其他地方重復使用。
可以訪問外部類的成員
實際需求中如果我們需要使用匿名內部類訪問外部類的一些成員(包括屬性和方法) , 那么其中是否有什么限制或者規則嗎 ? 有的 ! 我們后續通過代碼測試也可以知道,在這里我們先提前說明有怎樣的規則 :
匿名內部類可以訪問外部類的成員,包括成員變量和方法。對于外部類的局部變量,有一些規則,比如必須是final
或者事實上是final
的.
(1)外部類的成員變量:
匿名內部類中可以訪問外部類的成員變量,并且可以進行修改。這是因為外部類的成員變量在匿名內部類中有完整的作用域,而且匿名內部類實際上是外部類的一個擴展,可以直接訪問外部類的成員。(需要注意的是 : 在匿名內部類的方法中,可以訪問和修改外部類的成員變量。這些修改在匿名內部類的方法中生效,但不會影響外部類實例之外的其他代碼。這是因為匿名內部類實際上是一個新的類,其代碼塊被包含在外部類的方法內部,而成員變量的修改是在匿名內部類的方法中執行的。)
實例代碼如下 :
package src.main;public class Example {private int memberVar = 10;public void modifyMemberVar() {// 匿名內部類Runnable r1 = new Runnable() {@Overridepublic synchronized void run() {// 訪問外部類的成員變量System.out.println("Before modification: " + memberVar);// 修改外部類的成員變量memberVar = 20;System.out.println("After modification: " + memberVar);}};Runnable r2 = new Runnable() {@Overridepublic synchronized void run() {// 訪問外部類的成員變量System.out.println("Before modification: " + memberVar);// 修改外部類的成員變量memberVar = 20;System.out.println("After modification: " + memberVar);}};// 使用匿名內部類的實例new Thread(r1).start();new Thread(r2).start();}public static void main(String[] args) {Example example = new Example();example.modifyMemberVar();}
}
- 代碼中,匿名內部類實現了
Runnable
接口,其中的run
方法中訪問并修改了外部類Example
的成員變量memberVar
。 - 匿名內部類的作用域包括了它所在的方法,也就是
modifyMemberVar
方法。在這個方法中,您創建了兩個不同的匿名內部類的實例(r1
和r2
),每個實例都有自己的run
方法。因此,每個實例的run
方法中的對memberVar
的修改是在各自匿名內部類的作用域內完成的。 - 在
modifyMemberVar
方法中創建了兩個線程,每個線程都啟動了一個匿名內部類實例。這意味著兩個線程可以并發地執行各自匿名內部類的run
方法,但彼此之間的修改不會相互干擾。
(2)外部類的局部變量:
匿名內部類可以訪問外部類的成員變量和方法,但如果要訪問外部方法中的局部變量,這個局部變量必須是 final 或者是 effectively final。這是因為匿名內部類的實例可能會在方法執行完畢之后仍然存在,而且對局部變量的引用是在匿名內部類中存儲的。如果局部變量不是 final 或者 effectively final,那么在方法執行完畢后,外部局部變量的生命周期結束,但匿名內部類的實例可能仍然存在,這時如果訪問這個局部變量就會出現問題。
這也就是我們說的 變量捕獲機制,確切地說是對局部變量的捕獲機制。
變量捕獲機制的關鍵是,編譯器會生成一個匿名內部類的構造函數,并將外部的局部變量傳遞給這個構造函數,以確保匿名內部類在之后仍然能夠訪問這些變量。這種捕獲方式確保了在匿名內部類中對外部局部變量的訪問是安全的。
示例代碼如下:
public class Example {public void someMethod() {final int localVar = 42;//局部變量
//局部變量必須是 final 或者是 effectively finalRunnable r = new Runnable() {@Overridepublic void run() {//localVal = 1; 報錯,無法修改System.out.println(localVar);//在匿名內部類中訪問外部類的局部變量}};// 使用匿名內部類的實例new Thread(r).start();}
}
- 在外部類修改局部變量的值(報錯)

- 在匿名內部類中修改局部變量的值(報錯)

- 不修改外部類局部變量的值,而是顯式的讓其被final修飾

- 不修改外部類的局部變量的值,而是隱式的意味著事實上是final,也就是局部變量一旦被定義就不再被修改,那么在事實上就可以認為這個局部變量是一個final修飾的了,而且就是修改了,在匿名類中訪問這樣的被修改的外部類的局部變量是會報錯的!!! 因為變量捕獲機制的存在 !!!

在上述代碼中,localVar
是一個局部變量,因為匿名內部類 Runnable
中引用了這個局部變量,所以必須聲明為 final
。
(3) 所以,總結一下:
- 外部類的成員變量可以在匿名內部類中被訪問和修改。
- 外部類的局部變量必須要求是
final
的,或者是事實上是final
的,才能在匿名內部類中被訪問。(變量捕獲機制)
可以包含字段和方法
貼代碼
interface Greeting {void greet();
}public class AnonymousInnerClassExample {public static void main(String[] args) {// 使用匿名內部類實現接口并包含字段和方法Greeting greeting = new Greeting() {//字段private String message = "Hello from anonymous inner class";@Overridepublic void greet() {System.out.println(message);sayGoodbye(); // 調用匿名內部類中定義的方法}//方法private void sayGoodbye() {System.out.println("Goodbye from anonymous inner class");}};// 調用實例方法greeting.greet();}
}
不可以包含靜態成員
這是我的理解 :
匿名內部類的本質是一個實例,這個實例可以用來重寫父類中的實例方法,也可以用來實現接口中的抽象方法,也可以用來實現抽象類中的抽象方法,但是不可以聲明靜態成員變量或者定義靜態方法,雖然可以重寫父類的靜態方法,但是重寫的靜態方法是通過實例調用的,這違背了java面向對象編程的理念.所以匿名內部類是一個非具名的實例,之所以叫做類是因為匿名內部類既完成了拓展原有類或者接口的目的又完成了實例的創建.
( 匿名內部類是一個實例,而不是一個類。靜態成員是屬于類的,而匿名內部類沒有類名,無法定義屬于自己的靜態成員。)
貼代碼 :
package src.main;public class AnonymousInnerClassExample {public static void main(String[] args) {// 嘗試在匿名內部類中定義靜態成員(編譯錯誤)Runnable myRunnable = new Runnable() {// 嘗試定義靜態成員變量(編譯錯誤)// static int staticVariable = 20;@Overridepublic void run() {// 嘗試定義靜態方法(編譯錯誤)// staticMethod();System.out.println("Inside Runnable");}};// 調用匿名內部類中的 run 方法myRunnable.run();}// 嘗試在外部類中定義靜態方法(正常)public static void staticMethod() {System.out.println("Static method");}
}
分析 : 在這個例子中,我們嘗試在匿名內部類中定義靜態成員變量和靜態方法,但這會導致編譯錯誤。匿名內部類無法包含靜態成員,因為它本身是一個實例,而靜態成員是屬于類的,必須在類級別上聲明。
-
通過分析匿名內部類的結構和性質,我們已經解決了文章開頭我們提出的疑問了,現在還剩下最后一個疑問,那就是 : 匿名內部類是否有缺陷? 如果有缺陷是否有更好的替代方案 ? (答案是有缺陷,而且有更好的替代方案)
匿名內部類是一種便捷但有局限性的編程方式。雖然它在短期、輕量級的情況下很方便,但它的一次性使用、可讀性較差、不適合復雜繼承關系、對外部變量的訪問限制、不能包含靜態成員以及可能導致生命周期延長等缺陷,使得在一些復雜或長期維護的場景中,更傾向于使用具名類或Lambda表達式等更靈活的替代方式。
使用場景
在以下的場景中,匿名內部類的簡潔語法和臨時性的特性使得它成為一種方便的編碼方式。然而,需要在使用時權衡其便利性和一些限制,選擇適合當前情況的實現方式。
-
事件處理
在 GUI 編程中,匿名內部類常用于處理用戶界面上的事件,比如按鈕的點擊事件。這簡化了代碼,避免了為每個事件都創建一個獨立的類。
button.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// 處理按鈕點擊事件的邏輯} });
-
線程創建
匿名內部類可以用于創建簡單的線程對象,尤其在某個地方需要一次性的線程執行邏輯時。
new Thread(new Runnable() {@Overridepublic void run() {// 線程執行的邏輯} }).start();
-
實現接口或抽象類
當只需要實現某個接口或繼承某個抽象類的單一實例時,匿名內部類可以提供簡潔的語法。
SomeInterface instance = new SomeInterface() {@Overridepublic void someMethod() {// 實現接口的邏輯} };
-
測試和調試
在單元測試或調試時,有時需要臨時性地實現某個接口或者繼承某個類,匿名內部類能夠方便地提供這種快速的實現。
TestingTool.runTest(new TestInterface() {@Overridepublic void runTest() {// 測試邏輯} });
-
簡化工廠方法
在工廠方法中,如果只需要創建一個實例,匿名內部類可以用于快速創建。
Factory.createInstance(new SomeInterface() {@Overridepublic void someMethod() {// 實現接口的邏輯} });
-
回調函數
在某些設計模式或異步編程中,匿名內部類可以作為回調函數,用于定義異步操作完成后的回調邏輯。
asyncOperation.doAsync(new Callback() {@Overridepublic void onComplete() {// 異步操作完成后的回調邏輯} });
通過上述的分析, 相信大家應該對Java中的匿名內部類有了很深刻的理解了,如果文章中有什么地方寫的不對,或者理解有誤,歡迎大家在評論區留言, 教學相長 ~
如果覺得這篇文章有幫助到您,您的三連是對我最大的支持 !