web-dev-qa-db-fra.com

Différence entre List, List <?>, List <T>, List <E> et List <Object>

Quelles sont les différences entre List, List<?>, List<T>, List<E> et List<Object>

Maintenant, je ne pose pas aveuglément cette question, alors s'il vous plaît ne fermez pas ce fil. Permettez-moi d'abord d'introduire le code de base:

private static List<String> names = new ArrayList<String>();
static {
    names.add("Tom");
    names.add("Peter");
    names.add("Michael");
    names.add("Johnson");
    names.add("Vlissides");
}

public static void test(List<String> set){
    System.out.println(set);
}
public static void main(String args[]){
    test(names);
}

Je comprends que:

1 .List: est un type brut, donc pas typesafe. Il ne générera une erreur d'exécution que lorsque le casting est mauvais. Nous voulons une erreur de compilation lorsque la distribution est mauvaise. Non recommandé d'utiliser.

2 .List<?>: est un caractère générique non limité. Mais vous ne savez pas à quoi ça sert? Donc, si je change la méthode test en

public static void test(List<?> set){
    System.out.println(set);
}

ça marche toujours bien. Si vous pouvez expliquer l'utilisation de cela, je l'apprécierais grandement.

EDIT: Si je le fais:

public static void test(List<?> set){
    set.add(new Long(2)); //--> Error
    set.add("2");    //--> Error
    System.out.println(set);
}

mais si je change test en ceci:

public static void test(List<String> set){
    set.add(new Long(2)); //--> Error
    set.add("2");    //--> Work
    System.out.println(set);
}

3 .List<T>

public static void test(List<T> set){   //T cannot be resolved
    System.out.println(set);
}

Je suppose que je ne comprends pas cette syntaxe. J'ai vu quelque chose comme ça, et ça marche:

public <T> T[] toArray(T[] a){
    return a;   
}

S'il vous plaît expliquer cela pour moi s'il vous plaît? Parfois, je vois <T>, ou <E>, ou <U>, <T,E>. Sont-ils tous identiques ou représentent-ils quelque chose de différent?

4 .List<Object>

public static void test(List<Object> set){
    System.out.println(set);
}

Ensuite, j'ai eu l'erreur The method test(List<Object>) is not application for the argument List<String> pour le code ci-dessous. Je suis confus. Je pensais que String était un sous-ensemble de Object?

public static void main(String args[]){
    test(names); 
}

EDIT: Si j'essaie cela 

test((List<Object>)names);

alors j'ai eu Cannot cast from List<String> to List<Object>

178
Thang Pham

1) correct

2) Vous pouvez considérer cette liste comme une liste "en lecture seule", dans laquelle vous ne vous souciez pas du type d’éléments. être utilisé par une méthode qui renvoie la longueur de la liste. 

3) T, E et U sont identiques, mais les gens ont tendance à utiliser par exemple T pour le type, E pour l'élément, V pour la valeur et K pour la clé. La méthode qui compile indique qu'elle a pris un tableau d'un certain type et renvoie un tableau du même type.

4) Vous ne pouvez pas mélanger des oranges et des pommes. Vous pourrez ajouter un objet à votre liste de chaînes si vous pouviez passer une liste de chaînes à une méthode qui attend des listes d'objets. (Et tous les objets ne sont pas des chaînes)

70
Kaj

Pour la dernière partie: Bien que String soit un sous-ensemble de Object, mais List <String> n'est pas hérité de List <Object>.

24
Farshid Zaker

La notation List<?> signifie "une liste de quelque chose (mais je ne dis pas quoi)". Étant donné que le code dans test fonctionne pour tout type d'objet de la liste, cela fonctionne comme paramètre de méthode formelle.

L'utilisation d'un paramètre de type (comme dans votre point 3) nécessite que le paramètre de type soit déclaré. La syntaxe Java pour cela est de mettre <T> devant la fonction. Ceci est exactement analogue à la déclaration de noms de paramètres formels à une méthode avant d'utiliser les noms dans le corps de la méthode.

En ce qui concerne List<Object> n'accepter un List<String>, cela a du sens car une String n'est pas Object; c'est une sous-classe de Object. Le correctif consiste à déclarer public static void test(List<? extends Object> set) .... Mais alors le extends Object est redondant, car chaque classe étend directement ou indirectement Object.

18
Ted Hopp

La raison pour laquelle vous ne pouvez pas convertir List<String> en List<Object> est que cela vous permettrait de ne pas respecter les contraintes du List<String>.

Pensez au scénario suivant: Si j'ai un List<String>, il est supposé ne contenir que des objets de type String. (Ce qui est une classe final)

Si je peux convertir cela en un List<Object>, alors cela me permet d’ajouter Object à cette liste, violant ainsi le contrat initial de List<String>.

Ainsi, en général, si la classe C hérite de la classe P, vous ne pouvez pas dire que GenericType<C> hérite également de GenericType<P>.

N.B. J'avais déjà commenté cela dans une réponse précédente, mais je voulais développer.

13
Peter

