Bas*_*ass 5 java generics crtp
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> implements Comparable<SELF> {
@Override
public final int compareTo(final SELF o) {
// ...
}
}
final class C1 extends AbstractFoo<C1> {
// ...
}
final class C2 extends AbstractFoo<C2> {
// ...
}
Run Code Online (Sandbox Code Playgroud)
使用上面的代码(Comparable
为了清楚起见选择了界面;当然还有其他用例),我可以轻松地比较两个实例C1
:
new C1().compareTo(new C1());
Run Code Online (Sandbox Code Playgroud)
但不是AbstractFoo
具体类型不同的后代:
new C1().compareTo(new C2()); // compilation error
Run Code Online (Sandbox Code Playgroud)
但是,滥用模式很容易:
final class C3 extends AbstractFoo<C1> {
// ...
}
// ...
new C3().compareTo(new C1()); // compiles cleanly
Run Code Online (Sandbox Code Playgroud)
此外,类型检查纯粹是编译时的,即可以轻松地将单个实例混合在一起C1
,并将它们相互比较.C2
TreeSet
什么替代CRTP在Java的模拟自我类型如上图所示,而不滥用的可能?
PS我观察到模式在标准库中没有被广泛使用 - 只有EnumSet
它的后代实现它.
我不认为你展示的是“滥用”。所有使用AbstractFoo
并且C3
仍然完全类型安全的代码,只要它们不进行任何不安全的强制转换。SELF
in的边界AbstractFoo
意味着代码可以依赖SELF
是 的子类型的事实AbstractFoo<SELF>
,但代码不能依赖AbstractFoo<SELF>
是 的子类型的事实SELF
。因此,例如,如果AbstractFoo
有一个返回 的方法SELF
,并且它是通过返回实现的this
(如果它真的是“自类型”,这应该是可能的),它不会编译:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF someMethod() {
return this; // would not compile
}
}
Run Code Online (Sandbox Code Playgroud)
编译器不允许您编译它,因为它不安全。例如,运行此方法C3
将返回this
(实际上是一个C3
实例)为 type C1
,这将导致调用代码中的类转换异常。如果您试图通过使用return (SELF)this;
强制转换(例如 )绕过编译器,那么您会收到未经检查的强制转换警告,这意味着您要为其不安全负责。
如果你AbstractFoo
的使用方式真的只依赖于这个事实SELF extends AbstractFoo<SELF>
(正如绑定所说),而不依赖于AbstractFoo<SELF> extends SELF
,那么你为什么关心 的“滥用” C3
?你仍然可以编写你的类C1 extends AbstractFoo<C1>
并且C2 extends AbstractFoo<C2>
很好。如果其他人决定编写一个 class C3 extends AbstractFoo<C1>
,那么只要他们以一种不使用不安全强制转换的方式编写它,编译器就会保证它仍然是类型安全的。也许这样的类可能无法做任何有用的事情;我不知道。但它仍然是安全的;那为什么会有问题呢?
递归边界 like<SELF extends AbstractFoo<SELF>>
使用不多的原因是,在大多数情况下,它并不比 有用<SELF>
。例如,Comparable
接口的类型参数没有界限。如果有人决定写一个 class Foo extends Comparable<Bar>
,他们可以这样做,并且它是类型安全的,虽然不是很有用,因为在大多数使用 的类和方法中Comparable
,它们都有一个类型变量<T extends Comparable<? super T>>
,这要求它T
与自身相当,所以Foo
class 在任何这些地方都不能用作类型参数。但是,Foo extends Comparable<Bar>
如果有人愿意,他们仍然可以写作。
递归绑定喜欢的唯一地方<SELF extends AbstractFoo<SELF>>
是在实际利用SELF
extends事实的地方AbstractFoo<SELF>
,这是非常罕见的。一个地方是在类似于构建器模式的东西中,它具有返回对象本身的方法,可以链接。所以如果你有类似的方法
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF foo() { }
public SELF bar() { }
public SELF baz() { }
}
Run Code Online (Sandbox Code Playgroud)
并且你有一个一般的价值AbstractFoo<?> x
,你可以做一些你不能做的事情x.foo().bar().baz()
,如果它被声明为abstract class AbstractFoo<SELF>
。
Java泛型中没有办法使类型参数必须与当前实现类的类型相同。如果假设存在这样的机制,则可能会导致继承的棘手问题:
abstract class AbstractFoo<SELF must be own type> {
public abstract int compareTo(SELF o);
}
class C1 extends AbstractFoo<C1> {
@Override
public int compareTo(C1 o) {
// ...
}
}
class SubC1 extends C1 {
@Override
public int compareTo(/* should it take C1 or SubC1? */) {
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
在这里,SubC1
隐式继承AbstractFoo<C1>
,但这违反了SELF
必须是实现类的类型的约定。如果SubC1.compareTo()
必须接受一个C1
参数,那么接收到的事物的类型与当前对象本身的类型不再相同。如果SubC1.compareTo()
可以接受一个SubC1
参数,那么它不再是 overrides C1.compareTo()
,因为它不再需要像超类中的方法一样宽的一组参数。
归档时间: |
|
查看次数: |
207 次 |
最近记录: |