エンジニアのはしがき

プログラミングの日々の知見を書き連ねているブログです

JavaのStream APIでよく使いそうな処理をまとめてみる

Javaの勉強中、コレクションを扱う時にC#Linq的な書き方ができたらなぁと思っていたのですが、Stream APIでかなり近いことができるようなのでまとめました。

概要

ArrayListなどのCollectionインターフェースを実装するクラスは、strem()を呼ぶことでStreamインターフェースを取得することができます。(JDK1.8以降)

Streamインターフェースはコレクションの操作に長けており、複雑な処理も可読性の高いソースコードとして記述が可能です。

記述をより簡潔化する為、ラムダ式と合わせて用いられるケースが多いです。

Stream (Java SE 17 & JDK 17)

Collection (Java SE 17 & JDK 17)

具体的にどういうことが可能なのかを以下の章でピックアップしました。

処理一覧

参照系の処理

コレクションから順番に要素を取り出す

Stream<T>.forEach(Consumer<? super T> action)で拡張for文のように繰り返し処理ができます。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 3, 4, 5);
        intList.stream()
            .forEach(i -> {
                System.out.println(i);
            });
    }
}

出力結果

1
2
3
4
5

コレクションの要素数を取得する

Stream<T>.count()で要素数を取得できます。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(intList.stream().count());
    }
}

出力結果

5

コレクションの要素において最大値を取得する

Stream<T>.max(Comparator<? super T> comparator)で最大値の要素だけ取得できます。 なお、戻り値はTではなくOptional<T>となります。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(intList.stream().max(Integer::compareTo));
    }
}

出力結果

Optional[5]

コレクションの要素において最小値を取得する

Stream<T>.min(Comparator<? super T> comparator)で最小値の要素だけ取得できます。 なお、戻り値はTではなくOptional<T>となります。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(intList.stream().min(Integer::compareTo));
    }
}

出力結果

Optional[1]

更新系の処理

StreamをList, Arrayに変換する

Arrayへの変換はStream<T>.toArray(IntFunction<A[]> generator)、Listへの変換はStream<T>.toList()です。 Streamでコレクションを加工した後、以降の処理でさらに利用したい場合によく使いそうです。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 3, 4, 5);

        Integer[] intArrayFiltered = intList.stream()
            .filter(i -> i >= 3)
            .toArray(Integer[]::new);

        List<Integer> intListFiltered = intList.stream()
            .filter(i -> i >= 3)
            .toList();
    }
}

コレクションから条件に一致した要素だけを抽出する

Stream<T>.filter(Predicate<? super T> predicate)で任意の条件を満たした要素だけ取得できます。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 3, 4, 5);
        // 3以上の要素だけを抽出する
        intList.stream()
            .filter(i -> i >= 3)
            .forEach(System.out::println);
    }
}

出力結果

3
4
5

コレクションから順番に要素を取り出して加工する

Stream<T>.map(Function<? super T,? extends R> mapper)でコレクションの先頭から要素を順番に取り出し、任意の処理を施すことができます。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 3, 4, 5);
        // 各要素の数値を2倍に加工する
        intList.stream()
            .map(i -> i * 2)
            .forEach(System.out::println);
    }
}

出力結果

2
4
6
8
10

コレクションの並び順を特定のキーの値の昇順にする

Stream<T>.sorted()で昇順に並び替えができます。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(2, 5, 1, 4, 3);
        intList.stream()
            .sorted()
            .forEach(System.out::println);
    }
}

出力結果

1
2
3
4
5

コレクションの並び順を特定のキーの値の降順にする

Stream<T>.sorted(Comparator<? super T> comparator)で降順に並び替えができます。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(2, 5, 1, 4, 3);
        intList.stream()
            .sorted(Comparator.reverseOrder())
            .forEach(System.out::println);
    }
}

出力結果

5
4
3
2
1

コレクションを特定のキーの値を条件にグルーピングする

Stream<T>.collect(Collector<? super T,A,R> collector)SQLのGROUP BYのように任意のキーの値によって要素のグルーピングができます。

下記では例としてUserクラスを用意し、groupIdの値が同値であるUserインスタンス同士をグルーピングする例です。

public class User {
    private int groupId;
    private String name;

    public User(int groupId, String name) {
        this.groupId = groupId;
        this.name = name;
    }

    public int getGroupId() {
        return groupId;
    }

    public String getName() {
        return name;
    }
}
public class StreamAPISample {
    public static void main() {
        List<User> userList =  Arrays.asList(
            new User(1, "tanaka"),
            new User(1, "yamashita"),
            new User(2, "ueda")
        );
        // UserクラスのgroupIdをキーにグルーピングする
        // Collectors.groupingBy()の引数でキーを指定する
        Map<Integer, List<User>> userGroup = userList.stream()
            .collect(Collectors.groupingBy(user -> user.getGroupId()));
        
        for (Integer key : userGroup.keySet()) {
            System.out.println("groupId " + key + ":");
            for (User user : userGroup.get(key)) {
                System.out.println("    User: " + user.getName());
            }
        }
    }
}

出力結果

groupId 1:
    User: tanaka
    User: yamashita
groupId 2:
    User: ueda

コレクションから重複要素を取り除く

Stream<T>.distinct()でコレクションの重複要素を除外できます。 SQLのDISTINCT的な処理をしたい場合の処理です。

Object.equals(Object)によって、要素が同一であるかどうかの判定が為されます。

public class StreamAPISample {
    public static void main() {
        List<Integer> intList =  Arrays.asList(1, 2, 2, 3, 3, 4, 5);
        intList.stream()
            .distinct()
            .forEach(System.out::println);
    }
}

使いどころ

Stream APIでの繰り返し処理は、for文、拡張for文と比較するとオーバーヘッドがある為、シビアなパフォーマンスが必要とされない箇所で使っていくのがベターでしょう。 複雑な処理の可読性アップにはかなり貢献できますので有効に使っていきたいですね!

参考

Java8 のラムダ式と Stream API を試してみる - WonderPlanet Developers’ Blog

Stream (Java SE 17 & JDK 17)