3.3 把Lambda付诸实践:环绕执行模式

让我们通过一个例子,看看在实践中如何利用Lambda和行为参数化来让代码更为灵活,更为简洁。资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式,如图3-2所示。例如,在以下代码中,加粗显示的就是从一个文件中读取一行所需的模板代码(注意你使用了Java 7中的带资源的try语句,它已经简化了代码,因为你不需要显式地关闭资源了):

  1. public String processFile() throws IOException {
  2. try (BufferedReader br =
  3. new BufferedReader(new FileReader("data.txt"))) {
  4. return br.readLine(); ←----这就是做有用工作的那行代码
  5. }
  6. }

3.3 把Lambda付诸实践:环绕执行模式 - 图1

图 3-2 任务A和任务B周围都环绕着进行准备/清理的同一段冗余代码

3.3.1 第1步:记得行为参数化

现在这段代码是有局限的。你只能读文件的第一行。如果你想要返回头两行,甚至是返回使用最频繁的词,该怎么办呢?在理想的情况下,你要重用执行设置和清理的代码,并告诉processFile方法对文件执行不同的操作。这听起来是不是很耳熟?是的,你需要把processFile的行为参数化。你需要一种方法把行为传递给processFile,以便它可以利用BufferedReader执行不同的行为。

传递行为正是Lambda的拿手好戏。那要是想一次读两行,这个新的processFile方法看起来又该是什么样的呢?基本上,你需要一个接受BufferedReader并返回String的Lambda。例如,下面就是从BufferedReader中打印两行的写法:

  1. String result
  2. = processFile((BufferedReader br) -> br.readLine() + br.readLine());

3.3.2 第2步:使用函数式接口来传递行为

前面解释过了,Lambda仅可用于上下文是函数式接口的情况。你需要创建一个能匹配BufferedReader -> String,还可以抛出IOException异常的接口。让我们把这一接口叫作BufferedReaderProcessor吧。

  1. @FunctionalInterface
  2. public interface BufferedReaderProcessor {
  3. String process(BufferedReader b) throws IOException;
  4. }

现在你就可以把这个接口作为新的processFile方法的参数了:

  1. public String processFile(BufferedReaderProcessor p) throws IOException {
  2. ...
  3. }

3.3.3 第3步:执行一个行为

任何BufferedReader -> String形式的Lambda都可以作为参数来传递,因为它们符合BufferedReaderProcessor接口中定义的process方法的签名。现在你只需要一种方法在processFile主体内执行Lambda所代表的代码。请记住,Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。因此,你可以在processFile主体内,对得到的BufferedReaderProcessor对象调用process方法执行处理:

  1. public String processFile(BufferedReaderProcessor p) throws IOException {
  2. try (BufferedReader br =
  3. new BufferedReader(new FileReader("data.txt"))) {
  4. return p.process(br); ←---- 处理BufferedReader对象
  5. }
  6. }

3.3.4 第4步:传递Lambda

现在你就可以通过传递不同的Lambda来重用processFile方法,并以不同的方式处理文件了。

处理一行:

  1. String oneLine =
  2. processFile((BufferedReader br) -> br.readLine());

处理两行:

  1. String twoLines =
  2. processFile((BufferedReader br) -> br.readLine() + br.readLine());

图3-3总结了所采取的使pocessFile方法更灵活的四个步骤。

3.3 把Lambda付诸实践:环绕执行模式 - 图2

图 3-3 应用环绕执行模式所采取的四个步骤

我们已经展示了如何利用函数式接口来传递Lambda,但你还是得定义自己的接口。下一节会探讨Java 8中加入的新接口,你可以重用它来传递多个不同的Lambda。