在某些情况下,Java lambda 表达式能够访问在 lambda 函数体之外声明的变量。
Java lambda 可以捕获以下类型的变量:
局部变量
实例变量
静态变量
下文将逐一介绍这些变量捕获方式。
Java lambda 表达式可以捕获在 lambda 主体之外声明的局部变量的值。为了说明这一点,请先看一下这个单一方法接口:
public interface MyFactory { public String create(char[] chars); }
现在,请看这个实现了 MyFactory 接口的 lambda 表达式:
MyFactory myFactory = (chars) -> { return new String(chars); };
现在,这个 lambda 表达式只引用传给它的参数值(字符)。但我们可以改变这一点。下面是一个更新版本,它引用了在 lambda 函数体之外声明的字符串变量:
// myString 实际上是一个 final 修饰的变量 String myString = "Test"; MyFactory myFactory = (chars) -> { return myString + ":" + new String(chars); };
如您所见,lambda 主体现在引用了在 lambda 主体之外声明的局部变量 myString。只有当且仅当被引用的变量是“有效的最终变量”(即在赋值后不会改变其值)时,才有可能这样做。如果 myString 变量的值后来发生了变化,编译器就会抱怨 lambda 主体内部对它的引用。
Java lambda 表达式还可以捕获创建 lambda 的对象中的实例变量。下面的示例可以说明这一点:
public class EventConsumerImpl { private String name = "MyConsumer"; public void attach(MyEventProducer eventProducer){ eventProducer.listen(e -> { System.out.println(this.name); }); } }
请注意 lambda 主体中对 this.name 的引用。这将捕获外层 EventConsumerImpl 对象的 name 实例变量。甚至可以在捕获后更改实例变量的值,而该值将反映在 lambda 中。
这实际上是 Java lambda 与匿名接口实现不同的地方之一。匿名接口实现可以拥有自己的实例变量,并通过 this 引用进行引用。但是,lambda 不能拥有自己的实例变量,因此 this 总是指向外层对象。
注意:上述事件消费者的设计并不特别优雅。我这样做只是为了说明实例变量捕获。
Java lambda 表达式也可以捕获静态变量。这并不奇怪,因为只要静态变量是可访问的(包作用域或公共变量),Java 应用程序中的任何地方都可以访问静态变量。
下面是一个创建了 lambda 的示例类,它在 lambda 主体内部引用了一个静态变量:
public class EventConsumerImpl { private static String someStaticVar = "Some text"; public void attach(MyEventProducer eventProducer){ eventProducer.listen(e -> { System.out.println(someStaticVar); }); } }
静态变量的值也允许在 lambda 捕捉后发生变化。
同样,上述类的设计有点不合理。不要想太多。这个类的主要作用是向你展示 lambda 可以访问静态变量。