参考文章: /question/21395848
java为什么匿名内部类的参数引用需要添加final?
先看一段代码
这段代码有时可以对外部变量进行赋值,有的又不可以,这是为什么呢?
网上有很多答案,但都没有说明清楚,反而在实验的过程中发现的现象让人越来约迷糊。
public class Index04 {int var1 = 5;A a = new A() {@Overridepublic void method() {var1 = 6;}};public void method(){int var2 = 5;A b = new A() {@Overridepublic void method() {var2 = 6; // 编译无法通过var1 = 6;Index04.this.var1= 7;}};}public static void main(String[] args) {int var1 = 5;A c = new A() {@Overridepublic 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内部类是典型的闭包结构。内部类通过一个指向外部类的引用,做到自由访问外部环境变量的所有字段,变相把环境中的自由变量封闭到函数中,形成闭包。
匿名内部类可以访问外部类的所有成员,不论成员变量是否有final修饰。
内部类访问局部变量
java 匿名内部类访问局部变量时,变量必须是final修饰的。
在jdk8及以后的版本,编译器会自动为被引用的局部变量添加final。
public void method(){int var2 = 5;A b = new A() {@Overridepublic 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等变量找不到。
但我们可以看到在内部类中根据外部变量的名称分别定义了,vala、vala、vala、valkevin等变量,并在构造器中对变量做了赋值。
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() {@Overridepublic void method() {var1 = 6; // 通过类引用访问 等同于 Index04.this.var1 = 6;Index04.this.var1 = 6;}};public void method(){int var2 = 5; // var2 和 method方法无法构成类结构 无法使用类引用访问变量A b = new A() {@Overridepublic void method() {// Index04.this 中没有var2 成员变量// 编译器会将 外部的var2拷贝到匿名内部类中// 为了保证值一致必须在外部变量中添加finalvar2 = 6; // 编译无法通过var1 = 6; // 通过类引用访问 等同于Index04.this.var1= 6;Index04.this.var1= 7;}};}public static void main(String[] args) {int var1 = 5; // 静态方法中无法构成类结构 无法使用类引用访问变量A c = new A() {@Overridepublic void method() {var1 = 6; // 编译无法通过}};}}interface A{void method();}