Article

☕ JavaのStream.toList()とCollectors.toList()の違い

java, stream, backend

JavaでStreamをListに変換するとき、Stream.toList() と Collectors.toList() は似ていますが契約が違います。Java SE 27の公式APIを基準に整理します。

Stream.toList()は変更不可

List<String> names = users.stream()
        .map(User::name)
        .toList();

names.add("guest"); // UnsupportedOperationException

Stream.toList() が返すListは unmodifiable(変更不可) です。add、remove、clear、sort など、Listを変更する操作は実行時に失敗します。

Collectors.toList()は変更可能とは保証されない

List<String> names = users.stream()
        .map(User::name)
        .collect(Collectors.toList());

公式仕様では、返されるListの実装クラス、変更可能性、シリアライズ可能性、スレッドセーフ性は保証されません。

後から変更する必要があるなら、変更可能なListを明示します。

List<String> names = users.stream()
        .map(User::name)
        .collect(Collectors.toCollection(ArrayList::new));

names.add("guest");

Collectors.toCollection は生成するCollectionを指定できるAPIです。ArrayList::new と書けば、変更可能なArrayListが必要だという意図もコードから分かります。

よくある移行事故

既存コードの .collect(Collectors.toList()) を .toList() に機械的に置き換えると、後続の add、remove、removeIf、sort などで実行時エラーになることがあります。

PRレビューでは、変換した行だけでなく、そのListの後続利用まで確認するのが重要です。

toUnmodifiableList()との違い

Collectors.toUnmodifiableList() も変更不可ですが、公式仕様では入力に null があると NullPointerException を投げると明記されています。Stream.toList() と完全に同じAPIだと考えず、用途で選びます。

使い分け

  • 読み取り専用: Stream.toList()
  • 後から変更する: Collectors.toCollection(ArrayList::new)
  • Java 8など toList() がない環境: Collectors.toList()

コンパイルが通ることとListの契約が同じことは別です。「このListは後から変更するのか」を基準に選ぶと、リファクタリング時の不具合を防ぎやすくなります。

参考