web-dev-qa-db-ja.com

Java 8 Generics:消費者のストリームを単一の消費者に削減

Consumer.andThen(Consumer)を使用して、StreamConsumersを単一のConsumerに結合するメソッドを作成するにはどうすればよいですか?

私の最初のバージョンは:

_<T> Consumer<T> combine(Stream<Consumer<T>> consumers) {
    return consumers
            .filter(Objects::nonNull)
            .reduce(Consumer::andThen)
            .orElse(noOpConsumer());
}

<T> Consumer<T> noOpConsumer() {
    return value -> { /* do nothing */ };
}
_

このバージョンは、JavaCおよびEclipseでコンパイルされます。しかし、あまりにも具体的です:Streamを_Stream<SpecialConsumer>_にすることはできません。また、ConsumersのタイプがTのタイプではなく、スーパークラスの場合は使用できません。

_Stream<? extends Consumer<? super Foo>> consumers = ... ;
combine(consumers);
_

当然、コンパイルできません。改善されたバージョンは次のとおりです。

_<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {
    return consumers
            .filter(Objects::nonNull)
            .reduce(Consumer::andThen)
            .orElse(noOpConsumer());
}
_

しかし、EclipseもJavaCもそれをコンパイルしません:
Eclipse(4.7.3a):

タイプConsumerは、ここで適用可能なandThen(capture#7-of ? extends Consumer<? super T>, capture#7-of ? extends Consumer<? super T>)を定義しません

JavaC(1.8.0172):

エラー:互換性のない型:無効なメソッド参照
.reduce(Consumer::andThen)
互換性のない型:_Consumer<CAP#1>_は_Consumer<? super CAP#2>_に変換できません
ここでTは型変数です。
_T extends Object_メソッド<T>combine(Stream<? extends Consumer<? super T>>)で宣言されています
where _CAP#1_、_CAP#2_は新しい型変数です:
_CAP#1 extends Object super: T from capture of ? super T_
_CAP#2 extends Object super: T from capture of ? super T_

ただし、動作するはずです。ConsumerのすべてのサブクラスもConsumerとして使用できます。また、スーパータイプのXのすべてのコンシューマーもXを消費できます。ストリームバージョンの各行に型パラメーターを追加しようとしましたが、それは役に立ちません。しかし、従来のループで書き留めると、コンパイルされます:

_<T> Consumer<T> combine(Collection<? extends Consumer<? super T>> consumers) {
    Consumer<T> result = noOpConsumer()
    for (Consumer<? super T> consumer : consumers) {
        result = result.andThen(consumer);
    }
    return result;
}
_

(簡潔にするため、null値のフィルタリングは省略されています。)

したがって、私の質問は次のとおりです。コードが正しいことをJavaCとEclipseにどのように納得させることができますかまたは、正しくない場合:ループバージョンは正しいが、Streamバージョンは正しくないのはなぜですか?

31
user194860

1つの引数を使用します Stream.reduce(accumulator) 次のシグネチャを持つバージョン:

_Optional<T> reduce(BinaryOperator<T> accumulator);
_

_BinaryOperator<T> accumulator_はT型の要素のみを受け入れますが、次のものがあります。

_<? extends Consumer<? super T>>
_

代わりに、引数が3つのバージョンの Stream.reduce(...) メソッドを使用することをお勧めします。

_<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator
             BinaryOperator<U> combiner);
_

_BiFunction<U, ? super T, U> accumulator_は、2つの異なるタイプのパラメーターを受け入れることができ、制限が緩和されており、状況により適しています。考えられる解決策は次のとおりです。

_<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {
    return consumers.filter(Objects::nonNull)
                    .reduce(t -> {}, Consumer::andThen, Consumer::andThen);
}
_

3番目の引数_BinaryOperator<U> combiner_は並列ストリームでのみ呼び出されますが、とにかく正しい実装を提供するのが賢明でしょう。

さらに、理解を深めるために、上記のコードを次のように表すことができます。

_<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {

    Consumer<T> identity = t -> {};
    BiFunction<Consumer<T>, Consumer<? super T>, Consumer<T>> acc = Consumer::andThen;
    BinaryOperator<Consumer<T>> combiner = Consumer::andThen;

    return consumers.filter(Objects::nonNull)
                    .reduce(identity, acc, combiner);
}
_

今、あなたは書くことができます:

_Stream<? extends Consumer<? super Foo>> consumers = Stream.of();
combine(consumers);
_
27

メソッド定義の小さなことを忘れました。現在は次のとおりです。

<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {}

ただし、Consumer<? super T>を再取得しています。そのため、戻り値の型を変更することで、ほとんど機能します。タイプStream<? extends Consumer<? super T>>の引数consumersを受け入れます。現在は、Consumer<? super T>の異なるサブクラスと実装を使用しているため、機能しません(上限のワイルドカードextendsのため)。 Stream内のすべての? extends Consumer<? super T>を単純なConsumer<? super T>にキャストすることで、これを克服できます。次のように:

<T> Consumer<? super T> combine(Stream<? extends Consumer<? super T>> consumers) {
    return consumers
        .filter(Objects::nonNull)
        .map(c -> (Consumer<? super T>) c)
        .reduce(Consumer::andThen)
        .orElse(noOpConsumer());
}

これで動作するはずです

1
Lino

多数のコンシューマがある場合、Consumer.andThen()を適用すると、再帰的に処理されて元の各コンシューマを呼び出すコンシューマラッパーの巨大なツリーが作成されます。

したがって、単純にコンシューマーのリストを作成し、それらを繰り返す単純なコンシューマーを作成する方が効率的です。

<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {
    List<Consumer<? super T>> consumerList = consumers
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    return t -> consumerList.forEach(c -> c.accept(t));
}

あるいは、結果のコンシューマーが一度だけ呼び出され、Streamがその時点でまだ有効であることを保証できる場合、ストリームを直接繰り返し処理することができます。

return t -> consumers
        .filter(Objects::nonNull)
        .forEach(c -> c.accept(t));
0
Didier L