Je conseillerais de lire les casse-tête Java. Cela explique assez bien l'héritage, les génériques, les abstractions et les caractères génériques dans les déclarations. http://www.javapuzzlers.com/

5

Dans votre troisième point, "T" ne peut pas être résolu car il n'est pas déclaré. Généralement, lorsque vous déclarez une classe générique, vous pouvez utiliser "T" comme nom du paramètre de type lié , de nombreux exemples en ligne, y compris Tutoriels Oracle utilisez "T" comme nom du paramètre type, par exemple, vous déclarez une classe comme:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

vous dites que la méthode FooHandler'soperateOnFoo attend une variable de type "T" qui est déclarée dans la déclaration de classe elle-même. Dans cette optique, vous pourrez ajouter ultérieurement une autre méthode comme

public void operateOnFoos(List<T> foos)

dans tous les cas, T, E ou U y tous les identifiants du paramètre type, vous pouvez même avoir plus d'un paramètre de type qui utilise la syntaxe

public class MyClass<Atype,AnotherType> {}

dans votre quatrième ponint bien qu'Efectively Sting soit un sous-type d'Object, dans les classes génériques, il n'existe pas de telle relation, List<String> n'est pas un sous-type de List<Object> car ce sont deux types différents du point de vue du compilateur, ceci est mieux expliqué dans cette entrée de blog

5
Harima555

Le problème 2 est OK, car "System.out.println (set);" signifie "System.out.println (set.toString ());" set est une instance de List, le complier appellera donc List.toString ();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

Problème 3: ces symboles sont identiques, mais vous pouvez leur donner des spécifications différentes. Par exemple:

public <T extends Integer,E extends String> void p(T t, E e) {}

Problème 4: La collecte ne permet pas la covariance du paramètre de type. Mais le tableau permet la covariance.

2
yoso

Parlons-en dans le contexte de l’histoire de Java;

  1. List

Lister signifie qu'il peut inclure n'importe quel objet. La liste était dans la version antérieure à Java 5.0; Java 5.0 introduit List, pour la compatibilité en amont. 

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>

? signifie objet inconnu, pas d'objet; L'introduction générique ? est destinée à résoudre le problème créé par Generic Type; voir wildcards ; Mais cela pose aussi un autre problème:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E> 

Signifie Déclaration générique à la base de aucun type T ou E dans votre projet Lib.

  1. List< Object> signifie paramétrage générique.
1
phil

Théorie

String[] peut être converti en Object[]

mais

List<String> ne peut pas être converti en List<Object>.

Entraine toi

Pour les listes, il est plus subtil que cela, car à date de compilation, le type d'un paramètre List transmis à une méthode n'est pas vérifié. La définition de la méthode peut aussi dire List<?> - du point de vue du compilateur, elle est équivalente. C'est pourquoi l'exemple n ° 2 de l'OP donne des erreurs d'exécution, pas des erreurs de compilation.

Si vous gérez un paramètre List<Object> passé à une méthode avec précaution afin de ne pas forcer une vérification de type sur un élément de la liste, vous pouvez définir votre méthode à l'aide de List<Object> mais accepter en fait un paramètre List<String> du code appelant.

A. Donc, ce code ne donnera pas d'erreur de compilation ou d'exécution et fonctionnera réellement (et peut-être étonnamment?):

public static void test(List<Object> set) {
    List<Object> params = new List<>();  // This is a List<Object>
    params.addAll(set);       // A String can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

public static void main(String[] args) {
    List<String> argsList = Arrays.asList(args);
    test(argsList);  // The object passed is a List<String>
}

B. Ce code provoquera une erreur d'exécution:

public static void test(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
    set.add(new Long(2));       // Also a runtime error
    System.out.println(set);
}

public static void main(String[] args) {
    List<String> argsList = Arrays.asList(args);
    test(argsList);
}

C. Ce code donnera une erreur d'exécution (Java.lang.ArrayStoreException: Java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
    System.out.println(params);
}

public static void main(String[] args) {
    test(args);
}

En B, le paramètre set n'est pas un List typé lors de la compilation: le compilateur le voit comme List<?>. Il y a une erreur d'exécution car, au moment de l'exécution, set devient l'objet réel transmis depuis main(), et il s'agit d'un List<String>. Un List<String> ne peut pas être converti en List<Object>.

En C, le paramètre set nécessite un Object[]. Il n'y a pas d'erreur de compilation ni d'erreur d'exécution lorsqu'il est appelé avec un objet String[] en tant que paramètre. C'est parce que String[] jette sur Object[]. Mais l'objet reçu par test() reste un String[], il n'a pas changé. Ainsi, l'objet params devient également un String[]. Et l'élément 0 d'un String[] ne peut pas être affecté à une Long!

(J'espère que j'ai tout ici, si mon raisonnement est faux, je suis sûr que la communauté me le dira.)

0
radfast

Vous avez raison: String est un sous-ensemble de Object. Puisque String est plus "précis" que Object, vous devez le convertir en argument comme argument pour System.out.println ().

0
patapizza