web-dev-qa-db-fra.com

Génériques Java: Liste, Liste <Objet>, Liste <?>

Quelqu'un peut-il expliquer, aussi détaillé que possible, les différences entre les types suivants?

List
List<Object>
List<?>

Laissez-moi rendre cela plus spécifique. Quand voudrais-je utiliser

// 1 
public void CanYouGiveMeAnAnswer(List l) { }

// 2
public void CanYouGiveMeAnAnswer(List<Object> l) { }

// 3
public void CanYouGiveMeAnAnswer(List<?> l) { }
69
ForYourOwnGood

Comme l'ont noté d'autres publications, vous parlez d'une fonctionnalité Java appelée génériques. En C++, cela s'appelle des modèles. Les bestioles de Java sont beaucoup plus faciles à gérer.

Permettez-moi de répondre à vos questions de manière fonctionnelle (si ce n’est pas un vilain mot pour des discussions OO).

Avant les génériques, vous aviez de bonnes vieilles classes de béton comme Vector. 

Vector V = new Vector();

Les vecteurs contiennent tous les objets que vous leur avez donnés. 

V.add("This is an element");
V.add(new Integer(2));
v.add(new Hashtable());

Cependant, ils le font en convertissant tout ce que vous donnez dans un objet (la racine de toutes les classes Java). Cela n’est pas grave tant que vous n’allez pas récupérer les valeurs stockées dans votre vecteur. Dans ce cas, vous devez redéfinir la valeur dans la classe d'origine (si vous souhaitez effectuer un travail significatif avec celle-ci).

String s = (String) v.get(0);
Integer i = (Integer) v.get(1);
Hashtable h = (Hashtable) v.get(2);

Le casting vieillit vite. Plus que cela, le compilateur se plaint de propos incontrôlés. Pour un exemple frappant, utilisez la bibliothèque XML-RPC d’Apache (version 2 de toute façon). Le problème le plus important avec cela est que les consommateurs de votre vecteur doivent connaître la classe exacte de ses valeurs à temps de compilation pour pouvoir lancer correctement. Dans les cas où le producteur du vecteur et le consommateur sont complètement isolés l'un de l'autre, cela peut s'avérer fatal. 

Entrez les génériques. Les génériques tentent de créer des classes fortement typées pour effectuer des opérations génériques. 

ArrayList<String> aList = new ArrayList<String>();
aList.add("One");
String element = aList.get(0); // no cast needed
System.out.println("Got one: " + element); 

Maintenant, si vous jetez un coup d'œil au livre Design Patterns du gang de quatre, vous remarquerez qu'il est sage de divorcer des variables de leur classe d'implémentation. Mieux vaut penser aux contrats qu'à la mise en œuvre. Ainsi, vous pouvez dire que tous les objets de la liste font la même chose: add(), get(), size(), etc. Cependant, de nombreuses implémentations d'opérations List peuvent choisir de respecter le contrat de différentes manières (par exemple, ArrayList). Toutefois, le type de données traité par ces objets est considéré comme une considération d'exécution par l'utilisateur de la classe générique. Rassemblez tout et vous verrez la ligne de code suivante très fréquemment:

List<String> L = new ArrayList<String>();

Vous devriez lire que "L est une sorte de liste qui traite des objets String". Lorsque vous commencez à traiter avec des classes Factory, il est essentiel de traiter avec des contrats plutôt que des implémentations spécifiques. Les usines produisent des objets de différents types lors de l'exécution. 

Utiliser des génériques est assez facile (la plupart du temps). Cependant, un jour terrible, vous pouvez décider de mettre en œuvre une classe générique. Peut-être avez-vous pensé à une excellente nouvelle implémentation de la liste. Lorsque vous définissez cette classe, vous utilisez <t> comme espace réservé pour le type d'objet qui sera manipulé par les méthodes. Si vous êtes confus, utilisez les classes génériques pour Liste jusqu'à ce que vous soyez à l'aise. Ensuite, vous pouvez plonger dans la mise en œuvre avec un peu plus de confiance. Vous pouvez également consulter le code source des différentes classes List fournies avec JRE. Open source est génial comme ça.

Consultez la documentation Oracle/Sun sur les génériques .

100
jjohn

En mes propres termes simples:

Liste

Déclarerait une collection ordinaire, peut contenir n'importe quel type et renverra toujours Object.

Liste <Objet>

Crée une liste pouvant contenir n’importe quel type d’objet, mais ne peut être assignée qu’à un autre List <Object>  

