# Java Lambda 表达式

> 原文： [https://www.programiz.com/java-programming/lambda-expression](https://www.programiz.com/java-programming/lambda-expression)

#### 在本文中，我们将借助示例学习 Java lambda 表达式以及 lambda 表达式与函数式接口，通用函数式接口和流 API 的结合使用。

lambda 表达式是在 Java 8 中首次引入的。其主要目的是提高语言的表达能力。

但是，在进入 lambda 之前，我们首先需要了解函数式接口。

* * *

## 什么是函数式接口？

如果 Java 接口仅包含一个抽象方法，则将其称为函数式接口。 仅这一种方法指定了接口的预期用途。

例如，包`java.lang`中的`Runnable`接口； 由于它仅构成一种方法，即`run()`，因此它是一个函数式接口。

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

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

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

在这里，我们使用了注释`@FunctionalInterface`。 该注释会强制 Java 编译器指示该接口是函数式接口。 因此，不允许有多个抽象方法。 但是，它不是强制性的。

在 Java 7 中，函数式接口被视为[单一抽象方法](https://stackoverflow.com/a/23342625/4494547)或 **SAM** 类型。 SAM 通常用 Java 7 中的匿名类实现。

### 示例 2：使用 Java 中的匿名类实现 SAM

```java
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();
    }
}
```

**输出**：

```java
I just implemented the Runnable Functional Interface.
```

在这里，我们可以将匿名类传递给方法。 这有助于用 Java 7 用更少的代码编写程序。但是，语法仍然很困难，并且需要很多额外的代码行。

Java 8 进一步扩展了 SAM 的功能。 由于我们知道函数式接口只有一种方法，因此在将其作为参数传递时，无需定义该方法的名称。 Lambda 表达式使我们能够做到这一点。

* * *

## Lambda 表达式简介

Lambda 表达式本质上是一个匿名或未命名的方法。 lambda 表达式不能单独执行。 相反，它用于实现函数式接口定义的方法。

### 如何在 Java 中定义 Lambda 表达式？

这是我们如何在 Java 中定义 lambda 表达式。

```java
(parameter list) -> lambda body
```

使用的新运算符（`->`）被称为箭头运算符或 lambda 运算符。 目前语法尚不清楚。 让我们探索一些例子，

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

```java
double getPiValue() {
    return 3.1415;
}
```

我们可以使用 lambda 表达式编写此方法，如下所示：

```java
() -> 3.1415
```

在此，该方法没有任何参数。 因此，运算符的左侧包括一个空参数。 右侧是 lambda 主体，用于指定 lambda 表达式的操作。 在这种情况下，它将返回值 3.1415。

* * *

### Lambda 主体的类型

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

**1.具有单个表达式的正文**

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

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

**2.由代码块组成的正文**

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

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

**注意**：对于块体，您应该始终有一个`return`语句。 但是，表达式主体不需要`return`语句。

* * *

## 示例 3：Lambda 表达式

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

如前所述，lambda 表达式不是单独执行的。 相反，它形成了由函数式接口定义的抽象方法的实现。

因此，我们需要首先定义一个函数式接口。

```java
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());
    } 
}
```

**输出**：

```java
Value of Pi = 3.1415
```

在上面的示例中，

*   我们创建了一个名为`MyInterface`的函数式接口。 它包含一个名为`getPiValue()`的抽象方法
*   在`Main`类中，我们声明了对`MyInterface`的引用。 请注意，我们可以声明接口的引用，但不能实例化接口。 即
    ，

    ```java
    // it will throw an error
    MyInterface ref = new myInterface();

    // it is valid
    MyInterface ref;
    ```

*   然后，我们为参考分配了一个 lambda 表达式。

    ```java
    ref = () -> 3.1415;
    ```

*   最后，我们使用参考接口调用方法`getPiValue()`。 当
    时

    ```java
    System.out.println("Value of Pi = " + ref.getPiValue());
    ```

* * *

## 带参数的 Lambda 表达式

到目前为止，我们已经创建了不带任何参数的 lambda 表达式。 但是，类似于方法，lambda 表达式也可以具有参数。 例如，

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

在此，括号内的变量`n`是传递给 lambda 表达式的参数。 Lambda 主体接受参数并检查其是偶数还是奇数。

### 示例 4：将 lambda 表达式与参数一起使用

```java
@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"));
    }

}
```

**输出**：

```java
Lambda reversed = adbmaL
```

* * *

## 泛型函数式接口

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

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

上面的函数式接口仅接受`String`并返回`String`。 但是，我们可以使函数式接口通用，以便接受任何数据类型。 如果您不确定泛型，请访问 [Java 泛型](/java-programming/generics)。

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

```java
// 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));
    }
}
```

**输出**：

```java
Lambda reversed = adbmaL
factorial of 5 = 120
```

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

在主班里面

*   `GenericInterface<String> reverse` - 创建对接口的引用。 现在，该接口可处理`String`类型的数据。
*   `GenericInterface<Integer> factorial` - 创建对接口的引用。 在这种情况下，该接口对`Integer`类型的数据进行操作。

* * *

## Lambda 表达式和流 API

新的[`java.util.stream`](http://javadocs.techempower.com/jdk18/api/java/util/stream/package-summary.html)包已添加到 JDK8，它允许 Java 开发人员执行诸如`Lists`之类的搜索，过滤，映射，归约或操作集合之类的操作。

例如，我们有一个数据流（在我们的示例中为`String`的`List`），其中每个字符串都是国家名称和国家/地区的组合。 现在，我们可以处理此数据流，并且仅从尼泊尔检索位置。

为此，我们可以结合使用流 API 和 Lambda 表达式在流中执行批量操作。

### 示例 6：将 lambda 与流 API 一起使用的示例

```java
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));
    }

}
```

**输出**：

```java
Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA
```

在上面的示例中，请注意以下语句：

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

在这里，我们使用的是流 API 的`filter()`，`map()`和`forEach()`之类的方法。 这些方法可以将 lambda 表达式作为输入。

我们还可以根据上面学习的语法定义自己的表达式。 如上例所示，这使我们可以大大减少代码行。