1概要

Java 8で導入された

Spliterator

インターフェースは、

トラバースおよび分割シーケンス

に使用できます。これは

Streams

の基本ユーティリティ、特にパラレルユーティリティです。

この記事では、その使用方法、特性、メソッド、そして独自のカスタム実装の作成方法について説明します。


2.



Spliterator

API


2.1.



tryAdvance


これは、シーケンスをステップスルーするために使用される主な方法です。メソッドは、

Spliterator

の要素を1つずつ順番に消費するために使用される

Consumer

を取り、** 横断する要素がない場合は

false

を返します。

ここでは、それを使って要素をトラバースしたり分割したりする方法を見ていきます。

まず、35000の記事を含む

ArrayList

があり、

Article

クラスが次のように定義されているとします。

public class Article {
    private List<Author> listOfAuthors;
    private int id;
    private String name;

   //standard constructors/getters/setters
}

それでは、記事のリストを処理し、各記事の名前に「

– Baeldungで公開」

のサフィックスを追加するタスクを実装しましょう。

public String call() {
    int current = 0;
    while (spliterator.tryAdvance(a -> a.setName(article.getName()
      .concat("- published by Baeldung")))) {
        current++;
    }

    return Thread.currentThread().getName() + ":" + current;
}

このタスクは実行終了時に処理された記事の数を出力します。

もう1つの重要な点は、次の要素を処理するために

tryAdvance()

メソッドを使用したことです。


2.2.

trySplit


次に、

Spliterators

(したがって名前)を分割し、パーティションを個別に処理しましょう。


trySplit

メソッドはそれを2つの部分に分割しようとします。その後、呼び出し元のプロセス要素、そして最後に、返されたインスタンスが他の要素を処理し、2つを並行して処理できるようにします。

まず私たちのリストを生成しましょう。

public static List<Article> generateElements() {
    return Stream.generate(() -> new Article("Java"))
      .limit(35000)
      .collect(Collectors.toList());
}

次に、

spliterator()

メソッドを使用して

Spliterator

インスタンスを取得します。それから

trySplit()

メソッドを適用します。

@Test
public void givenSpliterator__whenAppliedToAListOfArticle__thenSplittedInHalf() {
    Spliterator<Article> split1 = Executor.generateElements().spliterator();
    Spliterator<Article> split2 = split1.trySplit();

    assertThat(new Task(split1).call())
      .containsSequence(Executor.generateElements().size()/2 + "");
    assertThat(new Task(split2).call())
      .containsSequence(Executor.generateElements().size()/2 + "");
}

  • 分割プロセスは意図したとおりに動作し、レコードを均等に分割しました** 。


2.3.



推定サイズ



estimatedSize

メソッドは、推定要素数を返します。

LOG.info("Size: " + split1.estimateSize());

これは出力されます:

Size: 17500


2.4.



hasCharacteristics


このAPIは、与えられた特性が

Spliteratorのプロパティと一致するかどうかをチェックします。その後、上記のメソッドを呼び出すと、出力はそれらの特性の

int__表現になります。

LOG.info("Characteristics: " + split1.characteristics());

Characteristics: 16464

3.


Spliterator

の特徴

それはその動作を説明する8つの異なる特性を持っています。

これらは外部ツールのヒントとして使うことができます。

正確な数の要素を返すことができる場合は



SIZED

**



estimateSize()

メソッドで



SORTED

** – ソートされたソースを反復処理する場合



  • SUBSIZED




    trySplit()

    メソッドを使用してインスタンスを分割した場合

同様に

SIZED

のSpliteratorを入手する



CONCURRENT

** – ソースを同時に安全に修正できる場合



  • DISTINCT


    – 検出された要素の各ペア__x、yに対して

!x.equals(y)




IMMUTABLE__ ** – ソースによって保持されている要素が構造的にできない場合

修正済み



NONNULL

** – sourceがnullを保持するかしないか



  • ORDERED


    – 順序付きシーケンスを反復処理する場合

4.カスタム

分割子


4.1. カスタマイズする場合

まず、以下のシナリオを想定しましょう。

著者のリストを含む記事クラスと、複数の著者を含めることができる記事があります。さらに、関連記事のIDが記事IDと一致する場合、その記事に関連する作者を検討します。


Author

クラスは次のようになります。

public class Author {
    private String name;
    private int relatedArticleId;

   //standard getters, setters & constructors
}

次に、一連の著者を横断しながら著者を数えるためのクラスを実装します。その後、クラスはストリームに対してリダクションを実行します。

クラスの実装を見てみましょう。

public class RelatedAuthorCounter {
    private int counter;
    private boolean isRelated;

   //standard constructors/getters

    public RelatedAuthorCounter accumulate(Author author) {
        if (author.getRelatedArticleId() == 0) {
            return isRelated ? this : new RelatedAuthorCounter( counter, true);
        } else {
            return isRelated ? new RelatedAuthorCounter(counter + 1, false) : this;
        }
    }

    public RelatedAuthorCounter combine(RelatedAuthorCounter RelatedAuthorCounter) {
        return new RelatedAuthorCounter(
          counter + RelatedAuthorCounter.counter,
          RelatedAuthorCounter.isRelated);
    }
}

