分析
1、內部類(不論是否是匿名內部類)可訪問外部類的變量(包括外部類的類變量、實例變量、外部類方法的局部變量等)、方法:可修改變量值、調用方法等。內部類定義時的位置有兩種:
在外部類的方法內:此時該內部類只能是匿名內部類(語法上不支持在方法內定義非匿名類)。此時內部類可訪問上述所有變量。
不在外部類的方法內:此時該內部類可以是匿名內部類也可以不是匿名內部類。此時內部類無法訪問外部類各方法的局部變量。
2、若在外部類的方法內定義類(只能是匿名內部類),則該內部類的實例的生命周期有可能超過局部變量的生命周期(此場景即所謂的閉包,在javascript等很多語言中都有)。典型的是回調函數的場景,示例:
private Animator createAnimatorView(final View view, final int position) { MyAnimator animator = new MyAnimator(); animator.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator arg0) { Log.d(TAG, "position=" + position); } }); return animator; }//onAnimationEnd事件可能在createAnimatorView方法結束后很久才觸發,觸發時用到了方法中的局部變量position
方法執行完后局部變量銷毀了,但內部類可能仍要訪問該局部變量,這時就會出錯,怎么辦?
Java解決方法是將局部變量復制一份到內部類,這樣方法執行完后匿名內部類里仍可使用該變量。但這種實現方式還需要確保在程序員看來他們是同一個,即值始終一樣,怎么做到?
法1:同步。當匿名內部類內對復制值做修改時同步回局部變量、在方法內的匿名內部類之后修改局部變量時復制值也跟着改,這種實現上困難且麻煩。
法2:不用同步,直接將局部變量聲明為final的以使其不可變。Java就是用此法。
結論
1、要求用final的場景:只有 被方法內的匿名內部類訪問的方法內的局部變量(方法參數、方法內的變量)才需要加final。非匿名內部類、方法外的匿名內部類訪問變量時沒有該要求。
局部變量不一定須加final,只有 是局部變量、被匿名內部類訪問到 的變量才必須加final
匿名內部類訪問到的變量不一定須加final,只有訪問的變量是局部變量才必須加final
2、要求用final的原因:匿名內部類在方法內時,匿名內部類對象生命周期可能超過方法內的局部變量的生命周期;為了延續生命周期Java復制了局部變量到匿名內部類,之后需要保證復制值與原始值始終一致;保證一致的方式是將局部變量聲明為final使其不可變。
其他
Java 8開始,如果局部變量聲明並初始化后沒有被修改過,則此時該變量也會被當成是final的(稱為effictively final),故此時也可被匿名內部類(或Lambda表達式)訪問。
Informally, a local variable is effectively final if its initial value is never changed -- in other words, declaring it
final
would not cause a compilation failure.
示例:
//正確 Callable<String> helloCallable(String name) { String hello = "Hello"; return () -> (hello + ", " + name); } //錯誤 int sum = 0; list.forEach(e -> { sum += e.size(); }); // ERROR
以下為舊摘
=================
大部分時候,類被定義成一個獨立的程序單元。在某些情況下,也會把一個類放在另一個類的內部定義,這個定義在其他類內部的類就被稱為內部類,包含內部類的類也被稱為外部類。
class Outer { private int a; public class Inner { private int a; public void method(int a) { a++; //局部變量 this.a++; //Inner類成員變量 Outer.this.a++; //Outer類成員變量 } } }
一般做法是在Outer中寫一個返回Inner類對象的方法
public Inner getInner()
{ return new Inner(); }
在其他類中使用內部類:
Outer outer = new Outer(); Outer.Inner inner = outer.getInner(); //或者Outer.Inner inner = outer.new Inner();
static內部類的使用:
Outer.Inner inner = new Outer.Inner();
匿名內部類不能訪問外部類方法中的局部變量(包括方法參數、方法內的變量),除非變量被聲明為final類型
1. 這里所說的“匿名內部類”主要是指在其外部類的成員方法內定義,同時完成實例化的類,若其訪問該成員方法中的局部變量,局部變量必須要被final修飾。
2. 原因是編譯程序實現上的困難:內部類對象的生命周期會超過局部變量的生命周期。局部變量的生命周期:當該方法被調用時,該方法中的局部變量在棧中被創建,當方法調用結束時,退棧,這些局部變量全部死亡。而內部類對象生命周期與其它類一樣:自創建一個匿名內部類對象,系統為該對象分配內存,直到沒有引用變量指向分配給該對象的內存,它才會死亡(被JVM垃圾回收)。所以完全可能出現的一種情況是:成員方法已調用結束,局部變量已死亡,但匿名內部類的對象仍然活着。
3. 如果匿名內部類的對象訪問了同一個方法中的局部變量,就要求只要匿名內部類對象還活着,那么棧中的那些它要所訪問的局部變量就不能“死亡”。
4. 解決方法:匿名內部類對象可以訪問同一個方法中被定義為final類型的局部變量。定義為final后,編譯程序的實現方法:對於匿名內部類對象要訪問的所有final類型局部變量,都拷貝成為該對象中的一個數據成員。這樣,即使棧中局部變量已死亡,但被定義為final類型的局部變量的值永遠不變,因而匿名內部類對象在局部變量死亡后,照樣可以訪問final類型的局部變量,因為它自己拷貝了一份,且與原局部變量的值始終一致。
最后,Java 8更加智能:如果局部變量被方法內的匿名內部類訪問,那么該局部變量相當於自動使用了final修飾。此外,Java 8的λ表達式也與此類似只能訪問final外部變量但不要求用final修飾,不過,變量同樣不能被重新賦值。
參考資料:
https://www.cnblogs.com/bootdo/p/10844032.html
http://www.cnblogs.com/eniac12/p/5240100.html
https://mp.weixin.qq.com/s/-2dGPhjbY7TKtR3Un31Kig(Java λ表達式)