Quand utiliser des méthodes génériques et quand utiliser wild-card?

Je lis sur les méthodes génériques de OracleDocGenericMethod . Je suis assez confus au sujet de la comparaison quand il dit Quand utiliser wild-card et quand utiliser des méthodes génériques. Citant le document.

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

Nous aurions pu utiliser des méthodes génériques ici à la place:

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!
}

[…] Cela nous dit que l'argument type est utilisé pour le polymorphisme; son seul effet est de permettre à une variété de types d'arguments réels d'être utilisé à différentes invocations site. Si c'est le cas, on devrait utilisez des caractères génériques. Les caractères génériques sont conçus pour prendre en charge le sous typage flexible, qui est ce que nous essayons d'exprimer ici.

Ne pensons-nous pas que wild card comme (Collection<? extends E> c); supporte également une sorte de le polymorphisme? Alors pourquoi l'utilisation de la méthode générique est considérée comme pas bonne dans ce domaine?

Continuer en avant, il déclare,

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 à un méthode et/ou son type de retour. S'il n'y a pas une telle dépendance, un générique la méthode ne doit pas être utilisé.

Qu'est-ce que cela signifie?

Ils ont présenté l'exemple

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

[…]

Nous aurions pu écrire la signature pour cette méthode d'une autre manière, sans utiliser de caractères génériques du tout:

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

Le document décourage la deuxième déclaration et favorise l'utilisation de la première syntaxe? Quelle est la différence entre le premier et le second déclaration? Les deux semble être en train de faire la même chose?

Quelqu'un peut-il mettre la lumière sur cette zone?

98
demandé sur emlai 2013-08-12 00:55:33

7 réponses

Il y a certains endroits où les caractères génériques et les paramètres de type font la même chose. Mais il y a aussi certains endroits, où vous devez utiliser des paramètres de type.

  1. Si vous voulez appliquer une relation sur les différents types d'arguments de méthode, vous ne pouvez pas le faire avec des caractères génériques, vous devez utiliser des paramètres de type.

En prenant votre méthode comme exemple, supposons que vous voulez vous assurer que la liste src et dest passée à la méthode copy() doit être de même paramétrable, vous pouvez le faire avec des paramètres de type comme ceci:

public static <T extends Number> void copy(List<T> dest, List<T> src)

Ici, vous êtes assuré que les deux dest et src ont le même type paramétré pour List. Donc, il est sûr de copier des éléments de src à dest.

Mais, si vous continuez à changer la méthode pour utiliser un caractère générique:

public static void copy(List<? extends Number> dest, List<? extends Number> src)

Cela ne fonctionnera pas comme prévu. Dans le 2ème cas, vous pouvez passer List<Integer> et List<Float>, comme dest et src. Donc, déplacer des éléments de src vers dest ne serait plus sûr. Si vous n'avez pas besoin d'une telle type de relation, alors vous êtes libre de ne pas utiliser les paramètres de type à tous.

Une autre différence entre l'utilisation de caractères génériques et les paramètres de type est:

  • Si vous n'avez qu'un seul argument de type paramétré, vous pouvez utiliser un caractère générique, bien que le paramètre type fonctionne également.
  • les paramètres de Type prennent en charge plusieurs limites, pas les caractères génériques.
  • Les caractères génériques prennent en charge les limites supérieures et inférieures, les paramètres de type ne prennent en charge que les limites supérieures. Donc, si vous voulez définir une méthode qui prend un List de type Integer, ou c'est super classe, vous pouvez faire:

    public void print(List<? super Integer> list)  // OK
    

    Mais vous ne pouvez pas utiliser le paramètre type:

     public <T super Integer> void print(List<T> list)  // Won't compile
    

Références:

130
répondu Rohit Jain 2013-08-26 19:38:50

Dans votre première question: cela signifie que s'il existe une relation entre le type du paramètre et le type de retour de la méthode, utilisez un générique.

Par exemple:

public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);

Ici, vous extrayez une partie du T en suivant certains critères. Si T vaut Long, vos méthodes retourneront Long et Collection<Long>; le type de retour réel dépend du type de paramètre, il est donc utile et conseillé d'utiliser des types génériques.

Lorsque ce n'est pas le cas vous pouvez utiliser le joker types:

public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);

Dans cet exemple deux quel que soit le type des éléments dans les collections les types de retour sera int et boolean.

Dans vos exemples:

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

Ces deux fonctions retourneront un booléen quel que soit le type des éléments dans les collections. Dans le second cas, il est limité aux instances D'une sous-classe de E.

Deuxième question:

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

