为什么java内部类访问局部变量必须声明为final?

先抛出让我疑惑了很久的一个问题

编程时,在线程中使用局部变量时候经常编译器会提示:局部变量必须声明为final

[java]  view plain  copy
  1. package test;  
  2.   
  3. public class ThreadTest {  
  4.       
  5.     public void function(String a) {  
  6.           
  7.         new Thread(){  
  8.             @Override  
  9.             public void run() {  
  10.                 System.out.println(a);  
  11.             }  
  12.         }.start();  
  13.     }  
  14.       
  15.       
  16.     public static void main(String[] args) {  
  17.         new ThreadTest().function("a");  
  18.           
  19.     }  
  20. }  

上图中由于方法function中的形参a没有声明为final,编译抛出异常:Cannot refer to the non-final local variable a defined in an enclosing scope


这个问题我特意问过老师,也百度过,都没有给出满意的解答。今天看安卓视频无意发现了答案,真是意外之喜啊!

其实原因就是一个规则:java内部类访问局部变量时局部变量必须声明为final

那为什么要这样呢?还有线程为什么和内部类一样?接下来我们慢慢揭秘。


[java]  view plain  copy
  1. public class Out {  
  2.       
  3.     public void test(final String a) {  
  4.         class In{  
  5.               
  6.             public void function() {  
  7.                 System.out.println(a);  
  8.             }  
  9.         }  
  10.         new In().function();  
  11.     }  
  12.       
  13.     public static void main(String[] args) {  
  14.         new Out().test("hi");  
  15.     }  
  16. }  
编译这个类后发现产生了两个class文件


也就是说内部类和外部类各一个class文件,这样就产生了一个问题,调用内部类方法的时候如何访问外部类方法中的局部变量呢?

实际上编译后的内部类的构造方法的里面,传了对应的外部类的引用和所有局部变量的形参。

(由于外部类方法执行完后局部变量会消亡,所以内部类构造函数中的局部变量实际是一份“复制”。而为了访问外部类中的私有成员变量,外部类编译后也产生了访问类似与getXXX的方法。)

这时产生了一个不一致的问题,如果局部变量不设为final,那内部类构造完毕后,外部类的局部变量又改变了那怎么办?

[java]  view plain  copy
  1. public class Out {  
  2.       
  3.     public void test(String a) {  
  4.         class In{     
  5.             public void function() {  
  6.                 System.out.println(a);  
  7.             }  
  8.         }  
  9.         a="hello";  
  10.         new In().function();  
  11.     }  
  12.        
  13.     public static void main(String[] args) {  
  14.         new Out().test("hi");  
  15.     }  
  16. }  

如代码中所示,这样调用内部类方法时会造成外部类局部变量和内部类中对应的变量的不一致。(注意内部类编译成class文件与new无关,a="hello"放在new In()前后不影响不一致关系,new在jvm运行class文件时才起效)


理解完内部类必须访问final声明的局部变量原因,我们回到最开始的问题:为什么线程和内部类一样

因为线程也是一个类,所以new Thread也相当于创建了一个内部类啦

我们编译一下最开始的ThreadTest.java文件


发现线程编译后也是产生了单独的class文件。

至此,问题全部解决啦~~


最后说明一下java1.8和之前版本对这个规则编译的区别。

如果在1.8的环境下,会很神奇的发现我们最开始的ThreadTest.java文件编译和运行是完全没有问题的,也就是说内部类使用的局部变量是可以不声明为final?!

且慢,如果我们给局部变量再赋下值会发现编译又会出现同样的错误

[java]  view plain  copy
  1. public class ThreadTest {  
  2.       
  3.     public void function(String a) {  
  4.         a="b";  
  5.         new Thread(){  
  6.             @Override  
  7.             public void run() {  
  8.                 System.out.println(a);  
  9.             }  
  10.         }.start();  
  11.     }  
  12.       
  13.       
  14.     public static void main(String[] args) {  
  15.         new ThreadTest().function("a");  
  16.           
  17.     }  
  18. }  
在a="b"这一行报错: Local variable a defined in an enclosing scope must be final or effectively final

也就是说规则没有改变,只是java1.8的编译变得更加智能了而已,在局部变量没有重新赋值的情况下,它默认局部变量为final型,认为你只是忘记加final声明了而已。如果你重新给局部变量改变了值或引用,那就无法默认为final了,所以报错

你可能感兴趣的:(为什么java内部类访问局部变量必须声明为final?)