Lambda 表达式可以理解为一种匿名函数: 没有名称, 但有参数列表, 函数主体, 返回类型. 它是行为参数化的一种实现, 行为参数化是指将不同的行为作为参数传递给方法, 方法的所具备的能力取决于它接收的行为参数. 使用 Lambda 表达式使我们不必为这些行为去编写一堆固定的实现类就能应对不断变化的需求, 在 1.8 之前, 可以使用匿名内部类的方式达到相同的效果, 只是相对于 Lambda 表达式来说, 匿名内部类的方式会显得啰嗦.
函数式接口
Lambda 表达式的使用依赖于函数式接口, 只有在接受函数式接口的地方才可以使用 Lambda 表达式. 函数式接口是指只声明了一个抽象方法的接口, 可以有多个静态方法, 默认方法, 如下所示:
- @FunctionalInterface
- public interface Calculation {
- int calculate(int a, int b);
- }
@FunctionalInterface 注解表示被标注的接口将被设计成一个函数式接口, 不是必须的, 它主要是在接口违背函数式接口原则时会出现编译错误. 比如修改 Calculation 接口, 再添加一个抽象方法就会出现 Multiple non-overriding abstract methods found in interface com.cf.demo.lambda.Calculation 编译错误:
- // 编译错误: Multiple non-overriding abstract methods found in interface com.cf.demo.lambda.Calculation
- @FunctionalInterface
- public interface Calculation {
- int calculate(int a, int b);
- int calculate2(int a, int b);
- }
注意: Object 类的方法是特例, 即使接口声明了多个 Object 类的方法, 也不会被算入 "只声明了一个抽象方法" 的计数中. 如下 Calculation 接口是正确的函数式接口:
- @FunctionalInterface
- public interface Calculation {
- int calculate(int a, int b);
- boolean equals(Object obj);
- }
Java8 提供了一些常用的函数式接口, 位于 java.util.function 包下, 并且为了避免装箱操作, 还提供了和基本类型对应的接口, 我们在实际使用时, 可以优先使用这些内置的函数式接口. 当然在某些情况我们也需要使用自定义的函数式接口, 如需要在 Lambda 表达式中抛异常时, 这种情况就需要自定义一个函数式接口, 并声明异常.
Lambda 表达式语法
Lambda 表达式由参数列表, 箭头(Lambda 操作符),Lambda 主体三个部分组成. Lambda 表达式的参数列表要和函数式接口的参数列表相对应, Lambda 主体的返回值也要和函数式接口的返回类型相对应. 现在有如下 doArithmetic 方法, 接收两个整型参数以及一个 Calculation,doArithmetic 方法的行为是由传递的 Calculation 来决定的, 我们可以调用该方法传递不同的 Calculation 来完成不同的计算:
- public static int doArithmetic(int a, int b, Calculation calculation){
- return calculation.calculate(a, b);
- }
现在要计算两个数的乘积, 用内部类的方式:
- int result = doArithmetic(3, 2, new Calculation() {
- @Override
- public int calculate(int a, int b) {
- return a * b;
- }
- });
- System.out.println(result);//6
用 Lambda 表达式的方式要更简洁:
- int result = doArithmetic(3, 2, (int a, int b) -> a * b);
- System.out.println(result);//6
(int a, int b)是 Lambda 表达式的参数列表部分, 只有一个参数的时候可以省略小括号, 这里有多个参数, 所以要保留小括号. 参数类型可以省略, 因为 Java 编译器能通过上下文推断出数据类型, 无需显示的声明:
- int result = doArithmetic(3, 2, (a, b) -> a * b);
- System.out.println(result);//6
Lambda 主体只有一个语句时, 可以省略 {} 和 return,(int a, int b) -> a * b)就是省略之后的写法, 我们也可以使用完整的写法:
- int result = doArithmetic(3, 2, (a, b) -> {
- return a * b;
- });
- System.out.println(result);//6
当需要在 Lambda 表达式中使用'外部局部变量'时, 这个'外部局部变量'默认是 final 的,'外部局部变量'这里是指非 Lambda 表达式内部定义的局部变量. 修改 doArithmetic 方法, 添加一个'外部局部变量', 为乘积赋个初始值, 以下代码是编译不通过的:
- int initialValue = 1;
- int result = doArithmetic(3, 2, (a, b) -> a * b + initialValue);
- initialValue = 2;//Variable used in lambda expression should be final or effectively final
- System.out.println(result);
方法引用
方法引用可以对'某种特殊情况'下的 Lambda 表达式进行简化,'某种特殊情况'是指 Lambda 表达式要做的事情别的方法实现了, 那我们就可以直接使用这个方法, 然后像 Lambda 表达式一样传递即可. 方法引用的语法为目标引用放在分隔符:: 前, 方法的名称放在后面, 目标引用可以是类名也可以是对象名. 通过以下三个例子来介绍方法引用的三种使用方法, 新增 Arithmetic 类, Arithmetic 类包含一个静态方法和一个实例方法:
- public class Arithmetic {
- public static int multiply(int a, int b){
- return a * b;
- }
- public int add(int a, int b){
- return a + b;
- }
- }
1. 指向静态方法的方法引用
- int multiplyResult = doArithmetic(3, 2, Arithmetic::multiply);
- System.out.println(multiplyResult);//6
2. 指向现有对象的实例方法的方法引用
- Arithmetic arithmetic = new Arithmetic();
- int addResult = doArithmetic(3, 2, arithmetic::add);
- System.out.println(addResult);//5
3. 指向任意类型实例方法的方法引用, 这种情况有个特点, 就是在引用一个对象的方法, 而这个对象本身是 Lambda 的一个参数. 比如现在需要实现比较两个数的大小, 首先修改 calculate 方法参数类型为包装类型 Integer:
- @FunctionalInterface
- public interface Calculation {
- int calculate(Integer a, Integer b);
- }
比较 a 和 b 的大小可以这样写:
- int result = doArithmetic(3, 2, Integer::compareTo);//Integer::compareTo 等于 a.compareTo(b)
- System.out.println(result);//1
构造函数引用
对于一个现有构造函数, 可以使用它的名称和 new 来创建一个它的引用: ClassName::new. 再使用构造函数引用时, 需要调用的构造器参数列表要和函数式接口的抽象方法的参数要一致. 举个例子, 现在添加了两个生成 String 对象的方法:
- public static String generateString(Supplier<String> supplier) {
- return supplier.get();
- }
- public static String generateString(String value, Function<String, String> function) {
- return function.apply(value);
- }
分别使用构造函数引用:
- String result = generateString(String::new);// 调用 String()构造方法
- System.out.println(result);
- result = generateString("hello Lambda", String::new);// 调用 String(String original)构造方法
- System.out.println(result);
来源: https://www.cnblogs.com/seve/p/12765098.html