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