Есть ли способ ссылаться на текущий тип с помощью переменной типа? - PullRequest
40 голосов
/ 09 сентября 2011

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

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

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

Ответы [ 7 ]

59 голосов
/ 09 сентября 2011

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

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

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

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

9 голосов
/ 09 сентября 2011

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

class A<T extends A<T>> {
    T foo();
}
class B extends A<B> {
    @Override
    B foo();
}
3 голосов
/ 09 сентября 2011

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

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

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

3 голосов
/ 09 сентября 2011

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

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

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

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

2 голосов
/ 03 июля 2016

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

    }
1 голос
/ 23 ноября 2018

Коллектор обеспечивает Java типом self с помощью аннотации @ 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");
1 голос
/ 06 ноября 2013

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

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