原文はこちら。
The original article was written by Dustin Marx.
http://marxsoftware.blogspot.com/2020/12/jdk16-stream-to-list.html
Javaの関数型Streamsの普及に伴い、新しいStreamsの操作をサポートしてほしい、という要望が増えています。
Java SE 8のストリームを使用したデータ処理、パート1
https://www.oracle.com/jp/technical-resources/articles/java/ma14-java-se-8-streams.html
Processing Data with Java SE 8 Streams, Part 1
https://www.oracle.com/technical-resources/articles/java/ma14-java-se-8-streams.html
このような多種多様な新しい操作に対する要望の中で、他の操作よりも多くの要望があるように思われるのが、Stream
から直接List
を提供する操作です。
Retrieving a List from a java.util.stream.Stream in Java 8
https://stackoverflow.com/questions/14830313/retrieving-a-list-from-a-java-util-stream-stream-in-java-8
List (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/List.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/List.html
java.util.stream (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/stream/package-summary.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/stream/package-summary.html
JDK 16 Early Access Build 27 では、この記事の主題である Stream.toList()
が導入されています。
JDK 16 Early-Access Builds
https://jdk.java.net/16/
JDK 16 Early Access Build 27でStream.toList()
がリリースされるまでは、Stream
からList
を取得する最も一般的な方法は、適切なCollector
を呼び出すことでした。
[JDK/JDK-8180352] Add Stream.toList() method
https://bugs.openjdk.java.net/browse/JDK-8180352?jql=project%20%3D%20JDK%20AND%20fixversion%20%3D%2016%20and%20%22resolved%20in%20build%22%20%3D%20b27%20order%20by%20component%2C%20subcomponent
collect / java.util.stream (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/stream/Stream.html#collect(java.util.stream.Collector)
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/stream/Stream.html#collect(java.util.stream.Collector)
stream.collect(Collectors.toList())
これはそれほど多くのコードではなく、一度見てしまえばかなり簡単なものですが、多くの人が、この頻繁に使用されるストリーム操作のために、より簡潔な構文を求めていました。JDK 16 ではこれを実現しました。
stream.toList()
自分のコード・ベースに入って、stream.collect(Collectors.toList())
の置き換えとして stream.toList()
を使用したくなるかもしれませんが、コードが ArrayList
を返す stream.collect(Collectors.toList())
の実装に直接または間接的に依存している場合は、動作に違いがある可能性があります。stream.collect(Collectors.toList())
が返すList
とstream.toList()
が返すList
の主な違いについては、この記事の残りの部分で説明します。
ArrayList (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/ArrayList.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/ArrayList.html
Collectors.toList()のJavadocベースのドキュメントには以下のような記述があります(太字は強調のために追加したものです)。
Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier).
toList() / Collectors (Java SE 15 & JDK 15)
入力要素を新しいListに蓄積するCollectorを返します。 返されるListの型、可変性、直列化可能性、またはスレッド安全性は一切保証されません。返されるListをより細かく制御する必要がある場合は、toCollection(Supplier)を使用してください。
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/stream/Collectors.html#toList()
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/stream/Collectors.html#toList()
Collectors.toList()
が提供するList
の「型や可変性(mutability)、シリアライズ可能性、スレッド安全性」は保証されませんが、現在はArrayList
であることを認識し、ArrayList
の特性に依存した使い方をしている人がいると予想されます。
以下のコードスニペット(コード全体はGitHubにあります)は、Collectors.toList()
とStream.toList()
が返すList
実装に対し実行可能なメソッドを示しており、共通する部分、異なる部分を確認できます。
StreamToListDemo.java
https://github.com/dustinmarx/javademos/blob/master/src/dustin/examples/jdk16/streams/StreamToListDemo.java
/**
* Analyzes the supplied {@code List} and writes to standard output
* some key characteristics of the supplied {@code List}.
*
* @param listDescription Description of {@code List} to be analyzed.
* @param listUnderAnalysis {@code List} to be analyzed.
*/
private static void analyzeList(
final String listDescription, final List<String> listUnderAnalysis)
{
out.println(listDescription + ": ");
out.println("\tClass Type: " + listUnderAnalysis.getClass().getCanonicalName());
out.println("\tAble to add to List? " + isListAddCapable(listUnderAnalysis));
out.println("\tAble to sort List? " + isListSortable(listUnderAnalysis));
}
簡単な上記の分析コードを
とStream.collect(Collectors.toList())
が返すStream.toList()
List
の実装に対して実行すると、以下のような出力が現れます。
Stream.collect(Collectors.toList()):
Class Type: java.util.ArrayList
Able to add to List? true
Able to sort List? true
Stream.toList():
Class Type: java.util.ImmutableCollections.ListN
Able to add to List? false
Able to sort List? false
[NOT Stream] List.of():
Class Type: java.util.ImmutableCollections.ListN
Able to add to List? false
Able to sort List? false
上記の出力結果は、Stream.toList()
がイミュータブル(追加や並べ替えが不可能なImmutableCollections.ListN
型)でList.of()
が提供するのと類似したList
実装を提供する、ということを説明しています。これはStream.collect(Collectors.toList())
が提供するミュータブル(変更や並べ替えが可能)なArrayList
とは対照的です。
of / List (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/List.html#of(E…)
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/List.html#of(E…)
Stream.collect(Collectors.toList())
が返すArrayList
をミュータブルにする機能に依存する既存のコードは、Stream.toList()
では動作せず、UnsupportedOperationException
がスローされます。
UnsupportedOperationException (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/lang/UnsupportedOperationException.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/UnsupportedOperationException.html
Stream.collect(Collectors.toList())
とStream.toList()
が返すリストの実装そのものは大きく異なりますが、どちらも List インターフェースを実装しているため、List.equals(Object) を使用して比較すると同等とみなされます。これは、GitHubのコードでわかります。
List (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/List.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/List.html
equals(Object) / List (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/List.html#equals(java.lang.Object)
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/List.html#equals(java.lang.Object)
StreamToListDemo.java
https://github.com/dustinmarx/javademos/blob/master/src/dustin/examples/jdk16/streams/StreamToListDemo.java
Stream インターフェースにtoList()
メソッドが追加されたことは小さなことがらではありますが、よく使われるテクニックがより便利になっています。
Stream (Java SE 15 & JDK 15)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/stream/Stream.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/stream/Stream.html