有没有办法用类型变量引用当前类型? [英] Is there a way to refer to the current type with a type variable?

查看:110
本文介绍了有没有办法用类型变量引用当前类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  A类{
< T扩展A> FOO();
}

class B extends A {
@Override
T foo();


解决方案

=https://stackoverflow.com/a/7354847/697449> StriplingWarrior的答案,我认为以下模式将是必要的(这是一个层级流利的生成器API的配方)。



解决方案

首先,基础抽象类(或接口)扩展类的实例的运行时类型:

  / ** 
* @param< SELF>实现者的运行时类型。
* /
抽象类SelfTyped< SELF extends SelfTyped< SELF>> {

/ **
* @return这个实例。
* /
抽象SELF self();

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

  public抽象类MyBaseClass< SELF扩展MyBaseClass< SELF>> 
扩展了SelfTyped< SELF> {

MyBaseClass(){}
$ b $公共自我baseMethod(){

//逻辑

return self );
}
}

其他派生类可以按照相同的方式进行操作。但是,这些类没有一个可以直接用作变量类型,而无需使用原始类型或通配符(这会破坏模式的目的)。例如(如果 MyClass 不是 abstract ):

  //错误:原始类型警告
MyBaseClass mbc = new MyBaseClass()。baseMethod();

//错误:类型参数不在SELF
的范围内MyBaseClass< MyBaseClass> mbc2 = new MyBaseClass< MyBaseClass>()。baseMethod();

//错误:无法正确声明类型,因为它的参数是递归的!
MyBaseClass< MyBaseClass< MyBaseClass>> mbc3 =
new MyBaseClass< MyBaseClass< MyBaseClass>>()。baseMethod();

这就是我将这些类称为中间的原因,因此它们都应该是标记为摘要。为了关闭循环并使用该模式,需要叶类,它用它自己的类型解析继承的类型参数 SELF 并实现自()。他们还应该标记为 final 以避免违约:

