这是我正在使用的代码的最小示例:
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编译器中。
我们可以进一步简化示例:
声明一个类似的方法
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中失败。
对我而言,编译器为SomeEnum
(可以匹配绑定的K
)和Enum<K>
为String
推断V
是不合逻辑的,但是当为K
指定了绑定时未能推断出这些类型。所以我认为这是一个错误。我不能排除在规范深度的某处有一个声明,它允许得出结论编译器应该以这种方式运行,但如果是这样,那么规范也应该被修复。
正如评论部分中的其他人所说,这个代码可以用Eclipse编译而没有问题。
如果我们明确提到类型而不是使用菱形运算符,那么它会成功编译。以下是相同的代码:
private static final Map<SomeEnum, String> TEST = new EnumMap<SomeEnum, String>(
Arrays.stream(SomeEnum.values())
.collect(Collectors.toMap(t -> t, a -> "")));
根据另一个link的参考,在某些情况下,不支持钻石运算符。如果这里讨论的代码片落在这个桶中,可以进一步挖掘它。