第 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) → void | map.forEach |
UnaryOperator<T> | T → T | 同类型转换,list.replaceAll |
BinaryOperator<T> | (T,T) → T | Stream.reduce |
IntPredicate 等 | int → 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 |
| 构造器 | 类名::new | ArrayList::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 生产 |
| Consumer | T -> void 消费 |
| Function | T -> R 转换 |
| Predicate | T -> boolean 判断 |
| 方法引用 | 类::方法,四种形式 |
| 变量捕获 | 必须 final 或事实 final |
this | Lambda 中指向外部类 |
动手练习
练习 1:基础题
用四大函数式接口各写一个例子:生成随机数、打印列表、字符串转长度、判断是否为偶数。
练习 2:进阶题
设计一个 Validator<T> 函数式接口,支持链式组合多个校验规则:
java
Validator<User> v = Validator.<User>of(u -> u.getName() != null, "姓名不能为空")
.and(u -> u.getAge() >= 0, "年龄不能为负");下一章:第 37 章:Stream API →