Skip to content

Lambda 表达式

你知道吗,Java 代码里 80% 的匿名内部类,其实可以只写一行。

不信?看看这个:

java
// 以前:匿名内部类
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
});
thread.start();

// 现在:Lambda 表达式
Thread thread = new Thread(() -> System.out.println("Hello"));
thread.start();

从 6 行变成 1 行。这就是 Lambda 的力量。

基本语法

Lambda 的核心是一个箭头符号:->

(参数) -> { 方法体 }

参数部分和以前方法声明一样,只是省略了类型(编译器会推断)。箭头后面是你要执行的代码。

几种写法

java
// 完整写法:参数类型、括号、大括号、return 全部保留
(BinaryOperator<Integer>) (int a, int b) -> {
    return a + b;
};

// 省略参数类型(编译器推断)
(BinaryOperator<Integer>) (a, b) -> {
    return a + b;
};

// 单行方法体:省略 return 和大括号
BinaryOperator<Integer> add = (a, b) -> a + b;

// 单参数:省略括号
Consumer<String> printer = s -> System.out.println(s);

参数规则速查

场景能省略不能省略
单参数括号
无/多参数括号
编译器能推断类型参数类型
需要显式声明多参数时至少一个

方法引用

当你只有一个方法调用时,Lambda 可以进一步简化为方法引用

四种类型

java
import java.util.function.*;
import java.util.*;

// 类型一:静态方法引用
// Integer.parseInt(str)  →  Integer::parseInt
Function<String, Integer> parser = Integer::parseInt;
parser.apply("123");  // 123

// 类型二:实例方法引用(任意对象)
// s.toUpperCase()  →  String::toUpperCase
Function<String, String> upper = String::toUpperCase;
upper.apply("hello");  // HELLO

// 类型三:实例方法引用(特定对象)
String str = "hello";
// str.length()  →  str::length
Supplier<Integer> len = str::length;
len.get();  // 5

// 类型四:构造方法引用
// new ArrayList<>()  →  ArrayList::new
Supplier<ArrayList<String>> factory = ArrayList::new;
ArrayList<String> list = factory.get();

什么时候用方法引用

记住这个优先级:方法引用 > Lambda > 匿名内部类

java
// 同一个目标,越来越简洁
list.stream()
    .filter(s -> s.isEmpty())        // Lambda
    .filter(String::isEmpty)         // 方法引用 ✅
    .collect(Collectors.toList());

list.forEach(s -> System.out.println(s));  // Lambda
list.forEach(System.out::println);          // 方法引用 ✅

与匿名内部类的区别

表面相似,本质不同。

java
// 匿名内部类
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println(this.getClass());  // 指向匿名类
    }
};

// Lambda
Runnable r2 = () -> {
    System.out.println(this.getClass());  // this 指向外层类
};
区别Lambda匿名内部类
this 指向外层类匿名类本身
编译产物invokedynamic生成新 .class
变量作用域访问 effectively final可修改局部变量
能定义多个方法

这里 this 的差异很重要。在 Lambda 里,this 不会指向 Lambda 本身,而是指向包含它的那个类——这和匿名内部类完全不同。

变量捕获

Lambda 可以"看到"外层变量,但这些变量必须是 effectively final

java
int factor = 2;  // 实际上是 final

// Lambda 读取外层变量
Function<Integer, Integer> multiply = n -> n * factor;
// multiply.apply(5) → 10

// 改一下试试?
factor = 3;  // ❌ 编译错误:Lambda 引用的变量不能被修改

背后的原理

java
// 编译器实际做了什么
// 1. 把外层变量"捕获"进来
// 2. 自动加上 final
// 3. 转换成匿名类能用的形式

// 你写的代码:
int x = 10;
Function<Integer, Integer> f = n -> n * x;

// 编译器内部近似生成:
int capturedX = 10;  // 实际 final
new Function<Integer, Integer>() {
    private final int capturedX = capturedX;  // 捕获副本
    @Override
    public Integer apply(Integer n) {
        return n * this.capturedX;
    }
};

实战场景

场景一:集合排序

java
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");

// 按字母排序
names.sort(String::compareTo);

// 按长度排序
names.sort(Comparator.comparingInt(String::length));

// 倒序
names.sort(Comparator.reverseOrder());

场景二:回调模式

java
@FunctionalInterface
interface Callback<T> {
    void onComplete(T result);
}

void fetchData(Callback<String> callback) {
    String data = "...";
    callback.onComplete(data);
}

// Lambda 调用
fetchData(result -> System.out.println("收到: " + result));

场景三:条件链

java
String input = null;

// 传统写法
String result = "";
if (input != null) {
    result = input.toUpperCase();
}

// Lambda + Optional
String result2 = Optional.ofNullable(input)
    .map(String::toUpperCase)
    .orElse("");

常见误区

误区一:过度使用

java
// ❌ 过度:3 个字的 Lambda 写成方法引用反而更难读
list.forEach(Consumer::accept);  // 这是什么?

// ✅ 适度:方法引用用于简单场景
list.forEach(System.out::println);

// ✅ 适度:Lambda 用于有逻辑的场景
list.forEach(s -> {
    if (s.length() > 5) {
        System.out.println(s);
    }
});

误区二:Lambda 嵌套

java
// ❌ 嵌套太深难读
list.stream()
    .filter(s -> s.stream()
        .anyMatch(c -> c > 'z'))
    .findAny();

// ✅ 拆成方法
list.stream()
    .filter(this::hasUpperCase)
    .findAny();

private boolean hasUpperCase(String s) {
    return s.chars().anyMatch(c -> c > 'z');
}

小结

Lambda 的学习曲线很短:

  • 记住三件事:-> 符号、参数推断、effectively final
  • 优先用方法引用,简洁且清晰
  • 注意 this 的指向——它指向外层类,不是 Lambda 本身
  • 不要为了用 Lambda 而用它,简单的循环遍历有时更直接

基于 VitePress 构建