Как я могу построить иерархию интерфейса для моего свободного API?
Я работаю над свободным API и пытаюсь использовать универсальные методы Java, чтобы предложить элегантный API, который обрабатывает преобразования типов для моих пользователей. Я сталкиваюсь с некоторыми проблемами, заставляя это работать, хотя, из-за стирания типа.
Вот упрощенная версия моих интерфейсов, которая показывает проблему, с которой я сталкиваюсь:
interface Query<T extends Query<T>> {
T execute();
Query<T> appendClause();
}
interface A<T extends A> extends Query<T> { }
class AImpl implements A<A> {
A execute() { ... }
Query<A> appendClause() { ... }
}
Я получаю сообщение об ошибке AImpl.appendClause()
, Компилятор говорит, что A
находится за его пределами и должен расширять Query. Насколько я могу судить, мое заявление о том, что AImpl
инвентарь A<A>
Значит это A
действительно распространяется Query<A>
,
Получив еще один ответ, я попытался разбить любую потенциальную неразрешимую рекурсию, изменив AImpl
чтобы:
class AImpl implements A<AImpl> {
AImpl execute() { ... }
Query<AImpl> appendClause() { ... }
}
Теперь я получаю сообщение об ошибке компиляции A
что говорит "параметр типа T не в его пределах".
Кто-нибудь получил какие-либо предложения о том, как справиться с этим? Дженерики Java вызывают головную боль.
РЕДАКТИРОВАТЬ
Я изменил определение А на
interface A<T extends A<T>> extends Query<T> { }
И это заставило работать вторую реализацию AImpl. Но я также хочу расширить API с возможностью запроса подклассов A:
interface B<T extends B> extends A<B> { }
class BImpl implements B<BImpl> {
BImpl execute() { ... }
Query<BImpl> appendClause() { ... }
}
Это определение дает мне ошибку в B
Объявление: "Параметр типа B находится за его пределами; должен расширять A".
Я могу устранить эту ошибку, изменив B на
interface B<T extends B<T>> extends A<B<T>> { }
но теперь мои определения интерфейса начинают выглядеть нелепо, и у меня возникает ощущение, что я делаю что-то не так. Кроме того, я по- прежнему получаю сообщение об ошибке в BImpl: "appendClause() в BImpl не может реализовать appendClause () в Query; пытается использовать несовместимый тип возвращаемого значения".
Любые предложения о том, как я могу очистить определения своего подкласса, чтобы мне не нужно было указывать всю иерархию наследования в extends
или как я могу заставить работать BImpl?
РЕДАКТИРОВАТЬ 2
Хорошо, я столкнулся с другой проблемой. У меня есть фабричный класс, который генерирует запросы:
public class QueryFactory {
public static <T extends Query<T>> Query<T> queryForType(Class<T> type) { ... }
}
и код клиента:
Query<B> bQuery = QueryFactory.queryForType(B.class);
Мой клиентский код выдает мне ошибку в объявлении для bQuery: "Параметр типа" B "находится за его пределами; должен расширять" Запрос "". В этот момент я подумал, что B продлил Query...
Эта ошибка исчезнет, если я изменю вызов queryForType() на
Query<? extends B> bQuery = QueryFactory.queryForType(B.class);
но я все еще получаю непроверенное предупреждение от компилятора:
unchecked method invocation: <T>queryForType(Class<T>) in QueryFactory is applied to Class<B>
unchecked conversion found: Query required: Query<B>
Похоже, стирание типа снова борется со мной, но я не понимаю эти предупреждения. Есть еще предложения, чтобы вернуть меня в нужное русло? Спасибо!
РЕДАКТИРОВАТЬ 3
Я могу получить его без предупреждения, если я изменю код клиента на
Query<BImpl> bQuery = QueryFactory.queryForType(BImpl.class);
Но я бы очень хотел скрыть классы реализации от пользователей API. Я попытался сделать B абстрактным классом вместо интерфейса на случай, если проблема связана с этим, но это не помогло.
1 ответ
Здесь ваш интерфейс A
объявляет параметр типа T
который расширяет необработанный тип A
, Тебе стоит попробовать
// v--- Add this
interface A<T extends A<T>> extends Query<T> { }
Это гарантирует, что T
расширяет обобщенное A
с T
, Сюда, T
будет в пределах, указанных в Query
интерфейс.
Это будет работать с вашей второй версией AImpl
который реализует A<AImpl>
,
РЕДАКТИРОВАТЬ
Должен сказать
interface B<T extends B<T>> extends A<B<T>> { }
выглядит слишком сложно. Для B
интерфейс, расширить его от A
так же, как вы продлили A
от Query
:
// v-- Don't mention B here
interface B<T extends B<T>> extends A<T> { }
Тогда ваш BImpl
класс может выглядеть похожим на ваш AImpl
учебный класс:
class BImpl implements B<BImpl> {
public BImpl execute() { ... }
public Query<BImpl> appendClause() { ... }
}