171 votes

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

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

Par exemple, lorsque vous prenez un coup d'oeil à la classe java.util.concurrent.LinkedBlockingQueue il y a la signature suivante pour le constructeur:

public LinkedBlockingQueue(Collection<? extends E> c)

et pour une pour la méthode:

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

197voto

Jon Skeet Points 692016

La première dit que c'est "un type qui est un ancêtre de l'E"; le second dit que c'est "un type qui est une sous-classe de la E". (Dans les deux cas, E lui-même est d'accord.)

Ainsi, le constructeur utilise l' ? extends E formulaire il garantit que lorsqu'il récupère les valeurs de la collection, ils seront tous E ou de certains sous-classe (c'est à dire qu'il est compatible). L' drainTo méthode est d'essayer de placer les valeurs dans la collection, de sorte que la collection doit avoir un type d'élément de E ou une super-classe.

Par exemple, supposons que vous avez une hiérarchie de classe comme ceci:

Parent extends Object
Child extends Parent

et un LinkedBlockingQueue<Parent>. Vous pouvez construire ce passage dans un List<Child> qui va copier tous les éléments en toute sécurité, parce que chaque Child est un parent. On ne pouvait pas passer dans un List<Object> parce que certains éléments pourraient ne pas être compatibles avec Parent.

De même, vous pouvez drain file d'attente dans un List<Object> parce que chaque Parent est Object... mais vous ne pouviez pas de vidange en List<Child> parce que l' List<Child> attend de tous ses éléments pour être compatible avec Child.

148voto

Edwin Dalorzo Points 19899

Les raisons pour cela sont basées sur la façon dont Java implémente les génériques.

Un Exemple Des Tableaux

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

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

Mais, qu'arriverait-il si vous essayez de faire cela?

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

Cette dernière ligne de la compilation, mais si vous exécutez ce code, vous pouvez obtenir un ArrayStoreException. Parce que vous essayez de placer un double dans un tableau d'entiers (indépendamment d'être accessible par un numéro de référence).

Cela signifie que vous pouvez tromper le compilateur, mais vous ne pouvez pas tromper le moteur d'exécution type de système. Et il en est ainsi parce que les tableaux sont ce que nous appelons reifiable types. Cela signifie que lors de l'exécution de Java sait que ce tableau a été fait instancié comme un tableau d'entiers qui, tout simplement, il arrive à être consulté par le biais d'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 la référence que vous utilisez pour y accéder, droit?

Le Problème avec Java Génériques

Maintenant, le problème avec Java les types génériques, c'est que le type d'informations est ignoré par le compilateur et il n'est pas disponible au moment de l'exécution. Ce processus est appelé le type d'effacement. Il y a de bonnes raisons pour la mise en œuvre des génériques comme ça en Java, mais c'est une longue histoire, et il a à voir avec la compatibilité binaire avec pré-code existant.

Mais le point important ici est que, depuis que, au moment de l'exécution il n'y a pas de type d'informations, il n'y a aucun moyen de s'assurer que nous ne sommes pas commettre des tas de pollution.

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 polution

Si le compilateur Java ne vous empêche pas de le faire, le runtime type de système ne peut vous arrêter, car il n'existe aucun moyen, au moment de l'exécution, afin de déterminer que cette liste était censé être une liste de nombres entiers. Le Java runtime vous permet de mettre ce que vous voulez dans cette liste, quand il ne doit contenir que des entiers, parce que quand il a été créé, il a été déclaré comme une liste d'entiers.

En tant que tel, les concepteurs de Java fait en sorte que vous ne pouvez pas tromper le compilateur. Si vous ne pouvez pas tromper le compilateur (comme on peut le faire avec des tableaux) vous ne pouvez pas tromper le moteur d'exécution type de système.

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

De toute évidence, ce qui pourrait entraver le polymorphisme. Considérons 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 tentez de mettre en œuvre le même code avec des génériques de collections, vous ne réussirez pas:

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

