3.7 正确使用Lambda表达式

刚开始介绍Lambda表达式时,以能够输出一些信息的回调函数为示例。回调函数是一个合法的Lambda表达式,但并不能真正帮助用户写出更简单、更抽象的代码,因为它仍然在指挥计算机执行一个操作。清理掉样板代码很有帮助,但Java 8引入的Lambda表达式的作用远不止这些。

本章介绍的概念能够帮助用户写出更简单的代码,因为这些概念描述了数据上的操作,明确了要达成什么转化,而不是说明如何转化。这种方式写出的代码,潜在的缺陷更少,更直接地表达了程序员的意图。

明确要达成什么转化,而不是说明如何转化的另外一层含义在于写出的函数没有副作用。这一点非常重要,这样只通过函数的返回值就能充分理解函数的全部作用。

没有副作用的函数不会改变程序或外界的状态。本书中的第一个Lambda表达式示例是有副作用的,它向控制台输出了信息——一个可观测到的副作用。下面的代码有没有副作用?

  1. private ActionEvent lastEvent;
  2. private void registerHandler() {
  3. button.addActionListener((ActionEvent event) -> {
  4. this.lastEvent = event;
  5. });
  6. }

这里将参数event保存至成员变量lastEvent。给变量赋值也是一种副作用,而且更难察觉。在程序的输出中可能很难直接观察到,但是它的确更改了程序的状态。Java在这方面有局限性,例如下面这段代码,赋值给一个局部变量localEvent

  1. ActionEvent localEvent = null;
  2. button.addActionListener(event -> {
  3. localEvent = event;
  4. });

这段代码试图将event赋给一个局部变量,它无法通过编译,但绝非编写错误。这实际上是语言的设计者有意为之,用以鼓励用户使用Lambda表达式获取值而不是变量。获取值使用户更容易写出没有副作用的代码。如第二章所述,在Lambda表达式中使用局部变量,可以不使用final关键字,但局部变量在既成事实上必须是final的。

无论何时,将Lambda表达式传给Stream上的高阶函数,都应该尽量避免副作用。唯一的例外是forEach方法,它是一个终结方法。