9.1Lambda表达式

Lambda 表达式(Lambda expression)是一个匿名函数,基于数学中的λ演算得名,也可称为闭包(Closure)

主要作用是不需要创建对象实例,可直接运行方法体

  1. public class Test{
  2. public static Calculable calculate(char opr) {
  3. Calculable result;
  4. if (opr == '+') {
  5. // 匿名内部类实现Calculable接口
  6. result = new Calculable() {
  7. // 实现加法运算
  8. @Override
  9. public int calculateInt(int a, int b) {
  10. return a + b;
  11. }
  12. };
  13. } else {
  14. // 匿名内部类实现Calculable接口
  15. result = new Calculable() {
  16. // 实现减法运算
  17. @Override
  18. public int calculateInt(int a, int b) {
  19. return a - b;
  20. }
  21. };
  22. }
  23. return result;
  24. }
  25. public static Calculable calculate(char opr) {
  26. Calculable result;
  27. if (opr == '+') {
  28. // Lambda表达式实现Calculable接口
  29. result = (int a, int b) -> {
  30. return a + b;
  31. };
  32. } else {
  33. // Lambda表达式实现Calculable接口
  34. result = (int a, int b) -> {
  35. return a - b;
  36. };
  37. }
  38. return result;
  39. }
  40. }

格式

  1. (参数列表) -> {
  2. // Lambda表达式体
  3. }

->被称为箭头操作符或 Lambda 操作符,箭头操作符将 Lambda 表达式拆分成两部分:

  • 左侧:Lambda 表达式的参数列表。
  • 右侧:Lambda 表达式中所需执行的功能,用{ }包起来,即 Lambda 体

优缺点

优点:

  1. 代码简洁,开发迅速
  2. 方便函数式编程
  3. 非常容易进行并行计算
  4. Java 引入 Lambda,改善了集合操作(引入 Stream API)

缺点:

  1. 代码可读性变差
  2. 在非并行计算中,很多计算未必有传统的 for 性能要高
  3. 不容易进行调试

三种简写方式

①省略参数类型

Lambda 表达式可以根据上下文环境推断出参数类型

  1. public static Calculable calculate(char opr) {
  2. Calculable result;
  3. if (opr == '+') {
  4. // Lambda表达式实现Calculable接口
  5. 5 result = (a, b) -> {
  6. return a + b;
  7. };
  8. } else {
  9. // Lambda表达式实现Calculable接口
  10. 10 result = (a, b) -> {
  11. return a - b;
  12. };
  13. }
  14. return result;
  15. }

②省略参数小括号

  1. public static Calculable calculate(int power) {
  2. Calculable result;
  3. if (power == 2) {
  4. // Lambda表达式实现Calculable接口
  5. // 标准形式
  6. 25 result = (int a) -> {
  7. return a * a;
  8. };
  9. } else {
  10. // Lambda表达式实现Calculable接口
  11. // 省略形式
  12. 31 result = a -> {
  13. return a * a * a;
  14. };
  15. }
  16. return result;
  17. }

③省略return和大括号

如果 Lambda 表达式体中只有一条语句,那么可以省略 return 和大括号

  1. public static Calculable calculate(int power) {
  2. Calculable result;
  3. if (power == 2) {
  4. // Lambda表达式实现Calculable接口
  5. // 标准形式
  6. result = (int a) -> {
  7. return a * a;
  8. };
  9. } else {
  10. // Lambda表达式实现Calculable接口
  11. // 省略形式
  12. 12 result = a -> a * a * a;
  13. }
  14. return result;
  15. }

三种使用方式

①作为参数

Lambda 表达式一种常见的用途就是作为参数传递给方法,这需要声明参数的类型声明为函数式接口类型

  1. public static void main(String[] args) {
  2. int n1 = 10;
  3. int n2 = 5;
  4. // 打印加法计算结果
  5. display((a, b) -> {
  6. return a + b;
  7. }, n1, n2);
  8. // 打印减法计算结果
  9. display((a, b) -> a - b, n1, n2);
  10. }
  11. public static void display(Calculable calc, int n1, int n2) {
  12. System.out.println(calc.calculateInt(n1, n2));
  13. }

②访问变量

Lambda 表达式可以访问所在外层作用域定义的变量,包括成员变量和局部变量。

访问成员变量

成员变量包括实例成员变量和静态成员变量。在 Lambda 表达式中可以访问这些成员变量,此时的 Lambda 表达式与普通方法一样,可以读取成员变量,也可以修改成员变量

