Процессор аннотаций, кажется, ломает дженерики Java - PullRequest
0 голосов
/ 30 августа 2018

Фон

Я пытался использовать процессоры аннотаций для генерации реализаций определенных интерфейсов 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);
}

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

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

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

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


Если я добавлю универсальный тип в 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);

}

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

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

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

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

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

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

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

Ответы [ 2 ]

0 голосов
/ 06 сентября 2018

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

0 голосов
/ 03 сентября 2018

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

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);
}

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...