Java: caractères génériques limités ou paramètre de type limité?

récemment, j'ai lu cet article: http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html

ma question Est, au lieu de créer une méthode comme celle-ci:

public void drawAll(List<? extends Shape> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

je peux créer une méthode comme celle-ci, et ça marche très bien:

public <T extends Shape> void drawAll(List<T> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

par où dois-je aller? Est générique utile dans ce cas?

59
demandé sur Eric Leschinski 2010-08-15 12:19:09

5 réponses

Cela dépend de ce que vous besoin à faire. Vous devez utiliser le paramètre bounded type si vous voulez faire quelque chose comme ceci:

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

ici nous avons un List<T> shapes et un T shape , donc nous pouvons en toute sécurité shapes.add(shape) . Si elle a été déclarée List<? extends Shape> , vous pouvez pas en toute sécurité add à elle (parce que vous pouvez avoir un List<Square> et un Circle ).

ainsi en donnant un nom à un paramètre de type borné, nous avons l'option de l'utiliser ailleurs dans notre méthode générique. Cette information n'est pas toujours nécessaire, bien sûr, donc si vous n'avez pas besoin d'en savoir autant sur le type (par exemple votre drawAll ), alors un simple joker est suffisant.

même si vous ne faites pas référence à nouveau au paramètre bounded type, un paramètre bounded type est toujours requis si vous avez plusieurs bornes. Voici une citation de Angelika Langer Java Génériques FAQ

Quelle est la différence entre un paramètre Joker lié et un paramètre type lié?

Un générique peut avoir qu'une seule tenue, alors qu'un paramètre de type peut avoir plusieurs limites. Un caractère générique peut avoir une limite inférieure ou supérieure, alors qu'il n'existe pas de limite inférieure pour un paramètre de type.

les limites de caractères génériques et les limites de paramètres de type sont souvent confus, parce qu'ils sont tous les deux appelés limites et ont en partie la même syntaxe. [ ... ]

syntaxe :

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

Un générique peut avoir qu'un seul tenu, soit une baisse ou une limite supérieure. Une liste de jokers n'est pas autorisée.

un paramètre de type, en constrast, peut avoir plusieurs limites, mais il n'y a pas de limite inférieure pour un paramètre de type.

citations de Java Effective 2nd Edition, Item 28: Use bounded wildcards to increase API flexibility :

pour un maximum de flexibilité, utilisez des caractères génériques sur les paramètres d'entrée qui représentent les producteurs ou les consommateurs. [ ... ] PECS signifie producteur - extends , consommateur - super [ ... ]

N'utilisez pas de caractères génériques comme . Plutôt que de en offrant plus de flexibilité à vos utilisateurs, cela les obligerait à utiliser des types de jokers dans le code client. Correctement utilisés, les types de jokers sont presque invisibles pour les utilisateurs d'une classe. Ils provoquent des méthodes pour accepter les paramètres qu'ils doivent accepter et de rejeter ceux qu'ils devraient rejeter. si l'utilisateur de la classe doit penser aux types de jokers, il y a probablement quelque chose qui ne va pas avec L'API de la classe .

en appliquant le principe PECS, nous peut maintenant revenir à notre exemple addIfPretty et le rendre plus flexible en écrivant ce qui suit:

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

Maintenant, nous pouvons addIfPretty , disons, un Circle , à un List<Object> . C'est évidemment typesafe, et pourtant notre déclaration d'origine n'était pas assez souple.

questions connexes


résumé

  • N'utilisez délimitée paramètres de type et les jokers, ils augmentent la flexibilité de votre API
  • si le type nécessite plusieurs paramètres, vous n'avez pas d'autre choix que d'utiliser le paramètre de type borné
  • si le type nécessite un lowerbound, vous n'avez pas d'autre choix que d'utiliser un joker borné
  • Producteurs "qui ont upperbounds, "consommateurs" qui ont lowerbounds
  • N'utilisez pas de Joker dans les types de retour
100
répondu polygenelubricants 2017-05-23 12:17:59

dans votre exemple, vous n'avez pas vraiment besoin d'utiliser T, puisque vous n'utilisez ce type nulle part ailleurs.

mais si vous avez fait quelque chose comme:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

ou comme dit polygenlubricants, si vous voulez faire correspondre le paramètre type dans la liste avec un autre paramètre type:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

dans le premier exemple, vous obtenez un peu plus de sécurité de type puis de retourner juste forme, puisque vous pouvez alors passer le résultat à une fonction qui peut prends un enfant en forme. Par exemple, vous pouvez passer un List<Square> ma méthode, puis passer l'résultant Place à une méthode qui ne prend que des Carrés. Si vous avez utilisé"?'vous auriez à mouler la forme résultante à la place qui ne serait pas de type sûr.

dans le deuxième exemple, vous vous assurez que les deux listes ont le même paramètre de type (ce que vous ne pouvez pas faire avec '?', depuis chacun'?'est différent), de sorte que vous pouvez créer une liste qui contient tous les éléments des deux.

5
répondu Andrei Fierbinteanu 2010-08-15 18:33:40

considérons l'exemple suivant de la programmation Java par James Gosling 4ème édition ci-dessous où nous voulons fusionner 2 SinglyLinkQueue:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

les Deux méthodes ont la même fonctionnalité. Donc, ce qui est préférable? La réponse est 2e. Selon les propres mots de l'auteur :

" la règle générale est d'utiliser des caractères génériques lorsque vous le pouvez parce que le code avec des caractères génériques est généralement plus lisible que le code à caractères multiples paramètre. Au moment de décider si vous avez besoin d'un type variable, demandez - vous si cette variable de type est utilisée pour rapporter deux ou plusieurs paramètres, ou pour rapporter un paramètre tapez avec le type de retour. Si la réponse est non, un joker devrait suffire."

Note: dans le livre seulement deuxième méthode est donnée et le nom de paramètre de type est s au lieu de 'T'. La première méthode n'est pas dans le livre.

1
répondu chammu 2015-07-19 00:37:25

pour autant que je comprenne, le Joker permet un code plus concis dans les situations où un paramètre de type n'est pas requis (par exemple parce qu'il est référencé à plusieurs endroits ou parce que des limites multiples sont nécessaires comme détaillé dans d'autres réponses).

dans le lien vous indiquez que j'ai lu (sous "méthodes génériques") les déclarations suivantes qui font allusion dans cette direction:

les méthodes génériques permettent d'utiliser des paramètres de type pour exprimer dépendances entre les types d'un ou plusieurs arguments à une méthode et/ou son type de retour. S'il n'y a pas une telle dépendance, un la méthode ne doit pas être utilisé.

[...]

utiliser des caractères génériques est plus clair et plus concis que déclarer explicite les paramètres de type, et doit donc être préférée dans la mesure du possible.

[...]

Les jokers

ont aussi l'avantage de pouvoir être utilisés en dehors de signatures de méthode, comme les types de champs, variables locales et les tableaux.

1
répondu Martin Maletinsky 2018-03-16 09:40:15

la seconde voie est un peu plus verbeuse, mais elle vous permet de faire référence à T à l'intérieur:

for (T shape : shapes) {
    ...
}

c'est la seule différence, pour autant que je comprenne.

0
répondu Nikita Rybak 2010-08-15 08:28:18