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

194

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

1. Liste

List: est un type brut, donc non typesafe. Il ne générera une erreur d'exécution que lorsque le casting est mauvais. Nous voulons une erreur de temps de compilation lorsque le cast est mauvais. Non recommandé à utiliser.

2. Liste <?>

List<?>est un caractère générique illimité. Mais je ne sais pas à quoi ça sert? Je peux imprimer un List<?>sans problème:

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

Pourquoi ne puis-je pas ajouter d'articles à un List<?>?

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

3. Liste <T>

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

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

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

Parfois, je vois <T>, ou <E>, ou <U>, <T,E>. Sont-ils tous identiques ou représentent-ils quelque chose de différent?

4. Liste <Objet>

Cela donne l'erreur "La méthode test(List<Object>)n'est pas applicable pour l'argument List<String>":

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

Si j'essaye ceci, je reçois "Impossible de lancer la diffusion List<String>vers List<Object>":

test((List<Object>) names);

Je suis confus. Stringest une sous - classe de Object, alors pourquoi n'est pas List<String>une sous - classe List<Object>?

Thang Pham
la source

Réponses:

77

1) Correct

2) Vous pouvez considérer celle-ci comme une liste "en lecture seule", où vous ne vous souciez pas du type des éléments. Par exemple, elle pourrait être utilisée par une méthode qui renvoie la longueur de la liste.

3) T, E et U sont les mêmes, 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 dit qu'il a fallu 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 seriez en mesure d'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)

Kaj
la source
2
+1 pour la liste en lecture seule 2. J'écris quelques codes pour le démontrer 2. tyvm
Thang Pham
Pourquoi les gens utiliseraient List<Object>-ils?
Thang Pham
3
C'est un moyen de créer une liste qui accepte tout type d'éléments, vous l'utilisez rarement.
Kaj
En fait, je ne pense pas que quiconque l'utiliserait étant donné que vous ne pouvez pas avoir d'identifiant du tout maintenant.
if_zero_equals_one
1
@if_zero_equals_one Oui, mais vous obtiendrez alors un avertissement du compilateur (il avertira et dira que vous utilisez des types bruts), et vous ne voulez jamais compiler votre code avec des avertissements.
Kaj
26

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

Farshid Zaker
la source
11
Très bon point; beaucoup supposent que parce que la classe C hérite de la classe P, List <C> hérite également de List <P>. Comme vous l'avez souligné, ce n'est pas le cas. La raison en était que si nous pouvions convertir de List <String> en List <Object>, alors nous pourrions mettre des objets dans cette liste, violant ainsi le contrat original de List <String> lors de la tentative de récupération d'un élément.
Peter
2
+1. Bon point également. Alors, pourquoi les gens utiliseraient List<Object>-ils?
Thang Pham
9
List <Object> peut être utilisé pour stocker une liste d'objets de différentes classes.
Farshid Zaker
20

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

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>en avant de 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 le fait de List<Object>ne pas accepter a List<String>, cela a du sens parce que a Stringne l'est pas Object; c'est une sous-classe de Object. Le correctif est de déclarer public static void test(List<? extends Object> set) .... Mais alors le extends Objectest redondant, car chaque classe s'étend directement ou indirectement Object.

Ted Hopp
la source
Pourquoi les gens utiliseraient List<Object>-ils?
Thang Pham
10
Je pense que "une liste de quelque chose" est un meilleur sens List<?>car la liste est d'un type spécifique mais inconnu. List<Object>serait vraiment "une liste de tout" car elle peut en effet contenir n'importe quoi.
ColinD
1
@ColinD - Je voulais dire "n'importe quoi" dans le sens de "n'importe quelle chose". Mais tu as raison; cela signifie, "une liste de quelque chose, mais je ne vais pas vous dire quoi".
Ted Hopp
@ColinD, il voulait dire pourquoi avez-vous répété ses mots? oui, il a été écrit avec des mots un peu différents, mais le sens est le même ...
user25
14

