Обработчик аннотации, похоже, нарушает Java-дженерики

Фон

Я пытался использовать Обработчики аннотации, чтобы генерировать реализации конкретных интерфейсов Factory. Эти интерфейсы выглядят следующим образом:

public interface ViewFactory<T extends View> {

    <S extends Presenter<T>> T create(S presenter);

}

а также

public interface PresenterFactory<T extends View> {

    <S extends Presenter<T>> S create();

}

Обработчик аннотации делает правильную вещь и генерирует фабрику для каждого соответствующего класса, который аннотируется соответствующей аннотацией.

Эта проблема

Выходной сигнал обработчика аннотаций следующий:

public final class TestViewImplFactory implements ViewFactory {

    public final TestView create(TestPresenter presenter) {
        return new TestViewImpl(presenter);
    }
}

и соответствующий другой класс:

public final class TestPresenterImplFactory implements PresenterFactory {

    public final TestPresenter create() {
        return new TestPresenterImpl();
    }
}

Однако TestViewImplFactory не может быть скомпилирован. Сообщение об ошибке:

"Класс" TestViewImplFactory "должен быть объявлен абстрактным или реализовать абстрактный метод create (S) в" ViewFactory ""

Java говорит, что верно следующее:

@Override
public View create(Presenter presenter) {
    return new TestViewImpl(presenter);
}

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

  1. либо оба автогенерированных файла ошибочны
  2. или оба правильные

потому что они оба очень похожи. Я ожидал, что первое будет правдой.

Что мне здесь не хватает?


Если я добавлю тип Generic в TestViewImplFactory следующим образом:

public final class TestViewImplFactory implements ViewFactory<TestView> {

    @Override
    public <S extends Presenter<TestView>> TestView create(S presenter) {
        return new TestViewImpl(presenter);
    }
}

Возникает проблема, что параметр конструктора (который относится к типу TestPresenter) неверен. Изменение S на конкретный TestPresenter снова сделает класс не компилируемым по той же причине, что и выше.


Итак, я наткнулся на "решение", которое можно скомпилировать.

Что в основном должно быть сделано, это изменить интерфейс ViewFactory на следующее:

public interface ViewFactory<T extends View, S extends Presenter<T>> {

    T create(S presenter);

}

Таким образом, определение класса имеет тот же общий тип, что и метод в Вопросе выше.

После компиляции (на этот раз с типичной спецификацией типа) вывод выглядит следующим образом:

public final class TestViewImplFactory implements ViewFactory<TestView, TestPresenter> {
    public TestViewImplFactory() {
    }

    public final TestView create(TestPresenter presenter) {
        return new TestViewImpl(presenter);
    }
}

Это можно скомпилировать и выполнить успешно.

Это, однако, не отвечает на исходный вопрос. Почему родовое явно указано в правильности определения типа, но унаследовано и указано в объявлении метода неправильно и не компилируется?

Чтобы быть конкретным: почему Java может наследовать один Generic автоматически (внутри PresenterFactory), а другой нет (в ViewFactory, при методе и в объявлении типа)?

Ответы

Ответ 1

Почему он не работает:

public interface PresenterFactory<T extends View> {
    <S extends Presenter<T>> S create();
}

Эта подпись заставляет компилятор выводить S в том месте, где вызывается create(). S будет тем, что вы назначили create() как в:

FancyPresenter fp = presenterFactory.create();
SomeOtherPresenter sop = presenterFactory.create();

Это означает, что:

public TestPresenter create(){...}

не является реализацией:

<S extends Presenter<T>> S create();

но метод переопределяет. Не существует реализации метода интерфейса. Невозможно даже реализовать какую-либо реализацию с конкретным S Это похоже на:

public interface ViewFactory<T extends View> {
    <S extends Presenter<T>> T create(S presenter);
}

Здесь generic снова выводится на вызов метода. Таким образом, реализация должна принимать каждый подтип Presenter<T>. Единственная допустимая реализация для этого:

public interface ViewFactory<T extends View> {
    T create(Presenter<T> presenter);
}

Но тип возврата зависит от presenter параметра. Это может работать, если presenter предоставляет вам метод создания экземпляра только T

Почему другое решение работает:

Связывание метода generic с помощью типа означает, что реализация интерфейса обеспечивает конкретный тип. Поэтому для одного объекта вам не нужно предоставлять несколько разных привязок. Независимо от того, где вы вызываете метод create() для PresenterFactory<TestView, TestPresenter<TestView>> тип возвращаемого типа привязан к TestPresenter<TestView>. Таким образом, существует возможная реализация для каждого подтипа PresenterFactory<...>.

Ответ 2

Я думаю, что самая первая часть вашего заявления о проблеме должна быть рассмотрена, поскольку я замечаю, что ваш обработчик аннотаций реализует необработанный тип ViewFactory. Я думаю, с типом стирания, поскольку он сгенерировал код, на практике это не имеет особого значения. Но если процессор мог генерировать реализации с использованием параметризованного типа, по крайней мере было бы легче рассуждать о проблеме.

Итак, учитывая подпись сообщения <S extends Presenter<T>> T create(S presenter), вы могли бы генерировать его:

public class TestViewImplFactory implements ViewFactory<TestView> {
  @Override
  public <S extends Presenter<TestView>> TestView create(S presenter) { ... }
}

Или более минимально:

public class TestViewImplFactory implements ViewFactory<TestView> {
  @Override
  public TestView create(Presenter presenter) { ... }
}

Но тогда, с любым из них, вы не можете ограничить параметр TestPresenter. Вам нужно будет изменить ViewFactory на что-то вроде

public interface ViewFactory<T extends View, U extends Presenter<T>>

и они реализуют ViewFactory<TestView, TestPresenter>. Вам нужно использовать параметры типа в реализации для достижения требуемых ограничений типа.