上記のクラスの各メソッドは、移動中にカウントするための特定の操作を実行します。

最初に、


acccumulate()メソッドは作者を1つずつ反復的にトラバース


し、次に

combine()__がそれらの値を使って2つのカウンタを合計します

最後に、

getCounter()

がカウンタを返します。

それでは、これまでに行ったことをテストします。記事の著者リストを一連の著者に変換しましょう。

Stream<Author> stream = article.getListOfAuthors().stream();

そして

RelatedAuthorCounter


を使用してストリームのリダクションを実行する


countAuthor()

メソッドを実装します。

private int countAutors(Stream<Author> stream) {
    RelatedAuthorCounter wordCounter = stream.reduce(
      new RelatedAuthorCounter(0, true),
      RelatedAuthorCounter::accumulate,
      RelatedAuthorCounter::combine);
    return wordCounter.getCounter();
}

シーケンシャルストリームを使用した場合、出力は期待どおりになります

“ count = 9”

、ただし、操作を並列化しようとすると問題が発生します。

次のテストケースを見てみましょう。

@Test
void
  givenAStreamOfAuthors__whenProcessedInParallel__countProducesWrongOutput() {
    assertThat(Executor.countAutors(stream.parallel())).isGreaterThan(9);
}

どうやら何かが間違っている – ストリームをランダムな位置に分割すると著者が2回数えられるようになった。


4.2. カスタマイズ方法

これを解決するには、関連する

id



articleId

が一致する場合にのみ著者を分割する

Spliterator

を実装する必要があります。これが私たちのカスタム

Spliterator

の実装です。

public class RelatedAuthorSpliterator implements Spliterator<Author> {
    private final List<Author> list;
    AtomicInteger current = new AtomicInteger();
   //standard constructor/getters

    @Override
    public boolean tryAdvance(Consumer<? super Author> action) {
        action.accept(list.get(current.getAndIncrement()));
        return current.get() < list.size();
    }

    @Override
    public Spliterator<Author> trySplit() {
        int currentSize = list.size() - current.get();
        if (currentSize < 10) {
            return null;
        }
        for (int splitPos = currentSize/2 + current.intValue();
          splitPos < list.size(); splitPos++) {
            if (list.get(splitPos).getRelatedArticleId() == 0) {
                Spliterator<Author> spliterator
                  = new RelatedAuthorSpliterator(
                  list.subList(current.get(), splitPos));
                current.set(splitPos);
                return spliterator;
            }
        }
        return null;
   }

   @Override
   public long estimateSize() {
       return list.size() - current.get();
   }

   @Override
   public int characteristics() {
       return CONCURRENT;
   }
}


countAuthors()

メソッドを適用すると正しい出力が得られます。次のコードはそれを示しています。

@Test
public void
  givenAStreamOfAuthors__whenProcessedInParallel__countProducesRightOutput() {
    Stream<Author> stream2 = StreamSupport.stream(spliterator, true);

    assertThat(Executor.countAutors(stream2.parallel())).isEqualTo(9);
}

また、カスタム

Spliterator

は、作成者のリストから作成され、現在の位置を保持することによってそれを通過します。

各メソッドの実装についてさらに詳しく説明しましょう。



  • tryAdvance



    は、現在のインデックスで

    Consumer

    に作成者を渡します。

位置とその位置をインクリメントする




trySplit ** –は分割メカニズムを定義します。


RelatedAuthorSpliterator

はIDが一致したときに作成され、
分割すると、リストが2つの部分に分割されます



estimatedSize

** – リストサイズとサイズの差

現在繰り返されている著者の位置




特性** –

Spliterator

特性を返します。


estimatedSize()

メソッドによって返される値としての私たちのケース

SIZED

は正確です。さらに、

CONCURRENT

は、この

Spliterator

のソースが他のスレッドによって安全に変更される可能性があることを示します。


5プリミティブ値のサポート


  • Spliterator


    API

    は、

    double



    int

    、および

    long

    ** を含むプリミティブ値をサポートします。

ジェネリック専用とプリミティブ専用

Spliterator

を使用することの唯一の違いは、指定された

Consumer



Spliterator

の型です。

たとえば、

int

値に必要なときは、

intConsumer

を渡す必要があります。さらに、これはプリミティブ専用

分割器

のリストです。


  • OfPrimitive <T、T

    CONS、T__SPLITRはSpliteratorを拡張したものです。OfPrimitive<T、

T

CONS、T

SPLITR >>

:他のプリミティブの親インタフェース
**

OfInt

:

int

に特化した

Spliterator__


  • OfDouble

    :

    double

    専用の

    Spliterator


  • OfLong

    :

    long

    専用の

    Spliterator


6. 結論

この記事では、Java 8 ** Splitrator __の使用法、メソッド、特性、分割プロセス、プリミティブサポート、およびそれをカスタマイズする方法について説明しました。

いつものように、この記事の完全な実装はhttps://github.com/eugenp/tutorials/tree/master/core-java-8[Githubで動く]にあります。