匿名內部類 - ( 零基礎學java )

Java-匿名內部類

我們先分析匿名內部類的結構,然后逐一解釋,最后以下羅列的問題都會在下面的內容中一一得到解答 :

匿名內部類到底是什么?

我們為什么要學習匿名內部類 ?

匿名內部類都有怎樣的作用 ?

匿名內部類應用的場景又有哪些 ?

匿名內部類是否有缺陷?

讓我們帶著這些問題來學習Java中的匿名內部類吧 !

結構決定性質

結構

  • 匿名內部類基本語法 :
new 父類構造器(參數) / 實現接口() {// 類的主體部分
};
  • 解釋匿名內部類語法中所有的概念:

    • new 父類構造器(參數) 表示匿名內部類是某個類的子類實例。

    • 實現接口() 表示匿名內部類是某個接口的實現實例。

    • { ... } 內部是匿名內部類的主體部分,包含類的字段、方法等定義。


性質

匿名內部類(Anonymous Inner Class)是一種在聲明和創建對象的同時定義類的方式,它沒有顯式的類名。通過 匿名內部類 看這幾個字的字面意思我們都知道這是個沒有名字的類,即 非具名類 . 以下是匿名內部類的具備的一些性質 :

  1. 可以實現接口或繼承類: 匿名內部類可以實現接口或繼承某個類,從而提供具體的實現。
  2. 沒有顯式的類名: 匿名內部類沒有顯式的類名,因為它是一種臨時的、一次性的實現。
  3. 一次性使用: 通常用于臨時的、一次性的場景,不需要復用。因為匿名內部類沒有類名,所以無法在其他地方重復使用。
  4. 可以訪問外部類的成員: 匿名內部類可以訪問外部類的成員,包括成員變量和方法。對于外部類的局部變量,有一些規則,比如必須是final或者事實上是final的。
  5. 可以包含字段和方法: 在匿名內部類的主體部分,可以包含字段(成員變量)和方法的定義。
  6. 不可以包含靜態成員: 匿名內部類不能包含靜態成員,包括靜態方法和靜態變量。

我們來一個一個的解釋,同時我也會拿具體的代碼演示.

可以實現接口或繼承類

  • 匿名內部類可以用來實現接口或者繼承類,這也是匿名內部類最常用的一個用法,我們在實際開發中,如果需要實現(重寫)某個接口(類)的方法,而且這個方法只是臨時使用,不需要復用,而且會出現很多這樣的場景,我們肯定是不想一一都去額外的封裝一個類去實現接口或者繼承類然后再創建類的實例,取調用我們需要的方法,這個時候我們就 匿名內部類 就排上用場啦,我們可以通過匿名內部類去臨時創建一個接口的實例(接口不能實例化,這里只是形象比喻一下)或者創建一個臨時的子類重寫了父類方法的實例. 總結一下 : 當匿名內部類需要實現接口或者繼承類時,它可以直接在創建對象的地方定義類的主體,而不需要顯式地聲明一個具名的類。
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();}
}

通過上述的代碼,我演示了兩種使用匿名內部類的情況:

  1. 實現接口: 通過匿名內部類實現了 Chef 接口,提供了 cook 方法的具體實現。

    Chef chef1 = new Chef() {@Overridepublic void cook() {System.out.println("炒蛋炒飯");}
    };
    

    這部分代碼演示了匿名內部類用于實現接口的情況,通過創建一個實現了 Chef 接口的匿名內部類的實例,重寫了接口中的抽象方法 cook

  2. 重寫父類方法: 通過匿名內部類重寫了 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();}
}
  1. 代碼中,匿名內部類實現了Runnable接口,其中的run方法中訪問并修改了外部類Example的成員變量memberVar
  2. 匿名內部類的作用域包括了它所在的方法,也就是modifyMemberVar方法。在這個方法中,您創建了兩個不同的匿名內部類的實例(r1r2),每個實例都有自己的run方法。因此,每個實例的run方法中的對memberVar的修改是在各自匿名內部類的作用域內完成的。
  3. 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();}
}
  • 在外部類修改局部變量的值(報錯)
image-20231211084543439
  • 在匿名內部類中修改局部變量的值(報錯)
image-20231211084615734
  • 不修改外部類局部變量的值,而是顯式的讓其被final修飾
image-20231211084851143
  • 不修改外部類的局部變量的值,而是隱式的意味著事實上是final,也就是局部變量一旦被定義就不再被修改,那么在事實上就可以認為這個局部變量是一個final修飾的了,而且就是修改了,在匿名類中訪問這樣的被修改的外部類的局部變量是會報錯的!!! 因為變量捕獲機制的存在 !!!
image-20231211085216762

在上述代碼中,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表達式等更靈活的替代方式。


使用場景

