J'ai vu un certain nombre de questions similaires, mais je ne pense pas qu'elles étaient tout à fait isomorphes, et aucune n'a tout à fait répondu à ma question.

Supposons qu'il y ait deux interfaces, Tree et Named. Supposons en outre que l'on me donne une méthode dont la signature est

public <T extends Tree & Named> T getNamedTree();

Comment puis-je enregistrer la valeur renvoyée dans une variable, tout en conservant les informations qu'elle implémente à la fois Tree et Named? Je ne trouve pas de moyen de déclarer une variable comme

public <T extends Tree & Named> T mNamedTree;

Et essayer de le convertir en une interface étendant Tree et Named entraîne une exception de conversion de classe.

4
Erhannis 21 avril 2017 à 02:45

3 réponses

Meilleure réponse

Quelle est la portée de la variable?

Il y a trois possibilités ici.

A) la variable n'est qu'une variable locale. Dans ce cas, vous avez presque déjà la réponse ... il vous suffit de déclarer un paramètre de type pour la méthode englobante pour ce type:

interface ItfA { Number propA(); };
interface ItfB { Number propB(); };

class Main {

  private <T extends ItfA & ItfB> T getT() {
     return null;
  }

  private <TT extends ItfA & ItfB> void doStuffWithT() {
     TT theT = getT();
     System.err.println(theT.propA());
     System.err.println(theT.propB());
  }

}

B) La portée est la vie d'un objet et dans ce cas est un champ membre. La réponse évidente est de rendre la classe générique et le paramètre de type ont la même contrainte &:

interface ItfA { Number propA(); };
interface ItfB { Number propB(); };

class Main<T extends ItfA & ItfB> {

  T theT;

  public void setT(T newT) {
     theT = newT;
  }

  public void doStuffWithT() {
     System.err.println(theT.propA());
     System.err.println(theT.propB());
  }

}

C) La portée est le live du programme, alors la variable est un membre de classe statique. Ici, vous n'avez pas de solution générique.

C.1) Evidemment, si la classe des valeurs que vous allez gérer est connue, vous utiliserez simplement cette classe comme type de champ.

C.2) Sinon, vous pouvez contraindre le code à gérer uniquement les classes qui implémentent une interface qui étend ItfA et ItfB. Cette interface, dites ItfAB. Serait de type de champ.

C.3) Maintenant, qu'en est-il de ne pas imposer cette contrainte? Qu'en est-il de permettre au code de gérer les objets de n'importe quelle classe qui implémentent ces interfaces?

Malheureusement, il n'existe pas de solution claire à cela:

C.3.a) Vous pouvez taper le champ Object et fournir des méthodes pour y accéder comme ItfA ou comme ItfB (masquant essentiellement le casting).

C.3.b) Ou, au lieu de contenir directement une référence à l'objet, vous utilisez un objet proxy qui implémente ces interfaces et délègue les appels à ces méthodes d'interface à la valeur typée d'origine "T". La classe de ce proxy pourrait elle-même être un générique acceptant une valeur <T extends ItfA & ItfB> arbitraire (similaire à l'exemple B. ci-dessus).

0
Valentin Ruano 21 avril 2017 à 01:59

En supposant qu'aucune troisième interface n'hérite à la fois de Named et de Tree, vous ne pouvez pas conserver les informations sur les deux interfaces de manière statique. Le compilateur vous demandera de faire un cast pour l'un ou l'autre, ou pour les deux:

Object namedTree = getNamedTree();
Tree asTree = (Tree)namedTree;
Named asNamed = (Named)namedTree;

Les deux lancers devraient réussir.

Si vous avez une influence sur la conception de l'API pour la classe, demandez aux auteurs d'introduire une interface combinant à la fois Named et Tree, et renvoyant à la place une instance de cette interface.

3
dasblinkenlight 20 avril 2017 à 23:51

Une solution possible serait de créer un autre interface qui extends à la fois Tree et Named, et simplement le stocker comme variable:

interface NamedTree extends Tree, Named {

}

public NamedTree namedTree;

public NamedTree getNamedTree();
1
Jacob G. 20 avril 2017 à 23:52