public class LambdaDemo {
    // 实例成员变量
    private int value = 10;
    // 静态成员变量
    private static int staticValue = 5;
    // 静态方法,进行加法运算
    public static Calculable add() {
        Calculable result = (int a, int b) -> {
            // 访问静态成员变量,不能访问实例成员变量
            staticValue++;
            int c = a + b + staticValue;
            // this.value;
            return c;
        };
        return result;
    }
    // 实例方法,进行减法运算
    public Calculable sub() {
        Calculable result = (int a, int b) -> {
            // 访问静态成员变量和实例成员变量
            staticValue++;
            this.value++;
            int c = a - b - staticValue - this.value;
            return c;
        };
        return result;
    }
}

访问局部变量

对于成员变量的访问 Lambda 表达式与普通方法没有区别,但是访问局部变量时,变量必须是 final 类型的(不可改变)

public class LambdaDemo {
    // 实例成员变量
    private int value = 10;
    // 静态成员变量
    private static int staticValue = 5;
    // 静态方法,进行加法运算
    public static Calculable add() {
        // 局部变量
        int localValue = 20;
        Calculable result = (int a, int b) -> {
            // localValue++;
            // 编译错误
            int c = a + b + localValue;
            return c;
        };
        return result;
    }
    // 实例方法,进行减法运算
    public Calculable sub() {
        // final局部变量
        final int localValue = 20;
        Calculable result = (int a, int b) -> {
            int c = a - b - staticValue - this.value;
            // localValue = c;
            // 编译错误
            return c;
        };
        return result;
    }
}

方法引用

方法引用可以理解为 Lambda 表达式的快捷写法,它比 Lambda 表达式更加的简洁,可读性更高,有很好的重用性。

如果实现比较简单,复用的地方又不多,推荐使用 Lambda 表达式,否则应该使用方法引用。

Java 8 之后增加了双冒号::运算符,该运算符用于“方法引用”,注意不是调用方法。“方法引用”虽然没有直接使用 Lambda 表达式,但也与 Lambda 表达式有关,与函数式接口有关。 方法引用的语法格式如下:

ObjectRef::methodName 
//其中,ObjectRef 是类名或者实例名,methodName 是相应的方法名

注意:被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型一致

public class LambdaDemo {
    // 静态方法,进行加法运算
    // 参数列表要与函数式接口方法calculateInt(int a, int b)兼容
    public static int add(int a, int b) {
        return a + b;
    }
    // 实例方法,进行减法运算
    // 参数列表要与函数式接口方法calculateInt(int a, int b)兼容
    public int sub(int a, int b) {
        return a - b;
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        int n1 = 10;
        int n2 = 5;
        // 打印加法计算结果
        display(LambdaDemo::add, n1, n2);
        LambdaDemo d = new LambdaDemo();
        // 打印减法计算结果 
        display(d::sub, n1, n2);
    }
    public static void display(Calculable calc, int n1, int n2) {
        System.out.println(calc.calculateInt(n1, n2));
    }
}
/*
代码第 18 行声明 display 方法,第一个参数 calc 是 Calculable 类型,它可以接受三种对象:Calculable 实现对象、Lambda 表达式和方法引用。代码第 6 行中第一个实际参数LambdaDemo::add是静态方法的方法引用。代码第 9 行中第一个实际参数d::sub,是实例方法的方法引用,d 是 LambdaDemo 实例;
代码第 6 行的LambdaDemo::add和第 9 行的d::sub是方法引用,此时并没有调用方法,只是将引用传递给 display 方法,在 display 方法中才真正调用方法
*/

9.2函数式接口

Lambda 表达式实现的接口不是普通的接口,而是函数式接口。如果一个接口中,有且只有一个抽象的方法(Object 类中的方法不包括在内),那这个接口就可以被看做是函数式接口。这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda 表达式会发生编译错误:

这说明该接口不是函数式接口,为了防止在函数式接口中声明多个抽象方法,Java 8 提供了一个声明函数式接口注解 @FunctionalInterface,示例代码如下。

// 可计算接口
@FunctionalInterface
public interface Calculable {    
    // 计算两个int数值    
    int calculateInt(int a, int b);
}

在接口之前使用 @FunctionalInterface 注解修饰,那么试图增加一个抽象方法时会发生编译错误。但可以添加默认方法和静态方法。

@FunctionalInterface 注解与 @Override 注解的作用类似。Java 8 中专门为函数式接口引入了一个新的注解 @FunctionalInterface。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

提示:Lambda 表达式是一个匿名方法代码,Java 中的方法必须声明在类或接口中,那么 Lambda 表达式所实现的匿名方法是在函数式接口中声明的

9.3函数式编程