Lambda 表达式是 Java 8 引入的重要特性,它基于函数式接口,允许你将功能视为方法参数,或将代码视为数据。以下是 Lambda 表达式从基础到高阶的详细解析:
1. 基础概念与语法
Lambda 表达式本质是匿名函数,它没有名称,但有参数列表、函数主体、返回类型,可能还有异常列表。
基本语法:
(parameters) -> expression
// 或
(parameters) -> { statements; }
关键点:
- 参数列表:可省略参数类型;若只有一个参数,括号可省略。
- 箭头符号:
->
,分隔参数和主体。 - 主体:若为单个表达式,自动返回值;若为代码块,则需显式
return
语句。
2. 函数式接口
Lambda 表达式依赖函数式接口(Functional Interface):
- 仅含一个抽象方法的接口(可含默认方法、静态方法)。
- 需用
@FunctionalInterface
注解(可选,但推荐)。
示例接口:
@FunctionalInterface
interface MyFunction {
int apply(int a, int b); // 唯一抽象方法
}
3. Lambda 表达式的基础应用
3.1 无参数、无返回值
Runnable runnable = () -> System.out.println("Hello Lambda!");
3.2 单参数、单表达式
Consumer<String> printer = s -> System.out.println(s);
3.3 多参数、多语句
BiFunction<Integer, Integer, Integer> sum = (a, b) -> {
int result = a + b;
return result;
};
3.4 异常处理
@FunctionalInterface
interface CheckedExceptionHandler {
void handle() throws IOException; // 声明异常
}
// Lambda中抛出异常
CheckedExceptionHandler handler = () -> {
throw new IOException("Error occurred");
};
4. 方法引用与构造器引用
4.1 静态方法引用
// 原Lambda
Function<String, Integer> parser = s -> Integer.parseInt(s);
// 方法引用
Function<String, Integer> parser = Integer::parseInt;
4.2 实例方法引用
// 原Lambda
Predicate<String> isEmpty = s -> s.isEmpty();
// 方法引用
Predicate<String> isEmpty = String::isEmpty;
4.3 构造器引用
// 原Lambda
Supplier<List<String>> listCreator = () -> new ArrayList<>();
// 构造器引用
Supplier<List<String>> listCreator = ArrayList::new;
5. 变量捕获(闭包)
Lambda 可捕获外部的final 或 effectively final变量(即赋值后未再修改的变量)。
示例:
int offset = 10;
UnaryOperator<Integer> adder = x -> x + offset; // 捕获offset
System.out.println(adder.apply(5)); // 输出15
注意:
- 捕获的变量不可修改,否则编译错误。
- 局部内部类、匿名类也有类似机制,但 Lambda 更简洁。
6. 高阶函数与复合 Lambda
6.1 高阶函数
接收函数式接口作为参数或返回函数式接口的方法。
示例:
// 定义高阶函数
static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// 使用Lambda调用
List<String> filtered = filter(
Arrays.asList("apple", "banana", "cherry"),
s -> s.startsWith("a")
);
6.2 复合 Lambda
通过函数式接口的默认方法组合多个 Lambda。
示例:
// Predicate组合
Predicate<String> startsWithA = s -> s.startsWith("a");
Predicate<String> endsWithB = s -> s.endsWith("b");
Predicate<String> combined = startsWithA.and(endsWithB);
// Function组合
Function<Integer, Integer> add5 = x -> x + 5;
Function<Integer, Integer> multiply2 = x -> x * 2;
Function<Integer, Integer> add5ThenMultiply2 = add5.andThen(multiply2);
7. Stream API 与 Lambda
Java 8 的 Stream API 大量使用 Lambda 表达式,用于集合的过滤、映射、归约等操作。
示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.map(n -> n * 2) // 每个数乘以2
.reduce(0, Integer::sum); // 求和
System.out.println(sum); // 输出12 (2*2 + 4*2)
8. 高级特性
8.1 类型推断
编译器可根据上下文推断 Lambda 的参数类型:
// 无需显式声明参数类型
BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;
8.2 泛型函数式接口
自定义泛型函数式接口:
@FunctionalInterface
interface MyMapper<T, R> {
R map(T value);
}
// 使用
MyMapper<String, Integer> lengthMapper = s -> s.length();
8.3 与匿名类的区别
- this 引用:Lambda 中的
this
指向外部类实例,匿名类中的this
指向匿名类自身。 - 编译方式:Lambda 通过 invokedynamic 指令实现,匿名类会生成独立的.class 文件。
9. 最佳实践
- 保持简洁:避免复杂的 Lambda(超过 3 行逻辑建议使用方法引用或具名方法)。
- 明确命名:函数式接口和参数名应表意清晰。
- 避免副作用:Lambda 应是纯函数,不修改外部状态。
- 优先使用标准接口:如
Function
、Consumer
、Predicate
等,减少自定义接口。
总结
Lambda 表达式让 Java 更具函数式编程风格,通过简洁的语法和函数式接口,大幅提升代码可读性和生产力。结合 Stream API,它成为处理集合数据的强大工具。掌握 Lambda 是现代 Java 开发的必备技能。