Zademy

Java Lambda 表达式:集合和日期处理必备指南

Java; Lambdas; Stream; Dates; Tutorial
words 単語

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に進むのがよいと思います。