Quelle est la différence entre <? super E> et <? étend E>?


147

Quelle est la différence entre <? super E>et <? extends E>?

Par exemple, lorsque vous regardez la classe, java.util.concurrent.LinkedBlockingQueueil y a la signature suivante pour le constructeur:

public LinkedBlockingQueue(Collection<? extends E> c)

et pour un pour la méthode:

public int drainTo(Collection<? super E> c)

Réponses:


182

Le premier dit que c'est "un type qui est un ancêtre de E"; le second dit que c'est "un type qui est une sous-classe de E". (Dans les deux cas, E lui-même va bien.)

Ainsi, le constructeur utilise le ? extends Eformulaire pour garantir que lorsqu'il récupère les valeurs de la collection, elles seront toutes E ou une sous-classe (c'est-à-dire qu'elle est compatible). La drainTométhode essaie de mettre des valeurs dans la collection, donc la collection doit avoir un type d'élément E ou une superclasse .

À titre d'exemple, supposons que vous ayez une hiérarchie de classes comme celle-ci:

Parent extends Object
Child extends Parent

et a LinkedBlockingQueue<Parent>. Vous pouvez construire ce passage dans un List<Child>qui copiera tous les éléments en toute sécurité, car chacun Childest un parent. Vous ne pouvez pas passer un List<Object>car certains éléments peuvent ne pas être compatibles avec Parent.

De même, vous pouvez vider cette file d'attente dans un List<Object>car tout Parentest un Object... mais vous ne pouvez pas le vider dans un List<Child>car le List<Child>s'attend à ce que tous ses éléments soient compatibles avec Child.


25
+1. C'est vraiment la différence pratique. s'étend pour chercher, super pour insérer.
Yishai le

1
@Jon qu'entendez-vous par (Dans les deux cas, E lui-même va bien.) Dans le premier paragraphe?
Geek

2
@Geek: Je veux dire que si vous avez quelque chose comme ? extends InputStreamou ? super InputStreamalors vous pouvez utiliser un InputStreamcomme argument.
Jon Skeet

Je n'ai jamais vraiment eu l'explication PECS de Josh Block en Java efficace. Cependant @Yishai, c'est une façon utile de se souvenir. Peut-être pouvons-nous proposer un nouveau mnémonique, SAGE: Super -> Add / Get -> Extend
dcompiled

Donc, si je l'ai bien lu, "<? Extend E>" exige que "?" est une sous-classe de "E", et "<? super E>" exige que "E" soit une sous-classe de "?", non?
El Suscriptor Justiciero

130

Les raisons en sont basées sur la manière dont Java implémente les génériques.

Un exemple de tableaux

Avec les tableaux, vous pouvez le faire (les tableaux sont covariants)

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Mais que se passerait-il si vous essayez de faire cela?

myNumber[0] = 3.14; //attempt of heap pollution

Cette dernière ligne compilerait très bien, mais si vous exécutez ce code, vous pourriez obtenir un fichier ArrayStoreException. Parce que vous essayez de mettre un double dans un tableau d'entiers (indépendamment de l'accès via une référence numérique).

Cela signifie que vous pouvez tromper le compilateur, mais vous ne pouvez pas tromper le système de type d'exécution. Et c'est ainsi parce que les tableaux sont ce que nous appelons des types réifiables . Cela signifie qu'au moment de l'exécution, Java sait que ce tableau a en fait été instancié sous la forme d'un tableau d'entiers auquel il se trouve simplement accessible via une référence de type Number[].

Donc, comme vous pouvez le voir, une chose est le type réel de l'objet, et une autre chose est le type de référence que vous utilisez pour y accéder, non?

Le problème des génériques Java

Maintenant, le problème avec les types génériques Java est que les informations de type sont ignorées par le compilateur et ne sont pas disponibles au moment de l'exécution. Ce processus est appelé effacement de type . Il y a de bonnes raisons d'implémenter des génériques comme celui-ci en Java, mais c'est une longue histoire, et cela doit être lié, entre autres, à la compatibilité binaire avec du code préexistant (voir Comment nous avons obtenu les génériques que nous avons ).

Mais le point important ici est que puisque, au moment de l'exécution, il n'y a pas d'informations de type, il n'y a aucun moyen de garantir que nous ne commettons pas de pollution en tas.

Par exemple,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap pollution

Si le compilateur Java ne vous empêche pas de faire cela, le système de type d'exécution ne peut pas non plus vous arrêter, car il n'y a aucun moyen, au moment de l'exécution, de déterminer que cette liste était censée être une liste d'entiers uniquement. Le runtime Java vous permettrait de mettre ce que vous voulez dans cette liste, alors qu'il ne devrait contenir que des entiers, car lors de sa création, il a été déclaré sous forme de liste d'entiers.

