the below method compiles without problems:
static Stream<Optional<? extends Number>> getNumbers(Stream<Number> numbers) {
return numbers.map(Optional::of);
}
yet if I add a simple filtering to it like this:
static Stream<Optional<? extends Number>> getNumbers2(Stream<Number> numbers) {
return numbers.map(Optional::of).filter(number -> true);
}
it generates the following error:
incompatible types:
java.util.stream.Stream<java.util.Optional<java.lang.Number>> cannot be converted to
java.util.stream.Stream<java.util.Optional<? extends java.lang.Number>>
tested on openJdk-11 and openJdk-17.
I'd expect them both to do the same (either both compile ok or both generate the same compilation error), so I'm really puzzled by this: what is the general rule here that explains why the 1st method compiles ok yet the 2nd does not? Thanks!
Compatibility with the return type
Stream<Optional<? extends Number>>
in the first case is not obtained by virtue ofnumbers.map(Optional::of)
returning aStream<Optional<? extends Number>>
on its own; it's the compiler inferring the return type ofnumbers.map(...)
due to it being a generic method:while
Stream.filter()
is not:Therefore, in the first case the compiler can take into account the return statement's context (
getNumbers
's type) when inferring type ofnumbers.map(...)
.Compiler cannot do the same for
numbers.map(...)
in the second case, as there are subsequent chained calls, that may further change the type, so it would be very hard to guess what the right inferring should be at this stage. As a result, the most specific possible type is assumed fornumbers.map(...)
(Stream<Optional<Number>>
) and further carried on byfilter(...)
.As a different example to illustrate that, please figure out why both of these compile (
List.of()
is the same code, after all):Now, why does this fail:
That's because
List.subList(...)
does not infer the returned list'sE
type in context (i.e., the method is not generic), it carries theList
instance'sE
type, which, withList.of()
in that case gets defaulted toObject
(yes, when you havereturn List.of();
, return type inference kicks in, forcing the compiler to figure out that the intent is to makeE
matchString
, the type argument in the method's return type). Please note that this gets more complex than that, there are corners where inference doesn't work as wished/expected.Short answer:
return numbers.map(Optional::of)
takes advantage of type inference asmap()
is generic, andfilter()
does not, expecting theE
ofStream<E>
to be carried. And withnumbers.map(Optional::of)
,E
isOptional<Number>
, notOptional<? extends Number>
, andfilter
carries that.