web-dev-qa-db-fra.com

Le processeur d'annotation semble rompre avec les génériques Java

Contexte

J'essayais d'utiliser des processeurs d'annotation pour générer des implémentations d'interfaces Factory spécifiques. Ces interfaces ont l'aspect suivant:

public interface ViewFactory<T extends View> {

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

}

et

public interface PresenterFactory<T extends View> {

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

}

Le processeur d'annotation agit correctement et génère une fabrique pour chaque classe correspondante, annotée avec une annotation correspondante.

Le problème

La sortie du processeur d'annotation est la suivante:

public final class TestViewImplFactory implements ViewFactory {

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

et l'autre classe correspondante:

public final class TestPresenterImplFactory implements PresenterFactory {

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

Le TestViewImplFactory ne peut cependant pas être compilé. Le message d'erreur est:

"La classe 'TestViewImplFactory' doit être déclarée abstraite ou implémenter Méthode abstraite create (S) dans 'ViewFactory'"

Java dit, ce qui suit est correct:

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

ce qui ne fonctionnerait pas du tout, étant donné que l'utilisateur veut savoir quelle vue sera retournée et quel présentateur est requis. Je m'attendais à ce que:

  1. soit les deux fichiers générés automatiquement sont faux 
  2. ou les deux sont corrects

parce que les deux sont vraiment similaires. Je m'attendais à ce que le premier soit vrai.

Qu'est-ce que j'oublie ici?


Si j'ajoute le type générique à la TestViewImplFactory comme ceci:

public final class TestViewImplFactory implements ViewFactory<TestView> {

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

Le problème se pose, que le constructeur Parameter (qui est du type TestPresenter) est incorrect. Remplacer le S par un TestPresenter concret rendra la classe non compilable pour la même raison que ci-dessus.


Donc, je suis tombé sur une "solution" qui peut être compilée.

Ce qu’il faut faire en gros, c’est modifier l’interface ViewFactory comme suit:

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

    T create(S presenter);

}

La définition de la classe a donc le même type générique que la méthode de la question ci-dessus.

Après la compilation (cette fois avec la spécification de type générique), la sortie ressemble à ceci:

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

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

Cela peut être compilé et fonctionne avec succès.

Cela ne répond toutefois pas à la question initiale. Pourquoi le générique explicitement indiqué dans la définition de type est-il correct, mais hérité des et spécifiés dans la déclaration de méthode incorrect et non compilable?

Pour être concret: Pourquoi Java peut-il hériter automatiquement d’un générique (dans PresenterFactory) et d’autres pas (dans ViewFactory, à la méthode et à la déclaration de type)?

20
Thorben Kuck

Pourquoi ça ne marche pas:

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

Cette signature amène le compilateur à inférer S à l'emplacement où create() est appelé. S sera ce à quoi vous assignerez create() comme dans:

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

Cela implique que:

public TestPresenter create(){...}

n'est pas une implémentation de:

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

mais un remplacement de méthode. Il n'y a pas d'implémentation de la méthode de l'interface. Il n'est même pas possible de fournir une implémentation avec une S concrète. C'est similaire avec:

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

Ici, le générique est à nouveau déduit de l'invocation de méthode. Donc, une implémentation doit accepter chaque sous-type de Presenter<T>. La seule implémentation valide pour cela est:

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

Mais le type de retour dépend du paramètre presenter. Cela pourrait fonctionner si presenter vous fournit une méthode pour créer une instance de T uniquement.

Pourquoi l'autre solution fonctionne-t-elle:

Lier le générique de la méthode via le type signifie qu'une implémentation de l'interface fournit le type concret. Ainsi, pour un objet, vous n'avez pas besoin de fournir plusieurs liaisons différentes. Où que vous appeliez la méthode create() de PresenterFactory<TestView, TestPresenter<TestView>>, le type générique du type de retour est lié à TestPresenter<TestView>. Il existe donc une implémentation possible pour chaque sous-type de PresenterFactory<...>.

15
Trinova

Je pense que la toute première partie de votre déclaration de problème devrait être adressée car je remarque que votre processeur d'annotation implémente le type ViewFactory brut. J'imagine qu'avec l'effacement de type, puisqu'il s'agit de code généré, cela ne change pas vraiment la pratique. Mais si le processeur pouvait générer des implémentations utilisant le type paramétré, il serait au moins plus facile de raisonner sur le problème.

Donc, étant donné la signature du message <S extends Presenter<T>> T create(S presenter), vous pourriez le générer:

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

Ou plus minimalement:

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

Mais avec l'un ou l'autre, vous ne pouvez pas limiter le paramètre à TestPresenter. Il vous faudrait changer ViewFactory en quelque chose comme

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

et les implémenter ViewFactory<TestView, TestPresenter>. Vous devez en quelque sorte utiliser les paramètres de type dans l'implémentation pour obtenir les restrictions de type souhaitées.

0
Lucas Ross