简体   繁体   English

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

[英]Is there a way to refer to the current type with a type variable?

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 )?有没有办法让T引用确切的子类型(所以T应该引用B类中的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).为了建立在StriplingWarrior's answer 的基础上,我认为以下模式是必要的(这是分层流畅构建器 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 :所有中间扩展类必须是abstract并维护递归类型参数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 ):例如(如果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();

This is the reason I refer to these classes as "intermediate", and it's why they should all be marked abstract .这就是我将这些类称为“中级”的原因,也是为什么它们都应该被标记为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() .为了关闭循环并使用模式,“叶”类是必要的,它使用自己的类型解析继承的类型参数SELF并实现self() They should also be marked final to avoid breaking the contract:他们还应该被标记为final以避免违反合同:

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.以上是 Java 中奇怪重复模板模式的实现。 This pattern is not inherently safe and should be reserved for the inner workings of one's internal API only.这种模式本质不是安全的,应该仅用于内部 API 的内部工作。 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.原因是无法保证上述示例中的类型参数SELF会实际解析为正确的运行时类型。 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 . EvilLeafClass可以“撒谎”并将任何其他扩展MyBaseClass类型MyBaseClassSELF
  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.除此之外,不能保证self()实际上会返回this ,这可能是也可能不是问题,具体取决于基本逻辑中 state 的使用。

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使用包私有构造MyBaseClass ,它替换了隐式公共构造函数:

MyBaseClass() { }

If possible, keep self() package-private too, so it doesn't add noise and confusion to the public API.如果可能的话,也要保持self()包的私有性,这样它就不会给公共 API 增加噪音和混乱。 Unfortunately this is only possible if SelfTyped is an abstract class, since interface methods are implicitly public.不幸的是,这只有在SelfTyped是抽象类时才有可能,因为接口方法是隐式公开的。

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":正如 zhong.j.yu 在评论中指出的那样,对SELF的绑定可能会被简单地删除,因为它最终无法确保“自我类型”:

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. Yu 建议仅依赖合约,并避免由非直观的递归边界引起的任何混淆或错误的安全感。 Personally, I prefer to leave the bound since SELF extends SelfTyped<SELF> represents the closest possible expression of the self type in Java.就我个人而言,我更喜欢离开界限,因为SELF extends SelfTyped<SELF>表示 Java 中 self 类型的最接近的可能表达。 But Yu's opinion definitely lines up with the precedent set by Comparable .不过于的意见绝对符合Comparable的先例。


CONCLUSION结论

This is a worthy pattern that allows for fluent and expressive calls to your builder API.这是一种有价值的模式,它允许对构建器 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.关键点是QueryBuilder不仅仅是一个平面实现,而是从构建器类的复杂层次结构扩展的“叶子”。 The same pattern was used for the helpers like Where , Having , Or , etc. all of which needed to share significant code.相同的模式用于诸如WhereHavingOr等助手,所有这些都需要共享重要的代码。

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 .一些有经验的程序员对 CRT 模式采取强硬立场,或者至少对它的好处与增加的复杂性进行权衡持怀疑态度 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.归根结底,在实施之前仔细看看它是否真的有必要——如果你这样做了,不要让它公开扩展。

You should be able to do this using the recursive generic definition style that Java uses for enums:您应该能够使用 Java 用于枚举的递归泛型定义样式来执行此操作:

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

Just write:写就好了:

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

class B extends A {
    @Override
    B foo() { ... }
}

assuming you're using Java 1.5+ ( covariant return types ).假设您使用的是 Java 1.5+( 协变返回类型)。

If you want something akin to Scala's如果你想要类似于 Scala 的东西

trait T {
  def foo() : this.type
}

then no, this is not possible in Java.那么不,这在Java中是不可能的。 You should also note that there is not much you can return from a similarly typed function in Scala, apart from this .您还应该注意,除了this之外,您可以从 Scala 中类似类型的函数返回的内容不多。

I may not fully understood the question, but isn't it enough to just do this (notice casting to T):我可能没有完全理解这个问题,但仅仅这样做还不够(注意投射到 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;
        }
    }

then subclasses won't have to use overriding or covariance of types to make mother class methods return reference to them...那么子类将不必使用类型的覆盖或协方差来使母类方法返回对它们的引用......

    public class PersonBodyBuilder extends BodyBuilder<PersonBodyBuilder> {

        public PersonBodyBuilder(int height, String color) {
            super(height, color);
        }

    }

I found a way do this, it's sort of silly but it works:我找到了一种方法来做到这一点,这有点愚蠢,但它有效:

In the top level class (A):在顶级班级 (A) 中:

protected final <T> T a(T type) {
        return type
}

Assuming C extends B and B extends A.假设C扩展B,B扩展A。

Invoking:调用:

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

Manifold provides Java with the self type via the @Self annotation. Manifold通过@Self注释为 Java 提供了 self 类型。

The simple case:简单的情况:

public class Foo {
  public @Self Foo getMe() {
    return this;
  }
}

public class Bar extends Foo {
}

Bar bar = new Bar().getMe(); // Voila!

Use with generics:与泛型一起使用:

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

Use with extension methods :与扩展方法一起使用

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM