Java Lambda 表达式:集合和日期处理必备指南
Lambda 表达式是 Java 8 中引入的最重要特性之一。它们允许我们编写更简洁和可读的代码,特别是在处理集合和日期时。本指南将帮助您理解和使用日常开发中最常见的 lambda 表达式。
什么是 Lambda 表达式
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 遍历集合
遍历数组
数组可以使用 lambda 和 Streams 优雅地遍历:
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 表达式最频繁的地方:
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));遍历映射
映射也从 lambda 中获益匪浅:
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 中引入的日期和时间 API 与 lambda 完美配合。
处理 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"))
));实用组合示例
处理带日期的人列表
// 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");
});按月份分组日期
// 使用 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 仅调用现有方法时,我们可以使用方法引用:
// 而不是: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();避免副作用
Lambda 在可能的情况下应该是纯的:
// ✅ 好:纯 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 不能直接抛出受检异常:
// ✅ 在内部使用 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 代码的方式,使其更加简洁、可读和函数式。掌握它们在集合和日期上的使用对于任何现代 Java 开发者都是必不可少的。
请记住逐步练习这些概念,从简单示例开始,逐步进阶到更复杂的情况。Lambda 不仅使您的代码更加优雅,还为 Java 中的函数式编程范式打开了大门。
本指南基于 Oracle 的官方文档和 Java 社区建立的最佳实践。要加深您的知识,请查阅 Oracle 关于 Lambda 表达式和 Stream API 的官方教程。