Par exemple, cela ne fonctionne pas;

List<Object> l = new ArrayList<String>();

Bien sûr, vous pouvez tout ajouter, mais vous ne pouvez tirer qu’un objet.

List<Object> l = new ArrayList<Object>();

l.add( new Employee() );
l.add( new String() );

Object o = l.get( 0 );
Object o2 = l.get( 1 );

Finalement 

Liste <?>

Vous laissera assigner n'importe quel type, y compris 

List <?> l = new ArrayList(); 
List <?> l2 = new ArrayList<String>();

Cela s’appellerait collection de unknown et puisque le dénominateur commun de unknown is Object vous pourrez récupérer des objets (une coïncidence). 

L'importance de unknown vient quand il est utilisé avec le sous-classement:

List<? extends Collection> l = new ArrayList<TreeSet>(); // compiles

List<? extends Collection> l = new ArrayList<String>(); // doesn't,
// because String is not part of *Collection* inheritance tree. 

J'espère que l'utilisation de Collection comme type ne crée pas de confusion, c'était le seul arbre qui me vint à l'esprit.

La différence ici est que l est une collection de unknow qui appartient à la hiérarchie Collection .

40
OscarRyz

Pour ajouter aux réponses déjà bonnes ici:

Arguments de la méthode:

List<? extends Foo>

bon choix si vous n'avez pas l'intention de modifier la liste et veillez uniquement à ce que tout ce qui est dans la liste soit assignable au type 'Foo'. De cette façon, l'appelant peut passer dans une liste <FooSubclass> et votre méthode fonctionne. Habituellement le meilleur choix.

List<Foo>

bon choix si vous souhaitez ajouter des objets Foo à la liste de votre méthode. L'appelant ne peut pas transmettre une liste <FooSubclass>, car vous avez l'intention d'ajouter un Foo à la liste.

List<? super Foo>

c'est un bon choix si vous souhaitez ajouter des objets Foo à la liste, et peu importe ce qui se trouve dans la liste (par exemple, vous obtenez un objet List <Object> contenant un "Chien" n'ayant rien à voir avec Foo).

Valeurs de retour de la méthode

tout comme les arguments de méthode, mais avec les avantages inversés. 

List<? extends Foo> 

Garantit que tout dans la liste retournée est de type 'Foo'. Ce pourrait être la liste <FooSubclass> cependant. L'appelant ne peut pas ajouter à la liste. C'est votre choix et le cas le plus commun de loin.

List<Foo>

Tout comme List <? étend Foo> mais permet également à l'appelant d'ajouter à la liste. Moins fréquent.

List<? super Foo>

permet à l'appelant d'ajouter des objets Foo à la liste, mais ne garantit pas ce qui sera retourné par list.get (0) ... cela peut être n'importe quoi, de Foo à Object. La seule garantie est que ce ne sera pas une liste de 'Chien' ou un autre choix qui empêcherait list.add (foo) d'être légal. Cas d'utilisation très rare.

J'espère que ça aide. Bonne chance!

ps. En résumé ... deux questions ... 

avez-vous besoin d'ajouter à la liste? Vous souciez-vous de ce qui est dans la liste?

oui oui - utilisez Liste <Foo>.

oui non - utilisez Liste <? super Foo>.

non oui - utilisez <? étend Foo> --- le plus commun.

non non - utilisez <?>.

15
Eric Lindauer

Je vous renvoie à l'excellent tutoriel Java Generics et au le tutoriel "advanced" Generics , tous deux disponibles auprès de Sun Microsystems. Le livre Java Generics and Collections est une autre excellente ressource.

15
Rob

Je vais essayer de répondre à cela en détail. Avant les génériques, nous n'avions que List (une liste brute) et elle peut contenir presque tout ce à quoi nous pouvons penser.

List rawList = new ArrayList();
rawList.add("String Item");
rawList.add(new Car("VW"));
rawList.add(new Runnable() {
            @Override
            public void run() {
               // do some work.
            }
        });

Le problème majeur de la liste brute est que lorsque nous voulons extraire un élément de cette liste, il ne peut que garantir qu'il serait Object et pour cette raison, nous devons utiliser le casting comme:

   Object item = rawList.get(0); // we get object without casting.
   String sameItem = (String) rawList.get(0); // we can use casting which may fail at runtime.

Donc la conclusion est une List peut stocker Object (presque tout est Object en Java) et retourne toujours un Object.

