【问题标题】:How does java compiler deal with code like `public interface A extends B<A>`java编译器如何处理像`public interface A extends B<A>`这样的代码
【发布时间】:2020-09-29 09:19:12
【问题描述】:

我刚开始写java,今天我读了一些这样的代码:

public interface A extends B<A>{
    ...
}

public interface B<E extends B<E>>{
    ...
}

我当然能看懂代码,但这让我很困惑。看起来……我用自己来创造自己?编译器是怎么处理的?

【问题讨论】:

  • 是的:这很混乱。但很有用。
  • 你能详细说明莫里斯佩里吗?我已经用 java 编码超过 13 年了,从来没有见过这个成语。我看不出这有什么好处。
  • 您的问题到底是什么? “编译器如何处理它”的答案是它编译它,但这涉及词法、句法和语义分析,所有这些都是长期课程和大型书籍的主题。不清楚你到底在问什么。
  • @TreffnonX 看看Comparable。你一定已经看到了。
  • 我相信这应该回答你的问题:stackoverflow.com/questions/7282677/…

标签: java generics interface compiler-construction


【解决方案1】:

是的,编译器的能力令人惊讶。正如其他答案所说,通用参数通过引用使用。但是有编译(.class)及其依赖关系。注意:一些运行时错误是由不同步的编译引起的,你可以在 jar 中存储一个 .java 文件——我相信)。

这种与自引用的杂耍通常用于将子类的类传递给超类,以将事物(方法)限制在子类中。

但是循环行为的相同技巧可以作为纯类来完成:

public interface Foo {
    Foo ZERO = new Bar();
}

public class Bar implements Foo {
    ... ZERO ...
}

所以java编译器解决了这个鸡/蛋问题。

【讨论】:

  • 这里重要的是:接口的字段是静态最终的!那应该是明确的。这是一个 q/a:stackoverflow.com/questions/9446893/fields-in-interfaces 虽然解释是正确的 :)
  • @TreffnonX 确实,public static final 甚至。也许我还应该提到:类 Foo 和 Bar 已加载,在初始化 Foo 时需要初始化 Bar。我也更喜欢显式的,它对于像开放值域的常量这样的枚举并不是那么有用 - 没有语法绒毛(如this.)。
  • '泛型参数被引用使用'是没有意义的。
【解决方案2】:

为了回答您的明确问题,编译器通过相互检查约束来验证泛型,验证所讨论的特定类型是否合法/与泛型引用匹配。之后(对于输出代码)泛型类型被擦除。这称为“类型擦除”。这里解释了明确的步骤:https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

但更有趣的是你显然遇到的语义。

通用类型并不意味着“用于创建”,而是“关于”(以任何方式)。 以List 为例。 List&lt;A&gt; 表示“包含 A 类实例的列表”。对于调用其实例的代码,这意味着所有引用泛型类型的方法都将直接或间接地考虑类型 A 的对象。

任何实现public interface B&lt;E extends B&lt;E&gt;&gt; 的类都需要在这方面考虑自己。这一点可能是稍后引用E 的方法或属性将产生实现它们的类型。一个例子可能是:

public interface Chainable<E extends Chainable<E>> {
  public void append(E followup);

  public E getNext();
}

...

public class MyLink implements Chainable<MyLink> {
  public void append(MyLink l) {
    ...
  }

  public MyLink getNext() {
    ...
  }
}

这样可以确保任何实现 Chainable 的类都会有一个方法获取,并且返回该类的一个对象,而不仅仅是任何对象。

虽然仅上述内容在约束方面很有用,但我看不出更好地使用您的示例public interface A extends B&lt;A&gt;。这样做的原因是,现在实现A 的任何类都只保证关注A,而不是实现类本身(这将更有用)。示例:

public class C implements A {
  public A methodDeclaredInB(A someParam) {
    ...
  }
}

上面的方法在编译时只知道A,不知道C。如果有人写道:

public class C implements B<C> {
  public C methodDeclaredInB(C someParam) {
    ...
  }
}

然后可以使用类 C 而不是“仅”A

在某些情况下,知道类型 A 就足够了,但通常你想要类型 C,所以创建一个(本身非泛型)接口 A 以这种方式扩展 B 似乎上面更冗长但更有用的示例的脆弱快捷方式。

【讨论】:

  • 您的示例将使用声明为public interface Chainable&lt;E&gt;Chainable 进行编译,因此它没有说明extends Chainable&lt;E&gt; 的任何用途。
  • “这样可以确保任何实现 Chainable 的类都有一个方法,并且返回该类的一个对象”这是不正确的。实现Chainable&lt;E&gt; 的类将只保证有一个方法采用和一个方法返回E,不一定是实现类的类型。只有当实现类选择以自己作为类型参数来实现Chainable时,它才会有一个方法采用,一个方法返回,它自己的类型。
  • @newacct “只有当实现类选择以自身作为类型参数来实现 Chainable 时,它​​才会有一个方法采用,一个方法返回它自己的类型。” - 但这是我的示例“那样”指的是实现Chainable&lt;MyLink&gt; 的类,这正是重点。至于前面的评论:public interface Chainable&lt;E extends Chainable&lt;E&gt;&gt;E 绑定到Chainable&lt;E&gt;。这很重要并且会有所作为。您可以在 IDE 中轻松验证:类型 not Chainable&lt;E&gt; 不能是泛型类型 E,这就是目的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-10
相关资源
最近更新 更多