Quand utiliser des méthodes génériques et quand utiliser des caractères génériques?


122

Je lis sur les méthodes génériques d' OracleDocGenericMethod . Je suis assez confus au sujet de la comparaison quand il dit quand utiliser des caractères génériques 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 indique que l'argument type est utilisé pour le polymorphisme; son seul effet est de permettre l'utilisation d'une variété de types d'arguments réels sur différents sites d'invocation. Si tel est le cas, il faut utiliser des caractères génériques. Les caractères génériques sont conçus pour prendre en charge le sous-typage flexible, ce que nous essayons d'exprimer ici.

Ne pensons-nous pas que les caractères génériques (Collection<? extends E> c);soutiennent également une sorte de polymorphisme? Alors pourquoi l'utilisation de méthodes génériques n'est-elle pas considérée comme bonne dans ce domaine?

Continuant de l'avant, il déclare,

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

Qu'est-ce que ça veut dire?

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 du tout de jokers:

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 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 éclairer ce domaine.

Réponses:


173

Il y a certains endroits où les jokers 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 souhaitez 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 vouliez vous assurer que la liste srcet destpassée à la copy()méthode 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 destet srcont le même type paramétré pour List. Il est donc prudent de copier des éléments de srcvers dest.

Mais, si vous continuez à changer la méthode pour utiliser le 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 au List<Float>fur destet à mesure src. Ainsi, déplacer des éléments de srcà destne serait plus sûr de type. Si vous n'avez pas besoin d'un tel type de relation, vous êtes libre de ne pas utiliser du tout de paramètres de type.

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

  • 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 de type fonctionne également.
  • Les paramètres de type prennent en charge plusieurs limites, contrairement aux 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 Listtype Integerou une super classe, vous pouvez 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:


1
C'est une réponse étrange. Cela n'explique pas du tout pourquoi vous devez utiliser ?. Vous pouvez le réécrire comme `public static <T1 étend le nombre, T2 étend le nombre> copie vide (List <T1> dest, List <T2> src) et dans ce cas, ce qui se passe devient évident.
kan

@kan. Eh bien, c'est le vrai problème. Vous pouvez utiliser le paramètre de type pour appliquer le même type, mais vous ne pouvez pas le faire avec des caractères génériques. L'utilisation de deux types différents pour le paramètre de type est une chose différente.
Rohit Jain

1
@benz. Vous ne pouvez pas définir de limites inférieures dans un Listparamètre de type using. List<T super Integer>n'est pas valide et ne compilera pas.
Rohit Jain

2
@benz. De rien :) Je vous suggère fortement de parcourir le lien que j'ai publié à la fin. C'est la meilleure ressource sur les génériques que vous obtiendrez.
Rohit Jain

3
@ jorgen.ringen <T extends X & Y>-> plusieurs limites.
Rohit Jain

12

Considérez l'exemple suivant de la programmation Java de James Gosling 4e é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 ci-dessus ont la même fonctionnalité. Alors, lequel est préférable? La réponse est la deuxième. 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, car le code avec des 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 cette variable de type est utilisée pour relier deux paramètres ou plus, ou pour associer un type de paramètre au type de retour. Si la réponse est non, un caractère générique devrait suffire. "

Remarque: 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.


J'ai voté pour la citation d'un livre, il est direct et concis
Kurapika

9

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 certains des T suivant un certain critère. Si T est Longvos méthodes retourneront Longet 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 dans les collections, les types de retour seront intet boolean.

Dans vos exemples:

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

ces deux fonctions renverront un booléen quel que soit le type des éléments des collections. Dans le second cas, il est limité aux instances d'une sous-classe d'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 hétérogène List<? extends T> srcen paramètre. Cette liste peut contenir plusieurs éléments de différentes classes tant qu'ils étendent tous la classe de base T.

si tu avais:

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) {
    ...
}

contraint List<S> srcd'ê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 d'aucune autre classe, même s'ils implémentent T aussi. Vous ne pourriez pas 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. */

1
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();N'est pas une syntaxe valide. Vous devez instancier le ArrayList sans limites.
Arnold Pistorius

Impossible d'ajouter une pomme au panier dans l'exemple ci-dessus car le panier pourrait être une liste de poires. Exemple incorrect AFAIK. Et ne compile pas aussi.
Khanna111

1
@ArnoldPistorius Cela m'a dérouté. J'ai vérifié la documentation API de ArrayList et un constructeur est signé ArrayList(Collection<? extends E> c). Pouvez-vous m'expliquer pourquoi vous avez dit cela?
Kurapika

@Kurapika Est-ce que j'utilisais une ancienne version de Java? Le commentaire a été publié il y a presque 3 ans.
Arnold Pistorius

2

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

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

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

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

3
N'est-ce pas censé être un commentaire?
Buhake Sindi

@BuhakeSindi Désolé, qu'est-ce qui n'est pas clair? Pourquoi -1? Je pense que cela répond à la question.
kan

2

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

Ne pensons-nous pas que les caractères génériques (Collection<? extends E> c);soutiennent également une sorte 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. C'est un inconnu. Tout ce qu'il "sait", c'est que le "confinement" est d'un type E(quel qu'il soit défini). Ainsi, il ne peut pas vérifier et justifier si la valeur fournie correspond au type borné.

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 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?

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

Donc, supposons que vous vouliez copier toute la liste de nombres, 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 existe une limite supérieure au type paramétré trouvé dans dest.

La 2ème option vous obligera à lier Spour chaque type que vous souhaitez 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.

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

J'espère que ça aide.


Pouvez-vous expliquer ce que vous voulez dire dans votre dernier paragraphe
benz

Celui qui indique la deuxième option vous obligera à en lier un ...... pouvez-vous élaborer dessus
benz

<S extends T>indique qu'il Ss'agit d'un type paramétré qui est une sous-classe de T, donc il nécessite un type paramétré (pas de caractère générique) qui est une sous-classe de T.
Buhake Sindi

2

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
    }
}

0

Pour autant que je sache, il n'y a qu'un seul cas d'utilisation où 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 explicite). 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 des dépendances entre les types d'un ou plusieurs arguments d'une méthode et / ou de son type de retour. S'il n'y a pas une telle dépendance, une méthode générique ne doit pas être utilisée.

[...]

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

[...]

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


0

Principalement -> Les caractères génériques appliquent les génériques au niveau des paramètres / arguments d'une méthode non générique. Remarque. Il peut également être effectué dans genericMethod par défaut, mais ici au lieu de? nous pouvons utiliser T lui-même.

génériques d'emballage;

public class DemoWildCard {


    public static void main(String[] args) {
        DemoWildCard obj = new DemoWildCard();

        obj.display(new Person<Integer>());
        obj.display(new Person<String>());

    }

    void display(Person<?> person) {
        //allows person of Integer,String or anything
        //This cannnot be done if we use T, because in that case we have to make this method itself generic
        System.out.println(person);
    }

}

class Person<T>{

}

SO wildcard a ses cas d'utilisation spécifiques comme celui-ci.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.