lambda表达式之变量作用域问题
变量作用域
考虑下面这个例子:
public static void repeatMessage(String text,int delay){
ActionListener listener = event->{
System.out.println(text);
Toolkit.getDefaultToolkit().beep();
};
new Time(delay,listener).start();
}
注意lambda表达式中的变量text。这个变量并不是在这个lambda表达式中定义的,实际上这是一个repeatMessage的参数变量。
lambda表达式的代码可能在repeatMessage调用返回很久以后才运行,而那时这个参数变量已经不存在了。如何保留text变量呢?
巩固对lambda表达式的理解
lambda表达式有3个部分:
1.一个代码块
2.参数
3.自由变量的值,这是非参数而且不在代码块中定义的变量。
在上面这个例子中,lambda表达式中有一个自由变量text。表示lambda表达式的数据结构必须存储自由变量的值,我们说它被lambda表达式捕获。
注释
:关于代码块以及自由变量值有一个术语:闭包。在Java中,lambda表达式就是闭包。
在Java中,lambda表达式可以捕获外围作用域中变量的值。这里有一个重要的限制:在lambda表达式中,只能引用值不会改变变量。
例如下面的做法是不合理的:
public static void repeatMessage(int start,int delay){
ActionListener listener = event->{
System.out.println(start--);//ERROR:Can't mutate captured variable
};
new Time(delay,listener).start();
}
再例如我们想将一个Integer型的列表中的值求和,如果在外面定义一个全局变量用于记录最终的值,初始化为0,那么下面的代码:
public static void main(String[] args) {
Integer[] arr={1,2,3,4,5};
List<Integer> list = Arrays.asList(arr);
Integer sum=0;
//合法的,lambda表达式中可以引用值
list.forEach(item-> System.out.println(item+"---"+sum));
list.forEach(item->sum+=item);
//Error: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
}
这个限制是因为,如果在lambda表达式中更改变量,并发执行多个操作就会不安全。另外如果在lambda表达式中引用一个变量,而这个变量可能在外部改变,这也是不合法的。
总结
:lambda表达式中捕获的变量必须实际上是事实最终变量。在lambda表达式中this的含义并没有变化。