一. Lambda表达式
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。Lambda 表达式是在Java 8中引入的,并且成为了Java 8最大的特点。它使得功能性编程变得非常便利,极大地简化了开发工作。
要理解Lambda表达式,首先要了解函数式接口的概念。函数式接口是一种只有一个方法的接口,通过在接口里面添加一个方法,这个方法可以直接从接口中运行。如果一个接口定义个唯一一个方法,那么这个接口就成为函数式接口,可以通过@FunctionalInterface注解进行声明(非必须)。函数式接口可以隐式地转换成 Lambda 表达式。
函数式接口的重要属性是:我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码。
二. 语法
一个Lambda表达式由三个部分组成:第一部分为一个括号内用逗号分隔的参数列表,参数即函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,可以是表达式和代码块。语法如下:
1. 方法体为表达式,该表达式的值作为返回值返回。
(parameter) -> expression
2. 方法体为代码块,必须用 {} 来包裹起来,且需要一个 return 返回值,但若函数式接口里面方法返回值是 void,则无需返回值。
(parameters) -> { statements; }
下面列举了Lambda表达式的几个最重要的特征:
① 可选的类型声明:你不用去声明参数的类型。编译器可以从参数的值来推断它是什么类型。
② 可选的参数周围的括号:你可以不用在括号内声明单个参数。但是对于很多参数的情况,括号是必需的。
③ 可选的大括号:如果表达式体里面只有一个语句,那么你不必用大括号括起来。
④ 可选的返回关键字:如果表达式体只有单个表达式用于值的返回,那么编译器会自动完成这一步。若要指示表达式来返回某个值,则需要使用大括号。
三. 示例
1. 编写LambdaStarter类如下,对语句的解释可看注释:
public class LambdaStarter {
public static void main(String args[]) {
LambdaStarter starter = new LambdaStarter();
// 带有类型声明的表达式
MathOperation addition = (int a, int b) -> a + b;
// 没有类型声明的表达式
MathOperation subtraction = (a, b) -> a - b;
// 带有大括号、带有返回语句的表达式
MathOperation multiplication = (int a, int b) -> {
return a * b;
};
// 没有大括号和return语句的表达式
MathOperation division = (int a, int b) -> a / b;
// 输出结果
System.out.println("10 + 5 = " + starter.operate(10, 5, addition));
System.out.println("10 - 5 = " + starter.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + starter.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + starter.operate(10, 5, division));
// 没有括号的表达式
GreetingService greetService1 = message -> System.out.println("Hello " + message);
// 有括号的表达式
GreetingService greetService2 = (message) -> System.out.println("Hello " + message);
// 调用sayMessage方法输出结果
greetService1.sayMessage("Gordan");
greetService2.sayMessage("God");
}
// 下面是定义的一些接口和方法
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation) {
return mathOperation.operation(a, b);
}
}
运行结果如下:
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Gordan
Hello God
2. 作用域
Lambda表达式的作用域测试如下:
public class LambdaScope {
final static String salutation = "Hello "; // 正确,不可再次赋值
// static String salutation = "Hello "; //正确,可再次赋值
// String salutation = "Hello "; //报错
// final String salutation = "Hello "; //报错
public static void main(String args[]) {
// final String salutation = "Hello "; //正确,不可再次赋值
// String salutation = "Hello "; //正确,隐性为 final , 不可再次赋值
// salutation = "welcome to "; //错误,必须为final
GreetingService greetService1 = message -> System.out.println(salutation + message);
greetService1.sayMessage("Gordan");
}
interface GreetingService {
void sayMessage(String message);
}
}
可以得到以下结论:
①可访问 static 修饰的成员变量,如果是 final static 修饰,不可再次赋值,只有 static 修饰可再次赋值;
②可访问表达式外层的 final 局部变量(不用声明为 final,隐性具有 final 语义),不可再次赋值。