一、Lambda 表达式详解
1. 什么是 Lambda 表达式?
一句话概括:Lambda 表达式是一个可以传递的匿名函数。
- 匿名:它没有像普通方法那样的明确名称。
- 函数:它像方法一样有参数列表、函数体和返回类型,但它不属于任何特定的类。
- 可传递:你可以像传递一个变量一样,将 Lambda 表达式作为参数传递给方法,或者从方法中返回。
它的核心作用是简化代码,特别是在使用只有一个抽象方法的接口(即函数式接口)时。
2. Lambda 表达式的语法结构
Lambda 表达式的基本语法非常简洁,通常表现为:
(parameters) -> expression
或者
(parameters) -> { statements; }
(parameters): 参数列表。- 如果没有参数,就用空括号
()表示。 - 如果只有一个参数,括号可以省略,例如
param -> ...。 - 参数类型通常可以省略,因为编译器可以通过上下文推断出来(类型推断)。
- 如果没有参数,就用空括号
->: 箭头操作符或 ** Lambda 操作符 **。它将参数列表和函数体分开。expression或{ statements; }: 函数体。- 表达式体 (
expression): 当函数体只有一条语句时,可以省略花括号{}和return关键字。表达式的结果会自动作为返回值。 - 语句块体 (
{ statements; }): 当函数体包含多条语句时,必须使用花括号{}包裹。如果有返回值,必须显式使用return关键字。
- 表达式体 (
3. Lambda 表达式的使用前提:函数式接口
Lambda 表达式并不能在任何地方使用。它只能被赋值给一个函数式接口类型的变量,或者作为参数传递给一个接受函数式接口作为参数的方法。
函数式接口:是指只包含一个抽象方法的接口。
- 你可以使用
@FunnalInterface注解来明确标识一个接口是函数式接口,编译器会帮你检查。 - Java 8 内置了大量常用的函数式接口,都在
java.util.function包下。
示例:自定义一个函数式接口
@FunctionalInterface
public interface MyCalculator {
int calculate(int a, int b);
}
现在,我们可以用 Lambda 表达式来创建 MyCalculator 的实例:
public class LambdaExample {
public static void main(String[] args) {
// 使用 Lambda 表达式实现加法
MyCalculator adder = (a, b) -> a + b;
System.out.println("10 + 5 = " + adder.calculate(10, 5)); // 输出: 10 + 5 = 15
// 使用 Lambda 表达式实现乘法
MyCalculator multiplier = (a, b) -> {
System.out.println("正在计算乘法...");
return a * b;
};
System.out.println("10 * 5 = " + multiplier.calculate(10, 5)); // 输出: 正在计算乘法... \n 10 * 5 = 50
}
}
二、常用的 Lambda 表达式示例
以下是一些结合 Java Stream API 和 java.util.function 包中常用函数式接口的 Lambda 表达式示例。
1. Consumer<T> (消费者)
接收一个参数,没有返回值。常用于 forEach 方法。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 打印列表中的每个元素
names.forEach(name -> System.out.println(name));
// 使用方法引用(Method Reference)可以更简洁
names.forEach(System.out::println);
2. Predicate<T> (谓词)
接收一个参数,返回一个布尔值。常用于 filter 方法。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 过滤出所有偶数
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// evenNumbers 结果: [2, 4, 6]
3. Function<T, R> (函数)
接收一个类型为 T 的参数,返回一个类型为 R 的结果。常用于 map 方法。
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 将每个字符串映射为其长度
List<Integer> wordLengths = words.stream()
.map(word -> word.length())
.collect(Collectors.toList());
// wordLengths 结果: [5, 6, 6]
4. Supplier<T> (供应商)
不接收任何参数,返回一个类型为 T 的结果。常用于 supplyAsync 方法。
// 异步生成一个随机数
CompletableFuture<Integer> randomFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return new Random().nextInt(100);
});
5. Comparator<T> (比较器)
用于定义对象的排序规则。
List<String> fruits = Arrays.asList("Pineapple", "Apple", "Banana");
// 按字符串长度升序排序
fruits.sort((f1, f2) -> f1.length() - f2.length());
// fruits 结果: [Apple, Banana, Pineapple]
// 按字符串自然顺序(字典序)降序排序
fruits.sort((f1, f2) -> f2.compareTo(f1));
// fruits 结果: [Pineapple, Banana, Apple]
三、回调函数详解
1. 什么是回调函数?
一句话概括:回调函数是一个通过函数参数传递到另一个函数中,并在那个函数内部被调用的函数。
你可以把它想象成一个 “回头再打给你” 的场景:
- 你 (调用者) 给朋友 (被调用者) 打电话,提出一个请求(比如:“帮我查一下明天的天气”)。
- 你 告诉朋友:“查好了打我这个电话(你的电话号码)告诉我结果。” 这里的 “你的电话号码” 就是回调地址。
- 你 挂了电话,就可以去做别的事了(非阻塞)。
- 过了一会儿,朋友查好了天气,他主动拨打你的电话(执行回调),告诉你天气情况。
在这个例子中,你提供给朋友的那个 “可以回拨的电话号码”,在编程中就是回调函数。
2. 回调函数的分类
- 同步回调:被调用者在处理完请求后,立即调用回调函数,然后才返回。调用者会一直等待,直到回调执行完毕。这是一种 “阻塞” 的等待。
- 异步回调:被调用者接收请求后,会在另一个线程中处理任务,然后立即返回,不会等待任务完成。当任务处理完毕后,再在那个新线程中调用回调函数来通知调用者。这是一种 “非阻塞” 的等待。
3. 回调函数的例子
示例 1:同步回调 (使用 Lambda 表达式)
我们创建一个 Notifier 类,它的 notify 方法接收一个消息和一个 Callback。
// 1. 定义一个回调接口
@FunctionalInterface
interface Callback {
void onComplete(String result);
}
// 2. 被调用者
class Notifier {
public void notify(String message, Callback callback) {
System.out.println("Notifier: 开始处理消息...");
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String result = "消息处理完毕: " + message;
// 处理完成后,调用回调函数
callback.onComplete(result);
System.out.println("Notifier: 已调用回调,任务结束。");
}
}
// 3. 调用者
public class CallbackExample {
public static void main(String[] args) {
Notifier notifier = new Notifier();
System.out.println("Main: 准备调用 notify 方法...");
// 使用 Lambda 表达式作为回调函数
notifier.notify("Hello Callback", (result) -> {
System.out.println("Main: 收到回调通知,结果是: " + result);
});
System.out.println("Main: notify 方法调用返回,主线程继续执行。");
}
}
执行结果:
plaintext
Main: 准备调用 notify 方法...
Notifier: 开始处理消息...
Main: 收到回调通知,结果是: 消息处理完毕: Hello Callback
Notifier: 已调用回调,任务结束。
Main: notify 方法调用返回,主线程继续执行。
分析:主线程调用 notifier.notify() 后,会一直等待,直到 notify 方法内部的 callback.onComplete() 执行完毕,notify 方法才返回。这就是同步回调。
示例 2:异步回调 (使用 CompletableFuture)
CompletableFuture 是实现异步回调的绝佳例子。
public class AsyncCallbackExample {
public static void main(String[] args) {
System.out.println("Main: 开始异步任务...");
CompletableFuture.supplyAsync(() -> {
// 1. 这部分在另一个线程中执行 (类似“朋友”在查天气)
System.out.println("Async Task: 开始执行耗时操作...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async Task: 操作完成,准备返回结果。");
return "任务成功完成!";
}).thenAccept(result -> {
// 2. 这是回调函数 (类似“朋友”打回电话)
// 它会在 supplyAsync 的任务完成后,在同一个或另一个线程中自动执行
System.out.println("Callback: 收到异步任务的结果: " + result);
});
System.out.println("Main: 异步任务已提交,主线程可以做其他事了。");
// 等待一段时间,确保能看到回调的输出
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main: 程序结束。");
}
}
执行结果 (线程名称可能不同):
Main: 开始异步任务...
Main: 异步任务已提交,主线程可以做其他事了。
Async Task: 开始执行耗时操作...
Async Task: 操作完成,准备返回结果。
Callback: 收到异步任务的结果: 任务成功完成!
Main: 程序结束。
分析:主线程调用 supplyAsync 后,立即返回,继续执行下一行代码。而 supplyAsync 的任务在 ForkJoinPool 的线程中后台运行。当任务完成后,thenAccept 注册的回调函数被自动触发。这就是异步回调。
总结
- Lambda 表达式是一种语法糖,极大地简化了函数式接口的实例化。它让你能写出更紧凑、更易读的代码,尤其在处理集合和并发编程时。
- 回调函数是一种设计模式,描述了 “我把一个函数给你,你在需要的时候调用它” 的交互方式。
- Lambda 表达式和回调函数的关系:Lambda 表达式是实现回调函数的完美工具。在 Java 中,当你需要传递一个回调函数时,通常就是传递一个函数式接口的实例,而使用 Lambda 表达式可以非常方便地创建这个实例。
2138

被折叠的 条评论
为什么被折叠?



