什么是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语法演进 ===");
Comparator<Integer> comp1 = (Integer a, Integer b) -> { return a - b; };
Comparator<Integer> comp2 = (a, b) -> { return a - b; };
Comparator<Integer> comp3 = (a, b) -> a - b;
Function<String, Integer> strLen = s -> s.length();
Runnable run = () -> System.out.println("Hello Lambda!");
System.out.println("排序结果:" + comp3.compare(3, 5)); System.out.println("字符串长度:" + strLen.apply("Java")); run.run(); }
|
简化规则速查
| 场景 |
写法 |
示例 |
| 完整写法 |
(类型 参数) -> { 语句; } |
(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 interface MathOperation { int operate(int a, int b); }
MathOperation add = (a, b) -> a + b; MathOperation multiply = (a, b) -> a * b;
System.out.println("3 + 5 = " + add.operate(3, 5)); System.out.println("3 * 5 = " + multiply.operate(3, 5)); }
|
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"; int count = 10;
Runnable r1 = () -> System.out.println(greeting + ", count=" + count); r1.run();
int[] counter = {0}; Runnable r3 = () -> counter[0]++; r3.run(); System.out.println("counter = " + counter[0]); }
|
为什么不让改? 因为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 匿名内部类 ===");
Runnable r1 = new Runnable() { @Override public void run() { System.out.println("匿名内部类"); } };
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);
names.sort((a, b) -> a.length() - b.length()); System.out.println("长度排序:" + names);
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();
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);
numbers.forEach(n -> System.out.print(n + " ")); System.out.println();
List<Integer> list = new ArrayList<>(numbers); list.removeIf(n -> n % 2 == 0); System.out.println("删除偶数后:" + list);
numbers.stream() .filter(n -> n > 5) .forEach(n -> System.out.print(n + " ")); }
|
常见坑
坑1:Lambda不能用在有多个抽象方法的接口上
1 2 3 4 5 6
| interface TwoMethods { void method1(); void method2(); }
|
坑2:Lambda里的this不是指Lambda自身
坑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/) |