51 votes

Comment forcer un paramètre de type générique à être une interface?

Existe-t-il un moyen en Java de spécifier que le paramètre de type d'une classe générique doit être une interface (et non simplement l'étendre!)?

Ce que je veux faire est le suivant:

public class MyClass

Signifiant que Y doit être une sous-classe de SomeOtherClass ET implémenter X. Ce que j'obtiens actuellement du compilateur est

Le type X n'est pas une interface; il ne peut pas être spécifié en tant que paramètre borné

Alors, comment puis-je dire au compilateur que X doit toujours être une interface?

Éditer:
D'accord, je suppose que j'ai simplifié un peu mon problème. Utilisons mon domaine d'application réel pour le rendre plus clair:

J'ai une API pour représenter des diagrammes. Un Diagramme contient des objets Noeud et Arête. Ces trois classes implémentent toutes l'interface Forme. Les formes peuvent avoir des formes enfants, une forme parent et appartiennent à un diagramme.

Le problème est que j'ai besoin de créer deux versions de cette API: une open-source avec juste des fonctionnalités de base et une étendue avec plus de fonctionnalités. Cependant, l'API étendue ne doit fournir que des méthodes qui renvoient les types étendus (DiagrammeEtendu, NoeudEtendu, ArêteEtendue et (voici le problème) FormeEtendue).
Donc j'ai quelque chose comme ceci:

/* CLASSES DE BASE */
public interface Forme, Y extends Diagramme>{
    public List getFormesEnfant();
    public X getParent();
    public Y getDiagramme();
    ...
}

public class Diagramme, Y extends Diagramme> implements Forme{...}
public class Arete, Y extends Diagramme> implements Forme{...}
...

/* CLASSES ÉTENDUES */
public interface FormeEtendue extends Forme { ... }

public class DiagrammeEtendu extends Diagramme implements FormeEtendue { ... }
public class AreteEtendue extends Arete implements FormeEtendue { ... }
...

L'API étendue fonctionne bien et le code de l'API de base donne quelques avertissements, mais le principal problème survient lors de l'utilisation de l'API de base:

public class SomeImporter, Y extends Diagramme, E extends Arete>{
    private Y diagramme;

