Подтвердить что ты не робот

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

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

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

class B extends A {
    @Override
    T foo();
}
4b9b3361

Ответ 1

Чтобы использовать ответ StriplingWarrior, я думаю, что потребуется следующий шаблон (это рецепт для иерархического свободного API-интерфейса 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();
    }
}

Другие производные классы могут следовать аналогичным образом. Но ни один из этих классов не может использоваться непосредственно как типы переменных, не прибегая к rawtypes или wildcards (который побеждает цель шаблона). Например (если 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();
    }
}

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

  • EvilLeafClass может "лежать" и заменять любой другой тип, расширяющий MyBaseClass для SELF.
  • Независимо от этого, нет гарантии, что self() действительно вернет this, что может быть или не быть проблемой, в зависимости от использования состояния в базовой логике.

По этим причинам эта модель имеет большой потенциал для злоупотребления или злоупотребления. Чтобы этого не произошло, не разрешайте публично распространять ни один из классов, не обращайте внимание на мое использование конструктора 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.


Заключение

Это достойный образец, который позволяет свободно и выразительно обращаться к вашему 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 и т.д., Все из которых должны были совместно использовать значительный код.

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

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

Ответ 2

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

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

Ответ 4

Если вам нужно что-то похожее на Scala

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

тогда нет, это невозможно в Java. Вы также должны заметить, что вы не можете вернуться из аналогично типизированной функции в Scala, кроме this.

Ответ 5

Я не вполне понял вопрос, но недостаточно ли этого сделать (обратите внимание на Т):

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

    }

Ответ 6

Я нашел способ , это вроде глупо, но он работает:

В классе верхнего уровня (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");