为什么这段代码无法编译,提示类型推断是原因?

10
这是我正在处理的代码的最小示例:
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编译器中发生。

4
请务必使用javac进行测试,而非eclipse编译器。Eclipse编译器可以顺利地编译此代码。 - tterrag
2
在我的Intellij和使用Java 11.0.2的javac中都无法编译。 - Jacob G.
6
Intellij使用Java 1.8最初显示的代码很好,但是当使用javac进行编译和运行时,会在编译期间出现错误。 - SizableShrimp
3
评论中的解决方法是显式指定 Supplier,使用以下代码即可实现:new EnumMap<>( Arrays.stream(SomeEnum.values()) .collect(Collectors.toMap(t -> t, a -> "", (x, y) -> x, () -> new EnumMap<>(SomeEnum.class))));。参考自Holger's answer - Naman
3
当我编码时,这种类型推断错误并不罕见。我已经放弃(至少暂时放弃)精确理解Java的类型推断到何种程度以及什么时候需要帮助它(参考(SomeEnum t) -> t)。我会为它提供所需的帮助,一切都会顺利进行。 - Ole V.V.
显示剩余9条评论
2个回答

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 的键类型参数已被声明为 K extends Enum<K>
这似乎与声明的自我引用性有关,例如 K extends Serializable 不会导致错误,而 K extends Comparable<K> 会导致错误。
虽然这在 Java 8 到 Java 11 的所有 javac 版本中都失败了,但其行为并不像看起来那么一致。当我们将声明更改为:
static <K extends Enum<K>,V> Map<K,V> test(Map<? extends K,? extends V> m) {
    return Collections.unmodifiableMap(m);
}

这段代码可以在Java 8下重新编译,但在Java 9到11下依然无法通过。

对我来说,编译器推断K的类型为SomeEnum(与边界Enum<K>匹配),推断V的类型为String,但当为K指定了边界时,却无法推断这些类型。因此,我认为这是一个错误。不能排除规范文档中有某个声明允许编译器以这种方式运行,但如果确实如此,则规范应该进行修正。

正如评论部分其他人所说,这段代码可以在Eclipse下编译而没有问题。


-2
如果我们明确指定类型而不使用钻石操作符,则它将成功编译。以下是相应的代码:
private static final Map<SomeEnum, String> TEST = new EnumMap<SomeEnum, String>(
            Arrays.stream(SomeEnum.values())
                    .collect(Collectors.toMap(t -> t, a -> "")));

参考链接,在某些情况下,钻石操作符可能不被支持。如果问题代码片段属于此类情况,则可以进一步深入挖掘。


2
很明显,错误“无法推断类型参数”将在我们明确提供类型参数的时候消失。问题是为什么编译器在这种非常特定的情况下无法推断类型参数。 - walen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接