generics - Java 8 泛型方法...不适用于 Eclipse 中的参数

标签 generics java-8 language-lawyer type-inference ecj

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

我们目前主要使用Eclipse Mars (4.5.2) Windows 7 , 但可以使用 Neon 确认该行为(4.6) 也是如此。 Javac以及 ecj符合 1.7 的合规级别都可以无错误地编译我们的代码。

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

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>的契约(Contract),在 <T extends Foo & Bar> FooBar<T> makeFooBar() 中是必需的,所以实际上 T IMO 应绑定(bind)到 SubFooBar .

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

这让我觉得它可能是 ecj漏洞。在本类(class)中,我还研究了 Eclipse Bugzilla但找不到任何可比的东西,我看到了这些:
  • 430686 已验证修复 - 我的不是
  • 440019 与标量类型有关 - 我的不是
  • 492838、448793 与通配符有关 – 我的不是

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

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

    编辑

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

    最佳答案

    在方法中

    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&Bar 的实例和 SubFooBar延长 Foo并实现Bar是兼容的。

    可以证明 Eclipse 确实推断出与所有其他编译器相同的类型,因为插入了适当的类型转换
    public void doSomething(SubFooBar value) {
      FooBar.makeFooBar().create((Foo&Bar)value);
    }
    

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

    关于generics - Java 8 泛型方法...不适用于 Eclipse 中的参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38374408/

    相关文章:

    java - 将键值映射到 Java 8 流中的对象

    c++ - : bug or feature? 类内和类外定义的 friend 区别

    c++ - 在 C++ 的任何修订版中,是否明确定义了 union 的任何使用?

    java - 使用数组覆盖 Java 泛型方法

    java - 当另一组 CompletableFuture 完成时,你如何完成一个 CompletableFuture?

    Swift Cast 泛型类型

    java - tools.jar 丢失 - 但仅在第一次调用时(Tomcat 8/Java 8/Axis)

    c++ - ISO C++ 库子句中使用的 "convertible to"的定义

    java - 如何从 Eclipse 中的通用接口(interface)导航到实现类?

    java - 重载和覆盖泛型方法 - java