Java8新特性入门二(Lambda表达式一)

1. Lambda 管中窥豹

1.1 Lambda 定义:
把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

  • 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多
  • 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方
    法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
  • 简洁——无需像匿名类那样写很多模板代码。
    示例
    Apple.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Apple {

    private String name;

    private String color;

    private Double weight;
}

匿名类表示:

Comparator<Apple> byWeight1 = new Comparator<Apple>(){
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
};

修改为Lambda表示:

Comparator<Apple> byWeight2  = (Apple o1, Apple o2)-> o1.getWeight().compareTo(o2.getWeight());

是不是简洁了很多?

1.2 Lambda表达式有三个部分

  • 参数列表——这里它采用了 Comparator 中 compare 方法的参数,两个 Apple
  • 箭头——箭头 -> 把参数列表与Lambda主体分隔开
  • Lambda主体——比较两个 Apple 的重量。表达式就是Lambda的返回值了

Lambda例子:
Java8新特性入门二(Lambda表达式一)

2. 在哪里以及如何使用 Lambda

2.1 函数式接口: 函数式接口就是只定义一个抽象方法的接口
下面三个接口都是函数式接口

@FunctionalInterface
public interface Comparator<T> {
	int compare(T o1, T o2);
}

@FunctionalInterface
public interface Callable<V>{
	V call();
}

public interface PrivilegedAction<T> {
	T run();
}

用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。
下面代码有效:

//Lambda表达式
Runnable r2 = ()-> System.out.println("hello");
//匿名内部类
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

2.2 函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。
2.3 @FunctionalInterface 又是怎么回事
Java8新特性入门二(Lambda表达式一)
描述如下:
Java8新特性入门二(Lambda表达式一)

3. 把 Lambda 付诸实践:环绕执行模式
让我们通过一个例子,看看在实践中如何利用Lambda和行为参数化来让代码更为灵活,更为简洁。资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式
Java8新特性入门二(Lambda表达式一)

在以下代码中,高亮显示的就是从一个文件中读取一行所需的模板代码

/**
 * 现在这段代码是有局限的。你只能读文件的第一行。如果你想要返回头两行,甚至是返回使
 * 用最频繁的词,该怎么办呢?
 * @return
 * @throws IOException
 */
public static String processFile() throws IOException {
    try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
        return br.readLine();
    }
}

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

  • 第 1 步:记得行为参数化
String result = processFile((BufferedReader br) -> br.readLine()+br.readLine());
  • 第 2 步:使用函数式接口来传递行为
import java.io.BufferedReader;
import java.io.IOException;

@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader br) throws IOException;
}

//修改方法如下:
public static String processFile(BufferedReaderProcessor processor) throws IOException {}
  • 第 3 步:执行一个行为
public static String processFile(BufferedReaderProcessor processor) throws IOException {
    try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
        return processor.process(br);
    }
}
  • 第 4 步:传递 Lambda
//处理一行
String result1 = processFile((BufferedReader br) -> br.readLine());
//处理二行
String result2 = processFile((BufferedReader br) -> br.readLine()+br.readLine());

下图总结了所采取的使 pocessFile 方法更灵活的四个步骤:
Java8新特性入门二(Lambda表达式一)

4. 使用函数式接口

Java8中提供的函数式接口:
Java8新特性入门二(Lambda表达式一)