Java 中 CRTP 的替代品
Alternatives to CRTP in Java
Java中的CRTP pattern allows to emulate the so called self types,e。 g.:
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
:[=23 的两个实例=]
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
此外,类型检查纯粹是编译时的,i。 e.可以轻松地将 C1
和 C2
实例混合在一个 TreeSet
中,并将它们相互比较。
Java 中 CRTP 的替代方案可以模拟 self 类型 而无需如上所示是否存在滥用的可能性?
P。 S. 我观察到该模式在标准库中并未广泛使用——只有 EnumSet
及其后代实现了它。
我不认为你显示的是"abuse"。所有使用 AbstractFoo
和 C3
的代码仍然是完全类型安全的,只要它们不进行任何不安全的转换。 AbstractFoo
中 SELF
的边界意味着代码可以依赖于 SELF
是 AbstractFoo<SELF>
的子类型这一事实,但代码不能依赖于 AbstractFoo<SELF>
是 SELF
的子类型。因此,例如,如果 AbstractFoo
有一个 returned SELF
的方法,并且它是通过 returning this
实现的(如果它是真的是 "self-type"),它不会编译:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF someMethod() {
return this; // would not compile
}
}
编译器不允许您编译它,因为它不安全。例如,运行 C3
上的此方法会将 return this
(实际上是 C3
实例)作为类型 C1
,这将导致class 调用代码中的强制转换异常。如果您试图通过使用强制转换(例如 return (SELF)this;
)来绕过编译器,那么您会收到一个未经检查的强制转换警告,这意味着您要对其不安全负责。
而且如果你的 AbstractFoo
的使用方式真的只依赖于 SELF extends AbstractFoo<SELF>
(如绑定所说)这一事实,而不依赖于 AbstractFoo<SELF> extends SELF
,那你为什么要关心C3
的"abuse"呢?你仍然可以写你的 classes C1 extends AbstractFoo<C1>
和 C2 extends AbstractFoo<C2>
没问题。如果其他人决定编写 class C3 extends AbstractFoo<C1>
,那么只要他们以不使用不安全转换的方式编写它,编译器就会保证它仍然是类型安全的。也许这样的 class 可能无法做任何有用的事情;我不知道。但它仍然是安全的;那为什么会出现问题呢?
之所以没有像<SELF extends AbstractFoo<SELF>>
这样的递归绑定,是因为在大多数情况下,它并不比<SELF>
更有用。例如,Comparable
接口的类型参数没有界限。如果有人决定写一个 class Foo extends Comparable<Bar>
,他们可以这样做,而且它是类型安全的,虽然不是很有用,因为在大多数 classes 和方法中使用 Comparable
,它们有一个类型变量 <T extends Comparable<? super T>>
,它要求 T
与它自己是可比较的,因此 Foo
class 不能用作任何类型参数那些地方。但是有人愿意写Foo extends Comparable<Bar>
还是可以的。
唯一像 <SELF extends AbstractFoo<SELF>>
这样的递归边界是在实际利用 SELF
扩展 AbstractFoo<SELF>
这一事实的地方,这是非常罕见的。一个地方是类似于构建器模式的东西,它具有 return 对象本身的方法,可以链接。所以如果你有像
这样的方法
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 泛型中没有办法使类型参数必须与当前实现 class 的类型相同。如果假设有这样一种机制,那可能会导致棘手的继承问题:
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
必须是实现 class 类型的约定。如果 SubC1.compareTo()
必须采用 C1
参数,则接收到的事物的类型与当前对象本身的类型不再相同。如果 SubC1.compareTo()
可以采用 SubC1
参数,那么它不再覆盖 C1.compareTo()
,因为它不再采用与 super[=79= 中的方法一样广泛的参数集]需要。
Java中的CRTP pattern allows to emulate the so called self types,e。 g.:
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
:[=23 的两个实例=]
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
此外,类型检查纯粹是编译时的,i。 e.可以轻松地将 C1
和 C2
实例混合在一个 TreeSet
中,并将它们相互比较。
Java 中 CRTP 的替代方案可以模拟 self 类型 而无需如上所示是否存在滥用的可能性?
P。 S. 我观察到该模式在标准库中并未广泛使用——只有 EnumSet
及其后代实现了它。
我不认为你显示的是"abuse"。所有使用 AbstractFoo
和 C3
的代码仍然是完全类型安全的,只要它们不进行任何不安全的转换。 AbstractFoo
中 SELF
的边界意味着代码可以依赖于 SELF
是 AbstractFoo<SELF>
的子类型这一事实,但代码不能依赖于 AbstractFoo<SELF>
是 SELF
的子类型。因此,例如,如果 AbstractFoo
有一个 returned SELF
的方法,并且它是通过 returning this
实现的(如果它是真的是 "self-type"),它不会编译:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF someMethod() {
return this; // would not compile
}
}
编译器不允许您编译它,因为它不安全。例如,运行 C3
上的此方法会将 return this
(实际上是 C3
实例)作为类型 C1
,这将导致class 调用代码中的强制转换异常。如果您试图通过使用强制转换(例如 return (SELF)this;
)来绕过编译器,那么您会收到一个未经检查的强制转换警告,这意味着您要对其不安全负责。
而且如果你的 AbstractFoo
的使用方式真的只依赖于 SELF extends AbstractFoo<SELF>
(如绑定所说)这一事实,而不依赖于 AbstractFoo<SELF> extends SELF
,那你为什么要关心C3
的"abuse"呢?你仍然可以写你的 classes C1 extends AbstractFoo<C1>
和 C2 extends AbstractFoo<C2>
没问题。如果其他人决定编写 class C3 extends AbstractFoo<C1>
,那么只要他们以不使用不安全转换的方式编写它,编译器就会保证它仍然是类型安全的。也许这样的 class 可能无法做任何有用的事情;我不知道。但它仍然是安全的;那为什么会出现问题呢?
之所以没有像<SELF extends AbstractFoo<SELF>>
这样的递归绑定,是因为在大多数情况下,它并不比<SELF>
更有用。例如,Comparable
接口的类型参数没有界限。如果有人决定写一个 class Foo extends Comparable<Bar>
,他们可以这样做,而且它是类型安全的,虽然不是很有用,因为在大多数 classes 和方法中使用 Comparable
,它们有一个类型变量 <T extends Comparable<? super T>>
,它要求 T
与它自己是可比较的,因此 Foo
class 不能用作任何类型参数那些地方。但是有人愿意写Foo extends Comparable<Bar>
还是可以的。
唯一像 <SELF extends AbstractFoo<SELF>>
这样的递归边界是在实际利用 SELF
扩展 AbstractFoo<SELF>
这一事实的地方,这是非常罕见的。一个地方是类似于构建器模式的东西,它具有 return 对象本身的方法,可以链接。所以如果你有像
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 泛型中没有办法使类型参数必须与当前实现 class 的类型相同。如果假设有这样一种机制,那可能会导致棘手的继承问题:
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
必须是实现 class 类型的约定。如果 SubC1.compareTo()
必须采用 C1
参数,则接收到的事物的类型与当前对象本身的类型不再相同。如果 SubC1.compareTo()
可以采用 SubC1
参数,那么它不再覆盖 C1.compareTo()
,因为它不再采用与 super[=79= 中的方法一样广泛的参数集]需要。