有没有办法用typesvariables引用当前types?
假设我试图编写一个函数来返回当前types的一个实例。 有没有办法让T
指的是确切的子types(所以T
应该指B
中的B
)?
class A { <T extends A> foo(); } class B extends A { @Override T foo(); }
为了build立在StriplingWarrior的答案 ,我认为下面的模式将是必要的(这是一个层次stream利的build设者API的食谱)。
解
首先,一个基本的抽象类(或接口),为扩展该类的实例的运行时types设置合同:
/** * @param <SELF> The runtime type of the implementor. */ abstract class SelfTyped<SELF extends SelfTyped<SELF>> { /** * @return This instance. */ abstract SELF self(); }
所有的中间扩展类都必须是abstract
并保持recursiontypes参数SELF
:
public abstract class MyBaseClass<SELF extends MyBaseClass<SELF>> extends SelfTyped<SELF> { MyBaseClass() { } public SELF baseMethod() { //logic return self(); } }
其他派生类可以按照相同的方式。 但是,这些类都不能直接作为variables的types使用,而不使用原型或通配符(这会破坏模式的目的)。 例如(如果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();
这就是我把这些课程称为“中级”的原因,这就是为什么他们都应该被标记为abstract
。 为了closures循环并使用该模式,需要“叶”类,它用自己的typesparsinginheritance的types参数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 } }
这样的类使模式可用:
MyLeafClass mlc = new MyLeafClass().baseMethod().leafMethod(); AnotherLeafClass alc = new AnotherLeafClass().baseMethod().anotherLeafMethod();
这里的值是方法调用可以在类层次结构中链接,同时保持相同的特定返回types。
免责声明
以上是Java中奇怪的循环模板模式的实现。 这种模式本质上并不安全 ,只能用于内部API的内部工作。 原因在于,不能保证上述示例中的types参数SELF
实际上将被parsing为正确的运行时types。 例如:
public final class EvilLeafClass extends MyBaseClass<AnotherLeafClass> { @Override AnotherLeafClass self() { return getSomeOtherInstanceFromWhoKnowsWhere(); } }
这个例子公开了这个模式中的两个漏洞:
-
EvilLeafClass
可以“说谎”,并替代扩展MyBaseClass
任何其他types的SELF
。 - 除此之外,不能保证
self()
实际上会返回this
,这可能是也可能不是问题,这取决于基本逻辑中状态的使用。
由于这些原因,这种模式很有可能被滥用或滥用。 为了防止这种情况发生,请不要将涉及的类公开扩展 – 注意我在MyBaseClass
使用了包 – 私有构造MyBaseClass
,该构造函数replace了隐式的公共构造函数:
MyBaseClass() { }
如果可能的话,还要保持self()
包的私有性,所以它不会给公共API添加噪音和混淆。 不幸的是,只有当SelfTyped
是一个抽象类时才可能,因为接口方法是隐式公开的。
正如zhong.j.yu在评论中指出的那样 , SELF
的界限可能会被简单地删除,因为它最终无法确保“自我types”:
abstract class SelfTyped<SELF> { abstract SELF self(); }
余build议只依靠合同,避免来自不直观的recursion界限的混淆或虚假的安全感。 就我个人而言,我更喜欢离开界限,因为SELF extends SelfTyped<SELF>
表示Java中自我types的最接近的expression式。 但是俞的观点与“ Comparable
的先例Comparable
。
结论
这是一个有价值的模式,可以对您的构build器API进行stream畅而富有performance力的调用。 我已经使用过很多次了,特别是编写一个定制的查询生成器框架,它允许这样的调用网站:
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();
关键在于QueryBuilder
不仅仅是一个扁平的实现,而是从一个复杂的构build器类层次中延伸出来的“叶子”。 像Where
, Having
, Or
等帮助者使用相同的模式,所有这些都需要共享重要的代码。
然而,你不应该忽略这一切,最终只能是语法糖。 一些经验丰富的程序员对CRT模式采取强硬立场 ,或者至less对其增加的复杂性所带来的好处持怀疑态度 。 他们的担忧是合法的。
底线,在实施之前要认真考虑是否真的有必要 – 如果你这样做,不要公开扩展。
您应该能够使用Java用于枚举的recursiongenerics定义样式来执行此操作:
class A<T extends A<T>> { T foo(); } class B extends A<B> { @Override B foo(); }
只要写:
class A { A foo() { ... } } class B extends A { @Override B foo() { ... } }
假设你正在使用Java 1.5+( 协变返回types )。
如果你想要类似于Scala的东西
trait T { def foo() : this.type }
那么不,在Java中这是不可能的。 你也应该注意到,除了this
之外,你可以从Scala中类似的函数中返回的东西不多。
我发现了一个办法 ,这是一种愚蠢的,但它的作品:
在顶级课程(A)中:
protected final <T> T a(T type) { return type }
假设C扩展B和B扩展A.
调用:
C c = new C(); //Any order is fine and you have compile time safety and IDE assistance. c.setA("a").a(c).setB("b").a(c).setC("c");
我可能不完全明白这个问题,但仅仅这样做还不够(注意铸造到T):
private static class BodyBuilder<T extends BodyBuilder> { private final int height; private final String skinColor; //default fields private float bodyFat = 15; private int weight = 60; public BodyBuilder(int height, String color) { this.height = height; this.skinColor = color; } public T setBodyFat(float bodyFat) { this.bodyFat = bodyFat; return (T) this; } public T setWeight(int weight) { this.weight = weight; return (T) this; } public Body build() { Body body = new Body(); body.height = height; body.skinColor = skinColor; body.bodyFat = bodyFat; body.weight = weight; return body; } }
那么子类将不必使用重写或协方差types来使母类方法返回对它们的引用…
public class PersonBodyBuilder extends BodyBuilder<PersonBodyBuilder> { public PersonBodyBuilder(int height, String color) { super(height, color); } }