Article
☕ JavaのStream.toList()とCollectors.toList()の違い
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は後から変更するのか」を基準に選ぶと、リファクタリング時の不具合を防ぎやすくなります。
参考
- https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/stream/Stream.html#toList()
- https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/stream/Collectors.html#toList()
- https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/stream/Collectors.html#toCollection(java.util.function.Supplier)
- https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/stream/Collectors.html#toUnmodifiableList()