Как я могу построить иерархию интерфейса для моего свободного 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() { ... }
}
Другие вопросы по тегам