【问题标题】:Alternatives to CRTP in Java [closed]Java中CRTP的替代品[关闭]
【发布时间】:2018-10-02 15:04:34
【问题描述】:

CRTP 模式允许在 Java 中模拟所谓的self types,例如。 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());

但不是AbstractFoo具体类型不同的后代:

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

但是,这种模式很容易被滥用:

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

// ...

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

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

Java 中的 CRTP 有什么替代方法可以模拟 self 类型,而不会像上面显示的那样被滥用?

P. S. 我观察到该模式在标准库中没有被广泛使用——只有EnumSet 及其后代实现了它。

【问题讨论】:

  • 什么衡量标准决定了替代方案是否比 CRTP更好?这个问题听起来很主观。
  • @jaco0646 好吧,我理解这个问题听起来可能是基于意见的,但我希望任何回复描述可能的替代方案。相应地编辑了问题。
  • 如果 any 替代方案是可以接受的,那么问题就变得太宽泛了:没有比较潜在答案的基础。仅供参考,我认为这个问题很有趣,所以我试图让它变得不那么开放。或许,“Java 中有什么替代 CRTP 的方法可以模拟 self 类型而没有滥用的可能性,如上所示?
  • @jaco0646 谢谢,按照您的建议进行了编辑。
  • @Bass this may be?

标签: java generics crtp


【解决方案1】:

我不认为你展示的是“滥用”。所有使用AbstractFooC3 的代码仍然是完全类型安全的,只要它们不进行任何不安全的强制转换。 SELFAbstractFoo 中的边界意味着代码可以依赖SELFAbstractFoo&lt;SELF&gt; 的子类型,但代码不能依赖AbstractFoo&lt;SELF&gt;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&lt;SELF&gt; 的事实(如界所说),而不依赖于AbstractFoo&lt;SELF&gt; extends SELF 的事实,那么你为什么关心C3 的“滥用”?你仍然可以编写你的课程C1 extends AbstractFoo&lt;C1&gt;C2 extends AbstractFoo&lt;C2&gt; 很好。如果其他人决定编写一个类C3 extends AbstractFoo&lt;C1&gt;,那么只要他们以不使用不安全强制转换的方式编写它,编译器就保证它仍然是类型安全的。也许这样的类可能无法做任何有用的事情;我不知道。但它仍然是安全的;那为什么会有问题呢?

&lt;SELF extends AbstractFoo&lt;SELF&gt;&gt; 这样的递归绑定没有被大量使用的原因是,在大多数情况下,它并不比&lt;SELF&gt; 更有用。例如,Comparable 接口的类型参数没有界限。如果有人决定编写一个类Foo extends Comparable&lt;Bar&gt;,他们可以这样做,它是类型安全的,虽然不是很有用,因为在大多数使用Comparable 的类和方法中,它们都有一个类型变量&lt;T extends Comparable&lt;? super T&gt;&gt;,它要求 T 与自身具有可比性,因此 Foo 类在任何这些地方都不能用作类型参数。但是如果有人愿意,也可以写Foo extends Comparable&lt;Bar&gt;

&lt;SELF extends AbstractFoo&lt;SELF&gt;&gt; 这样的递归绑定的唯一地方是在一个实际上利用SELF 扩展AbstractFoo&lt;SELF&gt; 的地方,这是非常罕见的。一个地方是类似于构建器模式的东西,它具有返回对象本身的方法,可以链接。所以如果你有类似的方法

abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
    public SELF foo() { }
    public SELF bar() { }
    public SELF baz() { }
}

而您的一般值为AbstractFoo&lt;?&gt; x,您可以执行x.foo().bar().baz() 之类的操作,如果将其声明为abstract class AbstractFoo&lt;SELF&gt;,您将无法执行此操作。

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&lt;C1&gt;,但这违反了SELF 必须是实现类的类型的约定。如果SubC1.compareTo() 必须接受C1 参数,那么接收到的事物的类型与当前对象本身的类型不再是正确的。如果SubC1.compareTo() 可以接受SubC1 参数,则它不再覆盖C1.compareTo(),因为它不再需要与超类中的方法一样宽的参数集。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 2010-11-26
    • 2011-02-26
    • 2011-08-18
    • 2014-08-26
    • 2011-02-19
    相关资源
    最近更新 更多