假设我正在尝试编写一个函数来返回当前类型的实例。有没有办法让T
引用确切的子类型(因此T
应引用类B
中的B
)?
class A {
<T extends A> foo();
}
class B extends A {
@Override
T foo();
}
答案 0 :(得分:55)
要在StriplingWarrior's answer上构建,我认为需要以下模式(这是分层流畅构建器API的配方)。
<强>解强>
首先,一个基本抽象类(或接口),它规定了返回扩展该类的实例的运行时类型的契约:
/**
* @param <SELF> The runtime type of the implementor.
*/
abstract class SelfTyped<SELF extends SelfTyped<SELF>> {
/**
* @return This instance.
*/
abstract SELF self();
}
所有中间扩展类必须为abstract
并维护递归类型参数SELF
:
public abstract class MyBaseClass<SELF extends MyBaseClass<SELF>>
extends SelfTyped<SELF> {
MyBaseClass() { }
public SELF baseMethod() {
//logic
return self();
}
}
进一步派生的类可以以相同的方式遵循。但是,这些类中没有一个可以直接用作变量类型而不需要使用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();
这就是我将这些类称为“中间”的原因,这就是为什么它们都应该被标记为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
}
}
这些类使模式可用:
MyLeafClass mlc = new MyLeafClass().baseMethod().leafMethod();
AnotherLeafClass alc = new AnotherLeafClass().baseMethod().anotherLeafMethod();
这里的值是方法调用可以在类层次结构中上下链接,同时保持相同的特定返回类型。
<强>声明强>
以上是Java中curiously recurring template pattern的实现。此模式本质上不安全,应仅为一个内部API的内部工作保留。原因是无法保证上述示例中的类型参数SELF
实际上将被解析为正确的运行时类型。例如:
public final class EvilLeafClass extends MyBaseClass<AnotherLeafClass> {
@Override
AnotherLeafClass self() {
return getSomeOtherInstanceFromWhoKnowsWhere();
}
}
此示例在模式中显示两个孔:
EvilLeafClass
可以“撒谎”并替换任何其他类型MyBaseClass
SELF
。self()
实际上会返回this
,这可能是也可能不是问题,具体取决于基本逻辑中状态的使用。由于这些原因,这种模式很有可能被滥用或滥用。为了防止这种情况,允许公开扩展所涉及的类的 none - 请注意我在MyBaseClass
中使用package-private构造函数,它取代了隐式公共构造函数:
MyBaseClass() { }
如果可能的话,请保持self()
package-private,这样就不会给公共API增加噪音和混淆。不幸的是,只有SelfTyped
是一个抽象类才有可能,因为接口方法是隐式公开的。
作为评论中的zhong.j.yu points out,SELF
上的界限可能会被删除,因为它最终无法确保“自我类型”:
abstract class SelfTyped<SELF> {
abstract SELF self();
}
Yu建议仅依靠合同,避免因不直观的递归限制而产生的任何混淆或虚假安全感。就个人而言,我更喜欢离开绑定,因为SELF extends SelfTyped<SELF>
表示Java中自我类型的最接近的表达式。但是Yu的意见肯定与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();
关键点在于QueryBuilder
不仅仅是一个平面实现,而是从构建器类的复杂层次结构延伸出来的“叶子”。相同的模式用于帮助者,如Where
,Having
,Or
等,所有这些都需要共享重要的代码。
然而,你不应该忽视这样一个事实,即所有这些最终只相当于语法糖。一些有经验的程序员take a hard stance against the CRT pattern,或至少are skeptical of the its benefits weighed against the added complexity。他们的担忧是合法的。
最重要的是,在实施之前要仔细研究是否真的有必要 - 如果你这样做,不要公开扩展。
答案 1 :(得分:8)
您应该能够使用Java用于枚举的递归通用定义样式来执行此操作:
class A<T extends A<T>> {
T foo();
}
class B extends A<B> {
@Override
B foo();
}
答案 2 :(得分:3)
只需写下:
class A {
A foo() { ... }
}
class B extends A {
@Override
B foo() { ... }
}
假设您使用的是Java 1.5+(covariant return types)。
答案 3 :(得分:3)
如果你想要类似于Scala的东西
trait T {
def foo() : this.type
}
然后不,这在Java中是不可能的。您还应该注意,除了this
之外,您在Scala中从类似类型的函数返回的内容并不多。
答案 4 :(得分:2)
我可能还没有完全理解这个问题,但仅仅做到这一点还不够(通知投射到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;
}
}
然后子类不必使用类型的重写或协方差来使母类方法返回对它们的引用...
public class PersonBodyBuilder extends BodyBuilder<PersonBodyBuilder> {
public PersonBodyBuilder(int height, String color) {
super(height, color);
}
}
答案 5 :(得分:1)
我发现way这样做了,它有点傻但是有效:
在顶级班级(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");
答案 6 :(得分:1)
Manifold通过 @Self 注释为Java提供了self类型。
简单的情况:
public class Foo {
public @Self Foo getMe() {
return this;
}
}
public class Bar extends Foo {
}
Bar bar = new Bar().getMe(); // Voila!
与泛型一起使用:
public class Tree {
private List<Tree> children;
public List<@Self Tree> getChildren() {
return children;
}
}
public class MyTree extends Tree {
}
MyTree tree = new MyTree();
...
List<MyTree> children = tree.getChildren(); // :)
与扩展方法一起使用:
package extensions.java.util.Map;
import java.util.Map;
public class MyMapExtensions {
public static <K,V> @Self Map<K,V> add(@This Map<K,V> thiz, K key, V value) {
thiz.put(key, value);
return thiz;
}
}
// `map` is a HashMap<String, String>
var map = new HashMap<String, String>()
.add("bob", "fishspread")
.add("alec", "taco")
.add("miles", "mustard");