Lambda表达式

什么是Lambda表达式

一句话理解:Lambda就是匿名函数的简写,让你不用写一大堆匿名内部类的模板代码,用一行就能搞定

打个比方:以前点外卖要填一堆表格(匿名内部类),现在直接语音下单(Lambda)

核心前提:Lambda只能用在函数式接口上(接口里只有一个抽象方法),详见 方法引用与函数式接口

Lambda语法

完整语法:(参数类型 参数名) -> { 方法体; return 返回值; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testLambdaSyntax() {
System.out.println("=== Lambda语法演进 ===");

// 1. 完整写法
Comparator<Integer> comp1 = (Integer a, Integer b) -> { return a - b; };

// 2. 省略参数类型(编译器能推断出来)
Comparator<Integer> comp2 = (a, b) -> { return a - b; };

// 3. 方法体只有一行,省略大括号和return
Comparator<Integer> comp3 = (a, b) -> a - b;

// 4. 只有一个参数,连小括号都能省
Function<String, Integer> strLen = s -> s.length();

// 5. 无参数的情况
Runnable run = () -> System.out.println("Hello Lambda!");

System.out.println("排序结果:" + comp3.compare(3, 5)); // -2
System.out.println("字符串长度:" + strLen.apply("Java")); // 4
run.run(); // Hello Lambda!
}

简化规则速查

场景 写法 示例
完整写法 (类型 参数) -> { 语句; } (Integer a) -> { return a * 2; }
省略参数类型 (参数) -> { 语句; } (a) -> { return a * 2; }
单个参数省略括号 参数 -> { 语句; } a -> { return a * 2; }
单行省略大括号和return 参数 -> 表达式 a -> a * 2
无参数 () -> 表达式 () -> System.out.println("hi")

函数式接口与@FunctionalInterface

函数式接口就是只有一个抽象方法的接口,Lambda本质上就是这个接口的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testFunctionalInterface() {
System.out.println("=== 函数式接口 ===");

// 自定义函数式接口
// @FunctionalInterface 注解不是必须的,但加上后编译器会帮你检查
// 如果接口里有两个抽象方法,加了这个注解就会编译报错
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}

// 用Lambda实现
MathOperation add = (a, b) -> a + b;
MathOperation multiply = (a, b) -> a * b;

System.out.println("3 + 5 = " + add.operate(3, 5)); // 8
System.out.println("3 * 5 = " + multiply.operate(3, 5)); // 15
}

JDK自带的常用函数式接口在 方法引用与函数式接口 里详细讲

注意:接口里可以有default方法和static方法,它们不算抽象方法,不影响函数式接口的判定

变量捕获(effectively final)

Lambda可以访问外部的局部变量,但不能修改它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testVariableCapture() {
System.out.println("=== 变量捕获 ===");

String greeting = "Hello"; // 这个变量后面没有被修改过,是 effectively final
int count = 10;

// ✅ 可以读取外部变量
Runnable r1 = () -> System.out.println(greeting + ", count=" + count);
r1.run(); // Hello, count=10

// ❌ 不能修改外部变量!下面这样写会编译报错
// Runnable r2 = () -> count++; // 报错:Variable used in lambda should be final or effectively final

// ✅ 如果真的需要修改,用数组或AtomicInteger包装一下
int[] counter = {0};
Runnable r3 = () -> counter[0]++;
r3.run();
System.out.println("counter = " + counter[0]); // 1
}

为什么不让改? 因为Lambda可能在另一个线程里执行,如果允许修改局部变量,会出现线程安全问题。Java选择了最简单粗暴的方案:直接不让你改

Lambda vs 匿名内部类

对比项 匿名内部类 Lambda
语法 啰嗦,要写 new 接口名() { ... } 简洁,(参数) -> 表达式
适用范围 任何接口或抽象类 只能用于函数式接口(一个抽象方法)
this 指向 指向匿名内部类自身 指向外层类(Lambda没有自己的this)
编译结果 生成额外的 .class 文件 invokedynamic 指令,不生成额外类
性能 每次创建新对象 通常更优(JVM优化)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testLambdaVsAnonymous() {
System.out.println("=== Lambda vs 匿名内部类 ===");

// 匿名内部类写法(Java 8之前只能这么写)
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类");
}
};

// Lambda写法(清爽多了)
Runnable r2 = () -> System.out.println("Lambda");

r1.run();
r2.run();
}

实战代码示例

排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testSort() {
System.out.println("=== Lambda排序 ===");

List<String> names = Arrays.asList("Charlie", "Alice", "Bob");

// 按字母排序
names.sort((a, b) -> a.compareTo(b));
System.out.println("字母排序:" + names); // [Alice, Bob, Charlie]

// 按长度排序
names.sort((a, b) -> a.length() - b.length());
System.out.println("长度排序:" + names); // [Bob, Alice, Charlie]

// 还可以用方法引用进一步简化,详见 [方法引用与函数式接口](/2026/04/04/方法引用与函数式接口/)
names.sort(String::compareTo);
}

线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testThread() {
System.out.println("=== Lambda创建线程 ===");

// 以前的写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("老写法线程");
}
}).start();

// Lambda写法
new Thread(() -> System.out.println("Lambda线程")).start();
}

集合遍历和过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testCollectionOps() {
System.out.println("=== Lambda集合操作 ===");

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// forEach遍历
numbers.forEach(n -> System.out.print(n + " "));
System.out.println();

// removeIf 删除满足条件的元素
List<Integer> list = new ArrayList<>(numbers);
list.removeIf(n -> n % 2 == 0); // 删除偶数
System.out.println("删除偶数后:" + list); // [1, 3, 5, 7, 9]

// 配合 [Stream API](/2026/04/04/Stream API/) 更强大
numbers.stream()
.filter(n -> n > 5)
.forEach(n -> System.out.print(n + " ")); // 6 7 8 9 10
}

常见坑

坑1:Lambda不能用在有多个抽象方法的接口上

1
2
3
4
5
6
// ❌ 这个接口有两个抽象方法,不是函数式接口
interface TwoMethods {
void method1();
void method2();
}
// TwoMethods t = () -> {}; // 编译报错!

坑2:Lambda里的this不是指Lambda自身

1
2
// Lambda里的this指向外部类,不像匿名内部类指向自身
// 如果你需要引用"自身"对象,只能用匿名内部类

坑3:不要在Lambda里做太复杂的逻辑

Lambda的优势是简洁,如果逻辑超过3行,建议提取成方法再用方法引用

坑4:Lambda和泛型擦除

Lambda无法直接推断交叉类型,遇到重载方法时可能需要显式强转

练习题

题1:用Lambda实现一个字符串列表按长度倒序排序

提示:(a, b) -> b.length() - a.length()

题2:自定义一个函数式接口 StringProcessor,包含方法 String process(String input),用Lambda分别实现:转大写、加前缀、截取前3个字符

题3:用Lambda + Collections.sort() 对一个 List<int[]> 按数组第二个元素排序

题4:下面代码能编译通过吗?为什么?

1
2
3
4
int num = 10;
Runnable r = () -> System.out.println(num);
num = 20;
r.run();

答案:不能编译通过。num 在Lambda之后被修改了,不再是 effectively final


上一章 目录 下一章
IO流 java基础 [Stream API](/2026/04/04/Stream API/)