OhleC
OhleC

Reputation: 2950

Chain of Stream operations over generic types leads to Type errors

The following classes and methods:

class A<T extends B> { }
class B {}

Stream<A<? extends B>> find() {
    return findAll()                        // Stream<Optional<A<? extends B>>>
            .filter(Optional::isPresent)    // Stream<Optional<A<? extends B>>>
            .map(Optional::get)             // Stream<A<capture of ? extends B>>
            .filter(a -> false);            // Stream<A<capture of ? extends B>>
}

Stream<Optional<A<? extends B>>> findAll() {
    return Stream.empty();
}

Compile fine with javac, but lead to type errors in IDEA: screenshot

The errors disappear when I either

I can't make any sense of that. Is it an IDEA bug?

Edit: Some new insights thanks to LuCio :

  1. While my version of javac (10.0.2) does not complain, other javac versions and IDEs do, so it's not an IDEA bug

  2. Splitting the expression in two and giving the intermediate value an explicit type helps:

    Stream<A<? extends B>> aStream = findAll()
            .filter(Optional::isPresent)
            .map(Optional::get);
    return aStream
            .filter(a -> false);
    

    (note that the type IDEA infers for aStream is Stream<? extends A<? extends B>>, although it just accepts the assignment to this type)

So the question becomes: Why is the intermediate type the way it is, and why is it apparently OK to just ignore it by assigning to another type?

Upvotes: 2

Views: 137

Answers (1)

Ben R.
Ben R.

Reputation: 2153

It's because Stream.map has the following signature:

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

In this case, R is A<? extends B>. Hence, the return value of the function is implicitly

? extends A<? extends B>

At which point, I believe this question is to an extent answered by

Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?

This can be made to compile nicely by either changing the return type:

<T extends B> Stream<? extends A<? extends B>> find() {
    return findAll()                        // Stream<Optional<A<? extends B>>>
      .map(Optional::get)             // Stream<A<capture of ? extends B>>
      .filter(a -> false);            // Stream<A<capture of ? extends B>>
}

or explicitly casting the function to return A<? extends B>:

<T extends B> Stream<A<? extends B>> find() {
    return findAll()
      .map((Function<Optional<A<? extends B>>, A<? extends B>>) Optional::get)
      .filter(a -> false);
}

To clarify, a Stream<C>, where C extends A<B>, is not itself a Stream<A<? extends B>>.

Upvotes: 2

Related Questions