Java Lambda 表达式:集合和日期处理必备指南
Java 8 的 lambda 表达式用了就回不去了。尤其是集合遍历和日期处理这两个场景,代码量直接砍半。这篇整理的是日常开发里出现频率最高的用法。
什么是 Lambda 表达式
本质上就是一个匿名函数,可以当参数传给方法。它的价值在于把"行为"当数据来用,代码不再被一堆匿名内部类撑得臃肿。
基本语法
(parameters) -> { function body }看个对比就明白了:
// 使用匿名类的传统方式
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Hello World");
}
};
// Lambda 方式(更简洁)
Runnable r2 = () -> System.out.println("Hello World");使用 Lambda 遍历集合
遍历数组
String[] names = {"Ana", "Carlos", "Beatriz", "David"};
// 使用 forEach 和 lambda 遍历
Arrays.stream(names)
.forEach(name -> System.out.println("Hello " + name));
// 或使用方法引用
Arrays.stream(names)
.forEach(System.out::println);遍历列表
列表大概是 lambda 用得最多的地方。基本遍历、带索引、过滤——全都能用 Stream 写得干净利落。
List<String> fruits = Arrays.asList("Apple", "Banana", "Orange", "Strawberry");
// 基本遍历
fruits.forEach(fruit -> System.out.println("I like " + fruit));
// 带位置编号的遍历
IntStream.range(0, fruits.size())
.forEach(i -> System.out.println(i + ": " + fruits.get(i)));
// 过滤和遍历
fruits.stream()
.filter(fruit -> fruit.length() > 6)
.forEach(fruit -> System.out.println("Long fruit: " + fruit));遍历映射
Map 的 forEach 接受一个 BiConsumer,键值对同时拿到,写起来比 entrySet() 迭代舒服多了。
Map<String, Integer> ages = new HashMap<>();
ages.put("Ana", 25);
ages.put("Carlos", 32);
ages.put("Beatriz", 28);
// 遍历条目集(键值对)
ages.forEach((name, age) ->
System.out.println(name + " is " + age + " years old")
);
// 仅遍历键
ages.keySet().forEach(key ->
System.out.println("Person: " + key)
);
// 仅遍历值
ages.values().forEach(value ->
System.out.println("Age: " + value)
);使用 Lambda 处理日期
Java 8 的 java.time API 和 lambda 配合得很好。LocalDate、LocalTime、LocalDateTime 配上 Stream,过滤、排序、分组都写得很自然。
处理 LocalDate
LocalDate 只有日期,没有时间。
// 创建日期
LocalDate today = LocalDate.now();
LocalDate christmas = LocalDate.of(2025, 12, 25);
// 使用流操作日期
List<LocalDate> importantDates = Arrays.asList(
LocalDate.of(2025, 1, 1),
LocalDate.of(2025, 5, 10),
LocalDate.of(2025, 9, 16),
LocalDate.of(2025, 12, 25)
);
// 过滤未来日期
importantDates.stream()
.filter(date -> date.isAfter(today))
.forEach(date -> System.out.println("Future date: " + date));
// 格式化日期
importantDates.forEach(date ->
System.out.println(date.format(DateTimeFormatter.ofPattern("MMMM dd, yyyy")))
);处理 LocalTime
LocalTime 只有时间,没有日期。
// 创建时间
LocalTime now = LocalTime.now();
LocalTime lunch = LocalTime.of(14, 30);
// 一天中时间的列表
List<LocalTime> times = Arrays.asList(
LocalTime.of(8, 0), // 工作开始
LocalTime.of(12, 0), // 午餐
LocalTime.of(14, 30), // 午餐后返回
LocalTime.of(18, 0) // 工作结束
);
// 过滤上午时间
times.stream()
.filter(time -> time.isBefore(lunch))
.forEach(time -> System.out.println("Morning: " + time));
// 转换为一天中的分钟数
times.forEach(time ->
System.out.println("Minutes of day: " + time.toSecondOfDay() / 60)
);处理 LocalDateTime
LocalDateTime 是日期加时间。
// 创建带时间的日期
LocalDateTime nowFull = LocalDateTime.now();
LocalDateTime meeting = LocalDateTime.of(2025, 11, 15, 10, 30);
// 事件列表
List<LocalDateTime> events = Arrays.asList(
LocalDateTime.of(2025, 11, 8, 9, 0), // 今天,上午会议
LocalDateTime.of(2025, 11, 15, 10, 30), // 下次会议
LocalDateTime.of(2025, 12, 25, 0, 0) // 圣诞节
);
// 过滤当月事件
events.stream()
.filter(event -> event.getMonth() == nowFull.getMonth())
.forEach(event -> System.out.println("Event this month: " + event));
// 按日期排序事件
events.stream()
.sorted()
.forEach(event -> System.out.println(
"Event: " + event.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm"))
));实用组合示例
处理带日期的人列表
集合遍历加日期计算,日常开发里经常组合使用。这里用 Period.between 算年龄,再过滤成年人。
// Person 类
class Person {
private String name;
private LocalDate birthDate;
// 构造函数、getter...
}
List<Person> people = Arrays.asList(
new Person("Ana", LocalDate.of(1995, 6, 15)),
new Person("Carlos", LocalDate.of(1990, 12, 3)),
new Person("Beatriz", LocalDate.of(1998, 8, 20))
);
// 计算年龄并过滤成年人
LocalDate today = LocalDate.now();
people.stream()
.filter(p -> Period.between(p.getBirthDate(), today).getYears() >= 18)
.forEach(p -> {
int age = Period.between(p.getBirthDate(), today).getYears();
System.out.println(p.getName() + " is " + age + " years old");
});按月份分组日期
Collectors.groupingBy 是 Stream API 里最实用的收集器之一,配合方法引用一行就能按属性分组。
// 使用 lambda 按月份分组日期
List<LocalDate> dates = Arrays.asList(
LocalDate.of(2025, 1, 15),
LocalDate.of(2025, 1, 20),
LocalDate.of(2025, 2, 10),
LocalDate.of(2025, 2, 25),
LocalDate.of(2025, 3, 5)
);
Map<Month, List<LocalDate>> datesByMonth = dates.stream()
.collect(Collectors.groupingBy(LocalDate::getMonth));
datesByMonth.forEach((month, dateList) -> {
System.out.println("Month: " + month);
dateList.forEach(date ->
System.out.println(" - " + date.format(DateTimeFormatter.ofPattern("MM/dd")))
);
});最佳实践
方法引用
当 lambda 体只是调用一个已有方法时,方法引用比 lambda 更简洁。System.out::println 比 name -> System.out.println(name) 短,而且更易读。
// 而不是:name -> System.out.println(name)
names.forEach(System.out::println);
// 而不是:date -> date.getMonth()
dates.stream().map(LocalDate::getMonth).collect(Collectors.toList());
// 而不是:(String s) -> s.length()
words.stream().mapToInt(String::length).sum();避免副作用
Stream 操作应该是无状态的。在 forEach 里修改外部集合看起来能跑,但并发场景下会出问题,而且破坏了 Stream 的声明式语义。
// ✅ 好:纯 lambda
List<Integer> doubled = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
// ❌ 避免:带副作用的 lambda
List<Integer> result = new ArrayList<>();
numbers.forEach(n -> {
n = n * 2;
result.add(n); // 修改外部状态
});异常处理
lambda 里的受检异常很烦。标准函数式接口(Consumer、Function 等)的签名不允许抛出受检异常,所以要么在 lambda 内部 try-catch,要么抽个辅助方法包装。
// ✅ 在内部使用 try-catch
dates.forEach(date -> {
try {
// 可能抛出异常的代码
LocalDate.parse(date.toString());
} catch (DateTimeParseException e) {
System.err.println("Invalid date: " + date);
}
});
// ✅ 创建辅助方法
private static void processDate(LocalDate date) throws DateTimeParseException {
// 可能抛出异常的代码
}
dates.forEach(LambdaGuide::processDate); // 包装异常结论
lambda 不只是语法糖。它改变了写 Java 代码的思维方式——从命令式的"怎么做"变成声明式的"做什么"。集合遍历和日期处理是入门最快的两个场景,先把这两个吃透,再去碰 flatMap、reduce、collect 这些更复杂的操作。
参考 Oracle 官方的 Lambda 表达式和 Stream API 教程。