详解 Java 中的 Lambda

开发 后端
如果你想给一个 Java 变量赋一段“代码”,该怎么办呢?在 Java 8 之前,这是不可能的。但在 Java 8 出现后,可以使用 Lambda 特性来做到这一点。

什么是 Lambda

我们知道,对于一个 Java 变量,我们可以给它赋一个“值”,然后可以用它做一些操作。

Integer a = 1;
String s = "Hello";
System.out.println(s + a);

如果你想给一个 Java 变量赋一段“代码”,该怎么办呢?例如,我想把右边的代码块赋给一个名为 codeBlock 的 Java 变量。

在 Java 8 之前,这是不可能的。但在 Java 8 出现后,可以使用 Lambda 特性来做到这一点。

以下就是最直观的写法:

实际上是不允许这样写的会编译失败,这里只是为了让大家方便理解

codeBlock = public void doSomething(String s) {
    System.out.println(s);
}

这种写法不是很简洁。我们可以去掉一些无用的声明对代码进行简化。

codeBlock = public void doSomething(String s) {
   System.out.println(s);
}
// 这里的 public 是多余的,因为在这个上下文中不需要访问修饰符。
codeBlock = void doSomething(String s) {
   System.out.println(s);
}
// 函数名 doSomething 也是多余的,因为已经将函数体赋值给了 codeBlock。
codeBlock = void (String s) {
   System.out.println(s);
}
// 编译器可以自行推断返回类型,这里不需要显式地写出 void。
codeBlock = (String s) {
   System.out.println(s);
}
// 编译器可以自行推断输入参数类型,这里不需要显式地写出 String 类型。
codeBlock = (s) -> System.out.println(s);

这样,我们就将一段“代码”赋给了一个变量。而“这段代码”,或者说“赋给变量的这个函数”,就是一个 Lambda 表达式。

但这里还有一个问题,即变量 codeBlock 应该是什么类型呢?在 Java 8 中,所有 Lambda 类型都是一个接口,而 Lambda 表达式本身,也就是“这段代码”,需要是这个接口的一个实现。在我看来,这是理解 Lambda 的关键。简而言之,Lambda 表达式本身就是一个接口的实现。直接这么说可能还是有点让人困惑,所以我们继续举例。我们给上面的 codeBlock 添加一个类型:

codeBlock = (s)->System.out.println(s);

interface LambdaInterface {
    public void doSomething(String s);
}

这种只有一个函数需要实现的接口称为“函数式接口”。为了防止后来的人给这个接口添加接口函数,导致有多个接口函数需要实现而变成“非函数式接口”,我们可以给这个接口添加一个声明@FunctionalInterface,这样其他人就不能给它添加新函数了。

@FunctionalInterface
interface LambdaInterface {
    public void doSomething(String s);
}

这样,我们就得到了一个完整的 Lambda 表达式声明。

LambdaInterface codeBlock =(s)System.out.println(s);

Lambda 表达式的作用是什么

最直观的作用就是使代码极其简洁。我们可以比较一下 Lambda 表达式和传统 Java 对同一接口的实现:

interface LambdaInterface {
public void doSomething(String s);
}

// Java 8
LambdaInterface codeBlock = (s) -> System.out.println(s);

// Java 7
publicclass LambdaInterfaceImpl implements LambdaInterface {
@Override
public void doSomething(String s) {
   System.out.println(s);
 }
}

这两种写法本质上是等价的。但显然,Java 8 中的写法更优雅简洁。而且,由于 Lambda 可以直接赋给变量,我们可以直接将 Lambda 作为参数传递给函数,而 java7 必须有明确的接口实现和初始化定义:

// 定义了一个静态方法 useLambda,它接受一个 LambdaInterface 类型的参数和一个 String 类型的参数。
public static void useLambda(LambdaInterface lambdaInterface, String s) {
    lambdaInterface.doSomething(s);
}

// Java 8
// 直接使用 Lambda 表达式调用 useLambda 方法。
   useLambda(s -> System.out.println(s), "Hello");
// Java 7
// 定义了一个 LambdaInterface 接口和一个实现该接口的 LambdaInterfaceImpl 类。
   interface LambdaInterface {
    public void doSomething(String s);
}

publicclass LambdaInterfaceImpl implements LambdaInterface {
    @Override
    public void doSomething(String s) {
        System.out.println(s);
    }
}
// 实例化 LambdaInterfaceImpl 类,并将实例传递给 useLambda 方法。
LambdaInterface myLambdaInterface = new LambdaInterfaceImpl();
useLambda(myLambdaInterface, "Hello");

在某些情况下,这个接口实现只需要使用一次。Java 7 要求你定义一个接口然后实现它。相比之下,Java 8 的 Lambda 看起来干净得多。Lambda 结合了函数式接口库、forEach、stream()、方法引用等新特性,使代码更加简洁!我们直接看例子。

