💡

Stream APIの中間操作

2024/02/04に公開

はじめに

JavaのStream APIは、データコレクションの処理を簡潔かつ宣言的に行うため標準APIです。
この記事では、私自身忘れがちなSteam APIの中間操作を備忘録としてまとめていきます。

前準備

実行環境

以下の環境で確認しています。

java -version
openjdk version "17.0.4" 2022-07-19 LTS
OpenJDK Runtime Environment Corretto-17.0.4.8.1 (build 17.0.4+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.4.8.1 (build 17.0.4+8-LTS, mixed mode, sharing)

データ

以下のようなユーザのリストを生成して説明していきます。

Sample.java
public class Sample {
    public static void main(String[] args) {
        var users = List.of(
                new User("yamada", User.Gender.FEMALE, 21),
                new User("tanaka", User.Gender.MALE, 26),
                new User("suzuki", User.Gender.FEMALE, 33),
                new User("sato", User.Gender.OTHER, 19)
        );
    }
}

class User {
    private String name;
    private Gender gender;
    private int age;

    enum Gender {
        MALE,
        FEMALE,
        OTHER
    }

    public User(String name, Gender gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Gender getGender() {
        return gender;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "User{name=" + name + ", gender=" + gender + ", age=" + age + "}";
    }
}

中間操作

Stream APIの中間操作を用いることで、コレクションの各要素に対して様々な変換やフィルタリングを行うことができます。

filter

filterメソッドは指定された条件を満たす要素のみを抽出します。

users.stream()
        .filter(user -> user.getAge() >= 20)
        .forEach(System.out::println);
結果
User{name=yamada, gender=FEMALE, age=21}
User{name=tanaka, gender=MALE, age=26}
User{name=suzuki, gender=FEMALE, age=33}

上記の例では、20歳以上のユーザだけが出力されます。

また、複数の条件を組み合わせることも可能です。
例えば、20歳以上かつ男性のユーザを抽出するには、次のようにします。

users.stream()
        .filter(user -> user.getAge() >= 20)
        .filter(user -> user.getGender() == User.Gender.MALE)
        .forEach(System.out::println);
結果
User{name=tanaka, gender=MALE, age=26}

以下のように、複合条件を&&(AND)や||(OR)を用いて簡潔に記述することもできます。

users.stream()
        .filter(user -> user.getAge() >= 20 && user.getGender() == User.Gender.MALE)
        .forEach(System.out::println);

map

mapメソッドは、ストリーム内の要素を別の形へと変換します。

users.stream()
        .map(user -> user.getName())
        .forEach(System.out::println);
結果
yamada
tanaka
suzuki
sato

この例では、ユーザオブジェクトのストリームをユーザ名のストリームへと変換しています。

mapメソッドはFunction<T, R>関数型インタフェースを引数に取り、入力された型Tから出力される型Rへの変換を行います。この柔軟性により、様々な変換処理を簡単に実装できます。

distinct

distinctメソッドは、ストリームから重複要素を削除します。この操作は、要素の等価性に基づいて行われます。

users.stream()
        .map(user -> user.getGender())
        .distinct()
        .forEach(System.out::println);

この例では、各ユーザの性別を抽出し、重複を削除した後結果を出力します。

結果
FEMALE
MALE
OTHER

sorted

sortedメソッドは、ストリームの要素を自然順序または指定されたコンパレータに従ってソートします。
例えばユーザを年齢で昇順にソートするには次のようにします。

users.stream()
        .sorted(Comparator.comparingInt(User::getAge))
        .forEach(System.out::println);
結果
User{name=sato, gender=OTHER, age=19}
User{name=yamada, gender=FEMALE, age=21}
User{name=tanaka, gender=MALE, age=26}
User{name=suzuki, gender=FEMALE, age=33}

skipとlimit

skipメソッドは、ストリームの最初のn要素をスキップし、limitメソッドは、ストリームの最初のn要素に制限します。これらは特に大きなデータセットを扱う際にページネーションを実装するのに役立ちます。

例えば2番目のユーザから始まる最初の2人のユーザを取得するのには次のようにします。

users.stream()
        .skip(1) // 最初の1要素をスキップ
        .limit(2) // 次の2要素に制限
        .forEach(System.out::println);
結果
User{name=tanaka, gender=MALE, age=26}
User{name=suzuki, gender=FEMALE, age=33}

takeWhile

takeWhileメソッドは、指定された条件が真である間、ストリームの要素を取得し続けます。
条件が偽となった最初の要素で処理が停止し、それ以降の要素は無視されます。
filterメソッドも指定した条件で操作できますが、処理が停止する点が違います。

users.stream()
        .takeWhile(user -> user.getName().contains("a"))
        .forEach(System.out::println);
結果
User{name=yamada, gender=FEMALE, age=21}
User{name=tanaka, gender=MALE, age=26}

dropWhile

dropWhileメソッドは、指定された条件が真である間、ストリームの要素を無視し続けます。
条件が偽となった最初の要素でスキップが停止し、その要素を含む残りのストリームが処理されます。

users.stream()
        .dropWhile(user -> user.getName().contains("a"))
        .forEach(System.out::println);
結果
User{name=suzuki, gender=FEMALE, age=33}
User{name=sato, gender=OTHER, age=19}

filterとの違い

takeWhiledropWhilefilterメソッド同様指定した条件でストリームの操作が可能です。
filterメソッドは、ストリームの各要素に対して条件を適用し、その条件を満たすすべての要素を保持します。filterはストリームの順序に依存せず、条件を満たすすべての要素を含めます。

主な違いは、条件が偽となった時点で処理を停止または開始することです。これにより、filterよりも特定のシナリオでより制御が可能になりますが、全ての要素に対して単純なフィルタリングを行いたい場合はfilterが適しています。

まとめ

今回は、JavaのStream APIを利用したデータ処理における中間操作に焦点を当てて備忘録としてまとめました。
どんなメソッドあったっけ?とド忘れしてしまった方のお役に立てれたら幸いです。

リファレンス

https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/stream/Stream.html

Discussion