2

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

  • Remove the filter(Optional::isPresent()).map(Optional::get) pair
  • Remove the ultimate filter call

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?

OhleC
  • 2,821
  • 16
  • 29

1 Answers1

2

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>>.

Ben R.
  • 1,965
  • 1
  • 13
  • 23
  • Right, I think the `map` signature makes it clear why there would be a covariance issue. So I guess the fact that `javac` compiles this without complaints is that it inferred the return type from the concrete function I passed in? – OhleC Feb 08 '19 at 21:00
  • If you're asking why your second insight worked - `Splitting the expression in two and giving the intermediate value an explicit type helps` - then yes. Assigning it to a variable with the correct signature will have the same effect as explicitly casting it as in my answer above. In one, we're explicitly casting, in the other, it's implicitly done by the compiler inferring the return type from having been assigned to that variable. – Ben R. Feb 11 '19 at 11:07
  • That's not what I meant — the original, unchanged code, without any casting (implicit or explicit) compiles fine with javac (10.0.2). – OhleC Feb 11 '19 at 12:58
  • 1
    Right. At this point, I don't think anyone will. I'll accept this answer, then. – OhleC Feb 11 '19 at 14:28
  • Thanks @OhleC, much appreciated. Shame we couldn't figure it out together! – Ben R. Feb 11 '19 at 16:32