Java Lambda 表达式

在本文中,我们将通过示例学习 Java lambda 表达式、函数式接口和 Stream API。

在本文中,我们将通过示例学习 Java lambda 表达式、函数式接口和 Stream API。

Lambda 表达式是在 Java 8 中引入的, Lambda 表达式能让程序更加简洁。

Lambda 表达式的基础是函数式接口,我们先来了解一下函数式接口。

什么是函数式接口?

如果一个 Java 接口有且只有一个抽象方法,那么它被称为函数式接口。接口中的唯一的抽象方法指明了接口的预期用途。

例如, java.lang 包中的 Runnable 接口就是一个函数式接口,因为它只有一个 run() 方法。

示例 1:在 java 中定义函数式接口

import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
  // 唯一的抽象接口
  double getValue();
}

在上面的例子中,接口 MyInterface 只有一个抽象方法 getValue()。因此,它是一个函数式接口。

在这里,我们使用了注解 @FunctionalInterface,用以向 Java 编译器指示该接口是一个函数式接口。 Java 编译器在编译阶段就会检查此接口是否符合函数式接口的规则。

示例 2:在 java 中用匿名类实现函数式接口

public class Main {
  public static void main(String[] args) {

    new Thread(
            // 匿名类
            new Runnable() {
              @Override
              public void run() {
                System.out.println("通过匿名类实现了函数式接口。");
              }
            })
        .start();
  }
}

输出

通过匿名类实现了函数式接口。

在这里,我们将匿名类对象传递给 Thread 类的构造方法。这有助于用更少的代码编写程序。

Java 8 进一步扩展了函数式接口的写法。由于函数式接口只有一个方法,Lambda 表达式允许我们直接在表达式中实现函数式接口。

lambda 表达式简介

Lambda 表达式用于实现由函数式接口定义的方法,本质上是一种匿名或未命名的方法。lambda 表达式不会自行执行, lambda 表达式只是定义了一个函数式接口的实例。

如何在 Java 中定义 lambda 表达式?

lambda 表达式的语法如下:

(parameter list) -> lambda body

运算符 -> 称为箭头运算符或 lambda 运算符。语法目前可能有些过于简单,让我们看几个例子,

假设,我们有一个这样的方法:

double getPiValue() {
    return 3.1415;
}

我们可以使用 lambda 表达式编写此方法为:

() -> 3.1415

在这里,该方法没有任何参数。因此,-> 的左侧的 () 中的参数列表为空 。-> 右侧是 lambda 表达式操作的 lambda 主体。在本例中,返回值是 3.1415

Lambda 体的类型

在 Java 中,lambda 主体有两种类型。

  1. 单一表达式主体

    () -> System.out.println("Lambdas are great");
    

    这种类型的 lambda 主体称为表达式主体。

  2. 代码块组成的主体

    () -> {
        double pi = 3.1415;
        return pi;
    };
    

    这种类型的 lambda 主体和普通方法的代码块没有区别,代码块中允许多个语句,允许返回值。

注意:对于代码块体,如果有返回一个值,必须使用 return 语句;而表达式主体不需要 return 关键字直接返回值。

示例 3:Lambda 表达式

让我们编写一个 Java 程序,使用 lambda 表达式返回 Pi 的值。

如前所述,lambda 表达式不会自行执行,lambda 表达式只是定义了一个函数式接口的实例。

所以,我们需要先定义一个函数式接口。

import java.lang.FunctionalInterface;

// 函数式接口
@FunctionalInterface
interface MyInterface {
  // 抽象方法
  double getPiValue();
}

public class Main {

  public static void main(String[] args) {

    // 通过 lambda 表达式定义函数式接口实例
    MyInterface ref = () -> 3.1415;

    System.out.println("Pi = " + ref.getPiValue());
  }
}

输出

Pi = 3.1415

在上面的例子中,

  • 我们创建了一个名为 MyInterface 的函数式接口, 它包含一个名为 getPiValue() 的抽象方法。

  • Main 类中,我们不能像下面一样直接实例化接口:

    // 报错
    MyInterface ref = new myInterface();
    
  • 然后我们为引用分配了一个 lambda 表达式。

    ref = () -> 3.1415;
    
  • 最后,我们使用接口的实例引用调用 getPiValue() 方法

    System.out.println("Pi = " + ref.getPiValue());
    

