文章

一起学Java(15)-[JDK篇]教你了解Java8特性,学习Lambda表达式

在文章《2024年主流使用的JDK版本及其核心特性介绍》中,我们详细介绍了Java8、Java11、Java17等主流Java版本主要新增特性。针对其中的一些核心特性,展开研究一二。

Lambda 表达式 (JEP 126) 详细介绍

Lambda 表达式是 Java 8 引入的一个重要新特性,它允许将函数作为参数传递,并简化了代码的表达方式。Lambda 表达式通常用于表达行为,而不需要定义完整的类或匿名内部类。Lambda 表达式最常见的用法是在集合和流(Stream)API中进行数据操作(Stream API (JEP 107) 流 API)。

1. Lambda 表达式的设计定位

Lambda 表达式的设计目的是简化函数式编程风格的实现,特别是针对单方法接口(即只有一个抽象方法的接口,通常被称为函数式接口)。在引入Lambda表达式之前,匿名内部类是处理回调或简短行为的常用手段,但它的语法冗长且复杂。

设计目标:

  • 简化代码表达:减少样板代码(boilerplate code)的编写,例如匿名内部类的定义。
  • 提高代码可读性:使代码更清晰、更简洁。
  • 支持函数式编程:允许将函数作为参数传递或返回,使得编程更加灵活。

核心概念:

  • 函数式接口:任何只包含一个抽象方法的接口都可以用 Lambda 表达式来实现。例如 java.lang.Runnablejava.util.Comparator 等。
  • 行为参数化:可以通过 Lambda 表达式传递行为(函数),避免传统通过类继承或接口实现来传递行为的方式。

2. Lambda 表达式的语法和原理

Lambda 表达式的语法形式如下:

1
2
3
(parameters) -> expression
或者
(parameters) -> { statements }

语法说明

  • 参数列表:Lambda 表达式的输入参数,可以有多个参数,如果只有一个参数,圆括号可以省略。
  • 箭头符号(->:分隔参数和Lambda体。
  • Lambda体:可以是一个表达式或者多个语句。如果是一个表达式,可以省略花括号和返回值。

3. Lambda 表达式的用法

(1) 函数式接口-无参数

Lambda 表达式只能用于实现函数式接口。函数式接口是只有一个抽象方法的接口,可以通过 @FunctionalInterface 注解显式声明一个接口为函数式接口。

示例:

1
2
3
4
5
6
package com.coderli.one.jdk8.lambda;

@FunctionalInterface
public interface LambdaFunctionalInterfaceDemo {
    void doSomething();
}

在这个例子中,MyFunctionalInterface 是一个函数式接口,它只有一个抽象方法 doSomething,因此可以使用 Lambda 表达式来实现它。如:

1
2
LambdaFunctionalInterfaceDemo lambdaDemo = () -> System.out.println("Do something.");
lambdaDemo.doSomething();

运行结果

1
Do something.

再例如:

1
2
Runnable r = () -> System.out.println("Running in a separate thread");
new Thread(r).start();

等同于:

1
2
3
4
5
6
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running in a separate thread");
    }
};

(2) 函数式接口-有参数

接口定义如下

1
2
3
4
5
package com.coderli.one.jdk8.lambda;

public interface LambdaInterfaceDemo {
    void doSomething(String name);
}

Lambda实现

1
2
LambdaInterfaceDemo lambdaInterfaceImpl = (name) -> System.out.println("LambdaInterface: " + name);
lambdaInterfaceImpl.doSomething("Lambda Demo");

运行结果:

1
LambdaInterface: Lambda Demo

(3) 在集合中的应用

Lambda 表达式和集合的组合通常通过 Stream API 来操作集合数据。这大大简化了对集合的过滤、映射和归约操作。

示例:

1
2
3
4
5
6
7
8
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 使用 Lambda 表达式过滤
List<String> filteredNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

System.out.println(filteredNames);

输出:

1
[Alice]

(4) 使用 forEach 简化遍历

在没有 Lambda 表达式的情况下,我们通常使用 for 循环来遍历集合:

1
2
3
for (String name : names) {
    System.out.println(name);
}

使用 Lambda 表达式,集合的遍历可以简化为:

1
names.forEach(name -> System.out.println(name));

(5) 方法引用

Lambda 表达式还支持方法引用,可以直接引用现有方法作为 Lambda 表达式的实现。

示例:

1
2
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
names.forEach(System.out::println);

这段代码与 names.forEach(name -> System.out.println(name)); 等效。

4. Lambda 表达式的内部原理

Lambda 表达式的原理基于匿名类的优化,但它并不直接生成匿名类对象,而是通过 Java 的动态方法调用技术实现,具有更高效的运行时性能。具体来说,Lambda 表达式通过 invokedynamic 字节码指令,在运行时动态生成目标方法的实现。

  • 目标类型:Lambda 表达式会推断其对应的函数式接口类型,称为目标类型。
  • 捕获变量:Lambda 表达式可以使用外部作用域中的变量,但这些变量必须是最终的(final)或者有效最终的(effectively final),即这些变量一旦赋值后不会再被修改。

总结

  • 设计定位:Lambda 表达式用于简化函数式接口的实现,减少样板代码,支持行为参数化。
  • 原理:通过动态方法调用,Lambda 表达式避免了传统匿名类的性能开销,并通过目标类型推断实现了灵活性。
  • 用法:常用于集合操作、事件处理等场景,通过简化的语法使代码更具可读性和可维护性。

以上只能算对Lambda表达式的一个简单介绍。Lambda表达式作为Java 8引入的重要特性,其应用范围非常广泛,涉及的内容也相当丰富。在实际开发中,我们还需要深入理解Lambda表达式的更多细节,如变量捕获、方法引用的不同形式、与Stream API的结合使用等。同时,合理使用Lambda表达式可以大大提高代码的简洁性和可读性,但过度使用也可能导致代码难以理解。因此,在实际应用中,我们需要权衡代码的简洁性和可读性,选择最适合的方式来使用Lambda表达式。


所有代码已上传至:https://github.com/lihongzheshuai/java-all-in-one

本文由作者按照 CC BY 4.0 进行授权