How can I write a method for combining a Stream
of Consumers
into a single Consumer
using Consumer.andThen(Consumer)
?
My first version was:
<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 */ };
}
This version compiles with JavaC and Eclipse. But it is too specific: The Stream
cannot be a Stream<SpecialConsumer>
, and if the Consumers
are not exactly of type T
but a super type of it, it cannot be used:
Stream<? extends Consumer<? super Foo>> consumers = ... ;
combine(consumers);
That won't compile, rightfully. The improved version would be:
<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {
return consumers
.filter(Objects::nonNull)
.reduce(Consumer::andThen)
.orElse(noOpConsumer());
}
But neither Eclipse nor JavaC compile that:
Eclipse (4.7.3a):
The type
Consumer
does not defineandThen(capture#7-of ? extends Consumer<? super T>, capture#7-of ? extends Consumer<? super T>)
that is applicable here
JavaC (1.8.0172):
error: incompatible types: invalid method reference
.reduce(Consumer::andThen)
incompatible types:Consumer<CAP#1>
cannot be converted toConsumer<? super CAP#2>
whereT
is a type-variable:
T extends Object
declared in method<T>combine(Stream<? extends Consumer<? super T>>)
whereCAP#1
,CAP#2
are fresh type-variables:
CAP#1 extends Object super: T from capture of ? super T
CAP#2 extends Object super: T from capture of ? super T
But it should work: Every subclass of Consumer can be used as a Consumer, too. And every Consumer of a super-type of X can consume Xs, too. I tried to add type parameters to each line of the stream version, but that won't help. But if I write it down with a traditional loop, it compiles:
<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;
}
(Filtering out the null values is left out for conciseness.)
Therefore, my question is: How can I convince JavaC and Eclipse that my Code is correct? Or, if it is not correct: Why is the loop-version correct but not the Stream
Version?