Génériques

Parlons maintenant des génériques. Prenons l'exemple suivant:

List<String> stringsList = new ArrayList<>();
stringsList.add("Apple");
stringsList.add("Ball");
stringsList.add(new Car("Fiat")); //error
String stringItem = stringsList.get(0);

Dans le cas ci-dessus, nous ne pouvons rien insérer d'autre que String dans stringsList car le compilateur Java applique une vérification de type renforcée au code générique et génère des erreurs si le code enfreint la sécurité du type. Et nous obtenons une erreur lorsque nous essayons d’y insérer une instance Car. En outre, cela élimine la distribution, car vous pouvez vérifier quand nous avons invoke méthode get. Vérifiez ce lien pour comprendre pourquoi nous devrions utiliser des génériques .

List<Object>

Si vous lisez à propos de l'effacement des types, vous comprendrez que List<String>, List<Long>, List<Animal>, etc. aura différents types statiques au moment de la compilation mais aura le même type dynamique List au moment de l'exécution.

Si nous avons List<Object>, il ne peut stocker que Object et presque tout est Object en Java. Donc on peut avoir:

 List<Object> objectList = new ArrayList<Object>();
 objectList.add("String Item");
 objectList.add(new Car("VW"));
 objectList.add(new Runnable() {
        @Override
        public void run() {

        }
 });
 Object item = objectList.get(0); // we get object without casting as list contains Object
 String sameItem = (String) objectList.get(0); // we can use casting which may fail at runtime.

Il semble que List<Object> et List soient identiques, mais en réalité ils ne le sont pas. Considérons le cas suivant:

List<String> tempStringList = new ArrayList<>();
rawList = tempStringList; // Ok as we can assign any list to raw list.
objectList = tempStringList; // error as List<String> is not subtype of List<Obejct> becuase generics are not convariant.

Vous pouvez voir que nous pouvons affecter n'importe quelle liste à une liste brute et la raison principale en est la compatibilité ascendante. De même, List<String> sera converti en List au moment de l'exécution en raison de l'effacement du type et l'attribution sera correcte dans tous les cas.

Mais List<Object> signifie qu'il ne peut faire référence qu'à une liste d'objets et peut également stocker des objets uniquement. Même si String est un sous-type de Object, nous ne pouvons pas affecter List<String> à List<Object> car les génériques ne sont pas des covariants comme les tableaux. Ils sont invariants. Vérifiez également ce lien pour plus d'informations. Vérifiez également la différence entre List et List<Object> dans cette question .

List<?>

Il ne nous reste maintenant que List<?>, ce qui signifie fondamentalement une liste de type inconnu et peut faire référence à n’importe quelle liste. 

List<?> crazyList = new ArrayList<String>();
 List<String> stringsList = new ArrayList<>();
 stringsList.add("Apple");
 stringsList.add("Ball");
 crazyList = stringsList; // fine

Le caractère ? est appelé caractère générique et List<?> est une liste de caractères génériques non liés. Il y a certains points à observer maintenant. 

Nous ne pouvons pas instancier cette liste car le code suivant ne sera pas compilé:

List<?> crazyList = new ArrayList<?>(); // any list.

Nous pouvons dire qu'un type paramétré avec caractère générique s'apparente davantage à un type d'interface car nous pouvons l'utiliser pour faire référence à un objet de type compatible mais pas pour lui-même. 

List<?> crazyList2 = new ArrayList<String>();

Nous ne pouvons y insérer aucun élément, car nous ne savons pas ce que ce type serait réellement.

crazyList2.add("Apple"); // error as you dont actually know what is that type.

Maintenant la question se pose Quand voudrais-je utiliser List<?>?

Vous pouvez considérer cela comme une liste en lecture seule où vous ne vous souciez pas du type des éléments. Vous pouvez l'utiliser pour appeler des méthodes telles que renvoyer la longueur de la liste, l'imprimer, etc.

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

Vous pouvez également vérifier la différence entre List, List<?>, List<T>, List<E>, and List<Object>ici _.

4
i_am_zero

Explication la plus simple qui ne soit pas "RTFM":

List

Générera de nombreux avertissements pour le compilateur, mais cela équivaut principalement à:

List<Object>

Tandis que:

List<?>

signifie fondamentalement quelque chose de générique, mais vous ne savez pas quel est le type générique. C'est idéal pour se débarrasser des avertissements du compilateur lorsque vous ne pouvez pas modifier les types de retour d'autres éléments qui viennent de renvoyer List. C'est beaucoup plus utile sous la forme:

