前面的文章中已经多次出现了Lambda表达式的身影,其简洁与灵活性相信读者也应有所感受。那么Lambda表达式究竟是个什么鬼?它的语法是什么?它由什么组成?我们该怎样使用Lambda表达式以及在哪里使用?本篇文章将解答这些问题。
Lambda表达式是什么?
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
Lambda表达式的语法与组成
Lambda表达式由参数、箭头、主体组成。如下图:
所以,Lambda表达式的基本语法可以总结为:(parameters) -> expression
或 (parameters) -> { statements; }
对照上面的语法,下表列出了一些常用的Lambda表达式:
使用案例 | Lambda示例 |
---|---|
布尔表达式 | (List list) -> list.isEmpty() |
创建对象 | () -> new Apple(10) |
消费一个对象 | (Apple a) -> {System.out.println(a.getWeight());} |
从一个对象中选择/抽取 | (String s) -> s.length() |
组合两个值 | (int a, int b) -> a * b |
比较两个对象 | (Apple a1, Apple a2) ->a1.getWeight().compareTo(a2.getWeight()) |
Lambda表达式的使用
在前面文章中那个过滤苹果的方法,我们用Lambda表达式实现的如下:
List greenApples = filter(inventory, (Apple a) -> "green".equals(a.getColor()));
那么为什么能这样使用呢?filter方法的第二个参数实际上是一个函数式接口Predicate
,它的定义如下:
public interface Predicate{
boolean test (T t);
}
函数式接口就是只定义一个抽象方法的接口
实际上,在java8以前,我们可能已经接触过下面这些函数式接口:
//java.util.Comparator
public interface Comparator {
int compare(T o1, T o2);
}
//java.lang.Runnable
public interface Runnable{
void run();
}
//java.awt.event.ActionListener
public interface ActionListener extends EventListener{
void actionPerformed(ActionEvent e);
}
//java.util.concurrent.Callable
public interface Callable{
V call();
}
//java.security.PrivilegedAction
public interface PrivilegedAction{
V run();
}
而Predicate
是java8中定义的一个新的函数式接口.
Lambda表达式允许我们以内联的方式实现一个函数式接口(Lambda表达式本身就是函数式接口的一个实例).
如下,我们即可以以函数式接口的方式,也可以以匿名类的方式传递runnable的实体,它们的实现效果相同,只是匿名类更为烦锁而已:
//使用Lambda实例化runnable
Runnable r1 = () -> System.out.println("Hello World 1");
//使用匿名类,实例化runnable
Runnable r2 = new Runnable(){
public void run(){
System.out.println("Hello World 2");
}
};
public static void process(Runnable r){
r.run();
}
process(r1);
process(r2);
//直接传递Lambda表达式
process(() -> System.out.println("Hello World 3"));
从上面的代码中可以看出,Lambda表达式可以赋值给一个变量,也可以作为参数直接传递给以函数式接口为参数的方法,前提是Lambda表达式的签名要和函数式接口的抽象方法一样,这个抽象方法也叫做函数描述符,java编译器就是根据上下文来匹配函数描述符来判断Lambda表达式是否合法.
Lambda表达式在环绕执行模式(execute around)中的应用
通俗来讲,环绕执行模式(execute around)是指代码执行的首尾都是模版代码,真正重要的代码在中间部分执行,比如文件处理:先是打开文件,然后对文件进行处理,最后释放资源,这里我们关注的重点是对文件的处理,而不是打开文件与资源的释放.
而文件的处理,因为需求的不同会有所区别,所以Lambda表达式特别适合环绕执行模式的代码开发,像打开文件与释放资源这种一成不变的模版代码写在方法内,不同需求对应的不同行为以Lambda的形式传递进去。
代码如下:
//定义函数式接口来传递处理文件的行为
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
//定义处理文件的方法(环绕执行模式)
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
//传递Lambda:读取一行
String oneLine = processFile((BufferedReader br) -> br.readLine());
//传递Lambda:读取两行
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());