En tant que tel, les concepteurs de Java ont veillé à ce que vous ne puissiez pas tromper le compilateur. Si vous ne pouvez pas tromper le compilateur (comme nous pouvons le faire avec les tableaux), vous ne pouvez pas non plus tromper le système de type d'exécution.

En tant que tel, nous disons que les types génériques ne sont pas réifiables .

Évidemment, cela entraverait le polymorphisme. Prenons l'exemple suivant:

static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Maintenant, vous pouvez l'utiliser comme ceci:

Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));

Mais si vous essayez d'implémenter le même code avec des collections génériques, vous ne réussirez pas:

static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Vous obtiendrez des erreurs de compilation si vous essayez de ...

List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error

La solution consiste à apprendre à utiliser deux fonctionnalités puissantes des génériques Java appelées covariance et contravariance.

Covariance

Avec la covariance, vous pouvez lire les éléments d'une structure, mais vous ne pouvez rien y écrire. Ce sont toutes des déclarations valables.

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>();
List<? extends Number> myNums = new ArrayList<Double>();

Et vous pouvez lire à partir de myNums:

Number n = myNums.get(0); 

Parce que vous pouvez être sûr que quel que soit le contenu de la liste réelle, il peut être converti en un nombre (après tout, tout ce qui étend le nombre est un nombre, non?)

Cependant, vous n'êtes pas autorisé à mettre quoi que ce soit dans une structure covariante.

myNumst.add(45L); //compiler error

Cela ne serait pas autorisé, car Java ne peut pas garantir quel est le type réel de l'objet dans la structure générique. Il peut s'agir de tout ce qui étend Number, mais le compilateur ne peut pas en être sûr. Vous pouvez donc lire, mais pas écrire.

Contravariance

Avec la contravariance, vous pouvez faire le contraire. Vous pouvez mettre des choses dans une structure générique, mais vous ne pouvez pas en lire.

List<Object> myObjs = new List<Object>();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

Dans ce cas, la nature réelle de l'objet est une liste d'objets, et par contravariance, vous pouvez y mettre des nombres, essentiellement parce que tous les nombres ont Object comme ancêtre commun. En tant que tel, tous les nombres sont des objets, et donc cela est valide.

Cependant, vous ne pouvez rien lire en toute sécurité dans cette structure contravariante en supposant que vous obtiendrez un nombre.

Number myNum = myNums.get(0); //compiler-error

Comme vous pouvez le voir, si le compilateur vous permettait d'écrire cette ligne, vous obtiendrez une ClassCastException au moment de l'exécution.

Principe Get / Put

En tant que tel, utilisez la covariance lorsque vous avez uniquement l'intention de retirer des valeurs génériques d'une structure, utilisez la contravariance lorsque vous avez uniquement l'intention de placer des valeurs génériques dans une structure et utilisez le type générique exact lorsque vous avez l'intention de faire les deux.

Le meilleur exemple que j'ai est le suivant qui copie n'importe quel type de nombres d'une liste dans une autre liste. Cela ne fait que éléments de la source et ne place que les éléments dans la cible.

public static void copy(List<? extends Number> source, List<? super Number> target) {
    for(Number number : source) {
        target(number);
    }
}

Grâce aux pouvoirs de covariance et de contravariance, cela fonctionne pour un cas comme celui-ci:

List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);

27
Cette réponse devrait aller en haut. Belle explication.
Suresh Atta

1
@edwindalorzo, il y a une petite faute de frappe que vous voulez corriger sous Contravariance. Vous dites List<Object> myObjs = new List<Object();(qui manque la fermeture >pour la seconde Object).

Des exemples fantastiques, faciles et clairs de ces concepts subtils!
db1234

Quelque chose que vous pourriez ajouter pour aider les autres à se souvenir des choses. Lorsque vous souhaitez appeler une méthode à partir d'une super classe, vous utilisez super.methodName. Lors de l'utilisation <? super E>, cela signifie «quelque chose dans la superdirection» par opposition à quelque chose dans la extendsdirection. Exemple: Objectest dans la superdirection de Number(puisqu'il s'agit d'une super classe) et Integerest dans la extendsdirection (puisqu'elle s'étend Number).
BrainStorm.exe

59

<? extends E>définit Ecomme limite supérieure: "Ceci peut être converti en E".

<? super E>définit Ecomme la limite inférieure: " Epeut être converti en ceci."


6
C'est l'un des meilleurs résumés simples / pratiques de la différence que j'ai vu.
JAB

1
Depuis des décennies maintenant (avec la POO), je lutte contre une inversion instinctive des notions «supérieur» et «inférieur». Aggravant! Pour moi, Objectc'est intrinsèquement une classe de niveau inférieur, malgré sa position en tant que superclasse ultime (et dessinée verticalement dans UML ou des arbres d'héritage similaires). Je n'ai jamais été en mesure d'annuler cela malgré des éons d'essais.

