Java Lambda 表达式:集合和日期处理必备指南
Java 8でラムダ式が入って以来、コレクションの操作と日付処理の書き方が根本的に変わりました。ここでは日常開発でよく使うパターンを集めています。Stream APIとjava.timeを組み合わせた実用的な例を中心に書きました。
ラムダ式とは
ラムダ式は匿名関数です。メソッドの引数として渡せるため、振る舞いをデータとして扱えるようになります。従来の匿名クラスに比べて記述量が劇的に減ります。
基本構文
三つの要素で構成されます。
(parameters) -> { function body }匿名クラスと比べてみましょう。
// 使用匿名类的传统方式
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Hello World");
}
};
// Lambda 方式(更简洁)
Runnable r2 = () -> System.out.println("Hello World");同じ処理が1行で書けます。
コレクションの走査
配列
配列はStreamに変換すれば、ラムダ式で扱いやすくなります。
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);リスト
リストはラムダ式の出番が一番多い場所です。forEach、stream、filterを組み合わせるだけで、見通しが全然違います。
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
MapもforEachでキーと値を同時に受け取れます。個別に回したい場合はkeySet()かvalues()を使います。
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)
);日付処理
Java 8で導入されたjava.time APIは、ラムダ式と組み合わせるとさらに強力になります。
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リスト
生年月日から年齢を計算し、成人だけを抽出する例です。Period.betweenで年齢を取り、filterで絞り込みます。
// 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を使えば、日付を月ごとにグループ化できます。メソッド参照(LocalDate::getMonth)と組み合わせるのがポイントです。
// 使用 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")))
);
});ベストプラクティス
メソッド参照
ラムダ式が既存メソッドを1つ呼ぶだけなら、メソッド参照に置き換えられます。読みやすくなるので推奨です。
// 而不是: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
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); // 修改外部状态
});例外処理
ラムダ式の中でchecked exceptionをそのまま投げることはできません。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); // 包装异常ラムダ式を使いこなすと、コレクションと日付処理のボイラープレートが消えて、やりたいことがそのまま書けるようになります。まずはforEachとfilterから触ってみて、慣れてきたらcollectとgroupingByに進むのがよいと思います。