Luke Hutchison
Luke Hutchison

Reputation: 9230

Why does Java type inference for generic supertypes break here?

Given this Java code:

import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;

public class Test {
    public static void main(String[] args) {
        SimpleEntry<Integer, String> simpleEntry = new SimpleEntry<>(1, "1");
        Optional<Entry<Integer, String>> optionalEntry = Optional.of(simpleEntry);
        Optional<SimpleEntry<Integer, String>> optionalSimpleEntry = Optional.of(simpleEntry);

        List<Entry<Integer, String>> list1 = Arrays.asList(simpleEntry);
        List<Optional<Entry<Integer, String>>> list2 = Arrays.asList(optionalEntry);
        List<Optional<SimpleEntry<Integer, String>>> list3 = Arrays.asList(optionalSimpleEntry);
        List<Optional<Entry<Integer, String>>> list4 = Arrays.asList(optionalSimpleEntry);
    }
}

The expressions initializing list, list2 and list3 work fine. However, the expression initializing list4 breaks with this error in Eclipse:

Type mismatch: cannot convert from List<Optional<AbstractMap.SimpleEntry<Integer,String>>>
to List<Optional<Map.Entry<Integer,String>>>

and this error in javac:

Test.java:16: error: incompatible types: inference variable T has incompatible bounds
        List<Optional<Entry<Integer, String>>> list4 = Arrays.asList(optionalSimpleEntry);
                                                                    ^
    equality constraints: Optional<Entry<Integer,String>>
    lower bounds: Optional<SimpleEntry<Integer,String>>
  where T is a type-variable:
    T extends Object declared in method <T>asList(T...)

But AbstractMap.SimpleEntry directly implements Map.Entry. So why does type inference break for list4 when it works for list1 to list3 (and also for the assignment to optionalEntry, for that matter)?

Particularly I don't understand why the assignment to list1 works when the assignment to list4 does not.

Upvotes: 5

Views: 159

Answers (2)

Erwin Bolwidt
Erwin Bolwidt

Reputation: 31299

I assume you understand why Optional<SimpleEntry<Integer,String>> cannot be assigned to a variable of type List<Optional<Entry<Integer, String>>>. If not, please read the Q&A Is List a subclass of List? Why are Java generics not implicitly polymorphic?

However, your question why the list1 declaration works but the list4 declaration.

There is a difference between the list1 and list4 declarations. For list1, the form is:

SimpleEntry<Integer, String> simpleEntry = ...;
List<Entry<Integer, String>> list = Arrays.asList<T>(simpleEntry);

In this case, the type variable T of the Arrays.asList method is not fixed to a particular type yet. It has an upper bound of SimpleEntry<Integer, String> (the type of simpleEntry).

According to the Java Language Specification, section 18.5.2, "Invocation Type Inference", the compiler will further constraint the type T by constraining the return type of asList (List<T>) to the invocation context target type (List<Entry<Integer, String>>).

This is possible; when the compiler selects T to be Entry<Integer, String>, the whole expression fits, because a value of type SimpleEntry<Integer, String> can be assigned to a variable of type Entry<Integer, String>.

For list4, the form is:

SimpleEntry<Integer, String> simpleEntry = new SimpleEntry<>(1, "1");
Optional<SimpleEntry<Integer, String>> optionalSimpleEntry = Optional.of(simpleEntry);
List<Optional<Entry<Integer, String>>> list4 = Arrays.asList<T>(optionalSimpleEntry);

Here, T is initially constrained to an upper bound of Optional<SimpleEntry<Integer, String>>. The target type of the expression context is List<Optional<Entry<Integer, String>>>. It is not possible for the compiler to come up with a T that fits both.

A value of type Optional<SimpleEntry<Integer, String>> cannot be assigned to a variable of type Optional<Entry<Integer, String>>>.

That's why the compiler complains.

In simpler terms

In simpler terms, for a method where the generic type is not constrained, and there is an expression context that constrains the generic type, it works for one level deep of parameterization.

You can say

Dog dog = ...;
List<Animal> animals = Arrays.asList(dog);

But it doesn't work at a deeper level of parameterization.

Optional<Dog> optionalDog = ...;
List<Optional<Animal>> optionalAnimals = Arrays.asList(optionalDog);

Upvotes: 2

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

So let's explicitly write the types we expect to infer. Also, we'll put the declaration near the use.

SimpleEntry<Integer, String> simpleEntry = ...
List<Entry<Integer, String>> list1 =
    Arrays.<Entry<Integer, String>>asList(simpleEntry);

SimpleEntry<xyz> is an Entry<xyz> so that is fine.

Optional<Entry<Integer, String>> optionalEntry = ...
List<Optional<Entry<Integer, String>>> list2 =
    Arrays.<Optional<Entry<Integer, String>>>asList(optionalEntry);

Optional<xyz> is trivially an Optional<xyz>.

Optional<SimpleEntry<Integer, String>> optionalSimpleEntry = ...
List<Optional<SimpleEntry<Integer, String>>> list3 =
    Arrays.<Optional<SimpleEntry<Integer, String>>>asList(optionalSimpleEntry);

Optional<xyz> is trivially an Optional<xyz> again.

Optional<SimpleEntry<Integer, String>> optionalSimpleEntry = ...
List<Optional<Entry<Integer, String>>> list4 =
    Arrays.<Optional<Entry<Integer, String>>>asList(optionalSimpleEntry);

Awooga! Optional<SimpleEntry<xyz>> is not an Optional<Entry<xyz>>.

You could use Optional<? extends Entry<xyz>>

Upvotes: 2

Related Questions