java为什么匿名内部类的参数引用需要添加final?

参考文章: https://www.zhihu.com/question/21395848

java为什么匿名内部类的参数引用需要添加final?

先看一段代码

这段代码有时可以对外部变量进行赋值,有的又不可以,这是为什么呢?

网上有很多答案,但都没有说明清楚,反而在实验的过程中发现的现象让人越来约迷糊。

public class Index04 {
    int var1 = 5;
    A a = new A() {
        @Override
        public void method() {
            var1 = 6;
        }
    };
    public void method(){
        int var2 = 5;
        A b = new A() {
            @Override
            public void method() {
                var2 = 6; // 编译无法通过
                var1 = 6;
                Index04.this.var1= 7;
            }
        };
    }
    public static void main(String[] args) {
        int var1 = 5;
        A c = new A() {
            @Override
            public void method() {
                var1 = 6; // 编译无法通过
            }
        };
    }
}

什么是闭包

简单可以理解为: 依赖于外部自由变量且可以访问自由变量的函数。

想到闭包,可能第一个想到的就是JavaScript中的闭包,其实java到处都是闭包。因为Java的“对象”其实就是一个闭包。其实无论是闭包也好,对象也好,都是一种数据封装的手段。

public class Add {
    int x = 2;
    
    public int add(){
        int y = 3;
        return x+y;// return this.x +y;
    }
}

add()函数其实是透过this关键字访问对象的成员变量。

java中的闭包: 包含指向外部类的指针

java内部类是典型的闭包结构。内部类通过一个指向外部类的引用,做到自由访问外部环境变量的所有字段,变相把环境中的自由变量封闭到函数中,形成闭包。

image

匿名内部类可以访问外部类的所有成员,不论成员变量是否有final修饰。

内部类访问局部变量

java 匿名内部类访问局部变量时,变量必须是final修饰的。

在jdk8及以后的版本,编译器会自动为被引用的局部变量添加final。

    public void method(){
        int var2 = 5;
        A b = new A() {
            @Override
            public void method() {
                var2 = 6; // 编译无法通过
            }
        };
    }

为什么匿名内部类访问局部变量需要添加final呢?

因为var2 变量和method 方法无法构成类结构。java实现了闭包,但支持不完整,编译器偷偷的把外部局部变量拷贝一份到匿名内部类中

所以: Java编译器实现的只是capture-by-value,并没有实现capture-by-reference。
而只有后者才能保持匿名内部类和外部环境局部变量保持同步。

但Java又不肯明说,只能粗暴地一刀切,就说既然内外不能同步,那就不许大家改外围的局部变量。

证明->对局部变量做了copy
原始类
public class Index03 {
    public void method(){
        int  a = 10;
        User kevin = new User("kevin");
        class Inner2{
            public void say(){
               // a= 5; 编译错误
                System.out.println(a);
                System.out.println(kevin);
            }
        }
        // 局部内部类 只能在方法中使用
        Inner2 inner2 = new Inner2();
        inner2.say();
        System.out.println("method");
    }

    public static void main(String[] args) {
        Index03 index03 = new Index03();
        index03.method();
    }
}

使用jad反编译后的文件

反编译后文件可能是有问题,比如i,user等变量找不到。

但我们可以看到在内部类中根据外部变量的名称分别定义了,val a 、 v a l a、val avalkevin等变量,并在构造器中对变量做了赋值。

public class Index03
{

    public Index03()
    {
    }

    public void method()
    {
        final int a = 10;
        final User kevin = new User("kevin");
        class _cls1Inner2
        {

            public void say()
            {
                System.out.println(a);
                System.out.println(kevin);
            }

            final int val$a; 
            final User val$kevin;
            final Index03 this$0;

            _cls1Inner2()
            {
                this.this$0 = Index03.this;
                a = i;
                kevin = user;
                super();
            }
        }

        _cls1Inner2 inner2 = new _cls1Inner2();
        inner2.say();
        System.out.println("method");
    }

    public static void main(String args[])
    {
        Index03 index03 = new Index03();
        index03.method();
    }
}

java为什么匿名内部类的参数引用需要添加final?

因为可以通过外部类引用访问变量,所以并不是匿名内部类引用参所都需要final。那么何时需要添加final呢?

结论:

  • 如果可以通过外部类引用访问变量,变量不需要添加final
  • 如果局部变量或者静态方法中的变量因为无法构成类结构,需要添加final
对开头的代码进行分析
public class Index04 {
    int var1 = 5;
    A a = new A() {
        @Override
        public void method() {
            var1 = 6; // 通过类引用访问 等同于 Index04.this.var1 = 6;
            Index04.this.var1 = 6;
        }
    };
    public void method(){
        int var2 = 5; // var2 和 method方法无法构成类结构 无法使用类引用访问变量
        A b = new A() {
            @Override
            public void method() {
                // Index04.this 中没有var2 成员变量
                // 编译器会将 外部的var2拷贝到匿名内部类中
                // 为了保证值一致必须在外部变量中添加final
                var2 = 6; // 编译无法通过
                var1 = 6; // 通过类引用访问 等同于Index04.this.var1= 6;
                Index04.this.var1= 7;
            }
        };
    }
    public static void main(String[] args) {
        int var1 = 5; // 静态方法中无法构成类结构 无法使用类引用访问变量
        A c = new A() {
            @Override
            public void method() {
                var1 = 6; // 编译无法通过
            }
        };
    }
}

interface A{
    void  method();
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值