Ce premier code permet de passer un List<? extends T> src hétérogène en paramètre. Cette liste peut contient plusieurs éléments de différentes classes tant qu'ils étendent tous la classe de base T.

Si vous aviez:

interface Fruit{}

Et

class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}

, Vous pourriez faire

List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>(); 

Collections.copy(fridge, basket);// works 

D'autre part

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

Contraindre List<S> src à être d'une classe particulière s qui est une sous-classe de T. La liste ne peut contenir que des éléments d'une classe (dans ce cas S) et aucune autre classe, même si elles implémentent T aussi. Vous ne seriez pas en mesure d'utiliser mon exemple précédent, mais vous pourriez faire:

List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();

Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
9
répondu ɭɘ ɖɵʊɒɼɖ 江戸 2017-02-13 10:13:09

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 la 2ème. Dans les propres mots de l'auteur:

"La règle générale est d'utiliser des caractères génériques quand vous le pouvez parce que code avec des caractères génériques est généralement plus lisible que le code avec plusieurs paramètres de type. Au moment de décider si vous avez besoin d'un type variable, demander vous-même si cette variable de type est utilisée pour relier deux paramètres ou plus, ou pour relier un paramètre tapez avec le type de retour. Si la réponse est non, un caractère générique devrait suffire."

Note: dans le livre, seule la deuxième méthode est donné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.

5
répondu chammu 2015-07-19 00:37:45

La méthode générique est également Générique-vous pouvez l'appeler avec une gamme de types.

La syntaxe <T> définit un nom de variable de type. Si une variable de type a une utilisation (par exemple dans l'implémentation de la méthode ou comme contrainte pour un autre type), il est logique de la nommer, sinon vous pouvez utiliser ?, comme variable anonyme. Alors, ressemble juste un raccourci.

De plus, la syntaxe ? n'est pas évitable lorsque vous déclarez un champ:

class NumberContainer
{
 Set<? extends Number> numbers;
}
2
répondu kan 2013-08-11 21:19:28

Je vais essayer de répondre à votre question, un par un.

Ne pensons-nous pas que le Joker comme (Collection<? extends E> c); est aussi soutenir le genre de polymorphisme?

Non. La raison en est que le caractère générique borné n'a pas de type de paramètre défini. Il est un inconnu. Tout ce qu'il" sait", c'est que le" confinement " est d'un type E (quelle que soit la définition). Ainsi, il ne peut pas vérifier et justifier si la valeur fournie correspond au type borné.

Donc, ce n'est pas raisonnable d'avoir comportements polymorphes sur les caractères génériques.

Le document décourage la deuxième déclaration et encourage l'utilisation de la première syntaxe? Quelle est la différence entre le premier et le second déclaration? Les deux semble être en train de faire la même chose?

La première option est meilleure dans ce cas car T est toujours bornée, et source aura certainement des valeurs (d'inconnues) qui sous-classent T.

Donc, supposons que vous voulez copier toute la liste des nombres, la première option sera

Collections.copy(List<Number> dest, List<? extends Number> src);

src, essentiellement, peut accepter List<Double>, List<Float>, etc. comme il y a une limite supérieure au type paramétré trouvé dans dest.

La 2ème option vous forcera à lier S pour chaque type que vous voulez copier, comme ceci

//For double 
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.

//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.

As S est un type paramétré qui nécessite une liaison.

J'espère que cela aide.

2
répondu Buhake Sindi 2013-08-11 21:26:56

Une autre différence qui n'est pas répertoriée ici.

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // correct
    }
}

Mais ce qui suit entraînera une erreur de compilation.

static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
    for (T o : a) {
        c.add(o); // compile time error
    }
}
2
répondu Vivek Kumar 2017-08-01 02:54:12

Pour autant que je comprenne, il n'y a qu'un seul cas d'utilisation lorsque le caractère générique est strictement nécessaire (c'est-à-dire peut exprimer quelque chose que vous ne pouvez pas exprimer en utilisant des paramètres de type explicites). C'est à ce moment que vous devez spécifier une limite inférieure.

En dehors de cela, les caractères génériques servent à écrire du code plus concis, comme décrit par les instructions suivantes dans le document que vous mentionnez:

Les méthodes génériques permettent d'utiliser des paramètres de type pour exprimer dépendances parmi les types d'un ou 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 la méthode ne doit pas être utilisé.

[...]

L'utilisation de caractères génériques est plus claire et plus concise que la déclaration explicite 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 qu'ils peuvent être utilisés en dehors de signatures de méthode, comme les types de champs, de variables locales et de tableaux.

0
répondu Martin Maletinsky 2018-03-16 09:40:00