Java | Java 標準ライブラリ:Collectors.toList

Java Java
スポンサーリンク

Collectors.toList をざっくり一言でいうと

Collectors.toList() は、

「Stream の結果を List に集め直すための“まとめ方テンプレ”」

です。

stream().filter(...).map(...) と“流して加工したもの”を、
最後に collect(Collectors.toList()) と書くことで

「はい、この結果を List にしてください」

と命令している、というイメージで OK です。

collect が「どうやって集めるか?」を指定する場所で、
Collectors.toList() は「List に集める」という“パターン”を表すオブジェクト、という関係です。


一番基本の形:filter + map + Collectors.toList

例題:3 文字以下の名前だけを List にする

まずは王道パターンからいきます。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ToListBasic {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Carol", "Dan");

        List<String> shortNames =
                names.stream()
                     .filter(name -> name.length() <= 3)   // 3文字以下に絞る
                     .map(String::toUpperCase)             // 大文字に変換
                     .collect(Collectors.toList());        // List に集める

        System.out.println(shortNames); // [BOB, DAN]
    }
}
Java

流れを日本語で追うと、

元の names から stream() を作る
filter で条件に合う要素だけ残す
map で形を変える(大文字にする)
collect(Collectors.toList()) で、流れてきた要素を新しい List に詰め直す

こうなっています。

ここでのポイントは、filtermap の時点ではまだ Stream<String> であって、
List<String> になっていない」という点です。

「結果を List として使いたい」
「メソッドの戻り値に List を返したい」

となった瞬間に、Collectors.toList() の出番になります。


collect と Collectors.toList の関係をちゃんと理解する

collect の「型」だけざっくり見る

collect は Stream の終端操作の一つで、シグネチャはざっくりこうです。

<R, A> R collect(java.util.stream.Collector<? super T, A, R> collector)
Java

やっていることを噛み砕くと、

T のストリームを、R という形にまとめるための“やり方(Collector)”を渡すと、
その通りまとめて R を返します」

というメソッドです。

この「やり方」が Collector というインターフェースで、
Collectors クラスが、その具体的な実装(テンプレ)をたくさん用意してくれています。

Collectors.toList() はその一つで、

「ストリームの要素を全部集めて、List にしてください」

というルールを持った Collector オブジェクトです。

つまり、

collect(まとめ方)
collect(Collectors.toList())

という形になっている、という理解で十分です。


Collectors.toList を使う場面を感覚でつかむ

メソッドの戻り値として List が欲しいとき

よくあるのが、メソッドで「条件に合う要素だけ返したい」という場面です。

従来の for 文だとこう書きます。

List<String> filterShortNames(List<String> names) {
    List<String> result = new ArrayList<>();
    for (String name : names) {
        if (name.length() <= 3) {
            result.add(name);
        }
    }
    return result;
}
Java

Stream と Collectors.toList() を使うと、こう書けます。

import java.util.List;
import java.util.stream.Collectors;

List<String> filterShortNames(List<String> names) {
    return names.stream()
                .filter(name -> name.length() <= 3)
                .collect(Collectors.toList());
}
Java

関数として、

「入力 List のうち、条件に合う要素だけを別の List にして返す」

という意図が、一行でかなり素直に表現できています。

中間結果を List として一旦保持したいとき

Stream の途中で「一旦 List に落としてから、また別の処理をしたい」
という場面でもよく使います。

List<String> filtered =
        names.stream()
             .filter(name -> name.length() <= 3)
             .collect(Collectors.toList());

// 別のメソッドに渡したり、for 文で回したり
useListSomewhere(filtered);
Java

「Stream のままでは扱いづらい」「デバッグで中身を見たい」
「一度 List に出してから、別の API に渡したい」
こういうときに、Collectors.toList() で一回「普通の List」に戻してあげる、という使い方をします。


toList と Arrays.asList の違い・使い分け

Arrays.asList は「配列 → List」、toList は「Stream → List」

例えばこんなコードがあります。

List<String> list1 = Arrays.asList("A", "B", "C");         // 配列から
List<String> list2 = stream.collect(Collectors.toList());  // Stream から
Java

Arrays.asList は「配列(または可変長引数)から List を作る」ためのメソッドです。
Collectors.toList() は「Stream の結果を List にする」ための Collector です。

どちらも「List を作る」という点では似ていますが、
入口(元データ)と利用シーンが違います。

もともと配列や可変長引数しかないなら Arrays.asList
Stream で一連の処理をしたあとなら collect(Collectors.toList())

と使い分けるイメージです。


Java 16 以降の Stream#toList との違い

stream.toList() と collect(toList()) は何が違うのか

Java 16 以降、Stream に toList() メソッドが追加されました。

List<String> list = names.stream()
                         .filter(...)
                         .map(...)
                         .toList();
Java

見た目には collect(Collectors.toList()) と同じです。

細かい話をすると、

stream.toList() が返す List は「基本的に変更不可」に近い性質
Collectors.toList() が返す List は実装に依存する(多くは ArrayList で変更可能)

などの差がありますが、初心者のうちは

「単に List にしたいだけなら toList() で良い」
collect と他の Collector(toSet, groupingBy など)と合わせて使うことを覚える」

くらいで十分です。

どのみち、collecttoList 以外の場面で必須になるので、
toList()collect(toList()) の略記版が増えた」程度の認識で問題ありません。


Collectors.toList を使うときに意識しておきたいこと

Stream の「終点」であるという感覚

collect(Collectors.toList()) は終端操作なので、ここを呼んだ瞬間に

Stream のパイプラインが一気に実行される
Stream は「消費済み」となり、二度と使えない

ということを頭に置いておいてください。

例えば、同じ Stream で二回 collect はできません。

Stream<String> s = names.stream();

List<String> a = s.filter(...).collect(Collectors.toList());
List<String> b = s.filter(...).collect(Collectors.toList()); // ここで IllegalStateException
Java

これは、Stream が「一回きりの使い捨て」だからです。
必要なら、names.stream() から別の Stream をもう一度作る必要があります。

List の中身は「変換後の要素」であることを意識する

map と合わせて使うとき、
「どのタイミングで何に変わっているか」を意識すると、バグが減ります。

例えば、

List<Integer> lengths =
        names.stream()
             .map(String::length)
             .collect(Collectors.toList());
Java

この時点で List<Integer> になっています。

後続の処理でうっかり「文字列として扱ってしまう」などが起きないように、
「collect した結果の型」をしっかり意識しておくと安全です。


まとめ:Collectors.toList を自分の中でどう位置づけるか

Collectors.toList() を初心者向けに一言でまとめると、

Stream で流した結果を、素直な List に戻すための、いちばん基本的な“集め方”

です。

特に大事なのは、次の感覚です。

  • Stream の途中までは filter / map などで「流れの形」を作る
  • 最後に collect(Collectors.toList()) で「その流れを List に落とす」
  • メソッドの戻り値や、他の API に渡すための“普通の List”が欲しくなったら toList を思い出す

タイトルとURLをコピーしました