    public void ajouterNouvelleArete(E nouvelleArete){
        diagramme.ajouterFormeEnfant(nouvelleArete);
    ...

Cette dernière ligne me donne l'avertissement suivant:

La méthode ajouterFormeEnfant(X) dans le type Diagramme n'est pas applicable pour les arguments (E)

Alors maintenant, j'aimerais simplement spécifier que E doit également implémenter X et tout serait parfait - du moins je l'espère ;)

Tout ceci a-t-il du sens? Connaissez-vous un moyen de le faire? Ou existe-t-il même une meilleure façon d'obtenir l'API étendue avec les restrictions mentionnées?
Merci de m'avoir suivi, toute aide est grandement appréciée!

5 votes

0 votes

Cette erreur est un peu incorrecte. Cela signifie vraiment que vous devez vous référer à une classe ou une interface réelle et NON à un autre type générique (c'est-à-dire Y étend SomeOtherClass & SomeInterface, comme indiqué dans le lien fourni par @Yochai Timmer)

0 votes

Mais c'est là le problème : j'ai besoin d'une interface générique, car j'ai déjà une sous-interface de SomeInterface (appelons-la ExtendedInterface). MyClass est parfois utilisé avec SomeInterface et parfois avec ExtendedInterface. De plus, il pourrait bien y avoir d'autres extensions de SomeInterface à l'avenir... Peut-être ai-je simplifié un tantinet trop ma déclaration de problème, je vais modifier ma question d'origine.

23voto

idichekop Points 51

Vous pouvez utiliser :

class Foo {...}

Une classe Foo avec un paramètre de type, T. Foo doit être instancié avec un type qui est un sous-type de Number et qui implémente Comparable.

3 votes

Ne peux pas dire que vous avez tort, mais cela ne résout pas mon problème. Si vous regardez à nouveau l'énoncé du problème, alors vous verrez que j'ai utilisé exactement cette construction dans la première ligne. Il semble que la norme Java actuelle ne permet tout simplement pas un paramètre de type générique après le '&', mais c'est exactement ce dont j'ai besoin.

6voto

Mike Thomsen Points 12074

Dans le contexte des génériques, gère à la fois les extensions et les implémentations. Voici un exemple :

public class GenericsTest {
    public static void main(String[] args) {
        GenericsTest t = new GenericsTest();
        GenericsTest t2 = new GenericsTest();
    }
}

class GT implements Runnable{
    public void run() {

    }
}

class GT2 {

}

~~

GenericsTest acceptera GT car il implémente Runnable. GT2 ne le fait pas, c'est pourquoi il échoue lors de la tentative de compilation de cette deuxième instantiation de GenericsTest.

~~

3 votes

Merci pour votre réponse, mais ce n'est pas le point que j'essayais de faire. Ce que je veux avoir est un paramètre de type générique (dans votre cas 'S'), qui doit être une interface. Ainsi, vos classes 'GT' et 'GT2' échoueraient à la compilation. Une interface 'Exécutable' avec : "interface Exécutable étend Runnable" passerait

1voto

Vincent Points 707

Peut-être pouvez-vous simplifier un peu votre modèle : trop de génériques deviennent rapidement un véritable casse-tête en termes de lisibilité, et c'est un problème assez important si vous définissez une API publique. En général, si vous ne comprenez plus ce qui devrait se trouver entre les crochets, alors vous allez trop loin pour vos besoins - et vous ne pouvez pas vous attendre à ce que les utilisateurs le comprennent mieux que vous...

Quoi qu'il en soit, afin de faire compiler votre code, vous pouvez essayer de définir quelque chose comme ceci, dans le type Shape:

public > void addChildShape(S shape);

~~

Cela devrait fonctionner.

J'espère que cela aide.

~~

0 votes

Merci pour votre réponse. Vos commentaires concernant trop de génériques sont tout à fait justes. Malheureusement - comme je l'ai mentionné à @nbryant - les enfants de mes Chiens doivent être des Chiens ; lorsque je descends les génériques au niveau de la méthode, alors je ne peux pas être sûr des types de retour corrects (quelqu'un pourrait ajouter un type d'enfant qui est différent de celui que j'essaie d'obtenir).

0 votes

Vous n'avez pas besoin d'éviter d'insérer les mauvais types : vous devez simplement exposer l'interface appropriée à vos utilisateurs, donc une List peut également contenir des ExtendedShape tant que les clients ne les voient que comme des simples Shape ... De plus, les clients n'ayant pas accès aux fonctionnalités étendues ne peuvent certainement pas créer de ExtendedShape pour les ajouter à une telle liste.

0 votes

Mon problème est plutôt le contraire : j'utilise principalement les classes étendues, mais je ne peux pas toujours caster les éléments d'une liste vers la classe étendue. L'un d'eux pourrait simplement être une classe simple. De plus, c'est une gêne de devoir caster vers les classes étendues à chaque appel de méthode.

1voto

nbryant Points 1

Vous avez écrit ce qui suit:

public interface Shape, Y extends Diagram>{
    public List getChildShapes();
    public X getParent();
    public Y getDiagram();
    ...
}

Je conseillerais, au minimum, de vous débarrasser de la variable de type X, comme suit:

public interface Shape{
    public List> getChildShapes();
    public Shape getParent();
    public Diagram getDiagram();
    ...
}

La raison en est que ce que vous avez écrit initialement souffre d'un imbriquement récursif potentiellement non borné des paramètres de type. Une forme peut être imbriquée dans une forme parente, qui peut être à son tour imbriquée dans une autre, et tout cela doit être pris en compte dans la signature de type... pas une bonne recette pour la lisibilité. Eh bien, cela ne se produit pas exactement de cette manière dans votre exemple, dans lequel vous déclarez "Shape" au lieu de "Shape>" mais c'est la direction que vous prenez, si vous souhaitez réellement utiliser Shape par lui-même...

Je recommanderais probablement également d'aller un pas plus loin et de vous débarrasser de la variable Y pour des raisons similaires. Les génériques Java ne fonctionnent pas très bien avec ce type de composition. Lorsque vous essayez d'imposer des types statiques pour ce type de modélisation via les génériques, j'ai constaté que le système de types commence à se dégrader lorsque vous commencez à étendre les choses plus tard.

C'est typique du problème Animal/Chien... Animal a un getChildren(), mais les enfants de Chien doivent également être des Chiens... Java ne gère pas bien cela car (en partie en raison du manque de types abstraits comme dans des langages comme Scala, mais je ne dis pas que vous devriez vous précipiter pour utiliser Scala pour ce problème non plus) les variables de type doivent commencer à être déclarées dans toutes sortes d'endroits où elles ne vont pas vraiment.

0 votes

Merci pour votre réponse détaillée. Mon problème ici est que les enfants de mon chien doivent être des chiens. C'est une exigence. J'ai développé un peu plus le code en utilisant mon approche initiale. J'ai des sous-classes, du code qui utilise les classes génériques et du code qui utilise les sous-classes et, à part quelques conversions nécessaires, tout semble fonctionner correctement. Je suppose que je vais simplement continuer avec ça...

0voto

alex Points 3964

Utilisez un préprocesseur pour générer la version "réduite" de votre code. Utiliser apt et des annotations pourrait être une bonne façon de le faire.

0 votes

Je ne suis pas tout à fait sûr de comment cela fonctionnerait car je n'ai jamais utilisé apt auparavant. Je soupçonne que cela pourrait être un peu excessif pour le cas d'utilisation, mais comme je l'ai dit, je ne suis pas sûr.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X