148 votes

Quand utiliser les méthodes génériques et quand utiliser le joker ?

Je suis en train de lire sur les méthodes génériques de OracleDocGenericMethod . Je suis assez perplexe quant à la comparaison entre l'utilisation des méthodes génériques et l'utilisation des jokers. Je cite 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 à 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 indique 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 d'être utilisés à différents sites d'invocation. Si c'est le cas, on devrait utiliser des caractères génériques. Les jokers sont conçus pour supporter un sous-typage flexible, ce qui est ce que nous essayons d'exprimer ici.

Ne pensons nous pas que la carte sauvage comme (Collection<? extends E> c); est aussi une sorte de soutien polymorphisme ? Alors pourquoi l'utilisation de méthodes génériques n'est pas considérée comme bonne dans ce cas ?

En poursuivant, il est dit,

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

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 de cette méthode d'une autre manière, sans utiliser de caractères de remplacement :

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

Le document déconseille la deuxième déclaration et encourage l'utilisation de la première syntaxe ? Quelle est la différence entre la première et la deuxième déclaration ? Les deux semblent faire la même chose ?

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

206voto

Rohit Jain Points 90368

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

  1. Si vous voulez imposer une certaine relation entre 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 vouliez vous assurer que l'élément src y dest passée à copy() doit être du même type paramétré, 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 y src ont le même type paramétré pour List . Il est donc possible de copier sans risque des éléments de src a dest .

Mais, si vous changez la méthode pour utiliser des caractères génériques :

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

il ne fonctionnera pas comme prévu. Dans le 2ème cas, vous pouvez passer List<Integer> y List<Float> como dest y src . Ainsi, le déplacement des éléments de src a dest ne serait plus en sécurité. Si vous n'avez pas besoin de ce type de relation, vous êtes libre de ne pas utiliser de paramètres de type du tout.

Les autres différences entre l'utilisation des caractères génériques et des paramètres de type sont les suivantes :

  • Si vous n'avez qu'un seul argument de type paramétré, vous pouvez utiliser le caractère générique, bien que le paramètre de type fonctionne également.
  • Les paramètres de type prennent en charge les limites multiples, ce qui n'est pas le cas des caractères génériques.
  • Les caractères génériques supportent à la fois les limites supérieures et inférieures, les paramètres de type supportent uniquement les limites supérieures. Ainsi, si vous souhaitez définir une méthode qui prend un paramètre de type List de type Integer ou c'est une super classe, vous pouvez le faire :

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

    mais vous ne pouvez pas utiliser le paramètre de type :

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

Références :

24voto

chammu Points 945

Considérons l'exemple suivant, tiré de The Java Programming de James Gosling, 4ème édition, 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 ci-dessus ont la même fonctionnalité. Laquelle est donc préférable ? La réponse est la deuxième. Selon les propres termes de l'auteur :

"La règle générale est d'utiliser des jokers quand vous le pouvez, car le code avec des jokers est généralement plus lisible que le code comportant plusieurs paramètres de type. Lorsque vous décidez si vous avez besoin d'une variable de type de type, demandez-vous si cette variable de type est utilisée pour relier deux paramètres ou plus, ou pour relier un type de paramètre au type de retour. paramètre avec le type de retour. Si la réponse est non, alors un joker devrait suffire."

Note : Dans le livre, seule la deuxième méthode est donnée et le nom du paramètre type est S au lieu de 'T'. La première méthode ne figure pas dans le livre.

12voto

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, il faut utiliser 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 des T suivant un certain critère. Si T est Long vos méthodes retourneront Long y 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 des types de caractères génériques :

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

Dans ces deux exemples, quel que soit le type des éléments des collections, les types de retour seront les suivants int y 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 vous permet de passer un code hétéroclite List<? extends T> src comme paramètre. Cette liste peut contenir plusieurs éléments de classes différentes tant qu'ils étendent tous la classe de base T.

si vous l'aviez fait :

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 pour ê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 elle implémente T aussi. Vous ne pourriez pas utiliser mon exemple précédent mais vous pourriez le 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. */

4voto

kan Points 12445

La méthode Wildcard est également générique - vous pouvez l'appeler avec un certain nombre de types.

El <T> La syntaxe définit un nom de variable de type. Si une variable de type a une utilité quelconque (par exemple, dans l'implémentation d'une méthode ou comme contrainte pour un autre type), il est logique de lui donner un nom, sinon vous pourriez utiliser ? comme variable anonyme. Donc, ça ressemble à un raccourci.

En outre, le ? n'est pas évitable lorsque vous déclarez un champ :

class NumberContainer
{
 Set<? extends Number> numbers;
}

3voto

Buhake Sindi Points 38654

Je vais essayer de répondre à vos questions, une par une.

Ne pensons nous pas que la carte sauvage comme (Collection<? extends E> c); est également le support d'un type de polymorphisme ?

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

Il n'est donc pas judicieux d'avoir des comportements polymorphes sur les caractères génériques.

Le document décourage la deuxième déclaration et encourage l'utilisation de première syntaxe ? Quelle est la différence entre la première et la deuxième déclaration ? Les deux semblent faire la même chose ?

La première option est préférable dans ce cas car T est toujours borné, et source aura certainement des valeurs (d'inconnues) que les sous-classes T .

Donc, supposons que vous vouliez copier toute la liste des numéros, la première option sera

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

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

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

//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.

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

J'espère que cela vous aidera.

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