web-dev-qa-db-ja.com

Java 8 Streams-collect vs reduce

collect()reduce()はいつ使用しますか?誰かがどちらかの道を行く方が間違いなく良いときの良い、具体的な例を持っていますか?

Javadocはcollect()が変更可能な縮小であると述べています

可変の削減であるため、同期が(内部的に)必要であり、これがパフォーマンスに悪影響を与える可能性があると思います。おそらく、reduce()は、reduceのすべてのステップの後に戻るための新しいデータ構造を作成しなければならないというコストで、より容易に並列化できます。

ただし、上記の説明は当て推量であり、ここで専門家にご相談ください。

115
jimhooker2002

reduceは " fold "操作です。これは、演算子の最初の引数が前のアプリケーションの戻り値であり、2番目の引数が現在のストリーム要素であるストリームの各要素にバイナリ演算子を適用します。

collectionは、「コレクション」が作成され、各コレクションがそのコレクションに「追加」される集約操作です。その後、ストリームのさまざまな部分のコレクションが一緒に追加されます。

リンクしたドキュメント は、2つの異なるアプローチを持つ理由を示します。

文字列のストリームを取得し、それらを単一の長い文字列に連結する場合は、通常の削減でこれを実現できます。

 String concatenated = strings.reduce("", String::concat)  

望ましい結果が得られ、並行して動作します。ただし、パフォーマンスに満足できない場合があります。このような実装では、大量の文字列のコピーが行われ、実行時の文字数はO(n ^ 2)になります。よりパフォーマンスの高いアプローチは、結果をStringBuilderに蓄積することです。StringBuilderは、文字列を蓄積するための可変コンテナです。通常のリダクションの場合と同じ手法を使用して、可変リダクションを並列化できます。

したがって、並列化は両方の場合で同じですが、reduceの場合、関数をストリーム要素自体に適用します。 collectの場合、関数を可変コンテナに適用します。

96

理由は単純です:

  • collect()動作のみ with mutable結果オブジェクト。
  • reduce()動作するように設計されている with immutable結果オブジェクトです。

「不変のreduce()」の例

public class Employee {
  private Integer salary;
  public Employee(String aSalary){
    this.salary = new Integer(aSalary);
  }
  public Integer getSalary(){
    return this.salary;
  }
}

@Test
public void testReduceWithImmutable(){
  List<Employee> list = new LinkedList<>();
  list.add(new Employee("1"));
  list.add(new Employee("2"));
  list.add(new Employee("3"));

  Integer sum = list
  .stream()
  .map(Employee::getSalary)
  .reduce(0, (Integer a, Integer b) -> Integer.sum(a, b));

  assertEquals(Integer.valueOf(6), sum);
}

collect() with mutable」の例

例えば。 collect()を使用して手動で合計を計算したい場合は、BigDecimalでは機能せず、たとえばorg.Apache.commons.lang.mutableMutableIntでのみ機能します。見る:

public class Employee {
  private MutableInt salary;
  public Employee(String aSalary){
    this.salary = new MutableInt(aSalary);
  }
  public MutableInt getSalary(){
    return this.salary;
  }
}

@Test
public void testCollectWithMutable(){
  List<Employee> list = new LinkedList<>();
  list.add(new Employee("1"));
  list.add(new Employee("2"));

  MutableInt sum = list.stream().collect(
    MutableInt::new, 
    (MutableInt container, Employee employee) -> 
      container.add(employee.getSalary().intValue())
    , 
    MutableInt::add);
  assertEquals(new MutableInt(3), sum);
}

これは、 accumulatorcontainer.add(employee.getSalary().intValue());が結果とともに新しいオブジェクトを返すのではなく、タイプcontainerの可変MutableIntの状態を変更することになっているため機能します。

BigDecimalの代わりにcontainerを使用する場合は、containerは不変であるため、collect()BigDecimalを変更しないため、container.add(employee.getSalary());メソッドを使用できません。 (BigDecimalには空のコンストラクタがないため、BigDecimal::newは機能しません)

30
Sandro

通常の縮約は、int、doubleなどの2つのimmutable値を組み合わせて新しい値を生成することを意味します。それは不変削減です。対照的に、collectメソッドは、コンテナをmutateして、生成されるはずの結果を蓄積するように設計されています。

問題を説明するために、以下のような単純なリダクションを使用してCollectors.toList()を達成したいとします。

    List<Integer> numbers = stream.reduce( new ArrayList<Integer>(), 
    (List<Integer> l, Integer e) -> {
     l.add(e); 
     return l; 
    },
     (List<Integer> l1, List<Integer> l2) -> { 
    l1.addAll(l2); return l1; });

これはCollectors.toList()と同等です。ただし、この場合、List<Integer>を変更します。知っているように、ArrayListはスレッドセーフではなく、反復中に値を追加/削除しても安全なので、リストを更新するときに並行例外、arrayIndexOutBound例外、または任意の種類の例外(特に並列実行の場合)が発生しますまたは、整数を累積(追加)してリストを変更しているため、コンバイナはリストをマージしようとします。このスレッドセーフにしたい場合は、毎回新しいリストを渡す必要がありますが、これはパフォーマンスを低下させます。

