为什么匿名内部类来自外部闭包环境的自由变量必须是final的【转】

转自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”。

猜你喜欢

转载自www.cnblogs.com/zhangbochao/p/12929799.html