问题研究-Cannot refer to the non-final local variable a defined in an enclosing scope
问题:在java线程中使用局部变量时候经常编译器会提示:Cannot refer to the non-final local variable a defined in an enclosing scope(局部变量必须声明为final)
由于方法中的形参i没有声明为final,编译抛出异常:Cannot refer to the non-final local variable a defined in an enclosing scope
编译后会产生两个class文件:
也就是说内部类和外部类各一个class文件,这样就产生了一个问题,调用内部类方法的时候如何访问外部类方法中的局部变量呢?
实际上编译后的内部类的构造方法的里面,传了对应的外部类的引用和所有局部变量的形参。(由于外部类方法执行完后局部变量会消亡,所以内部类构造函数中的局部变量实际是一份“复制”。而为了访问外部类中的私有成员变量,外部类编译后也产生了访问类似与getXXX的方法。)这时产生了一个不一致的问题,如果局部变量不设为final,那内部类构造完毕后,外部类的局部变量有改变的可能。
具体分析
如上面的第7行代码所示,变量p没有被声明为final,如果是这样的话,当执行完第22行的outInvoke()方法后,outInvoke()方法将出栈,出栈后outInvoke()方法里面定义的所有变量(x 和 in)都死亡了,(但是此时内部类的对象还活着,直到它不再被使用才会被回收,也就是说此内部类对象的生命周期比局部变量的生命周期长),并且在变量 in 死亡之前,in 的值赋值给了成员变量obj(第19行代码),这时obj 指向了内部类的对象,如果此时在23行执行一条代码: out.outObj.toString();那么这条代码将会访问到局部变量 p,但是此时 outObj 已经死亡了,内部类对象已经访问不到 p 了,因此这是相互矛盾的。所以上面的代码实际上并不能编译通过。(在jdk8.0能编译通过,那是因为它检测到局部内部类访问了 p,会默认给 p 的前面加上隐式的final,如果在第8行加上一句代码:p = "test";编译器将会报错,因为final不允许 p的值改变)
如果局部变量 p 被声明为final后,p 就代表了一个常量,那么第10行的代码实际上就变成了 System.out.println("study...");,这时内部类相当于访问了"study..."。这是没任何问题的,因此局部内部类访问它所在方法的局部变量时,要求该局部变量必须声明为final。究其根本原因,是局部内部类对象的生命周期比局部变量的生命周期长。
所以具体原因:java内部类访问局部变量时局部变量必须声明为final,局部内部类对象的生命周期比局部变量的生命周期长
创建线程也相当于创建内部类,编译后会产生两个class文件。