Consumer.andThen(Consumer)
を使用して、Stream
のConsumers
を単一の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
バージョンは正しくないのはなぜですか?
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);
_
メソッド定義の小さなことを忘れました。現在は次のとおりです。
<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());
}
これで動作するはずです
多数のコンシューマがある場合、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));