有没有办法用类型变量引用当前类型?

jz8*_*z87 42 java generics types

假设我正在尝试编写一个函数来返回当前类型的实例.有没有办法T引用确切的子类型(所以T应该B在课堂上引用B)?

class A {
    <T extends A> foo();
}

class B extends A {
    @Override
    T foo();
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*ora 61

为了构建StriplingWarrior的答案,我认为以下模式是必要的(这是分层流畅的构建器API的配方).

首先,一个基本抽象类(或接口),它规定了返回扩展类的实例的运行时类型的契约:

/**
 * @param <SELF> The runtime type of the implementor.
 */
abstract class SelfTyped<SELF extends SelfTyped<SELF>> {

   /**
    * @return This instance.
    */
   abstract SELF self();
}
Run Code Online (Sandbox Code Playgroud)

所有中间扩展类必须是abstract并维护递归类型参数SELF:

public abstract class MyBaseClass<SELF extends MyBaseClass<SELF>>
extends SelfTyped<SELF> {

    MyBaseClass() { }

    public SELF baseMethod() {

        //logic

        return self();
    }
}
Run Code Online (Sandbox Code Playgroud)

进一步派生的类可以以相同的方式遵循.但是,这些类中没有一个可以直接用作变量类型而不需要使用rawtypes或通配符(这会破坏模式的目的).例如(如果MyClass不是abstract):

//wrong: raw type warning
MyBaseClass mbc = new MyBaseClass().baseMethod();

//wrong: type argument is not within the bounds of SELF
MyBaseClass<MyBaseClass> mbc2 = new MyBaseClass<MyBaseClass>().baseMethod();

//wrong: no way to correctly declare the type, as its parameter is recursive!
MyBaseClass<MyBaseClass<MyBaseClass>> mbc3 =
        new MyBaseClass<MyBaseClass<MyBaseClass>>().baseMethod();
Run Code Online (Sandbox Code Playgroud)

这就是我将这些类称为"中间"的原因,这就是为什么它们都应该被标记的原因abstract.为了关闭循环并使用模式,需要"leaf"类,它们SELF使用自己的类型和实现来解析继承的类型参数self().还应标记它们final以避免违反合同:

public final class MyLeafClass extends MyBaseClass<MyLeafClass> {

    @Override
    MyLeafClass self() {
        return this;
    }

    public MyLeafClass leafMethod() {

        //logic

        return self(); //could also just return this
    }
}
Run Code Online (Sandbox Code Playgroud)

这些类使模式可用:

MyLeafClass mlc = new MyLeafClass().baseMethod().leafMethod();
AnotherLeafClass alc = new AnotherLeafClass().baseMethod().anotherLeafMethod();
Run Code Online (Sandbox Code Playgroud)

这里的值是方法调用,可以在类层次结构中上下链接,同时保持相同的特定返回类型.


免责声明

以上是Java中奇怪重复出现的模板模式的实现.这种模式本身并不安全,应该只为一个内部API的内部工作保留.原因是无法保证SELF上述示例中的type参数实际上将被解析为正确的运行时类型.例如:

public final class EvilLeafClass extends MyBaseClass<AnotherLeafClass> {

    @Override
    AnotherLeafClass self() {
        return getSomeOtherInstanceFromWhoKnowsWhere();
    }
}
Run Code Online (Sandbox Code Playgroud)

此示例在模式中显示两个孔:

  1. EvilLeafClass可以在"躺下"和替代的任何其它类型延伸MyBaseClassSELF.
  2. 独立于此,根据基础逻辑中的状态使用,不能保证self()实际返回this,这可能是也可能不是问题.

由于这些原因,这种模式很有可能被滥用或滥用.为了防止这种情况,让没有参与到公开扩展的类的-注意到我使用的包私有构造的MyBaseClass,它取代了隐公构造函数:

MyBaseClass() { }
Run Code Online (Sandbox Code Playgroud)

如果可能的话,self()也要保持package-private,这样就不会给公共API增加噪音和混乱.不幸的是,这只有SelfTyped在抽象类的情况下才有可能,因为接口方法是隐式公开的.

正如zhong.j.yu 在评论中指出的那样,SELF可能只是删除了绑定,因为它最终无法确保"自我类型":

abstract class SelfTyped<SELF> {

