轉:http://feiyeguohai.iteye.com/blog/1500108
為什麼匿名内部類參數必須為final類型
1) 從程式設計語言的理論上:局部内部類(即:定義在方法中的内部類),由于本身就是在方法内部(可出現在形式參數定義處或者方法體處),因而通路方法中的局部變量(形式參數或局部變量)是天經地義的.是很自然的
2) 為什麼JAVA中要加上一條限制:隻能通路final型的局部變量?
3) JAVA語言的編譯程式的設計者當然全實作:局部内部類能通路方法中的所有的局部變量(因為:從理論上這是很自然的要求),但是:編譯技術是無法實作的或代價極高.
4) 困難在何處?到底難在哪兒?
局部變量的生命周期與局部内部類的對象的生命周期的不一緻性!
5) 設方法f被調用,進而在它的調用棧中生成了變量i,此時産生了一個局部内部類對象inner_object,它通路了該局部變量i .當方法f()運作結束後,局部變量i就已死亡了,不存在了.但:局部内部類對象inner_object還可能 一直存在(隻能沒有人再引用該對象時,它才會死亡),它不會随着方法f()運作結束死亡.這時:出現了一個"荒唐"結果:局部内部類對象inner_object要通路一個已不存在的局部變量i!
6) 如何才能實作?當變量是final時,通過将final局部變量"複制"一份,複制品直接作為局部内部中的資料成員.這樣:當局部内部類通路局部變量時,其實真正通路的是這個局部變量的"複制品"(即:這個複制品就代表了那個局部變量).是以:當運作棧中的真正的局部變量死亡時,局部内部類對象仍可以通路局部變量(其實通路的是"複制品"),給人的感覺:好像是局部變量的"生命期"延長了.
那麼:核心的問題是:怎麼才能使得:通路"複制品"與通路真正的原始的局部變量,其語義效果是一樣的呢?
當變量是final時,若是基本資料類型,由于其值不變,因而:其複制品與原始的量是一樣.語義效果相同.(若:不是final,就無法保證:複制品與原始變量保持一緻了,因為:在方法中改的是原始變量,而局部内部類中改的是複制品)
當變量是final時,若是引用類型,由于其引用值不變(即:永遠指向同一個對象),因而:其複制品與原始的引用變量一樣,永遠指向同一個對象(由于是final,進而保證:隻能指向這個對象,再不能指向其它對象),達到:局部内部類中通路的複制品與方法代碼中通路的原始對象,永遠都是同一個即:語義效果是一樣的.否則:當方法中改原始變量,而局部内部類中改複制品時,就無法保證:複制品與原始變量保持一緻了(是以:它們原本就應該是同一個變量.)
一句話:這個規定是一種無可奈何.也說明:程式設計語言的設計是受到實作技術的限制的.這就是一例. 因為:我就看到不少人都持這種觀點:設計與想法是最重要的,實作的技術是無關緊要的,隻要你作出設計與規定,都能實作.
現在我們來看,如果我要實作一個在一個方法中匿名調用ABSClass的例子:
public static void test(final String s){
//或final String s = "axman";
ABSClass c = new ABSClass(){
public void m(){
int x = s.hashCode();
System.out.println(x);
}
};
//其它代碼.
}
從代碼上看,在一個方法内部定義的内部類的方法通路外部方法内局部變量或方法參數,是非常自然的事,但内部類編譯的時候如何擷取這個變量,因為内部類除了它的生命周期是在方法内部,其它的方面它就是一個普通類。那麼它外面的那個局部變量或方法參數怎麼被内部類通路?編譯器在實作時實際上是這樣的:
public static void test(final String s){
//或final String s = "axman";
class OuterClass$1 extends ABSClass{
private final String s;
public OuterClass$1(String s){
this.s = s;
}
public void m(){
int x = s.hashCode();
System.out.println(x);
}
};
ABSClass c = new OuterClass$1(s);
//其它代碼.
}
即外部類的變量被作為構造方法的參數傳給了内部類的私有成員.
假如沒有final,那麼:
public static void test(String s){
//或String s = "axman";
ABSClass c = new ABSClass(){
public void m(){
s = "other";
}
};
System.out.println(s);
}
就會編譯成:
public static void test(String s){
//或String s = "axman";
class OuterClass$1 extends ABSClass{
private String s;
public OuterClass$1(String s){
this.s = s;
}
public void m(){
s = "other";
}
};
ABSClass c = new OuterClass$1 (s);
}
内部類的s重新指向"other"并不影響test的參數或外部定義的那個s.同理如果外部的s重新指派内部類的s也不會跟着改變。
而你看到的
public static void test(String s){
//或String s = "axman";
ABSClass c = new ABSClass(){
public void m(){
s = "other";
}
};
System.out.println(s);
}
在文法上是一個s,在内部類中被改變了,但結果列印的出來的你認為是同一的s卻還是原來的"axman",
你能接收這樣的結果嗎?
是以final從文法上限制了實際上兩個不同變量的一緻性(表現為同一變量).
轉:http://blog.csdn.net/onisland/article/details/5807637
為什麼匿名内部類和局部内部類隻能通路final變量
是變量的作用域的問題,因為匿名内部類是出現在一個方法的内部的,如果它要通路這個方法的參數或者方法中定義的變量,則這些參數和變量必須被修飾為final。因為雖然匿名内部類在方法的内部,但實際編譯的時候,内部類編譯成Outer.Inner,這說明内部類所處的位置和外部類中的方法處在同一個等級上,外部類中的方法中的變量或參數隻是方法的局部變量,這些變量或參數的作用域隻在這個方法内部有效。因為編譯的時候内部類和方法在同一級别上,是以方法中的變量或參數隻有為final,内部類才可以引用。
Java代碼:
package com.cxz.j2se;
public class MyClass {
public MyClass() {
final int finalValue = 10;
int not$Final = 20;
MyInterface myInterface = new MyInterface() {
public void functionWithoutPara() {
//compile Error
//System.out.println(noFinal);
System.out.println(finalValue);
}
public void functionWithPara(int num) {
System.out.println("The parameter " + num
+ " has been passed by the method");
}
};
myInterface.functionWithoutPara();
myInterface.functionWithPara(not$Final);
System.out.println(myInterface.getClass().getName());
}
public static void main(String[] args) {
new MyClass();
}
}
二、為什麼局部内部類隻能通路final變量
簡單的來說是作用域的問題。就好像方法外面做的事情并不能改變方法内才定義的變量,因為你并不知道方法裡面這個時候已經存在了這個局部變量了沒有。在這個内部類中方法裡面的本地變量是失效的,也就是不在作用域内,是以是不能夠通路的
但是為什麼這裡用final卻又可以通路呢?
因為Java采用了一種copy local variable的方式來實作,也就是說把定義為final的局部變量拷貝過來用,而引用的也可以拿過來用,隻是不能重新指派。進而造成了可以access local variable的假象,而這個時候由于不能重新指派,是以一般不會造成不可預料的事情發生
三、如果定義一個局部内部類,并且局部内部類使用了一個在其外部定義的對象,為什麼編譯器會要求其參數引用是final呢?
注意:局部内部類,包括匿名内部類。
原因如下:
abstract class ABSClass{
public abstract void print();
}
public class Test2{
public static void test(final String s){//一旦參數在匿名類内部使用,則必須是final
ABSClass c=new ABSClass(){
public void print(){
System.out.println(s);
}
};
c.print();
}
public static void main(String[] args){
test("Hello World!");
}
}
JVM中每個程序都會有多個根,每個static變量,方法參數,局部變量,當然這都是指引用類型.基礎類型是不能作為根的,根其實就是一個存儲位址.垃圾回收器在工作時先從根開始周遊它引用的對象并标記它們,如此遞歸到最末梢,所有根都周遊後,沒有被标記到的對象說明沒有被引用,那麼就是可以被回收的對象(有些對象有finalized方法,雖然沒有引用,但JVM中有一個專門的隊列引用它們直到finalized方法被執行後才從該隊列中移除成為真正沒有引用的對象,可以回收,這個與本主題讨論的無關,包括代的劃分等以後再說明).這看起來很好.
但是在内部類的回調方法中,s既不可能是靜态變量,也不是方法中的臨時變量,也不是方法參數,它不可能作為根,在内部類中也沒有變量引用它,它的根在内部類外部的那個方法中,如果這時外面變量s重指向其它對象,則回調方法中的這個對象s就失去了引用,可能被回收,而由于内部類回調方法大多數在其它線程中執行,可能還要在回收後還會繼續通路它.這将是什麼結果?
而使用final修飾符不僅會保持對象的引用不會改變,而且編譯器還會持續維護這個對象在回調方法中的生命周期.是以這才是final變量和final參數的根本意義.