java为什么匿名内部类的参数引用时final?
1. 闭包(Closure)
什么是闭包,简答来说:
· 一个依赖于 外部环境自由变量 的函数
· 这个函数能够访问外部环境里的 自由变量
JavaScript举个例子:
function Add(y) {
return function(x) {
return x+y;
}
}
对内部函数function(x)来说,y就是自由变量,而且function()的返回值依赖于这个外部自由变量y。而外围的Add(y)函数正好就是那个包含自由变量y的环境。而且JavaScript语法允许内部函数function(x)访问外部函数Add(y)的局部变量。满足这三个条件,外部函数Add(y)对内部函数function(x)构成了闭包(Closure).
在Java中我们看不到这样的结构,因为Java语法不允许这样直接的函数嵌套和跨域访问变量。
2. Java中不存在闭包么?
看这个例子:
class Add {
private int x = 2;
public int add() {
int y = 3;
return x+y;
}
}
其实Java到处都是闭包,使我们反而感觉不出来。
3. Java内部类是闭包:包含指向 外部类 的指针。
public class Outer {
private int x = 100;
private class Inner {
private y = 100;
public int innerAdd() {
return x+y;
}
}
}
内部类(Inner Class)通过包含一个指向外部类的引用,做到自由访问外部环境类的所有字段,变相把环境中的自由变量封装到函数里,形成一个闭包。
4. 尴尬的匿名内部类
看下面的例子:
interface AnnoInner() { add(); }
public class Outer {
public AnnoInner getAnnoInner(final int x) {
final int y = 100;
return new AnnoInner() {
int z = 100;
public int add() { return x+y+z;}
//public void changY() { y+=1; } //这个函数无法修改外部环境中的自由变量Y
};
}
}
匿名内部类因为是匿名,所以不能显示地声明 构造函数,也不能忘构造函数里 传参。不但返回的只是个 叫 AnnoInner 的接口,而且还没有和它 外围环境getAnnoInner()方法的局部变量x和y构成任何 类的结构(Java的闭包也不能用)。 但是匿名内部类的add()方法却直接使用了 x和y,说明 外部getAnnoInner()方法 实际上已经对内部类AnnerInner构成了一个闭包。
但这里别扭的是,x和y都必须用 final 修饰,不可以修改。如果用 changY()函数试图修改外部getAnnoInner()函数的成员变量y,编译不通过。
其实是Java编译器在编译的时候偷偷对 函数做了手脚,将外部环境方法的x和y局部变量,拷贝了一份到 匿名内部类中。
interface AnnoInner() { add(); }
public class Outer {
public AnnoInner getAnnoInner(final int x) {
final int y = 100;
return new AnnoInner() {
int copyX = x;
int copyY = y;
int z = 100;
public int add() { return x+y+z;}
//public void changY() { y+=1; } //这个函数无法修改外部环境中的自由变量Y
};
}
}
对于匿名内部类,Java编译器实现的只是 值传递(capture-by-value),并未实现 引用传递(capture-by-reference)。
而只有引用传递才能保持匿名内部类 与外部环境的 局部变量保持同步!
但是 Java又不肯说明,只能粗暴的一刀切,就说 既然内外不能同步,那就不允许大家改变外围的局部变量,就必须用 final修饰了。
总结:
· Java设计者想要匿名函数或者Lambda函数内部外部保持数据一致性(知道了目的才好理解)
· 然而Java只是实现了capture-by-value形式的闭包,也就是匿名函数内部会重新拷贝一份自由变量,然后函数外部和函数内部就有两份数据
· 那么要想实现1中的目的,只能要求两处变量不变。JDK8之前要求使用final修饰,JDK8聪明些了,可以使用effectively final的方式。总之,两边不变就行了。