匿名内部类为什么是 Final 的呢?

写在前面

匿名内部类来自外部闭包环境的自由变量必须是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$dog

2 为什么要拷贝?
这时候就得考虑一下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 语言 而言。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值