为什么这段代码编译失败,原因是类型推断?
Why does this code fail to compile, citing type inference as the cause?
这是我正在使用的代码的最小示例:
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 编译器中仍然存在。
如果我们明确提及类型而不是使用菱形运算符,那么它会成功编译。以下是相同的代码:
private static final Map<SomeEnum, String> TEST = new EnumMap<SomeEnum, String>(
Arrays.stream(SomeEnum.values())
.collect(Collectors.toMap(t -> t, a -> "")));
参考另link,部分场景不支持菱形算子。如果这里有问题的代码片段落在这个桶中,它可以进一步挖掘。
我们可以进一步简化示例:
像这样声明一个方法
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编译是没有问题的
这是我正在使用的代码的最小示例:
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 编译器中仍然存在。
如果我们明确提及类型而不是使用菱形运算符,那么它会成功编译。以下是相同的代码:
private static final Map<SomeEnum, String> TEST = new EnumMap<SomeEnum, String>(
Arrays.stream(SomeEnum.values())
.collect(Collectors.toMap(t -> t, a -> "")));
参考另link,部分场景不支持菱形算子。如果这里有问题的代码片段落在这个桶中,它可以进一步挖掘。
我们可以进一步简化示例:
像这样声明一个方法
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编译是没有问题的