問題:
local variable S is accessed from within inner class; needs to be declared final
在內部類當中不能引用本地變量s,需要被聲明為常量
幾種說法:
0.在JVM中,內部類不是直接調用方法的參數,而是內部類將傳進來的參數通過自己的構造器備份到了自己的內部,自己內部的方法調用的實際是自己的屬性而不是外部類方法的參數。
比如:
class A{
final string a="aabbcc";
class B{
print(a);
}
}
類A中的內部類B的函數print()調用了定義在A中的參數a,這個時候如果不加final,就會出錯。
因為內部類被編譯的時候會生成一個單獨的內部類的.class文件,這個文件並不與外部類在同一class文件中。
如果內部類改掉了這些參數的值也不可能影響到原參數,然而這樣卻失去了參數的一致性,因為從編程人員的角度來看他們是同一個東西,如果編程人員在程序設計的時候在內部類中改掉參數的值,
但是外部調用的時候又發現值其實沒有被改掉,這就讓人非常的難以理解和接受,為了避免這種尷尬的問題存在,所以編譯器設計人員把內部類能夠使用的參數設定為必須是final來規避這種莫名
其妙錯誤的存在。
1. 內部類中使用但未聲明的任何局部變量必須在內部類的正文之前明確分配
2 為什么在方法中定義的內部類只能訪問方法中的final類型的局部變量?
java中規定,內部類只能訪問外部類中的成員變量,不能訪問方法中定義的變量,如果要訪問方法中的變量,就要把方法中的變量聲明為final(常量)的,因為這樣可以使變量全局化,就相當於是在外部定義的而不是在方法里定義的
3.現象描述
在 Java 8 之前,匿名內部類在使用外部成員的時候,會報錯並提示 “Cannot refer to a non-final variable arg inside an inner class defined in a different method”:

但是在 Java 8 之后,類似場景卻沒有再提示了:

難道是此類變量可以隨便改動了嗎?當然不是,當你試圖修改這些變量的時候,仍然會提示錯誤:

可以看到,當試圖修改基本數據類型的變量時,編譯器的警告變成了 “Varible 'num' is accessed from within inner class, need to be final or effectively final”,很遺憾,仍然不能修改。相比之下,Kotlin 是沒有這個限制的:

原因分析
從表面上當然看不出什么原因,看看編譯器做了什么工作吧!運行 javac 命令后生成了幾個 .class 文件:

不難推斷,這個 TestInnerClass$1.class 就是匿名內部類編譯后的文件,看看它反編譯后是什么內容:
class TestInnerClass$1 extends InnerClass { TestInnerClass$1(TestInnerClass var1, int var2, DataBean var3) { super(var1); this.this$0 = var1; this.val$num = var2; this.val$bean = var3; } void doSomething() { super.doSomething(); System.out.println("num = " + this.val$num); System.out.println("bean name is: " + this.val$bean.name); } }
原來,匿名也會被當作普通的類處理,只不過編譯器生成它構造方法的時候,除了將外部類的引用傳遞了過來,還將基本數據類型的變量復制了一份過來,並把引用數據類型的變量引用也傳遞了過來。因此,基本數據類型的變量當然不能修改了,不然就會跟外部的變量產生不一致,這樣的話變量的傳遞也就變得毫無意義了。
情景對比
但是為什么對於 Kotlin 來說可以在匿名內部類中直接修改基本數據類型的值呢?查看 Kotlin 編譯后反編譯回來的內容:
public final void useNestedClass(@NotNull final TestNestedClass.DataBean bean) { Intrinsics.checkParameterIsNotNull(bean, "bean"); final IntRef num = new IntRef();//---1 num.element = 1;//---2 String var3 = "before action, num = " + num.element; System.out.println(var3); <undefinedtype> nestedClass = new TestNestedClass.NestedClass() { public void doSomething() { num.element = 678;//---3 bean.setName("xyz"); String var1 = "num = " + num.element; System.out.println(var1); var1 = "bean name is: " + bean.getName(); System.out.println(var1); } }; nestedClass.doSomething(); String var4 = "after action, num = " + num.element;//---4 System.out.println(var4); }
可以發現,當需要傳遞基本數據類型的變量時,Kotlin 編譯器會將這些數據進行包裝,從而由值傳遞變為引用傳遞,這樣內部的修改當然就不會影響到外部了。
驗證一下,當變量不進行傳遞時,Kotlin 編譯器是怎么處理的:
public final void useNestedClass(@NotNull TestNestedClass.DataBean bean) { Intrinsics.checkParameterIsNotNull(bean, "bean"); int num = 1; String var3 = "before action, num = " + num; System.out.println(var3); int num = 678; var3 = "after action, num = " + num; System.out.println(var3); }
5.內部類中使用但未聲明的任何局部變量必須在內部類的正文之前明確分配