带参数的 Lambda 表达式

我们在上面例子中创建的是没有任何参数的 lambda 表达式。接下来我们展示一下带参数的 lambda 表达式。 例如,

(n) -> (n%2) == 0

这里,括号内的变量 n 是传递给 lambda 表达式的参数。lambda 主体接受参数并检查它是偶数还是奇数。

示例 4:使用带参数的 lambda 表达式

@FunctionalInterface
interface MyInterface {
  // 反转字符串
  String reverse(String n);
}

public class Main {

  public static void main(String[] args) {
    MyInterface ref =
        (str) -> {
          String result = "";
          for (int i = str.length() - 1; i >= 0; i--) result += str.charAt(i);
          return result;
        };

    System.out.println("Lambda 反转后 = " + ref.reverse("Lambda"));
  }
}

输出

Lambda 反转后 = adbmaL

带泛型的函数式接口

到目前为止,我们已经使用了只接受一种数据类型值的函数式接口。例如,

@FunctionalInterface
interface MyInterface {
    String reverseString(String n);
}

上述函数式接口的方法只接受 String 并返回 String 。但是,我们可以使用带泛型的函数式接口,以便接受任何数据类型。如果您不了解泛型,请访问 Java 泛型

示例 5:带泛型的函数接口和 Lambda 表达式

@FunctionalInterface
interface GenericInterface<T> {
  // 带泛型的方法
  T func(T t);
}

public class Main {

  public static void main(String[] args) {

    // 当类型为字符串时,实现字符串反转
    GenericInterface<String> reverse =
        (str) -> {
          String result = "";
          for (int i = str.length() - 1; i >= 0; i--) result += str.charAt(i);
          return result;
        };

    System.out.println("Lambda 反转后 = " + reverse.func("Lambda"));

    // 当类型为整数时,求从 1 到给定参数的连续乘积:阶乘
    GenericInterface<Integer> factorial =
        (n) -> {
          int result = 1;
          for (int i = 1; i <= n; i++) result = i * result;
          return result;
        };

    System.out.println("5 的阶乘 = " + factorial.func(5));
  }
}

输出

Lambda 反转后 = adbmaL
5 的阶乘 = 120

在上面的例子中,我们创建了一个名为 GenericInterface 的带泛型的函数式接口, 它包含一个名为 func() 的带泛型的方法。

在这里,在 Main 类中,

  • GenericInterface<String> reverse - 创建接口的实例,实现对 String 数据类型进行操作。
  • GenericInterface<Integer> factorial - 创建接口的实例,实现对 Integer 数据类型进行操作。

Lambda 表达式和流 API

Java 8 开始,在 java.util.stream 包中引入了 Stream API,它允许开发人员对集合和数组执行诸如搜索、过滤、映射、合并、排序之类的操作。

在下面的例子中,我们有一个 List<String> 列表,包含了不同国家的地点信息,我们要检索出所有 Nepal 的地点。

我们可以通过 Stream API 和 Lambda 表达式的组合用简洁的代码实现需求。

示例 6:Stream API 结合 lambda 表达式

import java.util.ArrayList;
import java.util.List;

public class Main {
  // 定义地点列表
  static List<String> places = new ArrayList<>();

  // 初始化地点数据
  public static List<String> getPlaces() {
    places.add("Nepal, Pokhara");
    places.add("Nepal, Kathmandu");
    places.add("India, Delhi");
    places.add("USA, New York");
    places.add("Africa, Nigeria");

    return places;
  }

  public static void main(String[] args) {

    List<String> myPlaces = getPlaces();
    System.out.println("Places from Nepal:");

    myPlaces.stream()
        .filter((p) -> p.startsWith("Nepal")) // 根据 Nepal 过滤数据
        .map((p) -> p.toUpperCase()) // 转为大写
        .sorted() // 排序
        .forEach((p) -> System.out.println(p)); // 输出
  }
}

输出

Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA

注意上面例子中的语句:

myPlaces.stream()
        .filter((p) -> p.startsWith("Nepal"))
        .map((p) -> p.toUpperCase())
        .sorted()
        .forEach((p) -> System.out.println(p));

在这里,我们使用 Stream API 了的 filter()map()forEach() 方法,这些方法可以将 lambda 表达式作为输入。

正如我们在上面的示例中看到的那样, lambda 表达式能够大幅减少代码行数。