为什么这个代码无法编译,引用类型推断作为原因?

tte*_*rag 11 java generics type-inference javac java-8

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

public class Temp {
    enum SomeEnum {}

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

}
Run Code Online (Sandbox Code Playgroud)

编译器输出是:

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

我发现,这可以通过更换被合作周围t -> tFunction.identity()(SomeEnum t) -> t,但我不理解为什么是这样的情况.javac有什么限制导致这种行为?

我最初在java 8中发现了这个问题,但已经证实它仍然发生在java 11编译器中.

Hol*_*ger 9

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

声明一个类似的方法

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

该声明

Map<SomeEnum, String> m = test(Collections.emptyMap());
Run Code Online (Sandbox Code Playgroud)

可以编译没有问题.现在,当我们将方法声明更改为

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

我们得到编译器错误.这表明将流表达式包装在new EnumMap<>(…)和表达式之间的区别new HashMap<>(…)在于键类型的类型参数声明,因为EnumMap's键类型参数已被声明为K extends Enum<K>.

它似乎与声明的自引用性质有关,例如K extends Serializable,同时不会引起错误K extends Comparable<K>.

虽然javac从Java 8到Java 11的所有版本都失败了,但行为并不像看起来那么一致.当我们将声明更改为

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

代码可以在Java 8下再次编译,但仍然在Java 9到11中失败.

对我来说,这是不合逻辑的是,编译器推断SomeEnumK(这将匹配绑定Enum<K>),并StringV,但没有在bound已指定推断这些类型K.所以我认为这是一个错误.我不能排除在规范深度的某处有一个声明,它允许得出结论编译器应该以这种方式运行,但如果是这样,那么规范也应该被修复.

正如评论部分中的其他人所说,这个代码可以用Eclipse编译而没有问题.