Skip to content
第 36 / 250 章后端⏱ 10 分钟阅读

第 36 章:Lambda 表达式

学习目标

  • 理解函数式编程思想
  • 掌握 Lambda 语法与方法引用
  • 熟悉四大内置函数式接口

一、Lambda 解决什么问题?

java
// ❌ 匿名内部类:5 行样板代码,只为传一段逻辑
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}).start();

// ✅ Lambda:一行
new Thread(() -> System.out.println("Hello")).start();

本质:Java 中方法不是「一等公民」,不能像变量一样传递。Lambda 让你能把一段代码当参数传。

二、Lambda 语法

java
(参数列表) -> { 方法体 }

简化规则

java
// 完整写法
(String s) -> { return s.length(); }

// ① 参数类型可推断,省略
(s) -> { return s.length(); }

// ② 只有一个参数,括号可省
s -> { return s.length(); }

// ③ 方法体只有一句,大括号和 return 可省
s -> s.length()

// ④ 无参数时括号不能省
() -> System.out.println("hi")

// ⑤ 多参数
(a, b) -> a + b

三、函数式接口

Lambda 只能赋给「函数式接口」——只有一个抽象方法的接口。

java
@FunctionalInterface          // ① 编译器校验:多写一个抽象方法就报错
public interface Calculator {
    int calculate(int a, int b);   // ② 唯一的抽象方法

    default void print() { }       // ③ default 方法不算,可以有多个
    static void help() { }         // ④ static 方法也不算
}

// 使用
Calculator add = (a, b) -> a + b;
Calculator mul = (a, b) -> a * b;
System.out.println(add.calculate(2, 3));   // 5

四、四大内置函数式接口

java
// Supplier:生产者,无参有返回
Supplier<String> supplier = () -> "hello";
System.out.println(supplier.get());              // hello

// Consumer:消费者,有参无返回
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("world");

// Function:转换,一进一出
Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("hello"));       // 5

// Predicate:断言,返回 boolean
Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test(""));            // true

常用变体

接口签名用途
BiFunction<T,U,R>(T,U) → R两个入参
BiConsumer<T,U>(T,U) → voidmap.forEach
UnaryOperator<T>T → T同类型转换,list.replaceAll
BinaryOperator<T>(T,T) → TStream.reduce
IntPredicateint → boolean避免装箱,性能更好

组合使用

java
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> shortStr = s -> s.length() < 10;

Predicate<String> valid = notEmpty.and(shortStr);     // 与
Predicate<String> either = notEmpty.or(shortStr);     // 或
Predicate<String> invalid = notEmpty.negate();        // 非

Function<String, Integer> len = String::length;
Function<Integer, String> toStr = i -> "长度=" + i;
Function<String, String> combined = len.andThen(toStr);   // 先 len 再 toStr
System.out.println(combined.apply("hello"));   // 长度=5

五、方法引用

当 Lambda 只是调用一个已有方法时,可以用 :: 进一步简化。

java
// Lambda → 方法引用
s -> System.out.println(s)     →   System.out::println
s -> s.length()                →   String::length
s -> new User(s)               →   User::new

四种形式

形式语法示例
静态方法类名::静态方法Integer::parseInt
实例方法(特定对象)对象::方法System.out::println
实例方法(任意对象)类名::实例方法String::length
构造器类名::newArrayList::new
java
List<String> names = List.of("张三", "李四", "王五");

// ① 特定对象的实例方法
names.forEach(System.out::println);

// ② 任意对象的实例方法:第一个参数变成方法接收者
names.stream().map(String::toUpperCase);
//                  等价于 s -> s.toUpperCase()

// ③ 静态方法
List.of("1", "2").stream().map(Integer::parseInt);
//                              等价于 s -> Integer.parseInt(s)

// ④ 构造器引用
Supplier<List<String>> listMaker = ArrayList::new;
Function<String, User> userMaker = User::new;

③ 的理解难点String::toUpperCase 是实例方法但没有对象。规则是:Lambda 的第一个参数充当方法的接收者,剩余参数作为方法参数。

六、变量捕获(闭包)

java
public void demo() {
    int count = 0;                              // ① 局部变量

    Runnable r = () -> {
        System.out.println(count);              // ② 可以读
        // count++;                             // ❌ 编译错误:不能修改
    };

    // count = 1;                               // ❌ 也不能在外面改
}

规则:Lambda 捕获的局部变量必须是 final 或事实上 final(赋值后不再改变)。

为什么? Lambda 可能在另一个线程中延迟执行。如果允许修改,会出现「读到的值和期望不一致」的并发问题。Java 干脆禁止修改。

绕过方法(需要可变时):

java
// ✅ 用数组或 AtomicInteger 包一层
int[] counter = {0};
Runnable r = () -> counter[0]++;    // 数组引用本身没变,改的是元素

AtomicInteger atomic = new AtomicInteger();
Runnable r2 = atomic::incrementAndGet;   // ✅ 线程安全,推荐

七、Lambda 中的 this

java
public class Demo {
    private String name = "外部类";

    public void test() {
        // 匿名内部类:this 指向匿名类自己
        Runnable r1 = new Runnable() {
            public void run() {
                // System.out.println(this.name);   // ❌ 匿名类没有 name
            }
        };

        // Lambda:this 指向外部类 ✅
        Runnable r2 = () -> System.out.println(this.name);   // 外部类
    }
}

原因:Lambda 不是一个新的类,它编译成外部类的一个私有方法,所以作用域和外部方法一致。

八、实战对比

java
List<User> users = getUsers();

// ❌ 传统写法:18 行
List<String> result = new ArrayList<>();
for (User u : users) {
    if (u.getAge() >= 18) {
        result.add(u.getName().toUpperCase());
    }
}
Collections.sort(result);

// ✅ 函数式写法:5 行,意图一目了然
List<String> result = users.stream()
        .filter(u -> u.getAge() >= 18)
        .map(u -> u.getName().toUpperCase())
        .sorted()
        .toList();

⚠️ 不要滥用:三层嵌套 Lambda 比 for 循环更难读。可读性优先

九、本章小结

要点关键
Lambda把代码当参数传
函数式接口只有一个抽象方法
Supplier() -> T 生产
ConsumerT -> void 消费
FunctionT -> R 转换
PredicateT -> boolean 判断
方法引用类::方法,四种形式
变量捕获必须 final 或事实 final
thisLambda 中指向外部类

动手练习

练习 1:基础题

用四大函数式接口各写一个例子:生成随机数、打印列表、字符串转长度、判断是否为偶数。

练习 2:进阶题

设计一个 Validator<T> 函数式接口,支持链式组合多个校验规则:

java
Validator<User> v = Validator.<User>of(u -> u.getName() != null, "姓名不能为空")
        .and(u -> u.getAge() >= 0, "年龄不能为负");

下一章第 37 章:Stream API

本站基于 VitePress 构建 · 由 Codebook 团队维护