Java 8 generics 该方法...不适用于 Eclipse 中的参数

Java 8 generics The method ... is not applicable for the arguments in Eclipse

在我们的代码库从 java 1.7 迁移到 1.8 的过程中,我们在多个代码位置收到错误消息 "The method ... is not applicable for the arguments",所有代码位置都遵循相同的泛型使用模式。

我们目前主要在 Windows 7 上使用 Eclipse Mars (4.5.2),但也可以确认 Neon (4.6) 的行为。 Javac 以及具有 1.7 合规级别的 ecj 都可以正确编译我们的代码。

这是一个最小的、完整的、可验证的例子:

public class ComplexInterfaceTest {

  public static class Foo {}

  public interface Bar {
    void print();
  }

  public static class SubFooBar extends Foo implements Bar {
    public void print() {
      System.out.println(this.getClass().getSimpleName());
    }
  }

  public static class FooBar<T extends Foo & Bar> {
    public static <T extends Foo & Bar> FooBar<T> makeFooBar() {
      return new FooBar<>();
    }

    public void create(T value) {
      value.print();
      return;
    }
  }

  public static class Base<T extends Foo> {}

  public static class Subclass extends Base<SubFooBar> {
    public void doSomething(SubFooBar value) {
//      FooBar.<SubFooBar>makeFooBar().create(value);
      FooBar.makeFooBar().create(value);
    }
  }

  public static void main(String[] args) {
    new Subclass().doSomething(new SubFooBar());
  }

}

现在切换 doSomething 方法中注释掉的行可以编译代码,所以我们有一个解决方法。错误消息似乎仍然不正确,因为 class SubFooBar 扩展了 Foo 并实现了 Bar,因此它满足了 <T extends Foo & Bar> 的约定,这是在<T extends Foo & Bar> FooBar<T> makeFooBar(),所以实际上 T IMO 应该绑定到 SubFooBar

我搜索了类似的问题并找到了这些: Differences in type inference JDK8 javac/Eclipse Luna? Type Inference Compiler Error In Eclipse with Java8 but not with Java7

这让我觉得这可能是一个 ecj 错误。在本课程中,我也研究了 Eclipse Bugzilla 但找不到任何可比的东西,我看到了这些:

现在 Eclipse Bugzilla 讨论充满了关于 ecj 内部运作的细节,我并不总能跟上。我的理解是那里的普遍共识,即 Eclipse 编译器必须严格遵循 JLS 而不是 javac (在错误的情况下),所以它不一定是ecj 中的错误。如果不是 ecj 错误,那么编译代码一定是 javac 错误。

我感兴趣的是 - 对于那些可以分析我的代码片段的类型推断过程的人 - 代码应该编译还是我在编码时出错?

编辑

正如我承诺 post 我向 Eclipse Bugzilla 报告的结果:缺陷的 ID #497905(Stephan Herrmann post 编辑了 link在接受的答案下方的评论中),目前针对 v4.7。

方法中

public void doSomething(SubFooBar value) {
  FooBar.makeFooBar().create(value);
}

方法 makeFooBar() 的类型参数 T 永远不会被推断为 SubFooBar。之后您要将 SubFooBar 的实例传递给 create 方法这一事实不会影响前面调用表达式 FooBar.makeFooBar().

的类型

这不会随着 Java 8 的目标类型而改变,因为这个新功能不适用于链式方法调用的接收者表达式。

因此在所有版本中,makeFooBar() 调用的 T 推断的类型将是交集类型 Foo & Bar,因此结果类型为 FooBar<Foo&Bar>。这也是 Eclipse 推断的内容,即使编辑器中的工具提示可能显示其他内容。

这意味着您可以将 SubFooBar 实例传递给 create 方法,因为 FooBar<Foo&Bar>.create(…) 需要 Foo&BarSubFooBar 的实例扩展 Foo 和实现 Bar 是兼容的。

可以证明 Eclipse 确实推断出与所有其他编译器相同的类型,因为插入适当的类型转换

public void doSomething(SubFooBar value) {
  FooBar.makeFooBar().create((Foo&Bar)value);
}

使编译器错误消失。所以这里的问题不是类型推断,而是这个 Eclipse 版本认为 SubFooBar 不能分配给 Foo & Bar.