web-dev-qa-db-fra.com

Différence entre le type générique et le type générique

Je suis un débutant en générique et ma question est la suivante: quelle différence entre deux fonctions:

fonction 1:

public static <E> void funct1  (List<E> list1) {

}

fonction 2: 

public static void funct2(List<?> list) {

}

Merci.

43
Fio

La première signature dit: list1 est une liste de Es.

La deuxième signature dit: list est une liste d'instances d'un type quelconque, mais nous ne connaissons pas le type.

La différence devient évidente lorsque nous essayons de changer de méthode afin qu'il prenne un deuxième argument, qui devrait être ajouté à la liste à l'intérieur de la méthode:

import Java.util.List;

public class Experiment {
    public static <E> void funct1(final List<E> list1, final E something) {
        list1.add(something);
    }

    public static void funct2(final List<?> list, final Object something) {
        list.add(something); // does not compile
    }
}

Le premier fonctionne bien. Et vous ne pouvez pas changer le deuxième argument en quelque chose qui compilera réellement.

En fait, je viens de trouver une démonstration encore plus agréable de la différence:

public class Experiment {
    public static <E> void funct1(final List<E> list) {
        list.add(list.get(0));
    }

    public static void funct2(final List<?> list) {
        list.add(list.get(0)); // !!!!!!!!!!!!!! won't compile !!!!!!!!!
    }
}

On pourrait se demander pourquoi nous avons besoin de <?> alors que cela ne limite que ce que nous pouvons en faire (comme l'a fait @Babu_Reddy_H dans les commentaires). Je vois les avantages suivants de la version générique:

  • L'appelant doit en savoir moins sur l'objet qu'il transmet. Par exemple, si j'ai une carte des listes: Map<String, List<?>>, je peux transmettre ses valeurs à votre fonction sans spécifier le type des éléments de la liste. Alors 

  • Si je distribue des objets paramétrés comme ceci, je limite activement ce que les gens savent sur ces objets et ce qu’ils peuvent en faire (à condition de rester à l’écart des castes dangereuses).

Ces deux choses ont un sens quand je les combine: List<? extends T>. Par exemple, considérons une méthode List<T> merge(List<? extends T>, List<? extends T>), qui fusionne les deux listes d’entrée en une nouvelle liste de résultats. Bien sûr, vous pouvez introduire deux paramètres de type supplémentaires, mais pourquoi voudriez-vous le faire? Ce serait trop préciser les choses. 

  • enfin, les jokers peuvent avoir des limites inférieures, donc avec des listes vous pouvez faire fonctionner la méthode add, alors que get ne vous donne rien d'utile. Bien sûr, cela soulève la question suivante: pourquoi les génériques n’ont-ils pas une limite inférieure?

Pour une réponse plus détaillée, voir: Quand utiliser des méthodes génériques et quand utiliser des cartes génériques? et http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ203

33
Jens Schauder

Les génériques rendent la collection plus sécurisée.

List<E>: E voici le paramètre Type, qui peut être utilisé pour déterminer le type de contenu de la liste, mais il y avait No moyen de vérifier quel était le contenu pendant la runtime

Generics are checked only during compilation time.

<? extends String>: ceci a été spécialement construit en Java pour gérer le problème qui était avec le paramètre de type. "? extends String" signifie que cette liste peut avoir 

objects which IS-A String.

Par exemple:

Classe animale Classe chien étend Animal Classe tigre étend animal

Donc, utiliser "public void go(ArrayList<Animal> a)" va NOT accept Dog ou Tiger comme contenu mais Animal. 

"public void go(ArrayList<? extends Animal> a)" est ce qui est nécessaire pour créer le ArrayList take in Dog and Tiger type.

Recherchez des références dans Head First Java.

6

J'explique généralement la différence entre <E> et <? > par comparaison avec les quantifications logiques, c'est-à-dire la quantification universelle et la quantification existentielle.

  • correspond à "pour tout E, ..."
  • correspond à "il existe quelque chose (noté par) tel que ...."

Par conséquent, la déclaration de méthode générique suivante signifie que, pour tout type de classeE, nous définissons funct1

public static <E> void funct1  (List<E>; list1) {

}

La déclaration de méthode générique suivante signifie que, pour une classe existante indiquée par <? >, nous définissons funct2.

public static void funct2(List<?> list) {

}
1
user3509406

Lister en tant que type de paramètre indique que le paramètre doit être une liste d'éléments avec n'importe quel type d'objet. De plus, vous pouvez lier le paramètre E pour déclarer des références à des éléments de la liste dans le corps de la fonction. 

La liste en tant que type de paramètre a la même sémantique, sauf qu'il n'existe aucun moyen de déclarer des références aux éléments de la liste autre que d'utiliser Object. D'autres messages donnent des différences subtiles supplémentaires.

1
Gene

La première est une fonction qui accepte un paramètre qui doit être une liste d’éléments de type E.

le second type d'exemple n'est pas défini 

List<?> list

afin que vous puissiez passer la liste de tout type d'objets. 

1
Pramod Kumar

(Depuis votre modification) Ces deux signatures de fonction ont le même effet sur le code extérieur: elles prennent toutes les List comme arguments. Un caractère générique est équivalent à un paramètre de type utilisé une seule fois.

0
newacct

Outre les différences mentionnées précédemment, il existe une différence supplémentaire: vous pouvez définir explicitement les arguments de type pour l'appel de la méthode générique:

List<Apple> apples = ...
ClassName.<Banana>funct2(apples); // for some reason the compiler seems to be ok
                               // with type parameters, even though the method has none

ClassName.<Banana>funct1(apples); // compiler error: incompatible types: List<Apple>
                                  //                 cannot be converted to List<Banana>

(ClassName est le nom de la classe contenant les méthodes.)

0
fabian