対照的に、Collectors.toList()は同様に機能します。ただし、値をリストに蓄積すると、スレッドセーフが保証されます。 collectメソッドのドキュメントから:

コレクターを使用して、このストリームの要素に対して可変リダクション操作を実行します。ストリームが並列で、コレクターが並行であり、ストリームが順序付けられていないか、コレクターが順序付けられていない場合、並行削減が実行されます。 並列で実行されると、複数の中間結果がインスタンス化、移植、およびマージされて、可変データ構造の分離が維持されます。そのため、スレッドセーフでないデータ構造(ArrayListなど)と並行して実行された場合でも、並列削減のために追加の同期は必要ありません。link =

あなたの質問に答えるために:

collect()reduce()はいつ使用しますか?

intsdoublesStringsなどの不変の値がある場合、通常のリダクションは正常に機能します。ただし、値をreduceからList(可変データ構造)に変更する必要がある場合は、collectメソッドで可変リダクションを使用する必要があります。

20
george

ストリームをa <-b <-c <-dとする

削減では、

((a#b)#c)#d

ここで、#は実行したい興味深い操作です。

コレクションでは、

コレクターには、何らかの収集構造Kがあります。

Kはaを消費します。 Kはbを消費します。 Kはcを消費します。 Kはdを消費します。

最後に、Kに最終結果を尋ねます。

Kはそれをあなたに与えます。

7
Yan Ng

veryは、実行時の潜在的なメモリフットプリントが異なります。 collect()は収集してallデータをコレクションに入れますが、reduce()はストリームを介してデータを削減する方法を指定するように明示的に要求します。

たとえば、ファイルからデータを読み取って処理し、データベースに格納する場合、次のようなJavaストリームコードになります。

streamDataFromFile(file)
            .map(data -> processData(data))
            .map(result -> database.save(result))
            .collect(Collectors.toList());

この場合、collect()を使用してJavaにデータを強制的にストリームさせ、結果をデータベースに保存します。 collect()がなければ、データは読み込まれず保存されません。

このコードは、ファイルサイズが十分に大きい場合、またはヒープサイズが十分に小さい場合、Java.lang.OutOfMemoryError: Java heap spaceランタイムエラーを生成します。明らかな理由は、ストリームを介して作成されたすべてのデータ(および実際には既にデータベースに保存されている)を結果のコレクションにスタックしようとするためであり、これによりヒープが爆発します。

ただし、collect()reduce()に置き換えた場合、問題はなくなります。後者は、それを通過したすべてのデータを削減して破棄するためです。

提示された例では、collect()reduceのあるものに置き換えるだけです。

.reduce(0L, (aLong, result) -> aLong, (aLong1, aLong2) -> aLong1);

Javaは純粋なFP(関数型プログラミング)言語ではなく、使用されていないデータを最適化できないため、resultに依存する計算を行う必要さえありません。副作用の可能性があるため、ストリームの最下部で。

2
averasko

ここにコード例があります

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().reduce((x,y) -> {
        System.out.println(String.format("x=%d,y=%d",x,y));
        return (x + y);
    }).get();

System.out.println(sum);

実行結果は次のとおりです。

x=1,y=2
x=3,y=3
x=6,y=4
x=10,y=5
x=15,y=6
x=21,y=7
28

リデュース関数は2つのパラメーターを処理します。最初のパラメーターはストリーム内の前の戻り値、2番目のパラメーターはストリーム内の現在の計算値、最初の値と現在の値を次の計算の最初の値として合計します。

1
JetQin

ドキュメント

Reduction()コレクターは、groupingByまたはpartitioningByの下流のマルチレベルリダクションで使用する場合に最も役立ちます。ストリームで単純なリダクションを実行するには、代わりにStream.reduce(BinaryOperator)を使用します。

したがって、基本的に、コレクト内で強制される場合にのみreducing()を使用します。別の を次に示します。

 For example, given a stream of Person, to calculate the longest last name 
 of residents in each city:

    Comparator<String> byLength = Comparator.comparing(String::length);
    Map<String, String> longestLastNameByCity
        = personList.stream().collect(groupingBy(Person::getCity,
            reducing("", Person::getLastName, BinaryOperator.maxBy(byLength))));

このチュートリアル によると、reduceは効率が悪い場合があります

縮小操作は常に新しい値を返します。ただし、アキュムレータ関数は、ストリームの要素を処理するたびに新しい値も返します。ストリームの要素をコレクションなどのより複雑なオブジェクトに縮小するとします。これは、アプリケーションのパフォーマンスを妨げる可能性があります。縮小操作でコレクションに要素を追加する場合、アキュムレータ関数が要素を処理するたびに、その要素を含む新しいコレクションが作成されますが、これは非効率的です。代わりに、既存のコレクションを更新する方が効率的です。これを行うには、次のセクションで説明するStream.collectメソッドを使用します...

したがって、IDは削減シナリオで「再利用」されるため、可能であれば.reduceを使用するほうがわずかに効率的です。

0
rogerdpack