  public final类MyLeafClass扩展了MyBaseClass< MyLeafClass> {

@Override
MyLeafClass self(){
return this;

$ b $ public MyLeafClass leafMethod(){

//逻辑

return self(); //也可以返回这个


code


这样的类使得模式可用:

  MyLeafClass mlc = new MyLeafClass()。baseMethod()。leafMethod(); 
AnotherLeafClass alc = new AnotherLeafClass()。baseMethod()。anotherLeafMethod();

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


$ b

免责声明



以上是Java中奇怪的循环模板模式的实现。这种模式不是固有的安全性,应该仅用于内部API的内部工作。原因在于,不能保证上述示例中的类型参数 SELF 实际上将被解析为正确的运行时类型。例如:

  public final class EvilLeafClass extends MyBaseClass< AnotherLeafClass> {

@Override
AnotherLeafClass self(){
return getSomeOtherInstanceFromWhoKnowsWhere();




$ b $ p
$ b

这个例子在模式中公开了两个漏洞: p>


  1. EvilLeafClass 可以撒谎并替换任何其他类型,扩展 MyBaseClass 用于 SELF

  2. 独立于此,不保证 self )实际上会返回这个,这可能是也可能不是问题,这取决于基本逻辑中状态的使用。

由于这些原因,这种模式很可能被误用或滥用。为了防止这种情况发生,请让所涉及的类的 none 公开扩展 - 注意我在 MyBaseClass 中使用了包 - 私有构造函数,隐式公共构造函数:

pre $ MyBaseClass(){}

如果可能,请保留 self() package-private,这样不会给公共API添加噪音和混淆。不幸的是,只有当 SelfTyped 是一个抽象类时,这是可能的,因为接口方法是隐式公开的。

正如zhong。 j.yu 指出,可能会简单地删除 SELF 的界限,因为它最终无法确保自我类型:

 抽象类SelfTyped< SELF> {

抽象SELF self();
}

余建议只依靠合约,避免任何混淆或错觉来自不直观的递归界限的安全性。就我个人而言,我更愿意离开> SELF extends SelfTyped< SELF> 代表Java中自我类型的最接近的可能的表达式。但余的观点与 Comparable






结论



这是一个有价值的模式,可以对您的构建器API进行流畅而富有表现力的调用。我已经在严肃的工作中使用了很多次,特别是写一个自定义查询生成器框架,它允许这样的调用网站:

 列表与LT;富> 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()

.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)
.off set(5)
.getResults();

关键是 QueryBuilder wasn'只是一个扁平的实现,而是从复杂的构建器类层次扩展而来的叶子。对于像其中拥有等等,所有这些都需要共享重要的代码。



然而,你不应该忽略这样的事实,即所有这些仅仅是语法糖结束。一些有经验的程序员对CRT模式采取强硬立场,或至少对增加复杂性的好处持怀疑态度。他们的担忧是合法的。

底线,在实施之前要认真考虑是否真的有必要 - 如果你这样做,不要公开扩展。

Suppose I'm trying to write a function to return an instance of the current type. Is there a way to make T refer to the exact subtype (so T should refer to B in class B)?

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

class B extends A {
    @Override
    T foo();
}

To build on StriplingWarrior's answer, I think the following pattern would be necessary (this is a recipe for a hierarchical fluent builder API).

SOLUTION

First, a base abstract class (or interface) that lays out the contract for returning the runtime type of an instance extending the class:

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

   /**
    * @return This instance.
    */
   abstract SELF self();
}

All intermediate extending classes must be abstract and maintain the recursive type parameter SELF:

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

    MyBaseClass() { }

    public SELF baseMethod() {

        //logic

        return self();
    }
}

Further derived classes can follow in the same manner. But, none of these classes can be used directly as types of variables without resorting to rawtypes or wildcards (which defeats the purpose of the pattern). For example (if MyClass wasn't 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();

This is the reason I refer to these classes as "intermediate", and it's why they should all be marked abstract. In order to close the loop and make use of the pattern, "leaf" classes are necessary, which resolve the inherited type parameter SELF with its own type and implement self(). They should also be marked final to avoid breaking the contract:

public final class MyLeafClass extends MyBaseClass<MyLeafClass> {

    @Override
    MyLeafClass self() {
        return this;
    }

    public MyLeafClass leafMethod() {

        //logic

        return self(); //could also just return this
    }
}

Such classes make the pattern usable:

MyLeafClass mlc = new MyLeafClass().baseMethod().leafMethod();
AnotherLeafClass alc = new AnotherLeafClass().baseMethod().anotherLeafMethod();

The value here being that method calls can be chained up and down the class hierarchy while keeping the same specific return type.


DISCLAIMER

The above is an implementation of the curiously recurring template pattern in Java. This pattern is not inherently safe and should be reserved for the inner workings of one's internal API only. The reason is that there is no guarantee the type parameter SELF in the above examples will actually be resolved to the correct runtime type. For example:

public final class EvilLeafClass extends MyBaseClass<AnotherLeafClass> {

    @Override
    AnotherLeafClass self() {
        return getSomeOtherInstanceFromWhoKnowsWhere();
    }
}

This example exposes two holes in the pattern:

  1. EvilLeafClass can "lie" and substitute any other type extending MyBaseClass for SELF.
  2. Independent of that, there's no guarantee self() will actually return this, which may or may not be an issue, depending on the use of state in the base logic.

For these reasons, this pattern has great potential to be misused or abused. To prevent that, allow none of the classes involved to be publicly extended - notice my use of the package-private constructor in MyBaseClass, which replaces the implicit public constructor:

MyBaseClass() { }

If possible, keep self() package-private too, so it doesn't add noise and confusion to the public API. Unfortunately this is only possible if SelfTyped is an abstract class, since interface methods are implicitly public.

As zhong.j.yu points out in the comments, the bound on SELF might simply be removed, since it ultimately fails to ensure the "self type":

abstract class SelfTyped<SELF> {

   abstract SELF self();
}

Yu advises to rely only on the contract, and avoid any confusion or false sense of security that comes from the unintuitive recursive bound. Personally, I prefer to leave the bound since SELF extends SelfTyped<SELF> represents the closest possible expression of the self type in Java. But Yu's opinion definitely lines up with the precedent set by Comparable.


CONCLUSION

This is a worthy pattern that allows for fluent and expressive calls to your builder API. I've used it a handful of times in serious work, most notably to write a custom query builder framework, which allowed call sites like this:

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();

The key point being that QueryBuilder wasn't just a flat implementation, but the "leaf" extending from a complex hierarchy of builder classes. The same pattern was used for the helpers like Where, Having, Or, etc. all of which needed to share significant code.

However, you shouldn't lose sight of the fact that all this only amounts to syntactic sugar in the end. Some experienced programmers take a hard stance against the CRT pattern, or at least are skeptical of the its benefits weighed against the added complexity. Their concerns are legitimate.

Bottom-line, take a hard look at whether it's really necessary before implementing it - and if you do, don't make it publicly extendable.

这篇关于有没有办法用类型变量引用当前类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