https://blog.csdn.net/z55887/article/details/49229491
先拋出讓我疑惑了很久的一個問題
編程時,在線程中使用局部變量時候經常編譯器會提示:局部變量必須聲明為final
package test;
public class ThreadTest {
public void function(String a) {
new Thread(){
@Override
public void run() {
System.out.println(a);
}
}.start();
}
public static void main(String[] args) {
new ThreadTest().function("a");
}
}
上圖中由於方法function中的形參a沒有聲明為final,編譯拋出異常:Cannot refer to the non-final local variable a defined in an enclosing scope
這個問題我特意問過老師,也百度過,都沒有給出滿意的解答。今天看安卓視頻無意發現了答案,真是意外之喜啊!
其實原因就是一個規則:java內部類訪問局部變量時局部變量必須聲明為final。
那為什么要這樣呢?還有線程為什么和內部類一樣?接下來我們慢慢揭秘。
public class Out {
public void test(final String a) {
class In{
public void function() {
System.out.println(a);
}
}
new In().function();
}
public static void main(String[] args) {
new Out().test("hi");
}
}
編譯這個類后發現產生了兩個class文件
也就是說內部類和外部類各一個class文件,這樣就產生了一個問題,調用內部類方法的時候如何訪問外部類方法中的局部變量呢?
實際上編譯后的內部類的構造方法的里面,傳了對應的外部類的引用和所有局部變量的形參。
(由於外部類方法執行完后局部變量會消亡,所以內部類構造函數中的局部變量實際是一份“復制”。而為了訪問外部類中的私有成員變量,外部類編譯后也產生了訪問類似與getXXX的方法。)
這時產生了一個不一致的問題,如果局部變量不設為final,那內部類構造完畢后,外部類的局部變量又改變了那怎么辦?
public class Out {
public void test(String a) {
class In{
public void function() {
System.out.println(a);
}
}
a="hello";
new In().function();
}
public static void main(String[] args) {
new Out().test("hi");
}
}
如代碼中所示,這樣調用內部類方法時會造成外部類局部變量和內部類中對應的變量的不一致。(注意內部類編譯成class文件與new無關,a="hello"放在new In()前后不影響不一致關系,new在jvm運行class文件時才起效)
理解完內部類必須訪問final聲明的局部變量原因,我們回到最開始的問題:為什么線程和內部類一樣
因為線程也是一個類,所以new Thread也相當於創建了一個內部類啦
我們編譯一下最開始的ThreadTest.java文件
發現線程編譯后也是產生了單獨的class文件。
至此,問題全部解決啦~~
最后說明一下java1.8和之前版本對這個規則編譯的區別。
如果在1.8的環境下,會很神奇的發現我們最開始的ThreadTest.java文件編譯和運行是完全沒有問題的,也就是說內部類使用的局部變量是可以不聲明為final?!
且慢,如果我們給局部變量再賦下值會發現編譯又會出現同樣的錯誤
public class ThreadTest {
public void function(String a) {
a="b";
new Thread(){
@Override
public void run() {
System.out.println(a);
}
}.start();
}
public static void main(String[] args) {
new ThreadTest().function("a");
}
}
在a="b"這一行報錯:Local variable a defined in an enclosing scope must be final or effectively final
也就是說規則沒有改變,只是java1.8的編譯變得更加智能了而已,在局部變量沒有重新賦值的情況下,它默認局部變量為final型,認為你只是忘記加final聲明了而已。如果你重新給局部變量改變了值或引用,那就無法默認為final了,所以報錯。
參考網站:
詳細原理版:java內部類訪問局部變量時的final問題 https://blog.csdn.net/xiancaieeee/article/details/8834352
對照原理版:關於java里方法的內部類只能訪問被final修飾的局部變量和... http://bbs.itheima.com/thread-136974-1-1.html
jdk不同帶來的區別:Java中方法內定義的內部類可以訪問方法中的局部變量的問題 https://bbs.csdn.net/topics/390918289?page=1