先來看一段代碼:java
/**
* Author: wang bo
* Date:2019/1/30
* Description:
*/
public class FinalTest {
private ArrayList list = new ArrayList();
public void f1() {
final Test test = new Test();
list.add(new Listener() {
@Override
public Test listen() {
Test t = test;
return t;
}
});
}
private interface Listener {
Test listen();
}
class Test {
}
}
複製代碼
能夠看到想在匿名內部類裏面return t,外面的test必須加上final修飾符。這是爲何呢?這是java的語言基礎,網上這個問題也被討論爛了,各類說法都有,因此一開始我被弄的稀裏糊塗,請教了公司前輩後,再加上本身的理解,算是弄懂了吧。bash
拋出疑問ide
1.若是f1()函數結束出棧,test會被回收嗎?函數
2.若是被回收,那麼test引用指向的對象會被gc掉嗎?工具
咱們先來看看這段代碼用JD-GUI反編譯後的結果:ui
一共有4個文件:this
FinalTest$1.class 這是虛擬機爲匿名內部類建立的實現類spa
FinalTest$Listener.class 這是FinalTest的內部接口code
FinalTest$Test.class 這是內部類Test對象
FinalTest.class 這是FinalTest
要理解這個問題重要的是FinalTest$1.class, 和 FinalTest.class 倆個文件。
先拋出答案:
1.若是f1()函數結束出棧,test引用會被回收嗎?會
2.若是被回收,那麼test引用指向的對象會被gc掉嗎?不會
FinalTest.class
public class FinalTest
{
private ArrayList list = new ArrayList();
public void f1()
{
final Test test = new Test();
this.list.add(new Listener()
{
public FinalTest.Test listen()
{
FinalTest.Test t = test;
return t;
}
});
}
class Test
{
Test() {}
}
private static abstract interface Listener
{
public abstract FinalTest.Test listen();
}
}
複製代碼
能夠看到反編譯後test 仍是被final修飾的。再來看看FinalTest$1.class。
class FinalTest$1
implements FinalTest.Listener
{
FinalTest$1(FinalTest this$0, FinalTest.Test paramTest) {}
public FinalTest.Test listen()
{
FinalTest.Test t = this.val$test;
return t;
}
}
複製代碼
這個反編譯後的結果就是關鍵,能夠看到匿名內部類的實現類的構造函數的參數是 this$0, paramTest ,這個this$0 是外部類的引用,而paramTest 是須要接收剛剛傳入的test局部變量的,而且咱們知道java是值傳遞的,因此test局部變量的引用的值被「拷貝了一份」指向了同一個地址。
繼續看listen方法,咱們能夠看到this.val$test ,不知道是否是反編譯工具的問題,在構造函數中應該是有
this.val$test = paramTest
這樣的語句在的。
因此這種現象能夠解決第一問和第二問了,函數退出test引用被銷燬。而在匿名內部類的實現類裏,拷貝的引用生命週期和匿名內部類實現類對應的對象的生命週期同樣長。並且由於對應的對象存在強引用(拷貝的引用),因此不會被gc。
因此爲了解決數據一致性的問題,java要保證匿名內部類的裏面拷貝的引用和局部變量的引用指向的是同一個對象,因此局部變量要加final,保證了拷貝的引用和它指向的是同一個對象。