2016-09-14 7 views
1

Java 8ストリームのコンテンツをリストに複数回追加するコードを記述する必要がありますし、何が最善の方法なのかわかりません。私はSOに読んだものに基づいて(主にこの質問:How to add elements of a Java8 stream into an existing List)と他の場所で、私は次のオプションにそれを絞り込むました:ストリームから既存のリストに要素を追加するより良い方法はありますか?

import java.util.ArrayList; 
import java.util.List; 
import java.util.function.Function; 
import java.util.stream.Collectors; 

public class Accumulator<S, T> { 


    private final Function<S, T> transformation; 
    private final List<T> internalList = new ArrayList<T>(); 

    public Accumulator(Function<S, T> transformation) { 
     this.transformation = transformation; 
    } 

    public void option1(List<S> newBatch) { 
     internalList.addAll(newBatch.stream().map(transformation).collect(Collectors.toList())); 
    } 

    public void option2(List<S> newBatch) { 
     newBatch.stream().map(transformation).forEach(internalList::add); 
    } 
} 

アイデアは方法が同じインスタンスに対して複数回呼び出されるだろうということですAccumulator。中間リストを使用し、ストリームの外側でCollection.addAll()を1回呼び出すか、各要素のストリームからcollection.add()を呼び出すかのどちらかを選択できます。

関数型プログラミングの精神の中でより多くのオプション2を好む傾向があり、中間リストを作成することは避けられますが、nが大きいときにadd()をn回呼び出すのではなく、addAll()を呼び出すと利点があります。

2つのオプションのどちらか一方が他のものよりも大幅に優れていますか?

EDIT:JB Nizetは、すべてのバッチが追加されるまで変換を遅らせる非常に涼しいanswerを持っています。私の場合は、変換がすぐに実行されることが必要です。

PS:私のサンプルコードでは、私は最善の解決策は、完全にその内部リストを避けて、第三ものであろうストリーム

+0

逆アセンブルされたバイトコード(javap)は、 –

+1

早すぎる最適化をしないでください。より洗練されたものを実行し、パフォーマンスの問題に遭遇した場合にのみ、このコードをプロファイラでチェックしてください。 –

+0

私は 'addAll()'を呼び出す利点はないと思います。 – shmosel

答えて

4

ファーストをするだろう、あなたの第2の変形は:

public void option2(List<S> newBatch) { 
    newBatch.stream().map(transformation).forEachOrdered(internalList::add); 
} 

となるはずです。

Collector APIは、ストリームがコレクターに予想される大きさについてのヒントを提供することが可能とするために、アキュムレータの機能を評価するためのストリームを必要としないように、そのほかに、

public void option1(List<S> newBatch) { 
    internalList.addAll(newBatch.stream().map(transformation).collect(Collectors.toList())); 
} 

addAllの利点は議論の余地がありますすべての要素は、現在の実装ではArrayList::add以外です。このアプローチはaddAllから任意の利益を得ることができる前に

だから、それは潜在的な容量増加操作を含む、繰り返しArrayListaddを呼び出すことによってArrayListを満たしました。だから、後悔することなくoption2に泊まることができます。

代替は、一時的なコレクションのためのストリームビルダを使用することです:

public class Accumulator<S, T> { 
    private final Function<S, T> transformation; 
    private final Stream.Builder<T> internal = Stream.builder(); 

    public Accumulator(Function<S, T> transformation) { 
     this.transformation = transformation; 
    } 

    public void addBatch(List<S> newBatch) { 
     newBatch.stream().map(transformation).forEachOrdered(internal); 
    } 

    public List<T> finish() { 
     return internal.build().collect(Collectors.toList()); 
    } 
} 

ストリームビルダは、その容量を増やす際に内容をコピーする必要はありません背骨のバッファを使用しますが、解決策はまだ実際に苦しみます最終的な収集ステップでは、適切な初期容量なしで(現在の実装では)ArrayListを埋め込む必要があります。我々は、一般的な配列型のためにそれを行うことができないとして、(現在の実装では

は、それが

public List<T> finish() { 
    return Arrays.asList(internal.build().toArray(…)); 
} 

として仕上げ工程を実装するためにはるかに効率的だが、これはどちらかが必要です、呼び出し側が提供するIntFunction<T[]> )、またはチェックされていない操作(Object[]T[]であると思われますが、これは正常ですが、依然として厄介な操作です)。

5

上で実行する必要がどのような操作のためのプレースホルダとしてtransformationを使用しました。ただ、ストリームは、あなたのための最終的なリストを作成してみましょう:あなたは、同じ変換が適用されなければならないであなたのNバッチを含む、List<List<S>>を持っていると仮定すると

、あなたはすべての

List<T> result = 
    batches.stream() 
      .flatMap(batch -> batch.stream()) 
      .map(transformation) 
      .collect(Collectors.toList()); 
+0

これは最適なオプションです。これは固定サイズのリストを作成するためです。 –

+2

丁寧な回答ですが、すべてのバッチが準備できるまで、前のバッチの処理を延期する必要があります。それは常にそうであるとは限りません。 – Andreas

+0

良い答えですが、私はそれについて考えなかったでしょうが、私の場合、@アンドレアスが述べたように、処理を延期しないようにしています。私はそれに応じて質問を編集しています – ahelix

関連する問題