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例子:
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 又是怎么回事
描述如下:
3. 把 Lambda 付诸实践:环绕执行模式
让我们通过一个例子,看看在实践中如何利用Lambda和行为参数化来让代码更为灵活,更为简洁。资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式
在以下代码中,高亮显示的就是从一个文件中读取一行所需的模板代码
/**
* 现在这段代码是有局限的。你只能读文件的第一行。如果你想要返回头两行,甚至是返回使
* 用最频繁的词,该怎么办呢?
* @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 方法更灵活的四个步骤:
4. 使用函数式接口
Java8中提供的函数式接口: