Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

filtering a stream changes its wildcard bounds?

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!

like image 550
morgwai Avatar asked Dec 21 '21 09:12

morgwai


People also ask

How does filter work in streams?

filter() is a intermediate Stream operation. It returns a Stream consisting of the elements of the given stream that match the given predicate. The filter() argument should be stateless predicate which is applied to each element in the stream to determine if it should be included or not.

How do you filter an object with a stream?

Java stream provides a method filter() to filter stream elements on the basis of given predicate. Suppose you want to get only even elements of your list then you can do this easily with the help of filter method. This method takes predicate as an argument and returns a stream of consisting of resulted elements.

What is filter in stream API?

The filter() function of the Java stream allows you to narrow down the stream's items based on a criterion. If you only want items that are even on your list, you can use the filter method to do this. This method accepts a predicate as an input and returns a list of elements that are the results of that predicate.


1 Answers

Compatibility with the return type Stream<Optional<? extends Number>> in the first case is not obtained by virtue of numbers.map(Optional::of) returning a Stream<Optional<? extends Number>> on its own; it's the compiler inferring the return type of numbers.map(...) due to it being a generic method:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

while Stream.filter() is not:

Stream<T> filter(Predicate<? super T> predicate);

Therefore, in the first case the compiler can take into account the return statement's context (getNumbers's type) when inferring type of numbers.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 for numbers.map(...) (Stream<Optional<Number>>) and further carried on by filter(...).

As a different example to illustrate that, please figure out why both of these compile (List.of() is the same code, after all):

static List<String> stringList() {
    return List.of();
}
static List<Integer> intList() {
    return List.of();
}

Now, why does this fail:

static List<String> stringList() {
    return List.of().subList(0, 0);
}

That's because List.subList(...) does not infer the returned list's E type in context (i.e., the method is not generic), it carries the List instance's E type, which, with List.of() in that case gets defaulted to Object (yes, when you have return List.of();, return type inference kicks in, forcing the compiler to figure out that the intent is to make E match String, 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 as map() is generic, and filter() does not, expecting the E of Stream<E> to be carried. And with numbers.map(Optional::of), E is Optional<Number>, not Optional<? extends Number>, and filter carries that.

like image 79
ernest_k Avatar answered Oct 17 '22 12:10

ernest_k