参考文章: 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内部类是典型的闭包结构。内部类通过一个指向外部类的引用,做到自由访问外部环境变量的所有字段,变相把环境中的自由变量封闭到函数中,形成闭包。
匿名内部类可以访问外部类的所有成员,不论成员变量是否有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 a、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() {
@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();
}