文章目录

  • 1. 什么是函数接口?
  • 示例1:在java中定义函数接口
  • 示例2:使用Java中的匿名类实现SAM
  • 2. Lambda表达式简介
  • 2.1 如何在Java中定义Lambda表达式?
  • 2.2 Lambda主体的类型
  • 示例3:Lambda表达式
  • 3. 带参数的Lambda表达式
  • 示例4:使用带参数的lambda表达式
  • 4. 通用函数接口
  • 示例5:泛型函数接口和Lambda表达式
  • 6. Lambda表达式和流API
  • 示例6:将lambdas与流API一起使用的演示
  • 参考文档


    在本文中,我们将通过示例了解java lambda表达式以及lambda表达式与函数接口、通用函数接口和流API的使用。
    lambda表达式是在Java 8中首次引入的。其主要目的是提高语言的表达能力。
    但是,在进入lambda之前,我们首先需要了解函数接口(functional interface)。

1. 什么是函数接口?

    如果一个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类型。SAM通常用Java 7中的匿名类实现。

示例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.

    在这里,我们可以将匿名类传递给方法。这有助于在Java7中用更少的代码编写程序。但是,语法仍然很困难,需要大量额外的代码行。
    Java8进一步扩展了SAM的功能。因为我们知道一个函数接口只有一个方法,所以在将它作为参数传递时不需要定义该方法的名称。Lambda表达式允许我们这样做。

2. Lambda表达式简介

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

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

    下面是如何在Java中定义lambda表达式。

(parameter list) -> lambda body

    新运算符(->)称为箭头运算符或lambda运算符。我们来看看一些例子,假设我们有这样一种方法:

double getPiValue() {
    return 3.1415;
}

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

() -> 3.1415

    这里,该方法没有任何参数。因此,运算符的左侧包含一个空参数。右侧是lambda主体,它指定lambda表达式的操作。在本例中,它返回值3.1415。

2.2 Lambda主体的类型

    在Java中,lambda主体有两种类型。
    1. 单一表达式的主体

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

    这种类型的lambda主体称为表达式主体。
    2. 由代码块组成的主体

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

    这种类型的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());
3. 带参数的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
4. 通用函数接口

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

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

    上面的函数接口仅接受String并返回String。但是,我们可以使函数接口通用,以便接受任何数据类型。

示例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型数据进行操作。
6. Lambda表达式和流API

    新的java.util.stream包已经被添加到JDK8中,它允许java开发人员执行诸如搜索、过滤、映射、减少或操作List之类的集合的操作。
    例如,我们有一个数据流(在我们的例子中是一个字符串列表),其中每个字符串都是国家名称和国家位置的组合。现在,我们可以处理这些数据流,只检索尼泊尔这个地方。
    为此,我们可以通过流API和Lambda表达式的组合在流中执行批量操作。

示例6:将lambdas与流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));

    在这里,我们使用流API的filter()、map()和forEach()等方法。这些方法可以将lambda表达式作为输入。
    我们还可以根据上面所学的语法定义自己的表达式。这使得我们可以大幅减少代码行,正如我们在上面的例子中看到的那样。

参考文档

[1]Parewa Labs Pvt. Ltd.Java autoboxing and unboxing[EB/OL].https://www.programiz.com/java-programming/lambda-expression,2020-01-01.