java 编译器如何处理像 `public interface A extends B<A>` 这样的代码
How does java compiler deal with code like `public interface A extends B<A>`
我刚开始写 java,今天我读了一些这样的代码:
public interface A extends B<A>{
...
}
public interface B<E extends B<E>>{
...
}
当然我能看懂代码,但是这让我真的很困惑。好像……我用自己创造自己?编译器是怎么处理的?
为了回答您的明确问题,编译器通过相互检查约束来验证泛型,验证所讨论的特定类型是 legal/matching 泛型引用。之后(对于输出代码)通用类型被擦除。这叫做'type erasure'。此处解释了显式步骤:https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
但更有趣的是您显然遇到的语义。
泛型类型并不意味着 'use to create' 而是 'regarding'(无论如何)。
以 List
为例。 List<A>
表示 'List containing instances of class A'。对于调用它的实例的代码,这意味着所有引用泛型类型的方法都将直接或间接地考虑类型 A 的对象。
任何实施 public interface B<E extends B<E>>
的 class 都需要在这方面考虑自己。这一点可能是稍后引用 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() {
...
}
}
通过这种方式,可以确保任何 class 实现 Chainable 的方法都将采用一种方法,并返回该 class 的对象,而不是任何对象。
虽然以上仅在约束方面很有用,但我看不出更好地使用您的示例 public interface A extends B<A>
。这样做的原因是,现在实施 A
的任何 class 只能保证考虑 A
而不是实施 class 本身(这会更有用)。示例:
public class C implements A {
public A methodDeclaredInB(A someParam) {
...
}
}
上面的方法在编译时只知道A
,而不知道C
。相反,如果有人写道:
public class C implements B<C> {
public C methodDeclaredInB(C someParam) {
...
}
}
然后 class C
可以用来代替 'only' A
.
在某些情况下,知道类型 A
就足够了,但通常您需要类型 C
,因此创建一个(本身 non-generic)接口 A
以这种方式扩展 B
似乎是通往上面更冗长但更有用的示例的脆弱捷径。
是的,编译器的能力令人惊讶。正如其他答案所说,通用参数通过引用使用。但是有编译(.class)和它们的依赖关系。注意:一些运行时错误是由不同步编译引起的,你可以在 jar 中存储一个 .java 文件 - 我相信)。
那个和self-references的玩弄经常被用来把子class的class传给超级class来限制子class的东西(方法)class.
但是循环行为的相同技巧可以像纯 classes 那样完成:
public interface Foo {
Foo ZERO = new Bar();
}
public class Bar implements Foo {
... ZERO ...
}
所以 java 编译器解决了这个 chicken/egg 问题。
我刚开始写 java,今天我读了一些这样的代码:
public interface A extends B<A>{
...
}
public interface B<E extends B<E>>{
...
}
当然我能看懂代码,但是这让我真的很困惑。好像……我用自己创造自己?编译器是怎么处理的?
为了回答您的明确问题,编译器通过相互检查约束来验证泛型,验证所讨论的特定类型是 legal/matching 泛型引用。之后(对于输出代码)通用类型被擦除。这叫做'type erasure'。此处解释了显式步骤:https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
但更有趣的是您显然遇到的语义。
泛型类型并不意味着 'use to create' 而是 'regarding'(无论如何)。
以 List
为例。 List<A>
表示 'List containing instances of class A'。对于调用它的实例的代码,这意味着所有引用泛型类型的方法都将直接或间接地考虑类型 A 的对象。
任何实施 public interface B<E extends B<E>>
的 class 都需要在这方面考虑自己。这一点可能是稍后引用 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() {
...
}
}
通过这种方式,可以确保任何 class 实现 Chainable 的方法都将采用一种方法,并返回该 class 的对象,而不是任何对象。
虽然以上仅在约束方面很有用,但我看不出更好地使用您的示例 public interface A extends B<A>
。这样做的原因是,现在实施 A
的任何 class 只能保证考虑 A
而不是实施 class 本身(这会更有用)。示例:
public class C implements A {
public A methodDeclaredInB(A someParam) {
...
}
}
上面的方法在编译时只知道A
,而不知道C
。相反,如果有人写道:
public class C implements B<C> {
public C methodDeclaredInB(C someParam) {
...
}
}
然后 class C
可以用来代替 'only' A
.
在某些情况下,知道类型 A
就足够了,但通常您需要类型 C
,因此创建一个(本身 non-generic)接口 A
以这种方式扩展 B
似乎是通往上面更冗长但更有用的示例的脆弱捷径。
是的,编译器的能力令人惊讶。正如其他答案所说,通用参数通过引用使用。但是有编译(.class)和它们的依赖关系。注意:一些运行时错误是由不同步编译引起的,你可以在 jar 中存储一个 .java 文件 - 我相信)。
那个和self-references的玩弄经常被用来把子class的class传给超级class来限制子class的东西(方法)class.
但是循环行为的相同技巧可以像纯 classes 那样完成:
public interface Foo {
Foo ZERO = new Bar();
}
public class Bar implements Foo {
... ZERO ...
}
所以 java 编译器解决了这个 chicken/egg 问题。