3
@ tgm1024 "superclass" et "subclass" doivent vous donner beaucoup de mal.
David Moles

@DavidMoles, pourquoi? Vous ne suivez clairement pas du tout ce que je dis. «superclasse» s'apparente à «sur-ensemble»; la notion de spécialisation est une réduction de l'applicabilité dans le cadre de la relation IS-A. Apple IS-A Fruit. Fruit (superclasse) est un sur-ensemble comprenant Apple (sous-classe) en tant que sous-ensemble. Cette relation verbale est bonne. Ce que je dis se décompose, c'est la notion que «supérieur» et «inférieur» ont des mappages intrinsèques vers «sur-ensemble» et «sous-ensemble». Les termes supérieur et inférieur doivent être évités comme termes en OO.

1
@ tgm1024 "Super-" vient du latin super "above, on", et "sub-" du latin sub "under, under". C'est-à-dire, étymologiquement, super est en haut et sous est en bas.
David Moles

12

Je vais essayer de répondre à cela. Mais pour obtenir une très bonne réponse, vous devriez consulter le livre de Joshua Bloch, Effective Java (2e édition). Il décrit le mnémonique PECS, qui signifie "Producer Extends, Consumer Super".

L'idée est que si votre code consomme les valeurs génériques de l'objet, vous devez utiliser extend. mais si vous produisez de nouvelles valeurs pour le type générique, vous devez utiliser super.

Donc par exemple:

public void pushAll(Iterable<? extends E> src) {
  for (E e: src) 
    push(e);
}

Et

public void popAll(Collection<? super E> dst) {
  while (!isEmpty())
    dst.add(pop())
}

Mais vous devriez vraiment consulter ce livre: http://java.sun.com/docs/books/effective/


12

<? super E> veux dire any object including E that is parent of E

<? extends E> veux dire any object including E that is child of E .


réponse courte et douce.
chirag soni le

7

Vous pouvez rechercher les termes contravariance ( <? super E>) et covariance ( <? extends E>) sur Google . J'ai trouvé que la chose la plus utile pour comprendre les génériques était pour moi de comprendre la signature de méthode de Collection.addAll:

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

Tout comme vous voudriez pouvoir ajouter un Stringà un List<Object>:

List<Object> lo = ...
lo.add("Hello")

Vous devriez également pouvoir ajouter un List<String>(ou toute collection de Strings) via la addAllméthode:

List<String> ls = ...
lo.addAll(ls)

Cependant, vous devez comprendre que a List<Object>et a List<String>ne sont pas équivalents et que ce dernier n'est pas non plus une sous-classe du premier. Ce qu'il faut, c'est le concept d'un paramètre de type covariant - c'est-à-dire le <? extends T>bit.

