CRTP模式允许模拟Java中所谓的self类型,例如:
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> {
// ...
}
使用上面的代码(选择Comparable
接口只是为了清楚起见;当然还有其他用例(,我可以轻松地比较C1
的两个实例:
new C1().compareTo(new C1());
但不是AbstractFoo
具体类型不同的后代:
new C1().compareTo(new C2()); // compilation error
但是,很容易滥用该模式:
final class C3 extends AbstractFoo<C1> {
// ...
}
// ...
new C3().compareTo(new C1()); // compiles cleanly
此外,类型检查是纯粹的编译时检查,即可以轻松地在单个TreeSet
中混合C1
和C2
实例,并将它们相互比较。
Java中CRTP的什么替代方案可以模拟自身类型而不会被滥用,如上所示?
附言我观察到该模式在标准库中并未广泛使用 - 只有EnumSet
及其后代实现它。
我不认为你展示的是"虐待"。所有使用AbstractFoo
和C3
的代码仍然是完全类型安全的,只要它们不执行任何不安全的强制转换。AbstractFoo
中SELF
的边界意味着代码可以依赖于SELF
是AbstractFoo<SELF>
子类型的事实,但代码不能依赖于AbstractFoo<SELF>
是SELF
子类型的事实。因此,例如,如果AbstractFoo
有一个返回SELF
的方法,并且它是通过返回this
来实现的(如果它真的是一个"自类型",它应该是可能的(,它就不会编译:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF someMethod() {
return this; // would not compile
}
}
编译器不允许您编译它,因为它不安全。例如,在C3
上运行此方法将返回this
(实际上是C3
实例(作为类型C1
,这将导致调用代码中出现类强制转换异常。如果您试图通过使用强制转换(如return (SELF)this;
(偷偷溜过编译器,则会收到未经检查的强制转换警告,这意味着您要对其不安全负责。
如果你的AbstractFoo
真的以一种只依赖于SELF extends AbstractFoo<SELF>
的事实(如绑定所说(的方式使用,而不依赖于AbstractFoo<SELF> extends SELF
的事实,那么你为什么要关心C3
的"滥用"?你仍然可以C1 extends AbstractFoo<C1>
和C2 extends AbstractFoo<C2>
编写你的类。如果其他人决定编写类C3 extends AbstractFoo<C1>
,那么只要他们以不使用不安全强制转换的方式编写它,编译器就可以保证它仍然是类型安全的。也许这样的类可能无法做任何有用的事情;我不知道。但它仍然是安全的;那么为什么这是一个问题呢?
像<SELF extends AbstractFoo<SELF>>
这样的递归绑定不常使用的原因是,在大多数情况下,它并不比<SELF>
更有用。例如,Comparable
接口的类型参数没有边界。如果有人决定编写一个类Foo extends Comparable<Bar>
,他们可以这样做,它是类型安全的,尽管不是很有用,因为在大多数使用Comparable
的类和方法中,它们都有一个类型变量<T extends Comparable<? super T>>
,这要求T
与自身相当,因此Foo
类在任何这些地方都不能用作类型参数。但是,如果有人愿意,写Foo extends Comparable<Bar>
还是可以的。
唯一像<SELF extends AbstractFoo<SELF>>
这样的递归界的地方是实际上利用SELF
扩展AbstractFoo<SELF>
这一事实的地方,这是非常罕见的。一个地方是类似于构建器模式的东西,它具有返回对象本身的方法,可以链接。所以如果你有这样的方法
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF foo() { }
public SELF bar() { }
public SELF baz() { }
}
而且你的一般值是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? */) {
// ...
}
}
在这里,SubC1
隐式继承AbstractFoo<C1>
,但这打破了SELF
必须是实现类类型的约定。如果SubC1.compareTo()
必须采用C1
参数,那么接收的事物的类型与当前对象本身的类型不再相同。如果SubC1.compareTo()
可以接受SubC1
参数,那么它就不再覆盖C1.compareTo()
,因为它不再像超类中的方法那样接受那么宽的参数集。