写在前面
匿名内部类来自外部闭包环境的自由变量必须是final的,除非自由变量来自类的成员变量。
什么是自由变量?
一个函数的“自由变量”就是既不是函数参数也不是函数内部局部变量的变量。
什么是外部环境
外部环境如果持有内部函数所使用的自由变量,就会对内部函数形成“闭包”。
- -
一个简单的列子
public class AnonymousDemo1
{
public static void main(String args[])
{
new AnonymousDemo1().play();
}
private void play()
{
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
// ....
}
}
public class Dog
{
private int age;
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public void happyBirthday()
{
this.age++;
}
}
编译上面的代码,编译出错
Variable ‘dog’ is accessed from within inner class,need to be declared final
意思说,需要将 dog 变量定义为 final 类型,可是为什么要定义为 final 类型,final 类型的 变量有什么优势呢?
final Dog dog = new Dog();
一个被final修饰的变量:
如果这个变量是基本数据类型,那么它的值不能改变;
暂且先放一下上面的疑问,看看匿名内部类到底长什么样子
反编译匿名内部类
反编译之后的内名内部类代码如下:
class AnonymousDemo1$1 implements Runnable {
final Dog val$dog;
final AnonymousDemo1 this$0;
AnonymousDemo1$1(final_anonymousdemo1,Dog)
{
this$0 = final_anonymousdemo1;
val$dog = Dog.this;
super();
}
public void run()
{
for(; val$dog.getAge() < 100; System.out.println(val$dog.getAge()))
val$dog.happyBirthday();
}
}
1 匿名内部类的构造函数,有两个参数:一个是外部类对象的引用,一个是局部变量 Dog 对象的引用,因为下面的 run 方法会使用到该对象。
2 那么 外部类 AnonymousDemo1 可以等同于如下的代码
public class AnonymousDemo1
{
public static void main(String args[])
{
new AnonymousDemo1().play();
}
private void play()
{
Dog dog = new Dog();
AnonymousDemo1$1 runnable = new AnonymousDemo1$1(this,dog)
new Thread(runnable).start();
}
}
还是没有回答主要问题
到这里我们已经看清匿名内部类的全貌了,其实Java就是把外部类的一个变量拷贝给了内部类里面的另一个变量。这个例子中,无论是内部类的val$dog变量,还是外部类的dog变量,他们都只是一个存储着对象实例地址的变量而已,而由于做了拷贝,这两个变量指向的其实是同一只狗(对象)。
因此,这个例子中,假如我们不加上final,那么我可以在代码后面加上这么一句dog = new Dog(); 就像下面这样:
// ...
new Thread(runnable).start();
// do other thing below when dog's age is increasing
dog = new Dog();
这样,外面的dog变量就指向另一只狗了,而内部类里的val$dog,还是指向原先那一只,就像这样:
**这样做导致的结果就是内部类里的变量和外部环境的变量不同步,指向了不同的对象。
因此,编译器才会要求我们给dog变量加上final,防止这种不同步情况的发生。**
那为什么要拷贝呢
1 拷贝了什么东西?
将 dog 的引用地址 拷贝到了 val$dog2 为什么要拷贝?
这时候就得考虑一下Java虚拟机的运行时数据区域了,dog变量是位于方法内部的,因此dog是在虚拟机栈上,也就意味着这个变量无法进行共享,匿名内部类也就无法直接访问,因此只能通过值传递的方式,传递到匿名内部类中。
哪种情况不需要拷贝呢?
拷贝的原因,是因为 内部类中 无法 直接访问外部方法中定义 的 dog 变量,如果可以直接访问,那么就没必要拷贝 应用了
我们修改一下 AnonymousDemo1 代码,将 dog 变量提升为 成员属性
private Dog dog = new Dog();
接下来反编译一下 AnonymousDemo1$1 类
class AnonymousDemo1$1 implements Runnable
{
final AnonymousDemo1 this$0;
AnonymousDemo1$1()
{
this$0 = AnonymousDemo1.this;
super();
}
public void run()
{
for(; AnonymousDemo1.access$000(AnonymousDemo1.this).getAge() < 100; System.out.println(AnonymousDemo1.access$000(AnonymousDemo1.this).getAge()))
AnonymousDemo1.access$000(AnonymousDemo1.this).happyBirthday();
}
}
1 现在 匿名内部类 不在需要 dog 变量的拷贝了,只需要引用 上层的 外部类对象就好了, 因为可以通过 外部类对象直接 获取到 dog 对象了,因而编译器也就不要求加上final了。
写在最后
匿名内部类来自外部闭包环境的自由变量必须是final的”:
首先,自由变量是什么?
一个函数的“自由变量”就是既不是函数参数也不是函数内部局部变量的变量,这种变量一般处于函数运行时的上下文,就像demo中的dog,有可能第一次运行时,这个dog指向的是age是10的狗,但是到了第二次运行时,就是age是11的狗了。然后,外部闭包环境是什么?
外部环境如果持有内部函数所使用的自由变量,就会对内部函数形成“闭包”,demo1中,外部play方法中,持有了内部类中的dog变量,因此形成了闭包。
当然,demo2中,也可以理解为是一种闭包,如果这样理解,那么这句经典的话就应该改为这样更为准确:
匿名内部类来自外部闭包环境的自由变量必须是final的,除非自由变量来自类的成员变量,仅针对 Java 语言 而言。