Eclipse Neon 中的类型推断异常

Type inference weirdness in Eclipse Neon

大家早上好,

一段时间以来,我一直在努力理解为什么以下代码不能在 Eclipse Neon (JDT 3.12.3.v20170301-0400) 中编译,但可以用 javac 或 Eclipse Mars 完美编译:

public class TestLambda {

    protected static <K, V> Map<K, V> newMap(final Function<K, V> loader) {
        return new HashMap<>();
    }

    private final Map<Integer, Integer> working = newMap(key -> {

        final List<String> strings = new ArrayList<>();

        final String[] array = strings.toArray(new String[strings.size()]);
        foo(array);

        return null;
    });

    private final Map<Void, Void> notWorking = newMap(key -> {

        final List<String> strings = new ArrayList<>();

        // This line seems to be the root of all evils
        foo(strings.toArray(new String[strings.size()]));

        return null;
    });

    private void foo(final String[] x) {}

    private void foo(final Integer[] x) {}

}

Eclipse 编译器说 "Type mismatch: cannot convert from Map<Object,Object> to Map<Void,Void>"。

它似乎无法知道必须调用哪个 foo 方法...

我做错了什么吗?

在此先感谢您的帮助

即使使用最新版本 Eclipse Oxygen,我也可以重现该问题。 Java 8 或 Java 9 beta 的任何 javac 版本都不会出现此问题。

我们可以清楚地得出结论,这是一个错误,而且很奇怪。

首先,这里没有歧义。 foo 的两次调用都必须在 foo(String[]) 结束,事实上,Eclipse 不会报告歧义问题。

其次,foo 调用对该 lambda 表达式的函数类型没有影响。

如果我们将行 foo(strings.toArray(new String[strings.size()])); 更改为 foo(strings.toArray());,我们将在 foo 处得到一个真正的错误,因为 foo(Object[]) 不存在,但类型推断将正确解析Function<Void, Void> 作为 lambda 表达式,Map<Void, Void> 作为 newMap 的 return 类型。

我们还可以通过将 foo 声明更改为

来创建真正的歧义问题
private void foo(final Serializable[] x) {}
private void foo(final CharSequence[] x) {}

这将导致两个 foo 调用都出错,因为 String[] 与这两种方法兼容并且 SerializableCharSequence 之间没有关系,但是newMap 调用的类型推断不受影响并解析为
<Void, Void> Map<Void, Void> newMap(Function<Void, Void>) 符合预期。

最奇怪的是,只需交换 foo 方法的顺序就可以使错误消失:

public class TestLambda {

    protected static <K, V> Map<K, V> newMap(final Function<K, V> loader) {
        return new HashMap<>();
    }

    private final Map<Integer, Integer> working = newMap(key -> {

        final List<String> strings = new ArrayList<>();

        final String[] array = strings.toArray(new String[strings.size()]);
        foo(array);

        return null;
    });

    private final Map<Void, Void> notWorking = newMap(key -> {

        final List<String> strings = new ArrayList<>();

        // This line seems to be the root of all evils
        foo(strings.toArray(new String[strings.size()]));

        return null;
    });

    private void foo(final Integer[] x) {}
    private void foo(final String[] x) {}
}

编译正常。

背后有规律。您可以拥有任意数量的 foo 重载,只要最后声明的重载 适用 foo 调用。它不一定是最终调用的最具体的一个,例如在 class 的末尾放置另一个 private void foo(final Object[] x) {} 也可以解决问题。

那个模式肯定没有匹配的 Java 语言规则…