6

在我们的代码库从 java 1.7 迁移到 1.8 期间,我们在多个代码位置收到错误消息“方法...不适用于参数”,所有这些都遵循泛型使用的相同模式。

我们目前主要使用Eclipse Mars(4.5.2) on ,但也可以使用(4.6)Windows 7来确认行为。以及1.7 合规级别都可以编译我们的代码而不会出错。NeonJavacecj

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

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可以编译代码,所以我们确实有一个解决方法。仍然错误消息似乎不正确,因为类SubFooBar扩展Foo并实现Bar了,所以它履行了 的合同<T extends Foo & Bar>,这是在 中要求的<T extends Foo & Bar> FooBar<T> makeFooBar(),所以实际上TIMO 应该绑定到SubFooBar

我搜索了类似的问题并发现了这些: Differences in type inference JDK8 javac/Eclipse Luna? 类型推断编译器错误在 Eclipse 中使用 Java8 但不是 Java7

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

  • 430686 已验证修复 - 我的不是
  • 440019 与标量类型有关 - 我的不是
  • 492838、448793 与通配符有关——我的不是

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

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

编辑

正如我承诺将我的报告结果发布到 Eclipse 的那样Bugzilla:该缺陷的 ID 为 #497905(Stephan Herrmann 在他的评论中发布了已接受答案下方的链接),目前针对 v4.7。

4

1 回答 1

2

在方法中

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

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

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

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

这意味着您可以按照期望的实例将SubFooBar实例传递给create方法,并且扩展和实现是兼容的。FooBar<Foo&Bar>.create(…)Foo&BarSubFooBarFooBar

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

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

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

于 2016-07-14T14:58:05.567 回答