web-dev-qa-db-ja.com

Java 8'Collector 'クラスがこのように設計されているのはなぜですか?

Java 8は新しいStreamAPIを導入し、Java.util.stream.Collectorは、データストリームを集約/収集する方法を定義するためのインターフェイスです。

ただし、コレクターインターフェイスは次のように設計されています。

public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
}

なぜ次のように設計されていないのですか?

public interface Collector<T, A, R> {
    A supply();
    void accumulate(A accumulator, T value);
    A combine(A left, A right);
    R finish(A accumulator);
}

後者の方がはるかに簡単に実装できます。前者として設計する際の考慮事項は何でしたか?

34
popcorny

実際、それはもともとあなたが提案したものと同じように設計されました。プロジェクトラムダリポジトリの 初期の実装 を参照してください(makeResultは現在supplierです)。後で 更新 現在の設計になりました。このような更新の理由は、コレクターコンビネーターを単純化することだと思います。このトピックに関する具体的な議論は見つかりませんでしたが、mappingコレクターが同じチェンジセットに表示されたという事実によって私の推測は裏付けられています。 Collectors.mappingの実装を検討してください。

public static <T, U, A, R>
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
                           Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return new CollectorImpl<>(downstream.supplier(),
                               (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
                               downstream.combiner(), downstream.finisher(),
                               downstream.characteristics());
}

この実装では、accumulator関数のみを再定義する必要があり、suppliercombiner、およびfinisherはそのままなので、suppliercombiner、またはfinisherを呼び出すときに追加の間接性はありません。元の関数から返された関数を直接呼び出すだけです。 collectingAndThenではさらに重要です。

public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
                                                            Function<R,RR> finisher) {
    // ... some characteristics transformations ...
    return new CollectorImpl<>(downstream.supplier(),
                               downstream.accumulator(),
                               downstream.combiner(),
                               downstream.finisher().andThen(finisher),
                               characteristics);
}

ここではfinisherのみが変更されていますが、元のsupplieraccumulator、およびcombinerが使用されています。 accumulatorはすべての要素に対して呼び出されるため、間接参照を減らすことが非常に重要になる可能性があります。提案された設計でmappingcollectingAndThenを書き直してみると、問題が発生します。 filteringflatMappingなどの新しいJDK-9コレクターも、現在の設計の恩恵を受けています。

26
Tagir Valeev

構成は継承よりも優先されます。

あなたの質問の最初のパターンは、一種のモジュール構成です。 Collectorインターフェースの実装は、Supplier、Accumulatorなどにさまざまな実装を提供できます。つまり、既存のSupplierのプールからCollector実装を構成できます。アキュムレータなどの実装。これは再利用にも役立ち、2つのコレクターが同じアキュムレーター実装を使用する場合があります。 Stream.collect()は、提供された動作を使用します。

2番目のパターンでは、コレクターの実装はすべての機能を単独で実装する必要があります。あらゆる種類のバリエーションは、親の実装をオーバーライドする必要があります。再利用する余地はあまりありません。さらに、2つのコレクターがステップに対して同様のロジック(たとえば、累積)を持っている場合は、コードが重複します。

18
S.D.

2つの関連する理由

  • 機能構成コンビネータ経由。 (まだOO構成を行うことができますが、以下の点を見てください)
  • 代入ターゲットが関数型インターフェースの場合、ラムダ式またはメソッド参照を介して簡潔な表現コードでビジネスロジックをフレーミングする可能性。

    機能構成

    Collectors APIは、コンビネータを介した機能構成への道を開きます。小さい/最小の再利用可能な機能を構築し、これらのいくつかを興味深い方法で組み合わせて高度な機能/機能にします。

    簡潔な表現コード

    以下では、関数ポインター(Employee :: getSalary)を使用して、マッパーの機能をEmployeeオブジェクトからintに入力しています。 summingIntは、intを追加するロジックを満たしているため、結合すると、1行の宣言型コードに給与の合計が書き込まれます。

    //従業員の給与の合計を計算しますinttotal = employee.stream()。collect(Collectors.summingInt(Employee :: getSalary)));

0
Ira