Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this code fail to compile, citing type inference as the cause?

Here's a minimal example of the code I'm working with:

public class Temp {
    enum SomeEnum {}

    private static final Map<SomeEnum, String> TEST = new EnumMap<>(
               Arrays.stream(SomeEnum.values())
                     .collect(Collectors.toMap(t -> t, a -> "")));

}

The compiler output is:

Temp.java:27: error: cannot infer type arguments for EnumMap<>
    private static final Map<SomeEnum, String> TEST = new EnumMap<>(Arrays.stream(SomeEnum.values())
                                                      ^

I have found that this can be worked around by replacing t -> t with Function.identity() or (SomeEnum t) -> t, but I'm not understanding why this is the case. What limitation in javac is causing this behavior?

I originally found this issue with java 8, but have verified it still occurs with the java 11 compiler.

like image 748
tterrag Avatar asked Feb 14 '19 04:02

tterrag


1 Answers

We can simplify the example further:

Declaring a method like

static <K,V> Map<K,V> test(Map<K,? extends V> m) {
    return Collections.unmodifiableMap(m);
}

the statement

Map<SomeEnum, String> m = test(Collections.emptyMap());

can be compiled without problems. Now, when we change the method declaration to

static <K extends Enum<K>,V> Map<K,V> test(Map<K,? extends V> m) {
    return Collections.unmodifiableMap(m);
}

we get a compiler error. This indicates that the difference between wrapping your stream expression with new EnumMap<>(…) and new HashMap<>(…) lies in the type parameter declaration of the key type, as EnumMap’s key type parameter has been declared as K extends Enum<K>.

It seems to be connected with the self-referential nature of the declaration, e.g. K extends Serializable does not cause an error while K extends Comparable<K> does.

While this fails in all javac versions from Java 8 to Java 11, the behavior is not as consistent as it seems. When we change the declaration to

static <K extends Enum<K>,V> Map<K,V> test(Map<? extends K,? extends V> m) {
    return Collections.unmodifiableMap(m);
}

the code can be compiled again under Java 8, but still fails with Java 9 to 11.

To me, it’s illogical that the compiler infers SomeEnum for K (which would match the bound Enum<K>) and String for V, but fails to infer these types when a bound has been specified for K. So I consider this a bug. I can’t preclude that there’s a statement somewhere in the depth of the specification which allows to conclude that a compiler should behave that way, but if so, the specification should be fixed as well.

As said by others in the comments section, this code can be compiled with Eclipse without problems.

like image 99
Holger Avatar answered Nov 06 '22 19:11

Holger