【问题标题】:Why does Java allow this code with generics to compile?为什么 Java 允许编译带有泛型的代码?
【发布时间】:2020-11-06 08:20:55
【问题描述】:

我最近被以下 Java 代码弄得措手不及:

interface Common {}

interface A extends Common {}

static class B implements Common {}

static class Impl {
  private A a;

  public <T extends A> T translate() {
    return (T) a;
  }
}

static class Usage {
  public void use() {
    Impl impl = new Impl();
    B b = impl.translate(); // Why does this compile?
  }
}

考虑到B 没有扩展A,我本来预计Impl.translate 上的类型约束不允许将结果存储在B 类型中以被编译器接受。 代码在运行时抛出UncheckedCastException,而不是编译器错误。

这只发生在方法返回类型T时;如果是方法参数:

  public <T extends A> void translate(T t) {}

那么B 的实例不允许作为translate 的参数,正如预期的那样。

这里发生了什么?为什么 Java 的类型系统允许这样做?

【问题讨论】:

  • 我也很好奇你是如何编译static class B implements Common {}的。 static 如果不在其他类的内部,则不能创建类。 static class Implstatic class Usage 相同
  • the Error Prone checker for this pattern的描述可能有助于理解。请注意,它可以编译,但带有未经检查的强制转换警告
  • 我认为这就是实际发生的情况:stackoverflow.com/questions/38092697/…
  • 这能回答你的问题吗? Why does this generic code compile in java 8?
  • ...请您不接受我的回答,以便我删除它吗?...“@AndyTurner — 我尊重一个不卖东西的人-为几十万的互联网点数出他的诚信:)

标签: java generics


【解决方案1】:

TL;DR 答案

关键是可能有一个扩展 [B] 并实现 [A] 的类,而该理论类型是 T 推断的.“——@JornVernee 2017 年 7 月 3 日 14:43

长答案

Jorn Vernee 实际上在 Oracle 的 Java 团队工作。 His comment quoted in the TL;DR 是他雇主的Chapter 18. Type Inference 的最简洁、最容易理解的提炼方法。

如果您对类型系统和 lambda 演算了如指掌,并且想要了解完整的细节,那么第 18 章将是您的理想选择。

在 Vernee 的TL;DR 的专家简洁和 JLS 的令人头痛的严谨之间,是 JLS 的 @987654324 的用户 @Radiodef 的 point-by-point digest @。

@JornVernee 和 @Radiodef 对 JLS 在这个谜题背后的推理的解释,是(对我而言)最清晰、最容易理解的七八个其他答案中的任何一个—— 至少——这个问题的重复:

  1. Why does this generic code compile in java 8?
  2. Generic return type upper bound - interface vs. class - surprisingly valid code
  3. Apparent type violation, but compiles
  4. Why can this generic method with a bound return any type?
  5. String gets assigned to a List without a compilation error
  6. Is this a Java generic type inference compiler bug?
  7. Java type inference: reference is ambiguous in Java 8, but not Java 7

【讨论】:

    【解决方案2】:

    您的泛型的 T 未分配。因为有可能有一个既是B 又是A 的值,编译器假定你没问题。

    如果您将类型分配给B,则会收到错误消息。

    B b = impl.<B>translate(); 
    

    Testy.java:16:错误:Impl 类中的方法转换不能应用于给定类型; B b = impl.translate(); ^
    必需:无参数
    找到:没有参数
    原因:显式类型参数 B 不符合声明的边界 A

    当类型由参数确定时,您会遇到类似的错误。

    public <T extends A> T translate(T t) {
        return (T) a;
      }
    
    B b = impl.translate(new B());
    

    类型由参数分配,参数是B,这不会编译。

    正如另一个答案中提到的,如果AB 都是类,那么将不会有一个T 可以同时满足两者。

    Testy.java:15:错误:不兼容的类型:推理变量 T 的上限 B、A 不兼容
    B b = impl.translate(); // 为什么会编译?
    ^
    其中 T 是类型变量:
    T 扩展了方法 translate() 中声明的 A

    【讨论】:

    • 我认为这与道格拉斯的解释很吻合
    【解决方案3】:

    这是可以编译的,因为原则上,一个对象可能既是 B 又是 A。比如这个类的一个实例:

    static class C extends B implements A {}
    

    对于编译器来说,不存在这样的类并不重要。其他人可能会从依赖项中导入此代码并自己定义C,这是有效的,必须允许工作。在编译时查找对象的实际类并不重要,因为编译器不进行那种分析。禁止此类存在的修饰符,例如将final 添加到B,也不考虑在内,尽管我不确定为什么。可能只是为了降低编译器逻辑的复杂性。

    A 更改为类会导致编译错误,因为 Java 不允许一个类扩展多个类。

    【讨论】:

    • 同意这个答案。如果 Impl 有一个“a”的 setter 方法,那么它会更容易理解。考虑在其他地方调用 ### impl.setA(new C()),然后再调用 ### C c=impl.translate();
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-01
    相关资源
    最近更新 更多