转自https://blog.csdn.net/hzy38324/article/details/77986095
package test; public class AnonymousDemo1 { public static void main(String args[]) { new AnonymousDemo1().play(); } private void play() { final Dog dog = new Dog(); Runnable runnable = new Runnable() { public void run() { while(dog.getAge()<100) { // 过生日,年龄加一 dog.happyBirthday(); // 打印年龄 System.out.println(dog.getAge()); } } }; new Thread(runnable).start(); // do other thing below when dog's age is increasing // .... } }
在dog前加上final即可通过编译,因为匿名内部类来自外部闭包环境的自由变量必须是final的。
其实Java就是把外部类的一个变量拷贝给了内部类里面的另一个变量。这个例子中,无论是内部类的val$dog变量,还是外部类的dog变量,他们都只是一个存储着对象实例地址的变量而已,而由于做了拷贝,这两个变量指向的其实是同一只狗(对象)。
那么为什么Java会要求外部类的dog一定要加上final呢?
一个被final修饰的变量:
- 如果这个变量是基本数据类型,那么它的值不能改变;
- 如果这个变量是个指向对象的引用,那么它所指向的地址不能改变。
因此,这个例子中,假如我们不加上final,那么我可以在代码后面加上这么一句dog = new Dog(); 这样做导致的结果就是内部类里的变量和外部环境的变量不同步,指向了不同的对象。
因此,编译器才会要求我们给dog变量加上final,防止这种不同步情况的发生。
为什么要拷贝
现在我们知道了,是由于一个拷贝的动作,使得内外两个变量无法实时同步,其中一方修改,另外一方都无法同步修改,因此要加上final限制变量不能修改。
那么为什么要拷贝呢,不拷贝不就没那么多事了吗?
这时候就得考虑一下Java虚拟机的运行时数据区域了,dog变量是位于方法内部的,因此dog是在虚拟机栈上,也就意味着这个变量无法进行共享,匿名内部类也就无法直接访问,因此只能通过值传递的方式,传递到匿名内部类中。
一定要加final吗
package test; public class AnonymousDemo1 { Dog dog = new Dog(); public static void main(String args[]) { new AnonymousDemo1().play(); } private void play() { Runnable runnable = new Runnable() { public void run() { while(dog.getAge()<100) { // 过生日,年龄加一 dog.happyBirthday(); // 打印年龄 System.out.println(dog.getAge()); } } }; new Thread(runnable).start(); // do other thing below when dog's age is increasing // .... } }
这里的dog成了成员变量,对应的在虚拟机里是在堆的位置,而且无论在这个类的哪个地方,我们只需要通过 this.dog,就可以获得这个变量。因此,在创建内部类时,无需进行拷贝,甚至都无需将这个dog传递给内部类。
Java8之后的变动
从JDK1.8开始,编译器不要求自由变量一定要声明为final,如果这个变量在后
int answer = 42; answer ++; // 不可以有这句 Thread t = new Thread( () -> System.out.println("The answer is: " + answer) );
面的使用中没有发生变化,就可以通过编译,Java称这种情况为“effectively final”。