为什么以类型推断为原因,此代码无法编译?

时间:2019-02-14 04:15:46

标签: java generics java-8 javac type-inference

这是我正在使用的代码的最小示例:

public class Temp {
    enum SomeEnum {}

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

}

编译器输出为:

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

我发现可以通过将t -> t替换为Function.identity()(SomeEnum t) -> t来解决此问题,但是我不明白为什么会这样。 javac中的哪些限制导致了此行为?

我最初在Java 8中发现了此问题,但已验证在Java 11编译器中仍然会发生。

2 个答案:

答案 0 :(得分:9)

我们可以进一步简化示例:

声明类似的方法

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

声明

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

可以毫无问题地进行编译。现在,当我们将方法声明更改为

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

我们收到一个编译器错误。这表明用new EnumMap<>(…)new HashMap<>(…)包装流表达式之间的区别在于键类型的类型参数声明,因为EnumMap的键类型参数已声明为{{ 1}}。

它似乎与声明的自我引用性质有关,例如K extends Enum<K>不会引起错误。

虽然从Java 8到Java 11的所有K extends Serializable版本中此操作均失败,但其行为并不像看起来那样一致。当我们将声明更改为

K extends Comparable<K>

可以在Java 8下再次编译该代码,但是对于Java 9到11仍然失败。

对我来说,编译器推断javac的{​​{1}}(将与绑定的static <K extends Enum<K>,V> Map<K,V> test(Map<? extends K,? extends V> m) { return Collections.unmodifiableMap(m); } 匹配)和SomeEnum推断K是不合逻辑的,但是失败在为Enum<K>指定界限时推断这些类型。 所以我认为这是一个错误。我不能排除在规范的深处有一条语句可以得出结论,即编译器应该以这种方式运行,但是如果是这样,则规范也应该得到固定。

正如其他人在评论部分中所述,可以使用Eclipse编译此代码而不会出现问题。

答案 1 :(得分:-2)

如果我们明确提及类型而不是使用菱形运算符,则它将成功编译。以下是相同的代码:

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

作为参考,link在某些情况下不支持菱形运算符。如果有问题的代码段位于此存储桶中,则可能会进一步挖掘。