Java 中 CRTP 的替代品 [关闭]

2024-01-29

The CRTP https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern模式允许模拟所谓的自我类型 http://web.archive.org/web/20130721224442/http:/passion.forco.de/content/emulating-self-types-using-java-generics-simplify-fluent-api-implementation in Java, 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:

new C1().compareTo(new C1());

but not AbstractFoo具体类型不同的后代:

new C1().compareTo(new C2()); // compilation error

然而,这种模式很容易被滥用:

final class C3 extends AbstractFoo<C1> {
    // ...
}

// ...

new C3().compareTo(new C1()); // compiles cleanly

此外,类型检查纯粹是编译时的,即。 e.可以轻松混合C1 and C2单个实例TreeSet并将它们相互比较。

有什么替代方案CRTP in Java模仿自我类型没有如上所示的滥用可能性?

P. S.我观察到该模式并未在标准库中广泛使用——仅EnumSet及其后代实现它。


我不认为你表现出的是“虐待”。所有使用的代码AbstractFoo and 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
    }
}

编译器不允许您编译它,因为它不安全。例如,运行此方法C3会回来this(这实际上是一个C3实例)作为类型C1,这会在调用代码中导致类转换异常。如果您尝试使用强制转换来绕过编译器,例如return (SELF)this;,然后您会收到未经检查的强制转换警告,这意味着您要对其不安全负责。

如果你的AbstractFoo实际上以一种仅依赖于以下事实的方式使用SELF extends AbstractFoo<SELF>(正如界限所说),并且不依赖于以下事实:AbstractFoo<SELF> extends SELF,那你为什么关心“滥用”C3?您仍然可以编写课程C1 extends AbstractFoo<C1> and 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? */) {
        // ...
    }
}

Here, SubC1隐式继承AbstractFoo<C1>,但这违反了合同SELF必须是实现类的类型。如果SubC1.compareTo()必须采取C1参数,那么接收到的事物的类型与当前对象本身的类型不再相同。如果SubC1.compareTo()可以采取SubC1参数,那么它不再被覆盖C1.compareTo(),因为它不再需要与超类中的方法一样宽的参数集。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 中 CRTP 的替代品 [关闭] 的相关文章

随机推荐