List<? extends SomeOtherThing>
4
John Gardner

L’explication la plus courte possible est la suivante: Le deuxième élément est une liste pouvant contenir n’importe quel type, et vous pouvez y ajouter des objets:

List<Object>

Le premier élément de votre liste est considéré comme équivalent à ceci, sauf que vous recevrez des avertissements du compilateur car il s'agit d'un "type brut".

List

La troisième est une liste pouvant contenir n’importe quel type, mais vous ne pouvez rien y ajouter:

List<?> 

Fondamentalement, vous utilisez le second formulaire (List<Object>) lorsque vous avez réellement une liste pouvant contenir n’importe quel objet et que vous souhaitez pouvoir ajouter des éléments à la liste. Vous utilisez le troisième formulaire (List<?>) lorsque vous recevez la liste en tant que valeur de retour de méthode et vous effectuerez une itération sur la liste sans rien y ajouter. N'utilisez jamais le premier formulaire (List) dans le nouveau code compilé sous Java 5 ou ultérieur.

3
Eddie

Je le formulerais ainsi: alors que List et List<Object> peuvent contenir n’importe quel type d’objets, List<?> contient des éléments d’un type inconnu, mais une fois ce type capturé, il ne peut contenir que des éléments de ce type. C’est pourquoi il s’agit de la seule variante sans danger de ces trois types, et donc généralement préférable.

1
Fabian Steeg

Pour compléter les tutoriels mentionnés par Rob, voici un wikibook expliquant le sujet:
http://fr.wikibooks.org/wiki/Java_Programming/Generics


Modifier:

  1. Aucune restriction sur le type d'éléments dans la liste

  2. Les éléments de la liste doivent être étendus.

  3. Caractère générique utilisé par lui-même, il correspond donc à tout

Serait-il naïf de ma part de conclure à ce stade qu'il n'y a pratiquement aucune différence?

0
Tim

List, List<?>, and List<? extends Object> sont la même chose. La seconde est plus explicite. Pour une liste de ce type, vous ne pouvez pas savoir quels types sont légaux, et vous ne savez rien des types que vous pouvez en extraire, à moins qu'il s'agisse d'objets.

List<Object> signifie spécifiquement que la liste contient n'importe quel type d'objet.

Disons que nous faisons une liste de Foo:

List<Foo> foos= new ArrayList<Foo>();

Il n'est pas légal de mettre une Bar dans foos.

foos.add(new Bar()); // NOT OK!

Il est toujours légal de mettre quelque chose dans un List<Object>

List<Object> objs = new ArrayList<Object>();
objs.add(new Foo());
objs.add(new Bar());

Mais il ne faut pas être autorisé à mettre une Bar dans un List<Foo> - c'est tout le problème. Cela signifie donc que:

List<Object> objs = foos; // NOT OK!

n'est pas légal.

Mais ça va de dire que foos est une liste de quelque chose mais on ne sait pas exactement ce que c'est:

List<?> dontKnows = foos;

Mais cela signifie alors qu'il doit être interdit d'aller

dontKnows.add(new Foo()); // NOT OK
dontKnows.add(new Bar()); // NOT OK

parce que la variable dontKnows ne sait pas quels types sont légaux.

0
PaulMurrayCbr

Quand voudrais-je utiliser

public void CanYouGiveMeAnAnswer( List l ){}

Lorsque vous ne pouvez pas faire tout le casting de votre auto.

Quand voudrais-je utiliser

public void CanYouGiveMeAnAnswer( List l<Object> ){}

Lorsque vous souhaitez limiter le type de la liste. Par exemple, ce serait un argument invalide.

 new ArrayList<String>();

Quand voudrais-je utiliser

public void CanYouGiveMeAnAnswer( List l<?> ){}

Surtout jamais.

0
OscarRyz

List <Object> est destiné à transmettre le paramètre de type d'entrée d'un objet. Alors que la liste <? > représente le type Wildcard. Le joker <? > est de type paramètre inconnu. Le caractère générique ne peut pas être utilisé comme argument de type pour une méthode générique ni pour créer une instance générique d'une classe. Les caractères génériques peuvent être utilisés pour étendre une classe de sous-type, Liste <? étend le nombre>. Assouplir la restriction d'un type d'objet et dans ce cas assouplir le type d'objet "Nombre".

0
Juniar