Skip to content

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 次之,匿名内部类只在必要时使用。

基于 VitePress 构建