Une fois que vous avez cela, il est simple de penser à des scénarios dans lesquels vous souhaitez également une contravariance (vérifiez l' Comparableinterface).


4

Avant la réponse; Veuillez être clair que

  1. Les génériques ne compilent que la fonction de temps pour assurer TYPE_SAFETY, elle ne sera pas disponible pendant RUNTIME.
  2. Seule une référence avec Generics forcera la sécurité du type; si la référence n'est pas déclarée avec des génériques, cela fonctionnera sans type safty.

Exemple:

List stringList = new ArrayList<String>();
stringList.add(new Integer(10)); // will be successful.

J'espère que cela vous aidera à comprendre le caractère générique plus clairement.

//NOTE CE - Compilation Error
//      4 - For

class A {}

class B extends A {}

public class Test {

    public static void main(String args[]) {

        A aObj = new A();
        B bObj = new B();

        //We can add object of same type (A) or its subType is legal
        List<A> list_A = new ArrayList<A>();
        list_A.add(aObj);
        list_A.add(bObj); // A aObj = new B(); //Valid
        //list_A.add(new String()); Compilation error (CE);
        //can't add other type   A aObj != new String();


        //We can add object of same type (B) or its subType is legal
        List<B> list_B = new ArrayList<B>();
        //list_B.add(aObj); CE; can't add super type obj to subclass reference
        //Above is wrong similar like B bObj = new A(); which is wrong
        list_B.add(bObj);



        //Wild card (?) must only come for the reference (left side)
        //Both the below are wrong;   
        //List<? super A> wildCard_Wrongly_Used = new ArrayList<? super A>();
        //List<? extends A> wildCard_Wrongly_Used = new ArrayList<? extends A>();


        //Both <? extends A>; and <? super A> reference will accept = new ArrayList<A>
        List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<A>();
                        list_4__A_AND_SuperClass_A = new ArrayList<Object>();
                      //list_4_A_AND_SuperClass_A = new ArrayList<B>(); CE B is SubClass of A
                      //list_4_A_AND_SuperClass_A = new ArrayList<String>(); CE String is not super of A  
        List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<A>();
                          list_4__A_AND_SubClass_A = new ArrayList<B>();
                        //list_4__A_AND_SubClass_A = new ArrayList<Object>(); CE Object is SuperClass of A


        //CE; super reference, only accepts list of A or its super classes.
        //List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<String>(); 

        //CE; extends reference, only accepts list of A or its sub classes.
        //List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<Object>();

        //With super keyword we can use the same reference to add objects
        //Any sub class object can be assigned to super class reference (A)                  
        list_4__A_AND_SuperClass_A.add(aObj);
        list_4__A_AND_SuperClass_A.add(bObj); // A aObj = new B();
        //list_4__A_AND_SuperClass_A.add(new Object()); // A aObj != new Object(); 
        //list_4__A_AND_SuperClass_A.add(new String()); CE can't add other type

        //We can't put anything into "? extends" structure. 
        //list_4__A_AND_SubClass_A.add(aObj); compilation error
        //list_4__A_AND_SubClass_A.add(bObj); compilation error
        //list_4__A_AND_SubClass_A.add("");   compilation error

        //The Reason is below        
        //List<Apple> apples = new ArrayList<Apple>();
        //List<? extends Fruit> fruits = apples;
        //fruits.add(new Strawberry()); THIS IS WORNG :)

        //Use the ? extends wildcard if you need to retrieve object from a data structure.
        //Use the ? super wildcard if you need to put objects in a data structure.
        //If you need to do both things, don't use any wildcard.


        //Another Solution
        //We need a strong reference(without wild card) to add objects 
        list_A = (ArrayList<A>) list_4__A_AND_SubClass_A;
        list_A.add(aObj);
        list_A.add(bObj);

        list_B = (List<B>) list_4__A_AND_SubClass_A;
        //list_B.add(aObj); compilation error
        list_B.add(bObj);

        private Map<Class<? extends Animal>, List<? extends Animal>> animalListMap;

        public void registerAnimal(Class<? extends Animal> animalClass, Animal animalObject) {

            if (animalListMap.containsKey(animalClass)) {
                //Append to the existing List
                 /*    The ? extends Animal is a wildcard bounded by the Animal class. So animalListMap.get(animalObject);
                 could return a List<Donkey>, List<Mouse>, List<Pikachu>, assuming Donkey, Mouse, and Pikachu were all sub classes of Animal. 
                 However, with the wildcard, you are telling the compiler that you don't care what the actual type is as long as it is a sub type of Animal.      
                 */   
                //List<? extends Animal> animalList = animalListMap.get(animalObject);
                //animalList.add(animalObject);  //Compilation Error because of List<? extends Animal>
                List<Animal> animalList = animalListMap.get(animalObject);
                animalList.add(animalObject);      


            } 
    }

    }
}



1
Bonne explication avec code. Mais si vous aviez utilisé des commentaires dans le code en dehors du bloc de code, ce serait mieux pour la visualisation et plus lisible.
Prabhu

3

Un caractère générique avec une limite supérieure ressemble à "? Extend Type" et représente la famille de tous les types qui sont des sous-types de Type, le type Type étant inclus. Le type est appelé la limite supérieure.

Un caractère générique avec une limite inférieure ressemble à "? Super Type" et représente la famille de tous les types qui sont des supertypes de Type, le type Type étant inclus. Le type est appelé la limite inférieure.


1

Vous avez une classe Parent et une classe Child héritées de la classe Parent.La classe Parent est héritée d'une autre classe appelée GrandParent Class.So l'ordre d'héritage est GrandParent> Parent> Child. Maintenant, <? étend Parent> - Cela accepte la classe Parent ou la classe Child <? super Parent> - Cela accepte la classe Parent ou l'une des classes GrandParent

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.