开发者

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

开发者 https://www.devze.com 2023-04-03 10:41 出处:网络
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)?

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.


You should be able to do this using the recursive generic definition style that Java uses for enums:

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


I may not fully understood the question, but isn't it enough to just do this (notice casting to 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);
        }

    }


Just write:

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

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

assuming you're using Java 1.5+ (covariant return types).


If you want something akin to Scala's

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

then no, this is not possible in Java. You should also note that there is not much you can return from a similarly typed function in Scala, apart from this.


I found a way do this, it's sort of silly but it works:

In the top level class (A):

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

Assuming C extends B and B extends 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");
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号