Considering these classes and accumulation function, which represent a simplification of my original context (yet reproducing the same problem):
abstract static class Foo {
abstract int getK();
}
static class Bar extends Foo {
int k;
Bar(int k) { this.k = k; }
int getK() { return this.k; }
}
private static Foo combined(Foo a1, Foo a2) {
return new Bar(a1.getK() + a2.getK());
}
I have attempted to perform an accumulation of items (originally data indexing reports) by relying on a separate function, combined
, which deals directly with elements of type Foo
.
Foo outcome = Stream.of(1,2,3,4,5)
.map(Bar::new)
.reduce((a,b) -> combined(a, b))
.get();
It turns out that this code results in a compilation error (OpenJDK "1.8.0_92"): "Bad return type in lambda expression: Foo cannot be converted to Bar". The compiler insists on attempting to reduce the stream using Bar
as the accumulative element, even when there is Foo
as a common type for both the arguments to the cumulative function and its return type.
I also find peculiar that I can still take this approach as long as I explicitly map the stream into a stream of Foo
s:
Foo outcome = Stream.of(1,2,3,4,5)
.<Foo>map(Bar::new)
.reduce((a,b) -> combined(a, b))
.get();
Is this a limitation of Java 8's generic type inference, a small issue with this particular overload of Stream#reduce
, or an intentional behaviour that is backed by the Java specification? I have read a few other questions on SO where type inference has "failed", but this particular case is still a bit hard for me to grasp.
The problem is that you're definitely creating a BinaryOperator<Foo>
- you have to be, as you're returning a Foo
. If you change combined()
to be declared to return Bar
(while still accepting Foo
) then you'd be fine. It's the fact that the return type is tied to the input type that's the problem - it can't be either covariant or contravariant, because it's used for input and output.
To put it another way - you're expecting reduce((a, b) -> combined(a, b))
to return an Optional<Foo>
, right? So that suggests that you're expecting the T
of the reduce()
call to be Foo
- which means that it should be operating on a Stream<Foo>
. A Stream<Bar>
only has a single-parameter reduce
method that takes a BinaryOperator<Bar>
, and your lambda expression using combined
simple isn't a BinaryOperator<Bar>
.
Another alternative is to add a cast to the lambda expression:
Foo outcome = Stream.of(1,2,3,4,5)
.map(Bar::new)
.reduce((a,b) -> (Bar)combined(a, b))
.get();
See more on this question at Stackoverflow