【发布时间】:2014-06-04 12:58:09
【问题描述】:
我遇到了以下使用泛型和继承的 Java 代码。我真的不明白以下 sn-p 做了什么:
class A<B extends A<B>> {
...
}
这段代码有什么作用?
(我从DBMaker in MapDB得到这个)
【问题讨论】:
我遇到了以下使用泛型和继承的 Java 代码。我真的不明白以下 sn-p 做了什么:
class A<B extends A<B>> {
...
}
这段代码有什么作用?
(我从DBMaker in MapDB得到这个)
【问题讨论】:
几乎很清楚,问题实际上分为两部分:
1) 为什么是B extends A?
2) 为什么A 里面的B extends A<B> 有泛型类型B?
这些部分的答案是:
1) 特别是这个类 (A) 是 builder 类(称为DBMaker),所以它的大多数方法返回一些类型,扩展了这个构建器的类类型。这就解释了为什么B 应该扩展A 类。
2) 但是,实际上,如果我们在第二部分隐藏...extends A<B>,我们只会收到class A<B>。所以A 的类型变量为B。这就是为什么在...extends A<B> A 被标记为类型A 具有类型变量B。
【讨论】:
这表明A 需要派生定义才能做一些工作:
public abstract class A<T extends A<T>> {
protected T instance;
T getOne() {
return instance;
}
}
public class B extends A<B> {
public B() {
instance = this;
}
}
public static void test() {
B b = new B();
b.getOne();
}
这在接口定义中最常用,人们希望在返回类型或参数中显式使用实现接口的类的实例,而不是接口本身:
public interface TimeSeries<T extends TimeSeries<T>> {
T join(T ts);
}
public class DoubleTimeSeries implements TimeSeries<DoubleTimeSeries> {
@Override
public DoubleTimeSeries join(DoubleTimeSeries ts) {
return null;
}
}
【讨论】:
所以我做了一些测试来解决这个问题,这是我的测试用例,看看如何使用这样一个通用案例:
public class A<B extends A<B>> {
int x = 10;
B test;
void printX() {
System.out.println(x);
}
void setB(B b) {
test = b;
}
void printB() {
System.out.println(test);
}
}
public class B extends A<B> {
}
public class Run {
public static void main(String[] args) {
A<B> test = new A<B>();
B myB = new B();
test.printX();
test.setB(myB);
test.printB();
myB.printB();
}
}
我希望代码可以不言自明。如果不发表评论,我将尝试解释发生了什么。看最后一行,myB.printB(),这里我们会得到一个 null,因为 B 还没有为 myB 设置,而只是为了测试。这表明我们可以无限递归到 A 内部(以及 B 内部)的 B 类。
我们可以说:
myB.test.printB();
这将得到一个错误(空指针),但表明我们现在可以从 B 访问 A 类中的测试,并且我们可以使用任意多的情况递归地进行任意深度。所以 A 类的功能是无限多个 B 类的包装器。这有意义吗?
【讨论】:
这使得定义方法返回类型时更容易,例如:
class A<B extends A<B>> {
public B getMe(){
return (B) this;
}
}
这告诉 Java 编译器你在 getMe() 方法中返回类 A 的子类。
class C extends A<C> {
}
C c = new C();
c.getMe(); //returns C
【讨论】: