Обработчик аннотации, похоже, нарушает 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);
}
который не будет работать вообще, учитывая, что пользователь хочет знать, какой вид будет возвращен и какой докладчик требуется. Я бы ожидал, что:
- либо оба автогенерированных файла ошибочны
- или оба правильные
потому что они оба очень похожи. Я ожидал, что первое будет правдой.
Что мне здесь не хватает?
Если я добавлю тип 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>
. Вам нужно использовать параметры типа в реализации для достижения требуемых ограничений типа.