1. 序章

この記事では、Streamの実装がJavaとVavrでどのように異なるかを見ていきます。

この記事は、 Java StreamAPIVavrライブラリの両方の基本に精通していることを前提としています。

2. 比較

どちらの実装も、レイジーシーケンスの同じ概念を表していますが、詳細が異なります。

Java Streamsは、堅牢な並列処理を念頭に置いて構築されており、並列化を簡単にサポートします。 一方、Vavr実装は、データのシーケンスを操作する便利な作業を優先し、並列処理のネイティブサポートを提供しません(ただし、インスタンスをJava実装に変換することで実現できます)。

これが、Javaストリームが Spliteratorインスタンスによってサポートされている理由です。はるかに古いIteratorへのアップグレードと、Vavrの実装は前述の Iterator によってサポートされています(少なくとも最新の実装の1つで)。

どちらの実装もそのバッキングデータ構造に緩く結びついており、基本的にストリームが通過するデータのソースの上にファサードがありますが、Vavrの実装はイテレータ-ベースではありません。 tソースコレクションの同時変更を許容します。

Javaのストリームソースの処理により、正常に動作するストリームソースを、ターミナルストリーム操作が実行される前に変更することができます。 

基本的な設計の違いにもかかわらず、Vavrはそのストリーム(およびその他のデータ構造)をJava実装に変換する非常に堅牢なAPIを提供します。

3. 追加機能

ストリームとその要素を処理するアプローチは、JavaとVavrの両方でストリームを処理する方法に興味深い違いをもたらします

3.1. ランダム要素アクセス

便利なAPIと要素へのアクセス方法を提供することは、VavrがJavaAPIよりも真に優れている分野の1つです。 たとえば、Vavrには、ランダム要素アクセスを提供するいくつかのメソッドがあります。

  • get()は、ストリームの要素へのインデックスベースのアクセスを提供します。
  • indexOf()は、標準のJavaList。と同じインデックスロケーション機能を提供します。
  • insert()は、指定された位置でストリームに要素を追加する機能を提供します。
  • intersperse()は、指定された引数をストリームのすべての要素の間に挿入します。
  • find()は、ストリーム内からアイテムを見つけて返します。 Javaは、要素の存在をチェックするだけのnoneMatchedを提供します。
  • update()は、指定されたインデックスの要素を置き換えます。 これは、置換を計算する関数も受け入れます。
  • search()は、並べ替えられた ストリーム内のアイテムを検索します(並べ替えられていないストリームは未定義の結果を生成します)

この機能は、検索に対して線形のパフォーマンスを持つデータ構造に支えられていることを覚えておくことが重要です。

3.2. 並列処理と並行変更

VavrのStreamsは、Javaの parallel()メソッドのような並列処理をネイティブにサポートしていませんが、並列化されたJavaベースの to JavaParallelStreamメソッドがあります。ソースVavrストリームのコピー。

Vavrストリームの比較的弱い領域は、非干渉の原理に基づいています。

簡単に言えば、 Javaストリームを使用すると、ターミナル操作が呼び出されるまで、基になるデータソースを変更できます。 特定のJavaストリームでターミナル操作が呼び出されていない限り、ストリームは基になるデータソースへの変更を取得できます。

List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
Stream<Integer> intStream = intList.stream(); //form the stream
intList.add(5); //modify underlying list
intStream.forEach(i -> System.out.println("In a Java stream: " + i)); 

最後の追加がストリームからの出力に反映されていることがわかります。 この動作は、変更がストリームパイプラインの内部であるか外部であるかにかかわらず一貫しています。

in a Java stream: 1
in a Java stream: 2
in a Java stream: 3
in a Java stream: 5

Vavrストリームはこれを許容しないことがわかりました。

Stream<Integer> vavrStream = Stream.ofAll(intList);
intList.add(5)
vavrStream.forEach(i -> System.out.println("in a Vavr Stream: " + i));

私たちが得るもの:

Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
  at java.util.ArrayList$Itr.next(ArrayList.java:851)
  at io.vavr.collection.StreamModule$StreamFactory.create(Stream.java:2078)