La raison pour laquelle vous ne pouvez pas jeter List<String>à List<Object>est qu'il vous permettra de violer les contraintes du List<String>.

Pensez au scénario suivant: si j'en ai un List<String>, il est censé ne contenir que des objets de type String. (Qui est une finalclasse)

Si je peux lancer cela sur un List<Object>, cela me permet d'ajouter Objectà cette liste, violant ainsi le contrat d'origine de List<String>.

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

NB J'ai déjà fait des commentaires à ce sujet dans une réponse précédente, mais je voulais développer ce point.

Peter
la source
Tyvm, je télécharge à la fois votre commentaire et votre réponse, car c'est une très bonne explication. Maintenant, où et pourquoi les gens utiliseraient-ils List<Object>?
Thang Pham
3
En règle générale, vous ne devriez pas utiliser List<Object>car cela défait en quelque sorte le but des génériques. Cependant, il existe des cas où l'ancien code peut avoir un Listtype différent acceptant, vous pouvez donc souhaiter adapter le code pour utiliser le paramétrage de type juste pour éviter les avertissements du compilateur pour les types bruts. (Mais la fonctionnalité est inchangée)
Peter
5

Je conseillerais de lire les puzzles Java. Il 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/

if_zero_equals_one
la source
5

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

  1. List:

La liste signifie qu'elle peut inclure n'importe quel objet. La liste était dans la version avant Java 5.0; Java 5.0 a introduit List, pour une compatibilité descendante.

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

?signifie un objet inconnu pas un objet; l' ?introduction générique sert à résoudre le problème créé par Generic Type; voir les caractères génériques ; mais cela provoque également un autre problème:

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

Signifie une déclaration générique en supposant qu'aucun type T ou E dans votre projet Lib.

  1. List< Object> signifie paramétrage générique.
phil
la source
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 les didacticiels d'Oracle, utilisent "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 FooHandler's operateOnFoométhode attend une variable de type "T" qui est déclarée sur la déclaration de classe elle-même, avec cela à l'esprit, vous pouvez ajouter plus tard une autre méthode comme

public void operateOnFoos(List<T> foos)

dans tous les cas soit T, E ou U il y a tous les identifiants du paramètre de 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 point, bien qu'effectivement Sting soit un sous-type d'objet, dans les classes génériques il n'y a pas une telle relation, ce List<String>n'est pas un sous-type de List<Object>ce sont deux types différents du point de vue du compilateur, cela est mieux expliqué dans cette entrée de blog

Harima555
la source
5

Théorie

String[] peut être converti en Object[]

mais

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

Entraine toi

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

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

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

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

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

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

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

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

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

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

En B, le paramètre setn'est pas typé Listau moment de la compilation: le compilateur le voit comme List<?>. Il y a une erreur d'exécution car au moment de l'exécution, setdevient l'objet réel transmis main(), et c'est unList<String> . Un List<String>ne peut pas être lancé List<Object>.

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

(J'espère que j'ai tout ici, si mon raisonnement est erroné, je suis sûr que la communauté me le dira. MISE À JOUR: J'ai mis à jour le code de l'exemple A afin qu'il compile réellement, tout en montrant le point soulevé.)

radfast
la source
J'ai essayé votre exemple A ce qu'il ne fonctionne: List<Object> cannot be applied to List<String>. Vous ne pouvez pas passer ArrayList<String>à une méthode qui attend ArrayList<Object>.
parsecer
Merci, assez tard dans la date, j'ai modifié l'exemple A pour que cela fonctionne maintenant. Le principal changement consistait à définir argsList de manière générique dans main ().
radfast
4

Le problème 2 est OK, car "System.out.println (set);" signifie "System.out.println (set.toString ());" set est une instance de List, donc complier appellera 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 n'autorise pas la covariance des paramètres de type. Mais le tableau permet la covariance.

yoso
la source
0

Vous avez raison: String est un sous-ensemble d'Object. Puisque String est plus "précis" qu'Object, vous devez le convertir pour l'utiliser comme argument pour System.out.println ().

patapizza
la source