内部类详解————匿名内部类
內部類三連擊:
《內部類詳解————匿名內部類》
《內部類詳解————局部內部類》
《內部類詳解————靜態嵌套類》
應用場景
由于匿名內部類不利于代碼的重用,因此,一般在確定此內部類只會使用一次時,才會使用匿名內部類。
形式
public class OutterClass {public Runnable task() {return new Runnable() {@Overridepublic void run() {System.out.println("匿名內部類...");}};} }這種實現方式是不是很眼熟呢?
// 初始化線程實例Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("匿名內部類...");}});我們為線程創建一個Runnable子類實例的方式,就是一種匿名內部類的寫法。我們通過這種沒有名字的類,實現了將實現類(下稱子類)實例創建與子類定義結合在一起的優雅格式,這也就是所謂的“使用類的定義直接創建實例”。
上面的代碼是實現了Runnable接口,并重寫了其中的run()方法,當然我們可以自己定義一個類(非接口)然后通過這種匿名內部類的方式來隱式的繼承,并重寫基類中的方法。
不論是繼承父類,還是實現接口,實際上拿到的是父類或接口的引用。這個父類引用實際指向的是一個由匿名內部類定義的類的實例。因此,這個被繼承的父類(或接口)必須是事先存在的。否則,編譯器會提示你創建這個類。
使用規則
經過查閱資料和實操得出的匿名內部類的幾條規則:
規則一:匿名內部類中的方法都是通過父類引用訪問的,所以,如果定義了一個在父類中沒有的方法,那么這個方法是不能被這個父類引用調用到的。(可以僅僅作為匿名內部類中方法之間的代碼共享)。
規則二:匿名內部類既可以繼承父類,也可以實現接口,但是不能兩者兼備。而且如果實現接口也只能實現一個接口。
規則三:匿名內部類中不可能有構造器。但可通過實例初始化塊 來達到構造器的效果,但是也不能重載實例初始化方法(即僅有一個這樣的“構造器”)。(關于實例初始化:《Java靜態初始化,實例初始化以及構造方法》)
規則四:在匿名內部類中如果希望使用一個其外部定義的對象,那么編譯器會要求其參數引用是final的。
關于第四條規則,這里牽涉了一個重要的且比較復雜的問題。
使用案例:
/** 定義接口*/ public interface MyInterface {void doSomething(); } public class TryUsingAnonymousClass {// 外部類成員方法public MyInterface useMyInterface() {final int number = 201855;// jdk1.8后可以省略finalfinal Object obj = new Object();// jdk1.8后可以省略finalMyInterface myInterface = new MyInterface() {// 匿名內部類@Overridepublic void doSomething() {System.out.println("匿名內部類中使用基本數據類型:" + number);System.out.println("匿名內部類中使用引用數據類型:" + obj);}};return myInterface;}public static void main(String[] args) {TryUsingAnonymousClass tc = new TryUsingAnonymousClass();MyInterface inter = tc.useMyInterface();inter.doSomething();} }輸出:
匿名內部類中使用基本數據類型:201855 匿名內部類中使用引用數據類型:java.lang.Object@15db9742我們通過匿名內部類的方式實現了接口MyInterface,并使用了外部類的成員方法useMyInterface() 中定義的兩個局部變量:
int number = 201855; Object obj = new Object();(在jdk1.8之后,新增了effectively final功能,開發者可以不必顯式地使用final關鍵字來修飾局部內部類或匿名內部類中用到的局部變量,由系統默認添加。)
因此我們在匿名內部類中用到的局部變量必須為常量(對于基本類型,其值恒定不變;對于引用類型,其引用,即指向的地址恒定不變)。
如果強行改值,則會報錯(這是在1.8程序上未使用final定義number時的嘗試,系統果然默認此值為final的):
不得不引出的局部變量與匿名內部類實例生命周期問題
我們知道成員方法中的局部變量是在運行期進行定義和初始化的,而局部內部類(包括匿名內部類)雖然是在方法中定義的,但是它卻依然會在編譯期實現從java文件到class文件的轉化,即編譯成class文件。
編譯期在前,運行期在后。而我們卻要在編譯期使用運行期定義的變量!
怎么辦?我們腦海中浮現了兩個在編譯期便能取得常量的相關關鍵字:static 和 final ?但顯然,static無法定義局部變量。
那final能為我們的程序帶來什么?
翻閱《Java編程思想》中對final關鍵字的剖析(第四版,140頁):
一個永不改變的編譯時常量。《深入理解Java虛擬機:JVM高級特性與最佳實踐》中(第二版,168頁)對于Class文件常量池也做出了相關解釋:
常量池(博主注:此常量池為class文件常量池,非運行時常量池,兩者最大的區別是后者具有動態性) 中主要存放兩大類常量:字面量和符號引用。字面量比較接近于Java語言層的常量概念,如文本字符串、聲明為final的常量值等。匿名內部類被編譯成了class文件,它將final定義的局部變量編譯進了class文件的常量池中,因此,我們會看上面的代碼:
public static void main(String[] args) {TryUsingAnonymousClass tc = new TryUsingAnonymousClass();MyInterface inter = tc.useMyInterface();inter.doSomething();}int型局部變量number和Object類型obj在方法useMyInterface()執行完畢之后即結束了生命周期,但是在下面通過調用inter對象的doSomething()方法依然可以有效的輸出這兩個值,說明這兩個常量并沒有受到外部類方法執行完畢而導致局部變量生命周期結束的問題,實際上number和obj已經存在于匿名內部類對應的class文件中的常量池中。
雖然final修飾的常量解決了在編譯期拿到運行期的變量的問題,但是final帶來的副作用是,這個值無法改變。
對于需要改變局部變量值的情況,我們可以通過在匿名內部類中使用賦值的方式(學名:引用拷貝 =.0)來“接管”局部變量的值,然后我們就可以隨意更改這個值了。
綜上,就是最近對匿名內部類的研究和討論。結合了final關鍵字的用法和class文件常量池來多角度討論匿名內部類的final常量問題。后期如果有什么新的理解還會繼續更新。文中的錯別字和排版不適感博主已經進行了糾錯和修改,如果各位在閱讀時發現了任何錯誤,都請在文末留言。
?
總結
以上是生活随笔為你收集整理的内部类详解————匿名内部类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTMLCSS————块元素与内联元素
- 下一篇: LeetCode算法入门- Longes