Javac和Eclipse编译器中的泛型和Lambda表达式行为不同

8
注意:我发现有多个问题指出了javac和Eclipse编译器之间的差异,但据我所见,它们都讨论了其他问题。
假设我们有这个方法:
public static <T, U> void foo(Supplier<T> a, Function<T, U> b, Consumer<U> c)
{
    c.accept(b.apply(a.get()));
}

我在编译调用此方法时发现了javac和Eclipse Java编译器之间的不同行为,我不确定哪个是正确的。这个方法的简单用法可能是:
// variant 1
foo(
    () -> Optional.of("foo"),
    value -> value.get(),
    value -> System.out.println(value));

编译器应该能够使用第一个参数将 T 绑定到 Optional<String>,并使用第二个参数将 U 绑定到 String。因此,在我看来,这个调用应该是有效的。

在使用 javac 进行编译时,这个代码可以正常编译,但在使用 Eclipse 时无法编译:

类型不匹配:无法从 void 转换为 <unknown>

在第一个参数 (() -> Optional.<String> of("foo")) 中添加一个类型参数可以使其在 Eclipse 中也能编译。

问题: 从规范的角度来看,Eclipse 拒绝这个调用是否正确 (为什么(不))?


现在假设我们想要在 Optional 为空时抛出自定义 (运行时) 异常:

// variant 2
foo(
    () -> Optional.of("foo"),
    value -> value.orElseThrow(() -> new RuntimeException()),
    value -> System.out.println(value));

这段代码被javac和Eclipse编译器都拒绝,但是错误信息不同:

  • javac: "未报告的异常X; 必须捕获或声明为抛出"
  • Eclipse编译器:"类型不匹配:无法从void转换为<unknown>"

当我像上面那样给第一个参数添加类型参数时,Eclipse成功编译,而javac仍然失败。当我将<RuntimeException>作为类型参数添加到第二个参数时,情况正好相反,Eclipse失败了,而javac成功了。

问题:编译器拒绝这个调用是正确的吗?为什么?


在我看来,两种变体都应该通过使用类型参数而无需其他提示而编译成功。如果是这样,我将为javac(关于“未报告的异常”)和Eclipse编译器(关于“类型不匹配”)填写一个bug报告。但是首先,我想确定规范是否分享我的观点。

所用版本:

  • javac:1.8.0_66
  • Eclipse JDT:3.11.1.v20151118-1100

编辑:

我为Eclipse中的问题填写了bug 482781

javac的问题已经报告为JDK-8056983,请参见Tunakis answer


1
在怀疑时,可以归咎于Eclipse :) 类型推断非常复杂,整个lambda语法仍然很新。Eclipse修复了几个错误,但在当前版本中还有一些未解决的问题,与这种边缘情况有关。 - zapl
1
Eclipse Mars的ECJ编译器在泛型扩展方面与最新的Luna相比确实存在很多漏洞。我已经遇到了至少三种情况,当javac和ECJ 3.10正确编译时,ECJ 3.11会失败甚至陷入无限循环。这就是为什么我仍然使用Luna的原因。 - Tagir Valeev
Eclipse的错误已经在4.6 M1版本中通过https://bugs.eclipse.org/470826进行修复,该修复程序也计划被移植到mars.2版本中。 - Stephan Herrmann
@TagirValeev,我没有找到你对ecj报告的任何错误。你愿意分享一下你的例子,以便改进Eclipse吗?你也可以尝试最近的里程碑版本,因为自4.5.1以来已经修复了六个类型推断中的错误。 - Stephan Herrmann
@TagirValeev 最新的 ecj.jar(包含批处理编译器)可以在 http://download.eclipse.org/eclipse/downloads/ 找到 - 只需选择一个版本,然后在下一页导航到“JDT Core Batch Compiler”,希望有所帮助。 - Stephan Herrmann
@StephanHerrmann,不知道,谢谢。这里是另一个报告。 - Tagir Valeev
1个回答

5

没错,你的每一个方面都是正确的。坦白地说,我无法链接到JLS上关于这个问题的具体行:类型推断是整个章节

声明:我使用的是Eclipse Mars 4.5.1和JDK 1.8.0_60进行测试。


变体1应该可以编译,但Eclipse在这里有一个错误。我在他们的Bugzilla中找不到任何相关信息,因此您可以继续提交。如果将示例简化为以下内容,则可以确保其可以编译:

public static <T> void foo(Supplier<T> a) {
    a.get();
}

foo(() -> Optional.of("foo"));

这个在Eclipse和javac中编译都没问题。添加参数不会改变编译期间对T推断的类型。
Variant 2无法编译javac,这确实是一个错误,如JDK-8056983所报告的。编译器应该能够推断出X是RuntimeException。至于为什么Eclipse仍然无法编译这个,我在他们的Bugzilla中也找不到任何东西,所以请随意报告!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接