JDK 16: Stream to List In One Easy Call

原文はこちら。
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()) が返すListstream.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).
入力要素を新しいListに蓄積するCollectorを返します。 返されるListの型、可変性、直列化可能性、またはスレッド安全性は一切保証されません。返されるListをより細かく制御する必要がある場合は、toCollection(Supplier)を使用してください。

toList() / Collectors (Java SE 15 & JDK 15)
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

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中