为什么这段代码编译失败,引用类型推断作为原因?



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

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下仍然失败。

对我来说,编译器推断SomeEnumK(这将匹配绑定Enum<K>)和StringV是不合逻辑的,但是当为K指定边界时无法推断这些类型。所以我认为这是一个错误。我不能排除在规范深处的某个地方有一个语句可以得出结论,编译器应该以这种方式运行,但如果是这样,规范也应该被修复。

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

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

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

对于根据另一个链接的参考,在某些情况下,不支持菱形运算符。如果此处有问题的代码片段落在此存储桶中,则可以进一步挖掘。

最新更新