web-dev-qa-db-fra.com

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

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

Par exemple, lorsque vous regardez la classe Java.util.concurrent.LinkedBlockingQueue, il 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)
128
Tomasz Błachowicz

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 est d'accord.)

Donc, le constructeur utilise le formulaire ? extends E afin qu’il garantisse que lorsqu’il récupère les valeurs de la collection, elles seront toutes E ou une sous-classe (c’est-à-dire qu’elles sont compatibles). La méthode drainTo tente de placer les valeurs dans la collection. La collection doit donc avoir un type d'élément E ou une superclasse .

Par exemple, supposons que vous ayez une hiérarchie de classes comme celle-ci:

Parent extends Object
Child extends Parent

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

De même, vous pouvez vider cette file d'attente dans un List<Object> car chaque Parent est une 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.

167
Jon Skeet

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 essayiez de faire cela?

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

Cette dernière ligne compilerait parfaitement, mais si vous exécutez ce code, vous pourriez obtenir une ArrayStoreException. Parce que vous essayez de mettre un double dans un tableau d’entier (indépendamment du fait qu’on y accède 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 types à l'exécution. Et c’est vrai parce que les tableaux sont ce que nous appelons types reifiables. 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 on accède simplement via une référence de type Number[].

Ainsi, comme vous pouvez le constater, une chose est le type réel de l'objet et une autre chose est le type de la référence que vous utilisez pour y accéder, n'est-ce pas?

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 qu’elles ne sont pas disponibles au moment de l’exécution. Ce processus s'appelle type erasure . Il y a de bonnes raisons d'implémenter des génériques comme celui-ci en Java, mais c'est une longue histoire qui concerne la compatibilité binaire avec du code préexistant.

Mais le point important ici est qu’étant donné qu’au moment de l’exécution, il n’existe aucune information de type, il n’existe aucun moyen de s’assurer que nous ne commettons pas de pollution excessive.

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 en empêche pas, le système de type à l'exécution ne peut pas vous arrêter non plus, car il est impossible, au moment de l'exécution, de déterminer que cette liste était supposée être une liste d'entiers uniquement. Le runtime Java vous permettrait de mettre ce que vous voulez dans cette liste, alors qu'elle ne devrait contenir que des entiers, car lors de sa création, elle était déclarée sous forme de liste d'entiers.

En tant que tels, 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 des tableaux), vous ne pouvez pas non plus tromper le système de types à l'exécution.

En tant que tels, nous disons que les types génériques sont non-reifiable.

Evidemment, 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 puissantes fonctionnalités des génériques Java, à savoir covariance et contravariance.

Covariance

Avec covariance, vous pouvez lire des éléments d’une structure, mais vous ne pouvez rien y écrire. Toutes ces déclarations sont 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 dans myNums:

Number n = myNums.get(0); 

Parce que vous pouvez être sûr que tout ce que la liste contient, elle peut être convertie en un nombre (après tout, tout ce qui étend le nombre est un nombre, n'est-ce pas?)

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 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 en être sûr. Donc, vous pouvez lire, mais pas écrire.

Contravariance

Avec contravariance, vous pouvez faire le contraire. Vous pouvez mettre les choses dans une structure générique, mais vous ne pouvez pas la 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, grâce à la contravariance, vous pouvez y placer des nombres, essentiellement parce que tous les nombres ont pour objet ancêtre commun. En tant que tels, tous les Nombres sont des objets et, par conséquent, cela est valide.

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

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

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

Principe Get/Put

En tant que tel, utilisez covariance lorsque vous souhaitez uniquement extraire des valeurs génériques d'une structure, utilisez contravariance lorsque vous souhaitez uniquement placer des valeurs génériques dans une structure et utilisez le type générique exact lorsque vous souhaitez effectuer les deux.

Le meilleur exemple que j'ai est le suivant, qui copie tout type de numéro d’une liste dans une autre. Il ne contient que obtient éléments de la source et il ne contient que met éléments de 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);
106
Edwin Dalorzo

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

<? super E> définit E en tant que limite inférieure: "E peut être converti en ceci."

46
David Moles

Je vais essayer de répondre à cela. Mais pour obtenir une très bonne réponse, consultez le livre Effective Java (2nd Edition) de Joshua Bloch. 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 une extension. 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/

11
codefinger

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

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

9
M Sach

Vous pouvez vouloir google pour les termescontravariance (<? super E>) et covariance (<? extends E>). 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 une 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 méthode addAll:

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

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

Une fois que vous avez cela, il est simple de penser à des scénarios dans lesquels vous voulez également {contravariance} _ (consultez l'interface Comparable).

7
oxbow_lakes

Avant la réponse; S'il vous plaît être clair que 

  1. La fonctionnalité de compilation des génériques uniquement pour assurer TYPE_SAFETY, elle ne sera pas disponible pendant l'exécution.
  2. Seule une référence avec Generics forcera le type safety; Si la référence n'est pas déclarée avec des génériques, elle fonctionnera sans le 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 clair. 

//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);      


            } 
    }

    }
}
4

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, Type Type étant inclus. Le type s'appelle 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 super-types de Type, Type Type étant inclus. Le type s'appelle la limite inférieure.

3
vinaynag

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.L'ordre d'héritage est GrandParent> Parent> Enfant . Maintenant, <? extend Parent> - Ceci accepte la classe Parent ou la classe Child <? super Parent> - Ceci accepte la classe Parent ou la classe GrandParent 

0
Crox