Generics are invariant, but this compiles without error

184 Views Asked by At

I was getting type mismatch errors, until I refactored code to this:

    public final Stream<Map.Entry<E, Integer>> orderedStreamOfEntries() {
        return this.m_map.entrySet()
                .stream()
                .sorted(Comparator.comparingInt(Entry::getValue))
                .map(AbstractMap.SimpleImmutableEntry::new);            
    }
  • The return type is Stream<Entry<E, Integer>>
  • The type of the stream at the end of this routine is Stream<SimpleImmutableEntry<E, Integer>>

The formal type parameter E has this definition:

<E extends Enum<E> & MyCustomInterface>

I don't understand why this appears to be acceptable to the compiler. Because Java generics are invariant, even though java.util.AbstractMap.SimpleImmutableEntry implements java.util.Map.Entry, I would have said that Stream<SimpleImmutableEntry<>> is not a subtype of the return type, Stream<Entry<>>.

1

There are 1 best solutions below

8
On BEST ANSWER

You're making two mistakes. The first is assuming SimpleImmutableEntry::new is a Function<Entry, SimpleImmutableEntry>*, when in fact it can be interpreted as a Function<Entry, Entry> which happens to return a SimpleImmutableEntry.

Secondly, look at the signature for map():

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

By returning ? extends R, the method is free to interpret R as a superclass of the lambdas return type, meaning even a Function<Entry, SimpleImmutableEntry> could result in a Stream<Entry>, or even Stream<Object>.

The actual interpretation depends on the inferred return type, which in your case is Stream<Entry>.

*Raw entry types used for brevity.