10

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.

Naman
  • 27,789
  • 26
  • 218
  • 353
tterrag
  • 330
  • 2
  • 10
  • 4
    Make sure you test with javac, not the eclipse compiler. Eclipse compiler compiles this just fine. – tterrag Feb 14 '19 at 04:22
  • 2
    Doesn't compile for me in both Intellij and with javac, both using Java 11.0.2 – Jacob G. Feb 14 '19 at 04:22
  • 6
    Intellij using java 1.8 intially displays the code as fine, but when actually compiling and running with javac this breaks during compile. – SizableShrimp Feb 14 '19 at 04:24
  • 3
    Moving to comments, what also fixes this is specifying the `Supplier` explicitly `new EnumMap<>( Arrays.stream(SomeEnum.values()) .collect(Collectors.toMap(t -> t, a -> "", (x, y) -> x, () -> new EnumMap<>(SomeEnum.class))));`. References from [Holger's answer](https://stackoverflow.com/a/25065237/1746118) – Naman Feb 14 '19 at 04:55
  • 1
    There are many workarounds, I listed a few. Function.identity() is probably the simplest, and even cleaner than the original code. However my question is *why* this happens. I'm aware it's possible to avoid the issue. – tterrag Feb 14 '19 at 04:57
  • 2
    Again from Holger and possibly [relevant answer](https://stackoverflow.com/a/28041480/1746118) linked with further detailed answers for a great explanation. – Naman Feb 14 '19 at 05:10
  • works fine with Java8 in eclipse IDE. – Ravindra Ranwala Feb 14 '19 at 05:20
  • Have you seen the first comment? `Make sure you test with javac, not the eclipse compiler. Eclipse compiler compiles this just fine.` – SizableShrimp Feb 14 '19 at 05:26
  • 3
    Such type inference errors are not uncommon when I code. I have given up (for now at least) on understanding precisely how far Java’s type inference goes and when it needs help (read `(SomeEnum t) -> t`). I give it the help it requires and all is well. – Ole V.V. Feb 14 '19 at 05:37
  • 1
    Can someone explain why this question is deemed useful (entertaining _maybe_, but useful)? Even if someone comes along and meticulously writes up the JLS justification for this type inference limitation, what is anyone going to be able to do with this specific occurrence? – Savior Feb 14 '19 at 05:45
  • @Savior there are actually a bunch of people that like that, like seriously like. this isn't a place just for "useful" – Eugene Feb 14 '19 at 05:49
  • this certainly looks like a bug to me, because if you change to `HashMap<>(....` it will work – Eugene Feb 14 '19 at 06:01
  • 2
    @Eugene `EnumMap(Map m)` compared to `HashMap(Map extends K, ? extends V> m)`.. could the key type be the issue? – Naman Feb 14 '19 at 06:12
  • If we explicitly mention type instead of diamond operator then it compiles successfully i.e. private static final Map TEST = new EnumMap(... – Anshul Singhal Feb 14 '19 at 09:16

2 Answers2

9

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
-2

If we explicitly mention type instead of using diamond operator then it compiles successfully. Following is code for the same:

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

For the reference that as per another link, in some scenarios, diamond operator is not supported. It could be further dig down if code snippet in question here lands in this bucket.

Anshul Singhal
  • 1,983
  • 20
  • 25
  • 2
    It is pretty obvious that the error `cannot infer type arguments` will go away the moment that we explicitly provide the type arguments. The question is why the compiler cannot infer the type arguments in this very specific case. – walen Feb 14 '19 at 10:46