1.Lambda 表达式的背景
Lambda 表达式是从 Java8 开始推出的功能,这个强大的功能弥补了 Java 在函数式编程方面的缺陷。Lambda 表达式看似是一个很普通的功能,其实背后隐藏了很深的编程思想。
要想深刻理解 Lambda 表达式,首先应该了解函数式编程 ( functional programming ),有关函数式编程的大致思想,可以参考这篇文章,此处不做解释。
2.Lambda 表达式的用法
在学习 Lambda 表达式用法之前,需要先明确一个概念,即函数式接口。所谓的函数式接口,其实就是只拥有一个方法的接口,这种接口之前被称为 SAM 类型 ( Single Abstract Method ) 。理解了函数式接口之后便可以开始学习 Lambda 表达式的使用方式。
下面是一些 Lambda 表达式:
() -> "lambda"
(int a, int b) -> a+b
(String s) -> { System.out.println(s); }
第一个表达式不接收参数,返回字符串 "lambda",第二个表达式接受两个 int 参数 a 和 b,返回两者的和,第三个表达式接受一个字符串并打印,不返回值。
Lambda 表达式主要分为三个部分:参数列表,箭头,函数体。函数体可以是一个单表达式或者一个语句块。如果是单表达式则表达式会被执行然后返回结果,可以省略 return 关键字。如果是一个语句块,则语句块就会像普通方法中的语句块一样被执行。
观察下面两个表达式:
() -> "lambda"
() -> { return "lambda"; }
由于第二个表达式是语句块,所以必须带 return,即使它里面只有一句话。当然在实际应用中并不会写像第二句这样的表达式,如果你写了,你的 IDE 估计也会提醒你可以使用表达式 Lambda 来代替语句块 Lambda。
3.Lambda 表达式的使用时机
简单了解了 Lambda 表达式的语法,那么它使用的地方又是在哪里呢?如果只是简单的将上面的表达式敲到程序里,你应该得不到你想要的结果。
其实 Lambda 表达式可以看作是匿名内部类的一种延申,观察下面的例子:
/* TestInter.java */
public interface TestInter {
String getMessage();
}
/* TestPrint.java */
public class TestPrint {
void print(TestInter test) {
System.out.println(test.getMessage());
}
}
如果是使用匿名内部类,那么在调用 TestPrint 类的 print() 时一般会像下面这样写:
/* Test.java */
public class Test {
public static void main(String[] args) {
TestPrint testPrint = new TestPrint();
testPrint.print(new TestInter() {
@Override
public String getMessage() {
return "lambda";
}
});
}
}
如果你使用的 IDE 比较新,可能已经注意到 IDE 提示你在匿名内部类处可以使用 Lambda 表达式代替。是的,上面的类可以通过使用 Lambda 表达式改写成下面的样子:
/* Test.java */
public class Test {
public static void main(String[] args) {
TestPrint testPrint = new TestPrint();
testPrint.print(() -> "lambda");
}
}
可以看到,通过使用 Lambda 表达式,将一个十一行的类缩减到了六行。
前面提到了函数式接口,可以看到上面的例子中 TestInter 就是一个函数式接口。当我们在用匿名内部类实现函数式接口的时候,就可以选择使用 Lambda 表达式代替匿名内部类。Lambda 表达式并不仅仅起到一个代码精简的作用,关于更深层次的作用本文不做探讨。
4.Lambda 表达式的类型
到此我们对 Lambda 表达式已经有了一个大概的了解,那么也应该会注意到一个问题,我们没有指定 Lambda 表达式的类型。在第三节的例子中,我们并没有指定 Lambda 表达式的类型,但是编译器通过上下文推导,推导出该 Lambda 表达式的类型为接口 TestInter,这个被推导出的类型称为 Lambda 表达式的目标类型,理解了目标类型的概念,我们就能更近一步地定义 Lambda 表达式的使用时机了。Lambda 表达式只能出现在目标类型为函数式接口的上下文中。
既然 Lambda 表达式是用来实现函数式接口的,那么对于接口方法的参数类型其实也可以推导出来,是不是也可以省略参数类型呢?答案是肯定的。例如我有这样一个接口:
public interface Test {
void testFunc(String a, String b);
}
那么我就可以像下面这样用 Lambda 表达式定义实例:
Test test = (x, y) -> x.compareTo(y);
特别的,当参数只有一个的时候,还可以省略参数两边的括号,就像下面这样:
x -> x == 1
5.结尾
该文章只是作为 Lambda 表达式的一个初见,关于 Lambda 表达式还有很多细节可以探讨,这些细节将会在之后的文章中出现。作为学习 Lambda 表达式的随笔,该文章肯定还有很多不足之处,欢迎指出交流。
参考文档:
docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.htm