160

いつcollect()vsを使いreduce()ますか? いずれかの方法に進む方が確実に良い場合の具体的な良い例はありますか?

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

これが変更可能なリダクションであることを考えると、(内部的に) 同期が必要であり、それがパフォーマンスに悪影響を与える可能性があると思います。おそらくreduce()、reduce の各ステップの後に返すために新しいデータ構造を作成する必要があるという犠牲を払って、より容易に並列化できます。

ただし、上記のステートメントは当て推量であり、ここで専門家に意見を求めたいと思います.

4

8 に答える 8

52

その理由は単純に次のとおりです。

  • collect() 変更可能な結果オブジェクトでのみ機能します。
  • reduce()不変の結果オブジェクトで動作するように設計されています。

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()可変」の例

たとえば、それを使用して手動で合計を計算したい場合は、たとえばfromでのみ使用collect()できます。見る:BigDecimalMutableIntorg.apache.commons.lang.mutable

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);
}

これが機能するのは、アキュムレータ container.add(employee.getSalary().intValue());が結果とともに新しいオブジェクトを返すのではなくcontainer、 type のミュータブルの状態を変更することになっているためMutableIntです。

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

于 2016-08-02T18:41:00.970 に答える
37

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

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

対照的にCollectors.toList()、同様の方法で動作します。ただし、リストに値を累積する場合、スレッド セーフが保証されます。メソッドドキュメントcollectから:

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

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

いつcollect()vsを使いreduce()ますか?

、などの不変の値がある場合ints、通常の削減は問題なく機能します。ただし、値を(可変データ構造) にする必要がある場合は、メソッドで可変リダクションを使用する必要があります。doublesStringsreduceListcollect

于 2016-06-01T22:18:52.000 に答える
3

これらは、実行時の潜在的なメモリ フットプリントが大きく異なります。はすべてのデータを収集してコレクションに入れcollect()ますが、ストリームを通過したデータを減らす方法を明示的に指定するように求めます。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);

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

于 2016-07-21T20:12:31.873 に答える