   abstract SELF self();
}
Run Code Online (Sandbox Code Playgroud)

Yu建议仅依靠合同,避免因不直观的递归限制而产生的任何混淆或虚假安全感.就个人而言,我更喜欢离开边界,因为它SELF extends SelfTyped<SELF>代表了Java中自我类型最接近的可能表达式.但是,俞的观点肯定与先例所确定的一致Comparable.


结论

这是一个有价值的模式,允许对构建器API进行流畅和富有表现力的调用.我已经在认真的工作中使用了几次,最值得注意的是编写一个自定义查询构建器框架,它允许这样的调用站点:

List<Foo> foos = QueryBuilder.make(context, Foo.class)
    .where()
        .equals(DBPaths.from_Foo().to_FooParent().endAt_FooParentId(), parentId)
        .or()
            .lessThanOrEqual(DBPaths.from_Foo().endAt_StartDate(), now)
            .isNull(DBPaths.from_Foo().endAt_PublishedDate())
            .or()
                .greaterThan(DBPaths.from_Foo().endAt_EndDate(), now)
            .endOr()
            .or()
                .isNull(DBPaths.from_Foo().endAt_EndDate())
            .endOr()
        .endOr()
        .or()
            .lessThanOrEqual(DBPaths.from_Foo().endAt_EndDate(), now)
            .isNull(DBPaths.from_Foo().endAt_ExpiredDate())
        .endOr()
    .endWhere()
    .havingEvery()
        .equals(DBPaths.from_Foo().to_FooChild().endAt_FooChildId(), childId)
    .endHaving()
    .orderBy(DBPaths.from_Foo().endAt_ExpiredDate(), true)
    .limit(50)
    .offset(5)
    .getResults();
Run Code Online (Sandbox Code Playgroud)

关键点在于,QueryBuilder它不仅仅是一个平面实现,而是从构建器类的复杂层次结构延伸出来的"叶子".被用于助手等相同的图案Where,Having,Or所有这些都需要共享显著代码等.

但是,你不应该忽视这样一个事实,即所有这些最终只相当于语法糖.一些有经验的程序员对CRT模式采取强硬态度,或者至少对其带来的好处与增加的复杂性持怀疑态度.他们的担忧是合法的.

最重要的是,在实施之前要仔细研究是否真的有必要 - 如果你这样做,不要公开扩展.

  • @StriplingWarrior同意 - 它更适合一个API的内部工作,不能公开扩展.我最近使用这个结构来建立一个流畅的构建模式.例如,可以调用`new MyFinalClass().foo().bar()`其中`foo()`在超类中声明,`bar()`在`MyFinalClass`中定义.只有`final`具体类暴露在包外面,所以我的团队成员不能搞乱`self()`等.直到现在我还没有意识到维护类型参数的中间类不能直接使用. (3认同)
  • 通过已经在`SelfTyped`类中为`self()`提供了一个(最终)实现,就不需要在每个子类中都具有它的琐碎实现。`@SuppressWarnings(“ unchecked”)保护了最终的SELF self(){返回(SELF)this; }``EvilLeafClass`不能返回任意对象。相反,如果不遵守合同,将抛出“ ClassCastException”。 (2认同)

Str*_*ior 9

您应该能够使用Java用于枚举的递归泛型定义样式来执行此操作:

class A<T extends A<T>> {
    T foo();
}
class B extends A<B> {
    @Override
    B foo();
}
Run Code Online (Sandbox Code Playgroud)


dm3*_*dm3 3

写吧:

class A {
    A foo() { ... }
}

class B extends A {
    @Override
    B foo() { ... }
}
Run Code Online (Sandbox Code Playgroud)

假设您使用的是 Java 1.5+(协变返回类型)。