Skip to content

Lambda表达式

前置知识

在阅读本章前,你需要了解:Java的接口定义和使用,以及基本的面向对象编程概念。

为什么需要Lambda表达式?

你有没有发现,每次写集合遍历、事件监听,或者定义小型回调时,都需要写一大堆匿名内部类?它们臃肿、难看,而且掩盖了代码的主要意图。想象一下,你需要把“给员工发工资”的逻辑传递给一个工资处理系统,但写成匿名类会让整个业务逻辑变得啰嗦且难以维护。这就是Lambda表达式“跳出来”帮忙的地方。

Lambda表达式可以让你写出更简洁、清晰、符合函数式编程风格的代码,省去模板式的匿名内部类,让重点更突出,代码更好读、更易维护。

具体章节

什么是函数式接口?

简单来说,函数式接口是只包含一个抽象方法的接口,Java 8引入了@FunctionalInterface注解用来标注这类接口。Lambda表达式实际上就是该接口的匿名实现。

为什么只有一个方法?因为Lambda表达式是一段行为(函数),它要适配一个唯一的函数签名。

举个小例子,大家都很熟悉的Runnable接口就是一个典型的函数式接口:

java
@FunctionalInterface
public interface Runnable {
    void run();
}

当你写一个Runnable的实例时,可以直接用Lambda表达式代替匿名内部类。

Lambda表达式的基础语法

Lambda表达式的核心语法格式是:

(parameters) -> expression
或者
(parameters) -> { statements; }
  • 左侧是参数列表(可以没有、可以多个)
  • 右侧是方法体:单表达式或者代码块

打个比方:如果说接口是个“合同”,Lambda就是“签名”,它告诉Java:“这就是我要交付的实现。”

代码示例一:Runnable的Lambda表达式

java
public class LambdaBasicExample {
    public static void main(String[] args) {
        // 传统匿名内部类写法
        Runnable runnableOld = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from old Runnable");
            }
        };
        runnableOld.run();

        // 用Lambda简化写法
        Runnable runnableLambda = () -> System.out.println("Hello from lambda Runnable");
        runnableLambda.run();
    }
}

这段代码做了什么?

  1. 第一部分使用传统的匿名内部类实现Runnable接口,打印文本。
  2. 第二部分则用Lambda表达式写了相同的逻辑,代码更简洁。
  3. 最后调用run()方法,观察两者输出相同内容。

函数式接口的标准库示例

Java标准库提供了很多函数式接口,如Consumer<T>Supplier<T>Function<T,R>等,它们都随着Lambda表达式而流行起来。

  • Consumer<T>:接受一个参数,无返回
  • Supplier<T>:无参数,返回一个结果
  • Function<T,R>:接受一个参数,返回一个结果

代码示例二:使用标准函数式接口Function

下面我们通过一个例子,演示如何使用Function来转换字符串:

java
import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        Function<String, Integer> stringLength = s -> s.length();

        String test = "Hello Lambda";
        int length = stringLength.apply(test);
        System.out.println("字符串\"" + test + "\"的长度是:" + length);
    }
}

这段代码做了什么?

  1. 用Lambda定义了一个Function接口的实现,功能是返回字符串长度。
  2. 调用apply方法执行功能,最终打印字符串长度。

通过Function接口,我们能把“计算规则”当作参数传递,代码因此更灵活。


方法引用:让Lambda表达式更简洁

我们刚看到Lambda函数体是s -> s.length(),它其实表示“调用某个对象的实例方法”,Java提供方法引用语法,让这句Lambda更简单:

  • 对象::实例方法
  • 类名::静态方法
  • 类名::实例方法(特殊用法)
  • 类名::new(构造器引用)

代码示例三:方法引用和构造器引用

java
import java.util.function.Function;
import java.util.function.Supplier;

public class MethodReferenceExample {
    public static void main(String[] args) {
        // Lambda形式
        Function<String, Integer> lengthLambda = s -> s.length();

        // 方法引用形式
        Function<String, Integer> lengthMethodRef = String::length;

        System.out.println("Lambda: " + lengthLambda.apply("Method Ref"));
        System.out.println("Method Ref: " + lengthMethodRef.apply("Method Ref"));

        // 构造器引用
        Supplier<String> stringSupplier = String::new;
        String emptyString = stringSupplier.get();
        System.out.println("构造器引用创建的字符串长度: " + emptyString.length());
    }
}

这段代码做了什么?

  1. 通过String::length使用方法引用替代Lambda表达式,代码更简洁。
  2. 演示了通过String::new调用构造器初始化字符串。
  3. 输出结果验证两种写法是等价的。

小结与对比

写法描述例子优缺点
匿名内部类传统写法,代码冗长new Runnable(){...}可读性差,模板代码多
Lambda表达式简洁表达函数性接口实现(args) -> {...}代码简洁,易读
方法引用语法糖,引用已有方法或构造器System.out::println更简洁,表达意图清晰

Lambda表达式和方法引用使Java代码更加函数式,更贴近业务逻辑本身,而不是实现细节,这正是它们存在的最大价值。


💡 实战建议

在实际项目中,尽量使用标准的函数式接口(如FunctionConsumer),避免自定义过多接口,从而提高代码复用性和理解成本。同时,推荐用方法引用简化Lambda表达式,提升代码可读性。

⚠️ 常见陷阱

  1. 函数式接口不能有多个抽象方法,否则Lambda无法推断。
  2. 变量捕获限制:Lambda只能访问局部变量中被final或“事实上的final”修饰的变量,这一点初学者易忽略。
  3. 方法引用的适用场景有限,并不是所有Lambda都能用方法引用替代,盲目使用可能降低代码语义。

延伸思考

  • 你觉得Lambda表达式中省略参数类型的设计会带来哪些便利和挑战?
  • 如果一个接口有多个默认方法,但依旧只有一个抽象方法,它仍是函数式接口吗?为什么?
  • 你能想到哪些日常编程场景用Lambda表达式能显著减少代码量?

小结

  • 函数式接口是Lambda表达式的“接口模板”,只含一个抽象方法。
  • Lambda表达式让实现函数式接口变得简洁且语义明确。
  • 方法引用和构造器引用是Lambda的语法糖,极大提升代码可读性。
  • 理解Lambda带来的语法简化,有助于写出更现代、更易维护的Java代码。

希望你享受本章的内容。在日常代码中多尝试Lambda与方法引用,你会发现冗长的匿名内部类逐渐消失,代码闪闪发光。下章我们将继续探索Stream API,让函数式思想更进一步。