Java 內部類與匿名內部類

問:什麼JDK8以前內部類中若是有訪問外部類的成員時,必需要加上final?爲何JDK8不用了?java

答:咱們先不說JDK版本,就說說爲何要加final。工具

我先給出問題的答案:用final修飾實際上就是爲了保護數據的一致性。線程

這裏所說的數據一致性,對引用變量來講是引用地址的一致性,對基本類型來講就是值的一致性。對象

這裏我插一點,final修飾符對變量來講,深層次的理解就是保障變量值的一致性。爲何這麼說呢?由於引用類型變量其本質是存入的是一個引用地址,說白了仍是一個值(能夠理解爲內存中的地址值)。用final修飾後,這個這個引用變量的地址值不能改變,因此這個引用變量就沒法再指向其它對象了。blog

首先,由於生命週期的緣由。方法中的局部變量,方法結束後這個變量就要釋放掉,內部類和外部類實際上是處於同一個級別,內部類不會由於定義在方法中就會隨着方法的執行完畢而跟隨者被銷燬,它的生命週期同外部類相同。問題就來了,那麼當外部類方法執行完畢的時候,這個局部變量確定也就出棧了,然而內部類的某個方法尚未執行完,這個時候他所引用的外部變量已經找不到了。生命週期

爲了解決這個問題,java會將這個變量複製一份做爲成員變量內置於內部類中,這樣的話,及時外部類方法的局部變量出棧失效,咱們也能夠引用到複製的內部類中的成員,至關於延長了局部變量的「生命」。 可是這也僅僅解決了局部變量的生命週期與局部內部類的對象的生命週期的不一致性問題。也是爲何局部變量要做爲內部類構造方法的參數傳入。內存

 

回到正題,爲何須要用final保護數據的一致性呢?編譯

若是咱們不用final修飾外部類方法局部變量,由於內部類(包括匿名內部類)對於局部變量的引用並非直接的引用,而是將數據拷貝到本身的類變量中在加以使用,則局部變量能夠發生變化。這裏到了問題的核心了,若是局部變量發生變化後,匿名內部類是不知道的(由於他只是拷貝了局部變量的值,並非直接使用的局部變量)。這裏舉個栗子:原先局部變量指向的是對象A,在建立匿名內部類後,匿名內部類中的成員變量也指向A對象。但過了一段時間局部變量的值指向另一個B對象,但此時匿名內部類中仍是指向原先的A對象。那麼程序再接着運行下去,可能就會致使程序運行的結果與預期不一樣。變量

在JDK8中若是咱們在匿名內部類中須要訪問局部變量,那麼這個局部變量不須要用final修飾符修飾。看似是一種編譯機制的改變,實際上就是一個語法糖(底層仍是幫你加了final)。但經過反編譯沒有看到底層爲咱們加上final,但咱們沒法改變這個局部變量的引用值,若是改變就會編譯報錯。垃圾回收

匿名內部類反編譯解析:

反編譯下

因爲反編譯工具的問題咱們沒法看到匿名內部類構造器中成員賦值的操做。

從上面三張圖咱們能夠看到,對於外部類成員變量的引用(Integer  a)將外部類對象淺拷貝到內部類一份,並用final修飾,而後經過此拷貝對象訪問外部類對象成員變量。

對於方法局部變量,咱們看到須要使用final修飾(String  s)(JDK 1.8之後再也不須要,經過底層語法糖將final添加到局部變量中)而且內部類會經過構造方法將引用的局部變量拷貝到本身的成員變量中(圖2 圖3)

 

局部內部類反編譯解析:

雖然反編譯工具沒有將所有內容展現出來,可是經過標紅地區的代碼,咱們仍是能夠感覺出來內部類與匿名內部類都是一樣的處理方式。

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

問:剛纔看到內部類,匿名內部類中都隱藏有外部類的強引用,這樣會致使外部類沒法進行垃圾回收,咱們該如何解決?

答:

1 將內部類定義爲static

2 用static的變量引用匿名內部類的實例或將匿名內部類的實例化操做放到外部類的靜態方法中

3 當使用匿名內部類建立線程是Thread時,若是線程運行沒有完成或者沒有被殺死,它將不會回收,也將致使外部類對象不會被回收,這將致使內存溢出的風險,因此咱們要養成爲Thread設置退出邏輯條件的習慣。

相關文章
相關標籤/搜索