在以下的場景中,匿名內部類的簡潔語法和臨時性的特性使得它成為一種方便的編碼方式。然而,需要在使用時權衡其便利性和一些限制,選擇適合當前情況的實現方式。

  1. 事件處理

    在 GUI 編程中,匿名內部類常用于處理用戶界面上的事件,比如按鈕的點擊事件。這簡化了代碼,避免了為每個事件都創建一個獨立的類。

    button.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// 處理按鈕點擊事件的邏輯}
    });
    
  2. 線程創建

    匿名內部類可以用于創建簡單的線程對象,尤其在某個地方需要一次性的線程執行邏輯時。

    new Thread(new Runnable() {@Overridepublic void run() {// 線程執行的邏輯}
    }).start();
    
  3. 實現接口或抽象類

    當只需要實現某個接口或繼承某個抽象類的單一實例時,匿名內部類可以提供簡潔的語法。

    SomeInterface instance = new SomeInterface() {@Overridepublic void someMethod() {// 實現接口的邏輯}
    };
    
  4. 測試和調試

    在單元測試或調試時,有時需要臨時性地實現某個接口或者繼承某個類,匿名內部類能夠方便地提供這種快速的實現。

    TestingTool.runTest(new TestInterface() {@Overridepublic void runTest() {// 測試邏輯}
    });
    
  5. 簡化工廠方法

    在工廠方法中,如果只需要創建一個實例,匿名內部類可以用于快速創建。

    Factory.createInstance(new SomeInterface() {@Overridepublic void someMethod() {// 實現接口的邏輯}
    });
    
  6. 回調函數

    在某些設計模式或異步編程中,匿名內部類可以作為回調函數,用于定義異步操作完成后的回調邏輯。

    asyncOperation.doAsync(new Callback() {@Overridepublic void onComplete() {// 異步操作完成后的回調邏輯}
    });
    

通過上述的分析, 相信大家應該對Java中的匿名內部類有了很深刻的理解了,如果文章中有什么地方寫的不對,或者理解有誤,歡迎大家在評論區留言, 教學相長 ~

如果覺得這篇文章有幫助到您,您的三連是對我最大的支持 !

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/213365.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213365.shtml
英文地址,請注明出處:http://en.pswp.cn/news/213365.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java (JDK 21) 調用 OpenCV (4.8.0)

Java 調用 OpenCV 一.OpenCV 下載和安裝二.創建 Java Maven 項目三.其他測試 一.OpenCV 下載和安裝 Open CV 官網 可以下載編譯好的包,也可以下載源碼自行編譯 雙擊安裝 opencv-4.8.0-windows.exe 默認為當前目錄 安裝即解壓縮 根據系統位數選擇 將 x64 目錄下 op…

外匯交易到哪開戶?外匯開戶所需流程有哪些?

外匯交易是一種全球性的金融市場活動,參與者可以通過買入或賣出不同國家的貨幣來獲取利潤。在進行外匯交易之前,開設一個外匯交易賬戶是必要的。本文將介紹外匯交易開戶的重要性、選擇外匯交易平臺的因素以及開戶所需的基本流程,幫助讀者更好…

開往渤海的列車:滄港鐵路如何扮演產業帶城市生態共贏的關鍵先生

新時代構建新格局,新格局呼喚新作為。在交通強國戰略背景下,鐵路運輸企業需要如何彰顯“鐵擔當”? 逢山開路、遇水架橋,身處重要地理區位,滄州滄港鐵路有限公司(以下簡稱“滄港鐵路”)不斷搶抓…

并查集帶壓縮路徑的find

目錄 原因: 優化: 原因: 當路徑比較特殊,如圖: 非常深,最底層進行find時,循環找根(或者遞歸找),消耗就比較大。 我們可以進行優化。 優化: &…

【C++】C++異常語法、使用、規范、異常安全及異常的優缺點

1. C異常概念 異常是一種處理錯誤的方式,當一個函數發現自己無法處理的錯誤時就可以拋出異常,讓函數的直接或間接的調用者處理這個錯誤。 throw: 當問題出現時,程序會拋出一個異常。這是通過使用 throw 關鍵字來完成的。catch: 在您想要處理…

給你的Python程序添點Emoji魔法:使用Emoji模塊增添趣味和個性!

當你想給你的Python程序增添一些趣味和個性時,Emoji模塊是一個很有用的工具。Emoji模塊允許你在Python中使用各種表情符號,從笑臉到動物,甚至是食物和天氣等。在本篇博客中,我們將介紹如何在Python中使用Emoji模塊,并展…

【小白專用】使用PHP創建和操作MySQL數據庫,數據表

php數據庫操作 php連接mysql數據庫 <?php $hostlocalhost; // 數據庫主機名 $username"root"; // 數據庫用戶名 $password"al6"; // 數據庫密碼 $dbname"mysql"; // 數據庫名 $connIDmysqli_connect($host,$username,$password,$dbn…

adb push報錯:remote couldn‘t create file: Is a directory

adb push報錯&#xff1a;remote couldn‘t create file: Is a directory 出現這個問題可能是電腦本地目錄中包含中文或者是目錄地址中多包含了一個/ 比如說以下兩種路徑 1. test/測試音頻文件1/a.mp3 2.test/test_audio/ 這兩種都是不可以的&#xff08;我是在as中執行的…

MQTT服務質量-QoS

QoS是消息發送方和接收方之間的協議&#xff0c;定義了指定消息發送保證等級。本文將深入探究MQTT中不同的QoS等級。 QoS是什么 MQTT提供三個QoS等級&#xff1a; 最多一次&#xff08;QoS 0&#xff09;至少一次&#xff08;QoS 1&#xff09;確切一次&#xff08;QoS 2&am…

科技提升安全,基于YOLOv5系列模型【n/s/m/l/x】開發構建商超扶梯場景下行人安全行為姿態檢測識別系統

在商超等人流量較為密集的場景下經常會報道出現一些行人在扶梯上摔倒、受傷等問題&#xff0c;隨著AI技術的快速發展與不斷普及&#xff0c;越來越多的商超、地鐵等場景開始加裝專用的安全檢測預警系統&#xff0c;核心工作原理即使AI模型與攝像頭圖像視頻流的實時計算&#xf…

2024年JAVA招聘行情如何?

大家都在說Java求職不好找&#xff0c;是真的嗎&#xff1f;我們來看看數據。 數據支持&#xff1a;根據TIOBE 5月份的編程語言排行榜&#xff0c;Java仍然是前三名之一。這意味著&#xff0c;Java在開發領域仍然占據重要地位。 而在中國的IT市場中&#xff0c;Java仍然是主要…

使用alpine鏡像部署go應用時踩的坑

使用alpine鏡像部署go應用時踩的坑 關于交叉編譯 實際上我在ubuntu的交叉編譯出來的exe并不能在alpine上運行&#xff0c;這邊采取拉鏡像編譯復制出來的做法&#xff0c;部署再用干凈的alpine 拉取golang:alpine踩坑 在Dockerhub上可以找到&#xff1a; 然而拉取的alpine中…

在普通的項目中創建web的功能

新增web功能: 1.創建一個新項目&#xff0c;不勾選模板&#xff1a;2.添加web功能&#xff1a; 1.創建一個新項目&#xff0c;不勾選模板&#xff1a; 發現普通項目沒有webapp文件夾&#xff0c;即沒有web的功能。 2.添加web功能&#xff1a; Add framework support:添加一些…

VHDL數碼管顯示控制器設計

題目要求&#xff1a; 初始狀態&#xff0c;開關 K1 為低電平&#xff0c;6 個數碼管上依次顯示 1-6。當 K1 變為高電平時&#xff0c;數據管顯示內容依次循環左移&#xff0c;當 K1 變為低電平時&#xff0c;保持當前顯示內容。 參考資料&#xff1a;使用VHDL實現動態掃描八位…

luceda ipkiss教程 45:在版圖上加LOGO

**在設計版圖時往往需要加上公司或者學校的LOGO,只需要LOGO的圖片&#xff0c;通過代碼就可以將LOGO加到版圖上&#xff0c;比如&#xff1a; ** 通過代碼可以得到版圖上的LOGO: ! 代碼如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3i3.TECH…

國際驗證碼有哪些具體的應用場景?

用戶注冊 在許多網站和應用程序中&#xff0c;用戶注冊是必要的第一步。通過使用驗證碼接口&#xff0c;可以防止惡意機器人或自動化程序大規模注冊賬號&#xff0c;從而保護網站或應用程序的安全性和可靠性。 密碼重置 當用戶忘記密碼或需要重置密碼時&#xff0c;驗證碼可…

MyBatis逆向工程

正向工程&#xff1a;先創建Java實體類&#xff0c;由框架負責根據實體類生成數據庫表。Hibernate是支持正向工程的。逆向工程&#xff1a;先創建數據庫表&#xff0c;由框架負責根據數據庫表&#xff0c;反向生成如下資源&#xff1a; Java實體類Mapper接口Mapper映射文件 1…

apply call bind三者區別區別

apply call bind三者區別區別 三者的相同點&#xff1a;都是用來改變this的指向 call()和apply()的區別&#xff1a; 相同點&#xff1a;都是調用一個對象的一個方法&#xff0c;用另一個對象替換當前對象&#xff08;功能相同&#xff09; B.call(A, args1,args2);即A對象調用…

docker的基本管理和概念

docker是什么&#xff1f; docker是開源的應用容器引擎。基于go語言開發的。運行在Linux系統中的開源的輕量級的“虛擬機”。 docker的容器技術可以在一臺主機上輕松的為任何應用創建一個輕量級的、可移植的、自給自足的容器 docker的宿主機是linux系統。集裝箱可以理解為相互…

CAN總線協議編程實例

1. can.h #ifndef __CAN_H #define __CAN_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/ /* CAN 引腳 定義 */#define CAN_RX_GPIO_PORT GPIOA #define CAN_RX_GPI…