Vous obtenez compilateur erros 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 est d'apprendre à utiliser deux puissantes fonctionnalités de Java génériques connu comme la covariance et la contravariance.

La Covariance

Avec la covariance vous pouvez lire les éléments d'une structure, mais vous ne pouvez pas écrire quoi que ce soit. Tous ceux-ci sont valables déclarations.

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 d' myNums:

Number n = myNums.get(0); 

Parce que vous pouvez être sûr que ce que la liste contient, il peut être upcasted à un certain Nombre (après tout, tout ce qui s'étend Nombre est un Nombre, non?)

Cependant, vous n'êtes pas autorisé à mettre n'importe quoi dans un covariant de la structure.

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

Ce ne serait pas autorisé, parce que Java ne peut pas garantir qu'est-ce que le type réel de l'objet dans la structure générique. Il peut être quelque chose qui s'étend Nombre, mais le compilateur ne peut pas être sûr. Donc vous pouvez le lire, mais pas écrire.

La Contravariance

Avec la contravariance vous pouvez faire l'inverse. Vous pouvez mettre les choses dans une structure générique, mais vous ne pouvez pas lire d'elle.

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 même de l'objet est une Liste d'Objets, et par le biais de la contravariance, vous pouvez mettre des Chiffres dans la, essentiellement parce que tous les numéros d'avoir un Objet comme leur ancêtre commun. En tant que tel, tous les Nombres sont des objets, et par conséquent, cela est valide.

Cependant, vous ne pouvez pas sûre de lire quelque chose de cette contravariant de la structure en supposant que vous obtiendrez un numéro.

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

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

Get/Put Principe

En tant que tel, l'utilisation de la covariance lorsque vous avez l'intention de prendre le générique des valeurs d'une structure, l'utilisation contravariance lorsque vous avez l'intention de mettre des valeurs génériques dans une structure et de l'utilisation exacte de type générique lorsque vous avez l'intention de faire les deux.

Le meilleur exemple que j'ai est le suivant qui copie tout type de numéros d'une liste dans une autre liste. Il ne obtient les éléments de la source, et il ne met éléments dans le destin.

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

Grâce aux pouvoirs de la covariance et la 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);

65voto

David Moles Points 7669

<? extends E> définit E comme limite supérieure: "Cela peut être jeté à l' E".

<? super E> définit E comme limite inférieure: "E peut être lancé à ce."

12voto

joekutner Points 1037

Je vais essayer de répondre à cette question. Mais pour obtenir une très bonne réponse, vous devriez vérifier Joshua Bloch du livre Effective Java (2e Édition). Il décrit le mnémonique PECS, qui signifie "Producteur s'Étend, de la Consommation de Super".

L'idée est que si vous code est de consommer des valeurs génériques de l'objet, alors vous devriez utiliser s'étend. mais si vous êtes à la production de nouvelles valeurs pour le type générique vous devriez utiliser super.

Ainsi, 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 vraiment, vous devriez vérifier ce livre: http://java.sun.com/docs/books/effective/

7voto

oxbow_lakes Points 70013

Vous pourriez vouloir à google pour les termes contravariance (<? super E>) et de covariance (<? extends E>). J'ai trouvé que la chose la plus utile lorsque la compréhension des génériques a été pour moi de comprendre la signature de la méthode de Collection.addAll:

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

Tout comme vous voudriez être en mesure d'ajouter un String d'un List<Object>:

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

Vous devez également être en mesure d'ajouter un List<String> (ou n'importe quelle collection d' Strings) par l'intermédiaire de l' addAll méthode:

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

Cependant, vous devez réaliser qu'un List<Object> et List<String> ne sont pas équivalents et ni est la dernière d'une sous-classe de l'ancien. Ce qui est nécessaire est le concept d'une covariante du type de paramètre - à-dire le <? extends T> bits.

Une fois que vous avez cela, c'est simple de penser à des scénarios où vous souhaitez la contravariance aussi (vérifier l' Comparable interface).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X