Lambda 表达式对比
Lambda 不是凭空出现的。在它之前,Java 有匿名内部类;有了 Lambda 之后,方法引用又比 Lambda 更简洁。它们三个解决了同一个问题,但各有各的适用场景。
搞清楚它们的区别,才能写出不炫技、只实用的代码。
三种写法的对照
同一个目标,三种写法:
java
// 写法一:匿名内部类
Comparator<String> byLength1 = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
};
// 写法二:Lambda 表达式
Comparator<String> byLength2 = (a, b) ->
Integer.compare(a.length(), b.length());
// 写法三:方法引用
Comparator<String> byLength3 =
Comparator.comparingInt(String::length);从左到右,代码越来越短,也越来越"专有"——方法引用只适用于你能找到现成方法的时候。
语法复杂度与可读性
| 维度 | 匿名内部类 | Lambda | 方法引用 |
|---|---|---|---|
| 语法复杂度 | 高(冗长) | 中 | 低(极简) |
| 可读性 | 较差(结构复杂) | 好(逻辑清晰) | 最好(一目了然) |
| 性能 | 相同 | 相同 | 相同 |
| 灵活性 | 高(可多方法) | 中(单方法) | 低(必须有对应方法) |
性能相同这一点很重要:三者最终都会被编译成字节码,运行时没有任何区别。选择哪个,只关乎代码的可读性。
匿名内部类的独有价值
Lambda 能简化匿名内部类,但有些场景匿名内部类仍然不可替代:
场景一:需要引用 this
java
public class MyService {
private String name = "MyService";
public Runnable createTask() {
// Lambda — this 指向外层类
Runnable lambda = () -> System.out.println(this.name);
// 匿名内部类 — this 指向匿名类自己
Runnable anonymous = new Runnable() {
@Override
public void run() {
System.out.println(this); // 输出: com.example.MyService$1
}
};
return anonymous;
}
}场景二:需要多个方法
java
// 需要状态或多个方法的场景,Lambda 无能为力
Handler handler = new Handler() {
private int count = 0;
@Override
public void onSuccess() {
count++;
notifyListeners();
}
@Override
public void onError(Exception e) {
count--;
log.error("Error", e);
}
};场景三:需要继承其他类
java
// 匿名内部类可以继承抽象类或实现带有默认方法的接口
abstract class AbstractTask {
abstract void execute();
void log() { System.out.println("logging"); }
}
AbstractTask task = new AbstractTask() {
@Override
void execute() {
log();
// 自己的逻辑
}
};Lambda 与方法引用的选择
当 Lambda 的方法体只是一个现有方法的调用时,直接换成方法引用:
java
// ✅ 改前:Lambda
list.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
// ✅ 改后:方法引用
list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// ❌ 不能改:方法不存在或需要额外处理
list.stream()
.map(s -> s.trim().toUpperCase()) // 两步操作,方法引用不行
.collect(Collectors.toList());
// ❌ 不能改:参数需要转换
list.stream()
.map(s -> Integer.parseInt(s)) // 不能写成 Integer::parseInt
.collect(Collectors.toList());四种方法引用详解
java
import java.util.function.*;
import java.util.*;
// 类型一:静态方法
// Integer.parseInt("123") → Integer::parseInt
Function<String, Integer> parser = Integer::parseInt;
parser.apply("123"); // 123
// 类型二:实例方法(任意对象)
// s.toUpperCase(),s 不固定 → String::toUpperCase
Function<String, String> upper = String::toUpperCase;
upper.apply("hello"); // HELLO
// 类型三:实例方法(特定对象)
String str = "hello";
// str.length(),str 固定 → str::length
Supplier<Integer> len = str::length;
len.get(); // 5
// 类型四:构造方法
// new ArrayList<>() → ArrayList::new
Supplier<ArrayList<String>> factory = ArrayList::new;
factory.get(); // []
// 构造方法(带参数)
Function<Integer, String[]> arrayFactory = String[]::new;
arrayFactory.apply(5); // String[5]什么时候用哪种
能静态 → 用静态方法引用
有现成实例方法 → 用实例方法引用
创建对象 → 用构造方法引用实战对比
场景一:过滤并收集
java
List<String> names = Arrays.asList("Tom", "Jerry", "Mike", "Anna");
// 三种方式结果一样
// 匿名内部类(啰嗦)
List<String> result1 = names.stream()
.filter(new Predicate<String>() {
@Override
public boolean test(String s) { return s.length() > 3; }
})
.collect(Collectors.toList());
// Lambda(推荐)
List<String> result2 = names.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// 方法引用(适用时最强)
List<String> result3 = names.stream()
.filter(((Predicate<String>) String::isEmpty).negate())
.filter(s -> s.length() > 3) // 这里必须用 Lambda
.collect(Collectors.toList());场景二:按条件分组
java
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));Person::getCity 是类型二的实例方法引用——对每个 Person 对象调用 getCity() 方法。
场景三:自定义排序
java
// 按年龄倒序
people.sort(Comparator.comparingInt(Person::getAge).reversed());
// 多条件排序
people.sort(Comparator
.comparing(Person::getCity)
.thenComparingInt(Person::getAge)
.reversed());性能注意事项
虽然三者运行时性能相同,但有一个细节值得注意:
java
// ❌ 每次调用都创建新的 lambda 对象
for (int i = 0; i < 1000; i++) {
list.forEach(s -> System.out.println(s)); // 1000 个 lambda 实例
}
// ✅ 方法引用在某些 JIT 优化下表现更好
for (int i = 0; i < 1000; i++) {
list.forEach(System.out::println); // JIT 可能内联优化
}这不是性能优化的银弹,但养成"能用方法引用就不用 Lambda"的习惯没有坏处。
小结
没有银弹。选择原则如下:
- 有现成方法可用 → 方法引用
- 单行简单逻辑 → Lambda
- 需要状态或多个方法 → 匿名内部类
- 需要
this引用 → 匿名内部类
最终目标:代码意图一目了然。方法引用最清晰,Lambda 次之,匿名内部类只在必要时使用。
