【JDK8新特性】Lambda表达式Day1
写在前面:这是JDK8新特性系列的第一篇。JDK8是Java历史上最重要的版本之一,Lambda表达式和Stream API的引入彻底改变了Java的编程范式。对于Java基础语法想必大家已经很熟悉了,然而Java很多好用,相对复杂的语法集中在JDK8的更新中。这个系列我会把JDK8的所有新特性讲透,建议收藏。

文章目录
一、为什么需要Lambda表达式?
实际场景:假设你要对一个List进行排序,在JDK8之前你需要这样写:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// JDK8之前的写法:匿名内部类
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
这段代码的问题:
- 代码冗长:为了一个简单的比较逻辑,写了6行代码
- 样板代码多:
new Comparator<String>()、@Override、public int compare都是样板 - 可读性差:核心逻辑(
a.length() - b.length())被淹没在样板代码中
Lambda表达式让代码变得简洁:
// JDK8的写法:Lambda表达式
Collections.sort(names, (a, b) -> a.length() - b.length());
// 或者更简洁
names.sort((a, b) -> a.length() - b.length());
// 使用方法引用
names.sort(Comparator.comparingInt(String::length));
经验之谈:Lambda表达式不只是语法糖,它代表了一种函数式编程的思想。学会用Lambda,你的代码会变得更简洁、更易读、更易维护。
二、Lambda表达式基础
2.1 Lambda表达式的语法
// 基本语法:(参数列表) -> { 方法体 }
// 1. 无参数,无返回值
Runnable runnable = () -> System.out.println("Hello Lambda");
// 2. 一个参数,可以省略括号
Consumer<String> consumer = s -> System.out.println(s);
// 3. 多个参数
Comparator<String> comparator = (a, b) -> a.compareTo(b);
// 4. 有返回值,方法体只有一行,可以省略return和大括号
Function<String, Integer> function = s -> s.length();
// 5. 方法体有多行,需要用大括号
Comparator<String> comparator2 = (a, b) -> {
int lenDiff = a.length() - b.length();
if (lenDiff != 0) {
return lenDiff;
}
return a.compareTo(b);
};
2.2 Lambda表达式的类型推断
踩坑提醒:Lambda表达式的类型是由上下文推断的,如果上下文不明确,编译会报错。
// ❌ 错误:类型不明确
// var lambda = (String s) -> s.length(); // 编译错误
// ✅ 正确:通过赋值给函数式接口明确类型
Function<String, Integer> func = (String s) -> s.length();
// ✅ 正确:通过方法参数明确类型
public void process(Function<String, Integer> func) {
func.apply("test");
}
process((String s) -> s.length());
2.3 参数列表的简化规则
// 1. 参数类型可以省略(编译器自动推断)
Function<String, Integer> f1 = (String s) -> s.length(); // 完整写法
Function<String, Integer> f2 = (s) -> s.length(); // 省略类型
Function<String, Integer> f3 = s -> s.length(); // 单个参数省略括号
// 2. 多个参数不能省略括号
BinaryOperator<Integer> add = (a, b) -> a + b; // 不能写成 a, b -> a + b
// 3. 参数可以加final修饰(但通常省略)
Function<String, Integer> f4 = (final String s) -> s.length();
三、Lambda表达式的使用场景
3.1 替代匿名内部类
场景1:Runnable线程
// 传统写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
}).start();
// Lambda写法
new Thread(() -> System.out.println("Hello")).start();
场景2:Comparator排序
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
// 传统写法
numbers.sort(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return b - a; // 降序
}
});
// Lambda写法
numbers.sort((a, b) -> b - a);
// 方法引用
numbers.sort(Comparator.reverseOrder());
场景3:事件监听
// Swing/JavaFX中的按钮点击
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked");
}
});
// Lambda写法
button.addActionListener(e -> System.out.println("Button clicked"));
3.2 函数式接口作为参数
// 自定义函数式接口
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
// 使用Lambda作为参数
public void execute(Calculator calculator, int a, int b) {
int result = calculator.calculate(a, b);
System.out.println("Result: " + result);
}
// 调用
execute((a, b) -> a + b, 3, 5); // 加法
execute((a, b) -> a * b, 3, 5); // 乘法
execute((a, b) -> Math.max(a, b), 3, 5); // 取最大值
四、Lambda表达式与闭包
4.1 变量捕获规则
踩坑提醒:Lambda表达式可以访问外部变量,但有严格限制。
public class ClosureExample {
public void demo() {
int localVar = 10; // 局部变量
final int finalVar = 20; // final变量
// Lambda可以捕获final或 effectively final 的局部变量
Runnable runnable = () -> {
System.out.println(localVar); // ✅ OK
System.out.println(finalVar); // ✅ OK
// localVar = 30; // ❌ 编译错误:不能修改
};
// localVar = 15; // ❌ 如果修改,上面的Lambda会编译错误
runnable.run();
}
}
规则:
- Lambda可以访问
final局部变量 - Lambda可以访问effectively final(事实上的final)局部变量
- Lambda不能修改局部变量(只能读取)
- Lambda可以读写实例变量和静态变量
4.2 this关键字的指向
public class ThisExample {
private String name = "Outer";
public void demo() {
// 匿名内部类中的this指向匿名类实例
Runnable anonymous = new Runnable() {
private String name = "Anonymous";
@Override
public void run() {
System.out.println(this.name); // 输出: Anonymous
}
};
// Lambda中的this指向外部类实例
Runnable lambda = () -> {
System.out.println(this.name); // 输出: Outer
};
anonymous.run();
lambda.run();
}
}
经验之谈:Lambda表达式不创建新的作用域,所以this指向外部类。这是Lambda和匿名内部类的重要区别。
五、Lambda表达式的性能
5.1 编译后的字节码
Lambda表达式在编译后不会生成匿名内部类,而是使用invokedynamic指令,在运行时动态生成实现类。
// 源代码
Runnable r = () -> System.out.println("Hello");
// 编译后的伪代码(简化)
// 使用invokedynamic调用LambdaMetafactory.metafactory()
// 运行时生成实现类,避免编译时生成大量.class文件
5.2 性能对比
| 特性 | 匿名内部类 | Lambda表达式 |
|---|---|---|
| 类加载 | 编译时生成.class文件 | 运行时动态生成 |
| 启动性能 | 稍慢(需要加载更多类) | 稍快 |
| 运行性能 | 基本相同 | 基本相同 |
| 内存占用 | 每个实例一个类 | 可以共享实例 |
经验之谈:在绝大多数场景下,Lambda和匿名内部类的性能差异可以忽略不计。选择Lambda的主要理由是代码简洁和可读性。
六、常见错误与最佳实践
6.1 常见错误
错误1:在Lambda中修改局部变量
int count = 0;
list.forEach(item -> {
// count++; // ❌ 编译错误
});
错误2:Lambda返回类型不匹配
// ❌ 错误:返回类型不匹配
// Function<String, Integer> func = s -> s; // String不能赋值给Integer
// ✅ 正确
Function<String, Integer> func = s -> s.length();
错误3:过度使用Lambda
// ❌ 过度使用,可读性差
list.stream()
.filter(s -> s.length() > 5)
.map(s -> s.toUpperCase())
.sorted((a, b) -> b.compareTo(a))
.collect(Collectors.toList());
// ✅ 适当提取,提高可读性
list.stream()
.filter(this::isLongEnough)
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
6.2 最佳实践
- 保持Lambda简短:如果Lambda超过3行,考虑提取为方法引用或独立方法
- 使用方法引用:当Lambda只是调用现有方法时,用方法引用更简洁
- 避免副作用:Lambda应该是纯函数,不要修改外部状态
- 注意异常处理:Lambda中抛出的受检异常需要处理
七、面试高频考点
考点1:Lambda表达式和匿名内部类的区别?
答案:
- this指向不同:Lambda的this指向外部类,匿名内部类的this指向自身
- 作用域不同:Lambda不创建新作用域,匿名内部类创建新作用域
- 编译机制不同:Lambda用invokedynamic,匿名内部类编译时生成.class文件
- 性能:Lambda启动稍快,运行性能基本相同
考点2:Lambda表达式可以修改局部变量吗?
答案:不能。Lambda只能访问final或effectively final的局部变量,不能修改。但可以读写实例变量和静态变量。
考点3:什么是函数式接口?
答案:只有一个抽象方法的接口。可以用@FunctionalInterface注解标识。JDK8提供了大量内置函数式接口,如Runnable、Comparator、Function、Consumer、Supplier等。
追问:为什么Lambda只能赋值给函数式接口?
答案:因为Lambda表达式本质上是一个匿名函数,需要有一个目标类型来承载。函数式接口只有一个抽象方法,正好匹配Lambda的签名。
八、总结
今天我们学习了:
- ✅ Lambda表达式的语法和类型推断
- ✅ Lambda替代匿名内部类的各种场景
- ✅ Lambda的变量捕获规则和this指向
- ✅ Lambda的性能特点和最佳实践
重点记忆:
- Lambda语法:
(参数) -> { 方法体 } - 只能赋值给函数式接口
- 不能修改局部变量,只能读取final或effectively final变量
- this指向外部类实例
下一步预告:
Day2我们将学习函数式接口,包括JDK8提供的四大核心函数式接口(Function、Consumer、Supplier、Predicate),以及如何自定义函数式接口。
参考资料
互动话题:你在项目中使用Lambda表达式遇到过什么坑?是变量捕获的问题,还是可读性的争议?欢迎在评论区分享你的经验!
如果这篇文章对你有帮助,欢迎点赞、收藏!这是【JDK8新特性全面教学】系列的第一篇,关注我看完整套教程 👇
本文为【JDK8新特性】系列第1篇,持续更新中…
更多推荐
所有评论(0)