Вопрос-ответ

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

Есть ли способ ссылаться на текущий тип с помощью переменной типа?

Предположим, я пытаюсь написать функцию, возвращающую экземпляр текущего типа. Есть ли способ заставить T ссылаться на точный подтип (поэтому T следует ссылаться на B в классе B)?

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

class B extends A {
@Override
T foo();
}
Переведено автоматически
Ответ 1

Чтобы основываться на ответе StriplingWarrior, я думаю, был бы необходим следующий шаблон (это рецепт для иерархического API fluent builder).

РЕШЕНИЕ

Во-первых, базовый абстрактный класс (или интерфейс), который устанавливает контракт на возврат типа среды выполнения экземпляра, расширяющего класс:

/**
* @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();
}
}

Другие производные классы могут следовать таким же образом. Но ни один из этих классов нельзя использовать напрямую как типы переменных, не прибегая к необработанным типам или подстановочным знакам (что противоречит цели шаблона). Например (если 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. Для того, чтобы замкнуть цикл и использовать шаблон, необходимы "конечные" классы, которые разрешают унаследованный параметр типа 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. Этот шаблон небезопасен по своей сути и должен быть зарезервирован только для внутренней работы вашего внутреннего API. Причина в том, что нет гарантии, что параметр типа SELF в приведенных выше примерах действительно будет преобразован в правильный тип среды выполнения. Например:

public final class EvilLeafClass extends MyBaseClass<AnotherLeafClass> {

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

В этом примере видны две дыры в шаблоне:


  1. EvilLeafClass может "лгать" и заменять любой другой тип, расширяющий MyBaseClass для SELF.

  2. Независимо от этого, нет гарантии, что self() действительно вернетсяthis, что может быть проблемой, а может и не быть, в зависимости от использования state в базовой логике.

По этим причинам этот шаблон имеет большой потенциал для неправильного использования. Чтобы предотвратить это, разрешите публично расширять ни один из задействованных классов - обратите внимание на мое использование конструктора package-private в MyBaseClass , который заменяет неявный открытый конструктор:

MyBaseClass() { }

Если возможно, сохраните self() package-private , чтобы это не добавляло шума и путаницы в общедоступный API. К сожалению, это возможно только в том случае, если SelfTyped является абстрактным классом, поскольку методы интерфейса неявно общедоступны.

Как указывает zhong.j.yu в комментариях, привязка к SELF может быть просто удалена, поскольку в конечном итоге она не обеспечивает "собственный тип":

abstract class SelfTyped<SELF> {

abstract SELF self();
}

Ю советует полагаться только на контракт и избегать любой путаницы или ложного чувства безопасности, которое возникает из-за неинтуитивной рекурсивной привязки. Лично я предпочитаю оставить границу, поскольку SELF extends SelfTyped<SELF> представляет собой ближайшее возможное выражение типа self в Java. Но мнение Ю определенно совпадает с прецедентом, созданным Comparable.


ЗАКЛЮЧЕНИЕ

Это достойный шаблон, который позволяет выполнять плавные и выразительные вызовы вашего builder API. Я несколько раз использовал его в серьезной работе, в первую очередь для написания пользовательского фреймворка query builder, который позволял вызывать сайты, подобные этому:

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 это была не просто плоская реализация, а "лист", исходящий из сложной иерархии классов builder. Тот же шаблон использовался для помощников, таких как Where, Having, Or и т.д. все они должны были совместно использовать значительный код.

Однако вы не должны упускать из виду тот факт, что в конечном итоге все это сводится только к синтаксическому сахару. Некоторые опытные программисты занимают жесткую позицию против шаблона CRT или, по крайней мере, скептически относятся к его преимуществам в сравнении с дополнительной сложностью. Их опасения вполне обоснованны.

В итоге, внимательно посмотрите, действительно ли это необходимо, прежде чем внедрять его, и если вы это сделаете, не делайте его общедоступным.

Ответ 2

Вы должны быть в состоянии сделать это, используя рекурсивный стиль универсального определения, который Java использует для перечислений:

class A<T extends A<T>> {
T foo();
}
class B extends A<B> {
@Override
B foo();
}
Ответ 3

Возможно, я не совсем понял вопрос, но разве недостаточно просто сделать это (обратите внимание на приведение к 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);
}

}
Ответ 4

Просто напишите:

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

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

предполагая, что вы используете Java 1.5+ (ковариантные возвращаемые типы).

java generics