Java Lambda 表达式

Lambda 表达式最早是在 Java 8 中引入的。其主要目的是增强语言的表达能力。

但是,在深入了解 Lambda 表达式之前,我们需要先理解函数式接口。


什么是函数式接口?

如果一个 Java 接口仅包含一个抽象方法,那么它就被称为函数式接口。这一个方法规定了接口的预期用途。

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

示例 1:在 Java 中定义一个函数式接口

import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
    // the single abstract method
    double getValue();
}

在上面的示例中,接口 `MyInterface` 仅包含一个抽象方法 `getValue()`。因此,它是一个函数式接口。

这里,我们使用了 `@FunctionalInterface` 注释。该注释会强制 Java 编译器表明该接口是函数式接口,因此不允许包含多个抽象方法。但是,这并非强制要求。

在 Java 7 中,函数式接口被视为单抽象方法(Single Abstract Method)或 **SAM** 类型。在 Java 7 中,SAM 通常通过匿名类来实现。

示例 2:在 Java 中使用匿名类实现 SAM

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

        // anonymous class
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("I just implemented the Runnable Functional Interface.");
            }
        }).start();
    }
}

输出:

I just implemented the Runnable Functional Interface.

在这里,我们可以将一个匿名类传递给一个方法。这有助于在 Java 7 中编写更少的代码。然而,语法仍然很复杂,并且需要大量的额外代码行。

Java 8 通过更进一步的方式扩展了 SAM 的功能。我们知道函数式接口只有一个方法,所以在将函数式接口作为参数传递时,应该没有必要定义该方法的名称。Lambda 表达式恰恰实现了这一点。


Lambda 表达式简介

Lambda 表达式本质上是一个匿名方法。Lambda 表达式本身不执行。相反,它用于实现由函数式接口定义的方法。

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

在 Java 中定义 Lambda 表达式的方法如下。

(parameter list) -> lambda body

使用的新运算符 (`->`) 被称为箭头运算符或 Lambda 运算符。此时语法可能还不清晰。让我们看一些例子。

假设我们有这样的方法

double getPiValue() {
    return 3.1415;
}

我们可以使用 Lambda 表达式将其写成

() -> 3.1415

这里,该方法没有任何参数。因此,运算符的左侧包含一个空的参数列表。右侧是 Lambda 体,它指定了 Lambda 表达式的操作。在这种情况下,它返回值为 3.1415。


Lambda 体的类型

在 Java 中,Lambda 体有两种类型。

1. 包含单个表达式的 Lambda 体

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

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

2. 由代码块组成的 Lambda 体

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

这种类型的 Lambda 体称为块体。块体允许 Lambda 体包含多个语句。这些语句包含在花括号内,您必须在花括号后添加分号。

注意:对于块体,如果 Lambda 体返回一个值,您可以使用 return 语句。但是,表达式体不需要 return 语句。


示例 3:Lambda 表达式

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

如前所述,Lambda 表达式本身不执行。相反,它构成了函数式接口定义的抽象方法的实现。

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

import java.lang.FunctionalInterface;

// this is functional interface
@FunctionalInterface
interface MyInterface{

    // abstract method
    double getPiValue();
}

public class Main {

    public static void main( String[] args ) {

    // declare a reference to MyInterface
    MyInterface ref;
    
    // lambda expression
    ref = () -> 3.1415;
    
    System.out.println("Value of Pi = " + ref.getPiValue());
    } 
}

输出:

Value of Pi = 3.1415

在上面的例子中:

  • 我们创建了一个名为 `MyInterface` 的函数式接口。它包含一个名为 `getPiValue()` 的单个抽象方法。
  • 在 `Main` 类中,我们声明了一个指向 `MyInterface` 的引用。请注意,我们可以声明接口的引用,但不能实例化接口。即:
    // it will throw an error
    MyInterface ref = new myInterface();
    
    // it is valid
    MyInterface ref;
  • 然后,我们将 Lambda 表达式赋给了该引用。
    ref = () -> 3.1415;
  • 最后,我们通过引用接口调用 `getPiValue()` 方法。当
    System.out.println("Value of Pi = " + ref.getPiValue());

带参数的 Lambda 表达式

到目前为止,我们已经创建了不带参数的 Lambda 表达式。然而,与 方法类似,Lambda 表达式也可以有参数。例如:

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

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

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

@FunctionalInterface
interface MyInterface {

    // abstract method
    String reverse(String n);
}

public class Main {

    public static void main( String[] args ) {

        // declare a reference to MyInterface
        // assign a lambda expression to the reference
        MyInterface ref = (str) -> {

            String result = "";
            for (int i = str.length()-1; i >= 0 ; i--)
            result += str.charAt(i);
            return result;
        };

        // call the method of the interface
        System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
    }

}

输出:

Lambda reversed = adbmaL

泛型函数式接口

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

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

上面的函数式接口只接受 `String` 并返回 `String`。但是,我们可以使函数式接口泛型化,从而接受任何数据类型。如果您不确定泛型,请访问 Java 泛型

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

// GenericInterface.java
@FunctionalInterface
interface GenericInterface<T> {

    // generic method
    T func(T t);
}

// GenericLambda.java
public class Main {

    public static void main( String[] args ) {

        // declare a reference to GenericInterface
        // the GenericInterface operates on String data
        // assign a lambda expression to it
        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 reversed = " + reverse.func("Lambda"));

        // declare another reference to GenericInterface
        // the GenericInterface operates on Integer data
        // assign a lambda expression to it
        GenericInterface<Integer> factorial = (n) -> {

            int result = 1;
            for (int i = 1; i <= n; i++)
            result = i * result;
            return result;
        };

        System.out.println("factorial of 5 = " + factorial.func(5));
    }
}

输出:

Lambda reversed = adbmaL
factorial of 5 = 120

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

在这里,在 Main 类中:

  • GenericInterface<String> reverse - 创建一个指向该接口的引用。该接口现在处理 `String` 类型的数据。
  • GenericInterface<Integer> factorial - 创建一个指向该接口的引用。在这种情况下,该接口处理 `Integer` 类型的数据。

Lambda 表达式与 Stream API

JDK8 新增了 `java.util.stream` 包,允许 Java 开发人员执行搜索、过滤、映射、归约或操作 List 等集合的操作。

例如,我们有一个数据流(在本例中是 `String` 的 `List`),其中每个字符串是国家名称和该国家所在地的组合。现在,我们可以处理这个数据流,并只检索来自尼泊尔的地方。

为此,我们可以通过结合 Stream API 和 Lambda 表达式来对流执行批量操作。

示例 6:演示 Lambda 与 Stream API 的使用

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

public class StreamMain {

    // create an object of list using ArrayList
    static List<String> places = new ArrayList<>();

    // preparing our data
    public static List getPlaces(){

        // add places and country to the list
        places.add("Nepal, Kathmandu");
        places.add("Nepal, Pokhara");
        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:");
        
        // Filter places from Nepal
        myPlaces.stream()
                .filter((p) -> p.startsWith("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 表达式作为输入。

我们还可以根据上面学到的语法定义自己的表达式。这允许我们大幅减少代码行数,正如我们在上面的示例中所见。

你觉得这篇文章有帮助吗?

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战