30

The Optional.or method was added in Java 9. This is the method signature

public Optional<T> or​(Supplier<? extends Optional<? extends T>> supplier)

Why is the type parameter of the Supplier taking ? extends Optional rather than just Optional, since Optional is a final class?

The same is true for the Optional.flatMap method. This is a change from Java 8.

In Java 8, it was Function<? super T, Optional<U>> mapper whereas it was changed to Function<? super T,​? extends Optional<? extends U>> in Java 9.

Boann
  • 48,794
  • 16
  • 117
  • 146
Thiyagu
  • 17,362
  • 5
  • 42
  • 79
  • Kinda seems like they're positioning it to not be a `final` class in future version? – nbrooks Dec 10 '18 at 01:42
  • @nbrooks It is because of the nested generics (and yes.. partly to due with the future changes) – Thiyagu Dec 10 '18 at 01:45
  • 3
    My understanding of how this works is that, if a type argument is not for a wildcard type, the match must always be invariant. If you want to allow a subtype anywhere within the generic parameters of a type, then you need to use a wildcard type (or a new type variable) out to the outermost generic declaration. I *think* this is implied by section 18.2.3, "Subtyping Constraints", of the "Type Inference" chapter of the Java Language Specification. But that chapter is hard for me to wrap my brain around and I'm not confident enough in my understanding to write an answer spelling it out exactly. – Daniel Pryden Dec 10 '18 at 17:52

3 Answers3

36

I found the reasoning behind this from Stuart Marks himself

http://mail.openjdk.java.net/pipermail/core-libs-dev/2016-October/044026.html

This has to do with nested generics (Optional is nested within Function). From the mail thread

 Function<..., Optional<StringBuilder>>

is not a subtype of

 Function<..., Optional<? extends CharSequence>>

To get around this, we have to add the outer wildcard as well, so that

 Function<..., Optional<StringBuilder>>

is a subtype of

 Function<..., ? extends Optional<? extends CharSequence>>
JAD
  • 2,035
  • 4
  • 21
  • 35
Thiyagu
  • 17,362
  • 5
  • 42
  • 79
  • 8
    +1. For me, the really mind-boggling thing here was why the easier "additional-type-parameter approach" ` Optional flatMap(Function super T, Optional> mapper)` would *not* work in all circumstances. – Stefan Zobel Dec 10 '18 at 08:48
11

FWIW, a similar issue with covariant arguments still exists in Stream.iterate and Stream.iterate in Java 11. The current method signatures are

static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)

These signatures do not allow for some combinations of seeds and UnaryOperators that are sound from a type perspective, e.g. the following doesn't compile:

UnaryOperator<String> op = s -> s; 
Stream<CharSequence> scs = iterate("", op); // error

The proposed solution is to change the method signatures to

static <T, S extends T> Stream<T> iterate(S seed, Predicate<? super S> hasNext, UnaryOperator<S> next)
static <T, S extends T> Stream<T> iterate(S seed, UnaryOperator<S> f)

So, in contrast to Optional.or and Optional.flatMap this is a case where the "additional-type-parameter approach" actually works.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
9

Yeah... it is said that wildcard with an extends-bound (upper bound) makes the type covariant, which means that for example List<Apple> is an actual subtype of List<? extends Fruit> (considering that Apple extends Fruit); this is also called covariance.

Or in the examples that you have shown, it means that Optional<StringBuilder> is a subtype of Optional<? extends Optional<? extends CharSequence>>, so you could for example do:

List<Optional<String>> left = new ArrayList<>();
List<? extends Optional<? extends CharSequence>> right = new ArrayList<>();

right = left; // will compile

or assign a Function to the other

Eugene
  • 117,005
  • 15
  • 201
  • 306