Les tableaux sont covariants
Les tableaux sont censés être covariants, ce qui signifie essentiellement que, compte tenu des règles de sous-typage de Java, un tableau de type T[]
peut contenir des éléments de type T
ou tout sous-type de T
. Par exemple
Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);
Mais non seulement cela, les règles de sous-typage de Java indiquent également qu'un tableau S[]
est un sous-type du tableau T[]
si S
est un sous-type de T
, par conséquent, quelque chose comme ceci est également valide:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Parce que selon les règles de sous-typage en Java, un tableau Integer[]
est un sous-type d'un tableau Number[]
car Integer est un sous-type de Number.
Mais cette règle de sous-typage peut conduire à une question intéressante: que se passerait-il si nous essayions de le faire?
myNumber[0] = 3.14; //attempt of heap pollution
Cette dernière ligne se compilerait très bien, mais si nous exécutons ce code, nous obtiendrions un ArrayStoreException
car nous essayons de mettre un double dans un tableau d'entiers. Le fait que nous accédions au tableau via une référence numérique n'est pas pertinent ici, ce qui importe, c'est que le tableau est un tableau d'entiers.
Cela signifie que nous pouvons tromper le compilateur, mais nous ne pouvons pas tromper le système de type d'exécution. Et c'est parce que les tableaux sont ce que nous appelons un type réifiable. Cela signifie qu'au moment de l'exécution, Java sait que ce tableau a été réellement instancié comme un tableau d'entiers auquel il est simplement possible d'accéder via une référence de type Number[]
.
Donc, comme nous pouvons le voir, une chose est le type réel de l'objet, une autre chose est le type de référence que nous utilisons pour y accéder, non?
Le problème avec les génériques Java
Maintenant, le problème avec les types génériques en Java est que les informations de type pour les paramètres de type sont ignorées par le compilateur après la compilation du code; par conséquent, ces informations de type ne sont pas disponibles au moment de l'exécution. Ce processus est appelé effacement de type . Il existe de bonnes raisons pour implémenter des génériques comme celui-ci en Java, mais c'est une longue histoire, et cela a à voir avec la compatibilité binaire avec du code préexistant.
Le point important ici est que, car au moment de l'exécution, il n'y a pas d'informations de type, il n'y a aucun moyen de s'assurer que nous ne commettons pas de pollution de tas.
Considérons maintenant le code dangereux suivant:
List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution
Si le compilateur Java ne nous empêche pas de le faire, le système de type d'exécution ne peut pas nous arrêter non plus, car il n'y a aucun moyen, au moment de l'exécution, de déterminer que cette liste était censée être uniquement une liste d'entiers. L'exécution Java nous permettrait de mettre tout ce que nous voulons dans cette liste, alors qu'elle ne devrait contenir que des entiers, car lorsqu'elle a été créée, elle a été déclarée comme une liste d'entiers. C'est pourquoi le compilateur rejette la ligne numéro 4 car elle n'est pas sûre et si elle est autorisée, elle pourrait casser les hypothèses du système de type.
En tant que tel, les concepteurs de Java se sont assurés que nous ne pouvons pas tromper le compilateur. Si nous ne pouvons pas tromper le compilateur (comme nous pouvons le faire avec les tableaux), nous ne pouvons pas non plus tromper le système de type d'exécution.
En tant que tels, nous disons que les types génériques ne sont pas réifiables, car au moment de l'exécution, nous ne pouvons pas déterminer la véritable nature du type générique.
J'ai ignoré certaines parties de ces réponses, vous pouvez lire l'article complet ici:
https://dzone.com/articles/covariance-and-contravariance