Lambda 表达式
概述
Lambda 表达式(Lambda Expression)是 JDK 8 引入的语法特性,用于简洁地表示可传递的匿名函数。它允许把一段代码(行为)当作参数传递,从而替代冗长的匿名内部类,让代码更简洁、可读性更好,并为 Stream API 和函数式编程风格打下基础。
通俗理解:Lambda 就是「匿名函数」的简写——不写方法名、不写 return 和花括号(在简单形式下),只保留「参数 + 箭头 + 表达式」,让「做什么」一目了然。
为什么需要 Lambda
在 JDK 8 之前,若要传递「一段逻辑」,通常需要写匿名内部类,代码冗长且把真正的逻辑埋在样板代码里:
// 匿名内部类:只关心「打印一句话」,却要写一整套类结构
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, Lambda!");
}
};
new Thread(r).start();用 Lambda 改写后,意图更清晰:
// Lambda:直接表达「无参、执行一句打印」
Runnable r = () -> System.out.println("Hello, Lambda!");
new Thread(r).start();Lambda 带来的好处:
- 简洁:减少样板代码,突出核心逻辑。
- 可读:更接近「行为描述」而非「类型描述」。
- 与 API 结合:与 Stream API、Optional 以及集合的
forEach等配合自然。
函数式接口
Lambda 不能单独存在,必须赋值给或传递给一个「函数式接口」类型的变量或形参。
函数式接口(Functional Interface)是指:只包含一个抽象方法的接口(可以有多个默认方法或静态方法)。JDK 8 用 @FunctionalInterface 标注,编译器会检查是否真的只有一个抽象方法。
常见函数式接口示例:
| 接口 | 抽象方法 | 典型用途 |
|---|---|---|
Runnable | void run() | 无参、无返回值 |
Supplier<T> | T get() | 无参、返回 T |
Consumer<T> | void accept(T t) | 接收一个 T,无返回值 |
Function<T, R> | R apply(T t) | 接收 T,返回 R |
Predicate<T> | boolean test(T t) | 接收 T,返回 boolean |
Comparator<T> | int compare(T a, T b) | 比较两个 T |
自定义函数式接口示例:
@FunctionalInterface
public interface Greeter {
void say(String message); // 仅此一个抽象方法
}
// 使用 Lambda 实现
Greeter g = msg -> System.out.println("Hi, " + msg);
g.say("Java"); // 输出: Hi, Java提示
@FunctionalInterface 不是必须的,但加上后编译器会帮你检查「只有一个抽象方法」,避免误加第二个抽象方法导致 Lambda 无法使用。
Lambda 语法
基本形式
( 参数列表 ) -> 表达式或语句块- 参数列表:与函数式接口中抽象方法的参数一致;只有一个参数时可省略括号。
- 箭头:
->,读作「变成」「得到」。 - 右侧:
- 表达式:直接写表达式,其值即为返回值(无需写
return)。 - 语句块:用
{ }包住多条语句;若有返回值,需写return。
- 表达式:直接写表达式,其值即为返回值(无需写
常见写法对比
// 无参
Runnable r1 = () -> System.out.println("run");
// 单参可省括号
Consumer<String> c = s -> System.out.println(s);
// 多参必须写括号
Comparator<String> cmp = (a, b) -> a.compareTo(b);
// 表达式体:自动返回结果
Function<String, Integer> len = s -> s.length();
// 语句块:多条语句或需要 return
Function<String, Integer> len2 = s -> {
if (s == null) return 0;
return s.length();
};使用示例
示例 1:Runnable 与线程
// 传统匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
// Lambda
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();示例 2:Comparator 与排序
import java.util.*;
List<String> list = Arrays.asList("banana", "apple", "cherry");
// 按字符串长度排序
list.sort((a, b) -> a.length() - b.length());
// 按字典序
list.sort((a, b) -> a.compareTo(b));
// 等价于 Comparator 的静态方法(方法引用,后续可深入)
list.sort(Comparator.comparing(String::length));示例 3:集合 forEach 与 Predicate
import java.util.*;
List<String> names = Arrays.asList("Tom", "Jerry", "Spike");
// forEach 接受 Consumer<String>
names.forEach(name -> System.out.println("Hello, " + name));
// 过滤:用 Predicate 表示「是否保留」
names.stream()
.filter(name -> name.length() > 3)
.forEach(System.out::println); // 仅 Jerry, Spike变量捕获(闭包)
Lambda 表达式中可以使用外层作用域的变量,但该变量必须是 effectively final(事实上的 final):即要么用 final 修饰,要么在赋值后不再被修改。
int base = 10;
// base 在 Lambda 之后不再被修改,即 effectively final
Function<Integer, Integer> addBase = x -> x + base;
System.out.println(addBase.apply(5)); // 15注意
在 Lambda 内不能修改捕获的局部变量(例如 base++),否则编译错误。若需要可变状态,应使用数组或对象引用等,或考虑其他设计。
// 错误示例:在 Lambda 中修改捕获的变量
int count = 0;
Runnable r = () -> count++; // 编译错误:Variable used in lambda should be final or effectively final注意事项
- 类型可推断:多数情况下编译器能根据目标类型(函数式接口)推断参数类型,无需写
(String s),写(s)或s即可。 - 仅用于函数式接口:Lambda 的类型由「目标类型」决定,只能用在需要单个抽象方法接口的地方。
- 与匿名内部类的区别:Lambda 不是匿名内部类的语法糖在所有层面都等价(如
this的语义、作用域),但在「实现一个函数式接口」的常见用法下,可以认为 Lambda 是更简洁的写法。
提示
需要「带名字」的复用逻辑时,仍可写普通方法;若该方法的签名与某函数式接口的抽象方法一致,可用方法引用(如 String::length、System.out::println)代替 Lambda,使代码更简洁。详见 Stream API 中的方法引用用法。
相关链接
- Stream API — Lambda 与 Stream 结合进行集合处理
- Optional — 常用函数式接口与 Lambda 的典型用法
- 接口 — 函数式接口的基础
- Oracle Java 教程 - Lambda 表达式