web-dev-qa-db-fra.com

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

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

Ma question est la suivante: 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 cela fonctionne bien:

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

De quelle manière devrais-je utiliser? Le caractère générique est-il utile dans ce cas?

61
Tony Le

Cela dépend de ce que vous devez faire. Vous devez utiliser le paramètre de type borné 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). S'il a été déclaré List<? extends Shape>, vous pouvez y PAS en toute sécurité add (car vous pouvez avoir un List<Square> et une Circle).

Donc, en donnant un nom à un paramètre de type borné, nous avons la possibilité de l’utiliser ailleurs dans notre méthode générique. Bien entendu, ces informations ne sont pas toujours nécessaires. Par conséquent, si vous n'avez pas besoin de beaucoup d'informations sur le type (par exemple, votre drawAll), un caractère générique suffit.

Même si vous ne faites pas à nouveau référence au paramètre de type lié, un paramètre de type lié est toujours requis si vous avez plusieurs limites. Voici une citation de FAQ sur les génériques Java de Angelika Langer

Quelle est la différence entre un caractère générique lié et un paramètre de type lié?

Un caractère générique ne peut avoir qu'une seule limite, 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 des caractères génériques et les limites des paramètres de type sont souvent confondues, car elles s'appellent toutes les deux limites et ont une syntaxe similaire. […]

Syntaxe:

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

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

Un caractère générique ne peut avoir qu'une seule limite, une limite inférieure ou supérieure. Une liste de bornes génériques n'est pas autorisée. 

Un paramètre de type, en contraste, peut avoir plusieurs limites, mais il n’existe pas de limite inférieure pour un paramètre de type. 

Citations de Effective Java 2nd Edition, Article 28: Utiliser des caractères génériques liés pour augmenter la flexibilité de l'API} _:

Pour une flexibilité maximale, utilisez des caractères génériques sur les paramètres d'entrée représentant les producteurs ou les consommateurs. […] PECS signifie producteur -extends, consommateur -super […]

N'utilisez pas les types génériques comme types de retour. Plutôt que d'offrir une flexibilité supplémentaire à vos utilisateurs, cela les obligerait à utiliser des types génériques dans le code client. Utilisés correctement, les types de caractères génériques sont presque invisibles pour les utilisateurs d'une classe. Elles font en sorte que les méthodes acceptent les paramètres qu’elles doivent accepter et rejettent celles qu’elles doivent rejeter. Si l'utilisateur de la classe doit penser aux types de caractère générique, il y a probablement un problème avec l'API de la classe.

En appliquant le principe PECS, nous pouvons 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, par exemple, une Circle, à un List<Object>. C'est évidemment typé, et pourtant notre déclaration originale n'était pas assez flexible pour le permettre.

Questions connexes


Résumé

  • Utilisez des paramètres de type délimité/caractères génériques, 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 une limite inférieure, vous n'avez pas d'autre choix que d'utiliser un caractère générique lié
  • Les "producteurs" ont des limites supérieures, les "consommateurs" ont des limites inférieures
  • Ne pas utiliser de caractère générique dans les types de retour
106
polygenelubricants

Dans votre exemple, vous n'avez pas vraiment besoin d'utiliser T, car 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 le font les polygenlubricants, si vous voulez faire correspondre le paramètre type de 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 que de retourner uniquement Shape, car vous pouvez ensuite transmettre le résultat à une fonction pouvant prendre un enfant de Shape. Par exemple, vous pouvez transmettre un List<Square> à ma méthode, puis transmettre le carré résultant à une méthode prenant uniquement des carrés. Si vous avez utilisé '?' vous auriez à convertir la forme résultante en carré qui ne serait pas 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 utiliser avec? .

5
Andrei Fierbinteanu

Pour autant que je sache, le caractère générique 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 plusieurs bornes sont requises, comme indiqué dans d'autres réponses).

Dans le lien que vous indiquez, je lis (sous "Méthodes génériques") les énoncés suivants qui vont dans ce sens:

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

[...]

L'utilisation de caractères génériques est plus claire et concise que de déclarer explicitement paramètres de type, et devrait donc être préféré chaque fois que possible.

[...]

Les caractères génériques ont également l'avantage de pouvoir être utilisés en dehors de signatures de méthode, comme les types de champs, variables locales et tableaux.

1
Martin Maletinsky

Vous pouvez suivre l'exemple de la programmation Java de James Gosling, 4ème édition, ci-dessous, dans lequel nous souhaitons 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 ci-dessus ont les mêmes fonctionnalités. Alors, lequel est préférable? La réponse est 2e. Dans les propres mots de l'auteur: 

"La règle générale consiste à utiliser des caractères génériques lorsque vous le pouvez, car le code avec caractères génériques est généralement plus lisible que le code avec plusieurs paramètres de type. Lorsque vous décidez si vous avez besoin d'une variable de type , Demandez-vous si utilisé pour associer deux paramètres ou plus, ou pour associer un paramètre type au type de retour. Si la réponse est non, un caractère générique devrait suffire. "  

Remarque: Dans le livre seulement, la deuxième méthode est indiquée et le nom du paramètre de type est S au lieu de 'T'. La première méthode n'est pas là dans le livre.

1
chammu

La deuxième façon 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 sache.

0
Nikita Rybak