Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 489
Luke Hutchison Avatar asked Nov 20 '19 01:11

Luke Hutchison


People also ask

How does type inference work in Java?

Type inference represents the Java compiler's ability to look at a method invocation and its corresponding declaration to check and determine the type argument(s). The inference algorithm checks the types of the arguments and, if available, assigned type is returned.

Why does Java not have type inference?

So, I can imagine that type inference was probably not supported because Java was aimed at programmers coming from C++, Pascal, or other mainstream languages that did not have it (principle of least surprise).

Is there type inference in Java?

Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable.

How do you declare a generic variable in Java?

A Generic Version of the Box Class To update the Box class to use generics, you create a generic type declaration by changing the code "public class Box" to "public class Box<T>". This introduces the type variable, T, that can be used anywhere inside the class.


1 Answers

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);
like image 115
Erwin Bolwidt Avatar answered Oct 06 '22 00:10

Erwin Bolwidt