Is there a way to refer to the current type with a type variable?
Есть ли способ ссылаться на текущий тип с помощью переменной типа?
Предположим, я пытаюсь написать функцию, возвращающую экземпляр текущего типа. Есть ли способ заставить T ссылаться на точный подтип (поэтому T следует ссылаться на B в классе B)?
classA { <T extendsA> foo(); }
classBextendsA { @Override T foo(); }
Переведено автоматически
Ответ 1
Чтобы основываться на ответе StriplingWarrior, я думаю, был бы необходим следующий шаблон (это рецепт для иерархического API fluent builder).
РЕШЕНИЕ
Во-первых, базовый абстрактный класс (или интерфейс), который устанавливает контракт на возврат типа среды выполнения экземпляра, расширяющего класс:
/** * @param <SELF> The runtime type of the implementor. */ abstractclassSelfTyped<SELF extendsSelfTyped<SELF>> {
/** * @return This instance. */ abstract SELF self(); }
Все промежуточные расширяющие классы должны быть abstract и поддерживать параметр рекурсивного типа SELF:
Другие производные классы могут следовать таким же образом. Но ни один из этих классов нельзя использовать напрямую как типы переменных, не прибегая к необработанным типам или подстановочным знакам (что противоречит цели шаблона). Например (если MyClass не было abstract):
//wrong: raw type warning MyBaseClassmbc=newMyBaseClass().baseMethod();
//wrong: type argument is not within the bounds of SELF MyBaseClass<MyBaseClass> mbc2 = newMyBaseClass<MyBaseClass>().baseMethod();
//wrong: no way to correctly declare the type, as its parameter is recursive! MyBaseClass<MyBaseClass<MyBaseClass>> mbc3 = newMyBaseClass<MyBaseClass<MyBaseClass>>().baseMethod();
Именно по этой причине я называю эти классы "промежуточными", и именно поэтому все они должны быть помечены abstract. Для того, чтобы замкнуть цикл и использовать шаблон, необходимы "конечные" классы, которые разрешают унаследованный параметр типа SELF с его собственным типом и реализуют self(). Они также должны быть отмечены final, чтобы избежать нарушения контракта:
Значение здесь заключается в том, что вызовы методов могут быть связаны цепочкой вверх и вниз по иерархии классов, сохраняя при этом один и тот же конкретный возвращаемый тип.
Отказ от ответственности
Приведенное выше является реализацией любопытно повторяющегося шаблонного шаблона в Java. Этот шаблон небезопасен по своей сути и должен быть зарезервирован только для внутренней работы вашего внутреннего API. Причина в том, что нет гарантии, что параметр типа SELF в приведенных выше примерах действительно будет преобразован в правильный тип среды выполнения. Например:
EvilLeafClass может "лгать" и заменять любой другой тип, расширяющий MyBaseClass для SELF.
Независимо от этого, нет гарантии, что self() действительно вернетсяthis, что может быть проблемой, а может и не быть, в зависимости от использования state в базовой логике.
По этим причинам этот шаблон имеет большой потенциал для неправильного использования. Чтобы предотвратить это, разрешите публично расширять ни один из задействованных классов - обратите внимание на мое использование конструктора package-private в MyBaseClass , который заменяет неявный открытый конструктор:
MyBaseClass() { }
Если возможно, сохраните self() package-private , чтобы это не добавляло шума и путаницы в общедоступный API. К сожалению, это возможно только в том случае, если SelfTyped является абстрактным классом, поскольку методы интерфейса неявно общедоступны.
Как указывает zhong.j.yu в комментариях, привязка к SELF может быть просто удалена, поскольку в конечном итоге она не обеспечивает "собственный тип":
abstractclassSelfTyped<SELF> {
abstract SELF self(); }
Ю советует полагаться только на контракт и избегать любой путаницы или ложного чувства безопасности, которое возникает из-за неинтуитивной рекурсивной привязки. Лично я предпочитаю оставить границу, поскольку SELF extends SelfTyped<SELF> представляет собой ближайшее возможное выражение типа self в Java. Но мнение Ю определенно совпадает с прецедентом, созданным Comparable.
ЗАКЛЮЧЕНИЕ
Это достойный шаблон, который позволяет выполнять плавные и выразительные вызовы вашего builder API. Я несколько раз использовал его в серьезной работе, в первую очередь для написания пользовательского фреймворка query builder, который позволял вызывать сайты, подобные этому:
Ключевым моментом является то, что QueryBuilder это была не просто плоская реализация, а "лист", исходящий из сложной иерархии классов builder. Тот же шаблон использовался для помощников, таких как Where, Having, Or и т.д. все они должны были совместно использовать значительный код.