java generics annotations annotation-processing

El procesador de anotaciones parece romper los genéricos de Java



generics annotations (2)

Fondo

Estaba intentando usar procesadores de anotación para generar implementaciones de interfaces de fábrica específicas. Esas interfaces tienen el siguiente aspecto:

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

y

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

El Procesador de Anotaciones está haciendo lo correcto y genera una fábrica para cada clase coincidente, que se anota con una anotación correspondiente.

El problema

La salida del Procesador de Anotación es la siguiente:

public final class TestViewImplFactory implements ViewFactory { public final TestView create(TestPresenter presenter) { return new TestViewImpl(presenter); } }

y la otra clase correspondiente:

public final class TestPresenterImplFactory implements PresenterFactory { public final TestPresenter create() { return new TestPresenterImpl(); } }

El TestViewImplFactory sin embargo no se puede compilar. El mensaje de error es:

"La clase ''TestViewImplFactory'' debe declararse abstracta o implementar el método abstracto create (S) en ''ViewFactory''"

Java dice, lo siguiente es correcto:

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

lo que no funcionaría en absoluto, teniendo en cuenta que el usuario desea saber, qué vista se devolverá y qué presentador es necesario. Hubiera esperado que:

  1. cualquiera de los dos archivos autogenerados son incorrectos
  2. o ambos son correctos

Porque ambos son muy parecidos. Esperaba que lo primero fuera cierto.

¿Que me estoy perdiendo aqui?

Si agrego el tipo genérico a TestViewImplFactory de esta manera:

public final class TestViewImplFactory implements ViewFactory<TestView> { @Override public <S extends Presenter<TestView>> TestView create(S presenter) { return new TestViewImpl(presenter); } }

El problema surge, que el Parámetro del constructor (que es del Tipo TestPresenter) es incorrecto. Cambiar la S a un TestPresenter concreto, de nuevo, hará que la clase no sea compilable por la misma razón que la anterior.

Entonces, me topé con una "solución" que se puede compilar.

Lo que básicamente se debe hacer es cambiar la interfaz de ViewFactory a lo siguiente:

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

Así que la definición de clase tiene el mismo tipo genérico, como el método en la pregunta anterior.

Después de la compilación (esta vez con especificación de tipo genérico), la salida se ve así:

public final class TestViewImplFactory implements ViewFactory<TestView, TestPresenter> { public TestViewImplFactory() { } public final TestView create(TestPresenter presenter) { return new TestViewImpl(presenter); } }

Esto puede ser compilado y se ejecuta con éxito.

Esto sin embargo no responde a la pregunta original. ¿Por qué el genérico declarado explícitamente en la definición de tipo es correcto, pero se hereda y se especifica en la declaración del método incorrecto y no compilable?

Para ser concretos: ¿Por qué Java puede heredar un Genérico automáticamente (dentro de PresenterFactory) y los otros no (dentro de ViewFactory, en el método y en la declaración de tipo)?


Creo que la primera parte de su declaración de problema se debe abordar ya que me doy cuenta de que su procesador de anotaciones está implementando el tipo de ViewFactory sin formato. Supongo que con el borrado de tipo, ya que es un código generado, no hay una diferencia real en la práctica. Pero si el procesador pudiera generar implementaciones utilizando el tipo parametrizado, al menos sería más fácil razonar sobre el problema.

Entonces, dada la firma del mensaje <S extends Presenter<T>> T create(S presenter) , puedes hacer que genere:

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

O más mínimamente:

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

Pero luego, con cualquiera de estos, no puede restringir el parámetro a TestPresenter . Tendrías que cambiar ViewFactory a algo como

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

y ellos implementan ViewFactory<TestView, TestPresenter> . Debe usar los parámetros de tipo en la implementación para lograr las restricciones de tipo que desea.


Por qué no funciona:

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

Esta firma hace que el compilador deduzca S en la ubicación donde se llama a create() . S será lo que asignes a create() como en:

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

Esto implica que:

public TestPresenter create(){...}

No es una implementación de:

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

pero un método de anulación. No hay implementación del método de la interfaz. Ni siquiera es posible proporcionar una implementación con una S concreta. Es similar con:

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

Aquí el genérico se infiere nuevamente en la invocación del método. Por lo tanto, una implementación debe aceptar cada subtipo de Presenter<T> . La única implementación válida para esto es:

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

Pero el tipo de retorno depende del presenter parámetros. Esto podría funcionar si el presenter proporciona un método para crear una instancia de T solamente.

¿Por qué funciona la otra solución?

La unión del método genérico a través del tipo significa que una implementación de la interfaz proporciona el tipo concreto. Por lo tanto, para un objeto no es necesario proporcionar varios enlaces diferentes. No importa dónde llame al método create() de PresenterFactory<TestView, TestPresenter<TestView>> el genérico del tipo de devolución está vinculado a TestPresenter<TestView> . Así que hay una posible implementación para cada subtipo de PresenterFactory<...> .