Appearance
第32课:Lambda 表达式
🎯 学习目标
- 理解函数式编程思想
- 掌握 Lambda 表达式语法
- 理解函数式接口
- 掌握方法引用和构造器引用
- 理解闭包和变量捕获
📖 一、为什么需要 Lambda?
传统写法 vs Lambda
场景:给列表排序
java
// 传统写法:匿名内部类(冗长)
List<String> list = Arrays.asList("banana", "apple", "cherry");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
// Lambda 写法(简洁)
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
// 更简洁:方法引用
Collections.sort(list, String::compareTo);📖 二、Lambda 语法
基本语法
java
// 完整形式
(参数列表) -> {
语句;
return 结果;
}
// 简化形式
参数 -> 表达式语法演变
java
// 1. 标准接口定义
interface Calculator {
int calculate(int a, int b);
}
// 2. 匿名内部类(Java 7)
Calculator add = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};
// 3. Lambda 完整形式
Calculator add = (int a, int b) -> {
return a + b;
};
// 4. 省略参数类型(类型推断)
Calculator add = (a, b) -> {
return a + b;
};
// 5. 单语句省略 return 和大括号
Calculator add = (a, b) -> a + b;
// 6. 单参数省略括号
Function<String, Integer> len = s -> s.length();
// 7. 无参数
Runnable r = () -> System.out.println("Hello");📖 三、函数式接口
什么是函数式接口?
只有一个抽象方法的接口
java
@FunctionalInterface // 可选注解,编译时检查
public interface MyFunction {
void run(); // 唯一的抽象方法
// 可以有默认方法
default void defaultMethod() { }
// 可以有静态方法
static void staticMethod() { }
}
// 使用
MyFunction func = () -> System.out.println("Running");
func.run();常用函数式接口
1. Supplier<T> - 供应者(无参数,有返回值)
java
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 使用
Supplier<String> supplier = () -> "Hello World";
String result = supplier.get(); // "Hello World"
// 实际应用:延迟计算
public String getValue(Supplier<String> supplier) {
if (某条件) {
return supplier.get(); // 需要时才计算
}
return null;
}2. Consumer<T> - 消费者(有参数,无返回值)
java
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
// 使用
Consumer<String> printer = str -> System.out.println(str);
printer.accept("Hello"); // 输出:Hello
// 实际应用:遍历
List<String> list = Arrays.asList("A", "B", "C");
list.forEach(item -> System.out.println(item));3. Function<T, R> - 函数(有参数,有返回值)
java
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
// 使用
Function<String, Integer> strLen = str -> str.length();
int length = strLen.apply("Hello"); // 5
// 实际应用:转换
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> lengths = names.stream()
.map(name -> name.length())
.collect(Collectors.toList());4. Predicate<T> - 断言(判断真假)
java
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
// 使用
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // true
// 实际应用:过滤
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evens = numbers.stream()
.filter(num -> num % 2 == 0)
.collect(Collectors.toList());5. BiFunction<T, U, R> - 双参数函数
java
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
int sum = add.apply(10, 20); // 306. UnaryOperator<T> - 一元操作符
java
// UnaryOperator<T> 相当于 Function<T, T>
UnaryOperator<Integer> square = x -> x * x;
int result = square.apply(5); // 25📖 四、方法引用
四种方法引用
1. 静态方法引用 - ClassName::staticMethod
java
// Lambda
Function<String, Integer> parser = str -> Integer.parseInt(str);
// 方法引用
Function<String, Integer> parser = Integer::parseInt;
int num = parser.apply("123"); // 1232. 实例方法引用 - object::instanceMethod
java
String prefix = "Hello, ";
// Lambda
Function<String, String> greeter = name -> prefix.concat(name);
// 方法引用
Function<String, String> greeter = prefix::concat;
String greeting = greeter.apply("Alice"); // "Hello, Alice"3. 类方法引用 - ClassName::instanceMethod
java
// Lambda
Function<String, Integer> lengthGetter = str -> str.length();
// 方法引用
Function<String, Integer> lengthGetter = String::length;
// 排序示例
List<String> list = Arrays.asList("banana", "apple", "cherry");
Collections.sort(list, String::compareTo);4. 构造器引用 - ClassName::new
java
// Lambda
Supplier<List<String>> listSupplier = () -> new ArrayList<>();
// 构造器引用
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
// 带参数的构造器
Function<Integer, int[]> arrayCreator = size -> new int[size];
Function<Integer, int[]> arrayCreator = int[]::new;
int[] array = arrayCreator.apply(10); // 创建长度为10的数组📖 五、闭包和变量捕获
1. 闭包概念
java
public static Function<Integer, Integer> makeAdder(int x) {
// Lambda 捕获外部变量 x
return y -> x + y;
}
Function<Integer, Integer> add5 = makeAdder(5);
System.out.println(add5.apply(10)); // 152. 变量捕获规则
java
public void test() {
int x = 10;
// ✅ 可以访问外部变量
Runnable r1 = () -> System.out.println(x);
// ❌ 但不能修改(必须是 final 或 effectively final)
Runnable r2 = () -> {
// x = 20; // 编译错误
};
// ❌ 外部修改也不行
// x = 20; // 这样会导致 Lambda 编译错误
}Effectively Final:
java
int x = 10; // 没有声明 final
// 但如果后续没有修改,就是 effectively final
Runnable r = () -> System.out.println(x); // ✅ 可以
// 如果修改了,就不是 effectively final
x = 20; // 导致上面的 Lambda 编译错误解决方案:使用数组或对象包装
java
final int[] counter = {0};
Runnable r = () -> {
counter[0]++; // ✅ 可以修改数组内容
};📖 六、实战应用
1. 集合操作
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 过滤
List<String> filtered = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
// 转换
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// 排序
names.sort((a, b) -> a.length() - b.length());
// 或
names.sort(Comparator.comparingInt(String::length));2. 线程
java
// 传统写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Running");
}
}).start();
// Lambda
new Thread(() -> System.out.println("Running")).start();3. 事件处理
java
// GUI 按钮点击
button.addActionListener(event -> {
System.out.println("Button clicked");
});4. 自定义排序
java
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
// 按年龄排序
people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));
// 或使用 Comparator
people.sort(Comparator.comparingInt(Person::getAge));
// 多条件排序
people.sort(Comparator.comparing(Person::getName)
.thenComparingInt(Person::getAge));⚠️ 常见陷阱
陷阱1:把 Lambda 写成复杂业务块
Lambda 适合表达短小行为。如果函数体超过几行、包含多个分支或异常处理,应该抽成具名方法。
陷阱2:误解变量捕获
Lambda 只能捕获 final 或 effectively final 的局部变量。不能在 Lambda 内修改外部局部变量。
陷阱3:异常处理变得隐蔽
函数式接口的方法签名如果没有声明受检异常,Lambda 内部就不能直接抛出受检异常,需要转换或在内部处理。
陷阱4:方法引用可读性反而更差
方法引用不是越多越好。如果 this::handle 让读者不知道处理逻辑在哪里,普通 Lambda 可能更直观。
陷阱5:忽略闭包持有对象生命周期
Lambda 捕获外部对象后,可能延长对象生命周期。异步任务、缓存回调、监听器中尤其要注意内存泄漏。
🆚 Java vs C 对比
| 特性 | C | Java Lambda |
|---|---|---|
| 行为传递 | 函数指针 | 函数式接口 |
| 上下文捕获 | 手动传上下文指针 | 自动捕获 effectively final 变量 |
| 类型检查 | 函数指针签名 | 编译期检查函数式接口 |
| 运行机制 | 直接函数地址调用 | invokedynamic + 运行时适配 |
对 C 程序员来说,Lambda 可以理解为“带类型约束和有限闭包能力的函数指针”。区别在于 Java 的 Lambda 必须落到某个函数式接口上,不能脱离类型系统单独存在。
💡 最佳实践
1. 保持简洁
java
// ❌ 过于复杂
list.forEach(item -> {
String processed = item.trim().toLowerCase();
if (processed.length() > 5) {
System.out.println(processed);
}
});
// ✅ 提取为方法
list.forEach(this::processAndPrint);
private void processAndPrint(String item) {
String processed = item.trim().toLowerCase();
if (processed.length() > 5) {
System.out.println(processed);
}
}2. 优先使用方法引用
java
// ❌ Lambda
list.forEach(item -> System.out.println(item));
// ✅ 方法引用
list.forEach(System.out::println);3. 避免副作用
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = new ArrayList<>();
// ❌ 有副作用(修改外部变量)
numbers.forEach(num -> result.add(num * 2));
// ✅ 无副作用(使用 Stream)
List<Integer> result = numbers.stream()
.map(num -> num * 2)
.collect(Collectors.toList());📝 练习
完成 练习/Ex32_Lambda.java:
- 基本 Lambda 语法
- 函数式接口使用
- 方法引用
- 闭包和变量捕获
- 集合操作
- 综合:使用 Lambda 重构旧代码
🎓 下一步
- 第33课:Stream API - 中间操作、终止操作、并行流