@Getter
@AllArgsConstructor
public static class Student {
    private String name;
    private Integer age;
}

List<Student> students = Arrays.asList(
        new Student("Bob", 18),
        new Student("Ted", 17),
        new Student("Zeka", 18)
);

现在你需要打印出 students 中所有 18 岁学生的名字。

原始的 Lambda 写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值 Lambda 表达式。

@FunctionalInterface
interface AgeMatcher {
    boolean match(Student student);
}

@FunctionalInterface
interface Executor {
    boolean execute(Student student);
}

public static void matchAndExecute(List<Student> students, AgeMatcher matcher, Executor executor) {
    for (Student student : students) {
        if (matcher.match(student)) {
            executor.execute(student);
        }
    }
}

public static void main(String[] args) {
    List<Student> students = Arrays.asList(
            new Student("Bob", 18),
            new Student("Ted", 17),
            new Student("zeka", 18)
    );
    matchAndExecute(students,
            s -> s.getAge() == 18,
            s -> System.out.println(s.getName())
    );
}

这段代码实际上已经比较简洁了,但我们还能更简洁吗?当然可以,Java 8 中有一个函数式接口包,它定义了大量可能用到的函数式接口(java.util.function (Java Platform SE 8))。

因此,我们根本不需要在这里定义 AgeMatcher 和 Executor 这两个函数式接口。我们可以直接使用 Java 8 函数式接口包中的 Predicate(T) 和 Consumer(T),因为它们的一对接口定义实际上与 AgeMatcher/Executor 相同。

第一步简化:利用函数式接口

public static void matchAndExecute(List<Student> students, Predicate<Student> predicate, Consumer<Student> consumer) {
    for (Student student : students) {
        if (predicate.test(student)) {
            consumer.accept(student);
        }
    }
}

matchAndExecute 中的 forEach 循环实际上很烦人。这里可以使用 Iterable 自带的 forEach 代替。forEach 本身可以接受一个 Consumer(T) 参数。

第二步简化:用 Iterable.forEach 代替 forEach 循环:

public static void matchAndExecute(List<Student> students, Predicate<Student> predicate, Consumer<Student> consumer) {
    students.forEach(s -> {
        if (predicate.test(s)) {
            consumer.accept(s);
        }
    });
}

由于 matchAndExecute 实际上只是对 List 的一个操作,这里我们可以去掉 matchAndExecute,直接使用 stream() 特性来完成它。stream() 的几个方法接受 Predicate(T) 和 Consumer(T) 等参数(java.util.stream (Java Platform SE 8))。一旦你理解了上面的内容,stream() 就很容易理解,不需要进一步解释。

第三步简化:用 stream() 代替静态函数:

students.stream()
       .filter(s -> s.getAge() == 18)
       .forEach(s -> System.out.println(s.getName()));

与最初的 Lambda 写法相比代码量已经减少了非常多。但如果我们要求改为打印学生的所有信息,并且s -> System.out.println(s);那么我们可以使用方法引用来继续简化。所谓方法引用,就是用已经编写好的其他 Object/Class 的方法来代替 Lambda 表达式。格式如下:

第四步简化:可以在 forEach 中使用方法引用代替 Lambda 表达式:

students.stream()
       .filter(s -> s.getAge() == 18)
       .map(Student::getName)
       .forEach(System.out::println);

这基本上是我能写出的最简洁的版本了。

关于 Java 中的 Lambda 还有一些需要讨论和学习的地方。例如,如何利用 Lambda 的特性进行并行处理等。总之,我只是给你一个大致的介绍,让你有个概念。网上有很多关于 Lambda 的相关教程,多读多练,随着时间的推移肯定能够掌握它。

责任编辑:赵宁宁 来源: 程序猿技术充电站
相关推荐

2024-03-12 08:23:54

JavaLambda函数式编程

2009-07-01 09:56:10

C#3.0

2019-10-10 17:53:36

大数据平台架构LambdaKappa

2021-01-21 05:46:22

JavaLambda开发

2023-05-31 13:32:08

Javalambda函数

2009-06-22 10:34:43

Boost库lambda

2009-09-14 13:57:20

C# Lambda表达Lambda表达式

2012-07-18 09:45:32

Java 8ScalaLambda

2013-01-05 02:19:50

JavaLambda表达式JVM

2020-09-26 07:19:46

Java

2016-09-18 16:58:09

JavaProperties

2020-03-29 20:38:35

PythonLambda语言

2009-11-12 10:55:17

Lambda表达式

2023-10-10 10:43:19

JavaJDK1.8

2009-06-30 15:18:10

StringBuildJava

2009-06-25 15:20:28

CollectionMap

2012-06-26 10:03:58

JavaJava 8lambda

2022-06-27 08:36:08

PythonLambda

2009-07-09 09:51:07

Lambda表达式C#

2023-04-23 08:49:17

Java接口Future
点赞
收藏

51CTO技术栈公众号