Vavrストリームは、Java標準では「正常に動作」していません。 Vavrは、プリミティブなバッキングデータ構造でより適切に動作します。

int[] aStream = new int[]{1, 2, 4};
Stream<Integer> wrapped = Stream.ofAll(aStream);

aStream[2] = 5;
wrapped.forEach(i -> System.out.println("Vavr looped " + i));

私たちに与える:

Vavr looped 1
Vavr looped 2
Vavr looped 5

3.3. 短絡操作とflatMap()

flatMap、は、 map 操作と同様に、ストリーム処理の中間操作です。どちらの実装も、中間ストリーム操作のコントラクト –基礎となるデータ構造からの処理に従います。ターミナル操作が呼び出されるまで発生しないはずです。

ただし、JDK 8および9には、バグがあり、 flatMap 実装がこのコントラクトを破り、findFirstやなどの短絡中間操作と組み合わせると熱心に評価されます。 X222X]制限。

簡単な例:

Stream.of(42)
  .flatMap(i -> Stream.generate(() -> { 
      System.out.println("nested call"); 
      return 42; 
  }))
  .findAny();

上記のスニペットでは、ネストされた Streamから単一の要素を取得するのではなく、 flatMap が熱心に評価されるため、findAnyから結果が得られることはありません。

このバグの修正はJava10で提供されました。

VavrのflatMapには同じ問題はなく、機能的に類似した操作がO(1)で完了します。

Stream.of(42)
  .flatMap(i -> Stream.continually(() -> { 
      System.out.println("nested call"); 
      return 42; 
  }))
  .get(0);

3.4. コアVavr機能

一部の地域では、JavaとVavrを1対1で比較することはできません。 Vavrは、Javaでは比類のない機能でストリーミングエクスペリエンスを強化します(または少なくともかなりの量の手作業が必要です)。

  • ジップ() ストリーム内のアイテムを、提供されたアイテムとペアにします反復可能。 この操作は、以前はJDK-8でサポートされていましたが、 ビルド後に削除された-93
  • partition()は、述語を指定して、ストリームのコンテンツを2つのストリームに分割します。
  • permutation()という名前で、ストリームの要素の順列(すべての可能な一意の順序)を計算します。
  • combinations()は組み合わせを提供します(つまり ストリームの可能なアイテムの選択)。
  • groupBy は、提供された分類子によって分類された、元のストリームの要素を含むストリームのMapを返します。
  •  Vavrのdistinctメソッドは、 compareTo ラムダ式を受け入れるバリアントを提供することにより、Javaバージョンを改善します。

高度な機能のサポートはJavaSEストリームでは多少影響を受けませんが、 Expression Language 3.0 は、奇妙なことに、標準のJDKストリームよりもはるかに多くの機能をサポートしています。

4. ストリーム操作

Vavrを使用すると、ストリームのコンテンツを直接操作できます。

  • 既存のVavrストリームに挿入します
Stream<String> vavredStream = Stream.of("foo", "bar", "baz");
vavredStream.forEach(item -> System.out.println("List items: " + item));
Stream<String> vavredStream2 = vavredStream.insert(2, "buzz");
vavredStream2.forEach(item -> System.out.println("List items: " + item));
  • ストリームからアイテムを削除します
Stream<String> removed = inserted.remove("buzz");
  • キューベースの操作 

Vavrのストリームがキューによってバックアップされることにより、一定時間のappendおよびappend操作を提供します。

ただし、 Vavrストリームに加えられた変更は、ストリームの作成元のデータソースには反映されません。

5. 結論

VavrとJavaにはどちらも長所があり、安価な並列処理に対するJavaと便利なストリーム操作に対するVavrという設計目標に対する各ライブラリの取り組みを示しました。

Vavrが独自のストリームとJavaの間で相互に変換することをサポートしているため、多くのオーバーヘッドなしで、同じプロジェクト内の両方のライブラリの利点を引き出すことができます。

このチュートリアルのソースコードは、Githubから入手できます。