202 votes

Pourquoi les tableaux sont covariants mais les génériques sont invariants ?

Extrait de Effective Java de Joshua Bloch,

  1. Les tableaux diffèrent des types génériques de deux façons importantes. Premièrement, les tableaux sont covariants. Les types génériques sont invariants.
  2. Covariant signifie simplement que si X est un sous-type de Y, alors X[] sera également un sous-type de Y[]. Les tableaux sont covariants car la chaîne de caractères est un sous-type d'objet.

    String[] is subtype of Object[]

    Invariant signifie simplement qu'indépendamment du fait que X soit un sous-type de Y ou non,

     List<X> will not be subType of List<Y>.

Ma question est la suivante : pourquoi avoir décidé de rendre les tableaux covariants en Java ? Il existe d'autres messages de la SO tels que Pourquoi les tableaux sont-ils invariants, mais les listes covariantes ? mais ils semblent se concentrer sur Scala et je ne suis pas en mesure de suivre.

1 votes

N'est-ce pas parce que les génériques ont été ajoutés plus tard ?

1 votes

Je pense que la comparaison entre les tableaux et les collections est injuste, les collections utilisent les tableaux en arrière-plan ! !!

6 votes

@EL-conteDe-monteTereBentikh Pas toutes les collections, par exemple LinkedList .

186voto

Paul Bellora Points 26524

Via wikipedia :

Les premières versions de Java et de C# ne comprenaient pas de génériques (alias polymorphisme paramétrique).

Dans un tel contexte, rendre les tableaux invariants exclut les programmes polymorphes utiles. Par exemple, envisagez d'écrire une fonction pour mélanger un tableau, ou une fonction qui teste l'égalité de deux tableaux en utilisant la fonction Object.equals sur les éléments. L'implémentation ne dépend pas du type exact d'élément stocké dans le tableau, il devrait donc être possible d'écrire une seule fonction qui fonctionne sur tous les types de tableaux. Il est facile d'implémenter des fonctions de type

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

Cependant, si les types de tableaux étaient traités comme invariants, il ne serait possible d'appeler ces fonctions que sur un tableau de type exact Object[] . On ne pourrait pas, par exemple, mélanger un tableau de chaînes de caractères.

Par conséquent, Java et C# traitent tous deux les types de tableaux de manière covariante. Par exemple, en C# string[] est un sous-type de object[] et en Java String[] est un sous-type de Object[] .

Cela répond à la question "Pourquoi les tableaux sont-ils covariants ?", ou plus précisément, "Pourquoi les tableaux sont-ils covariants ? étaient tableaux rendus covariants à l'époque ?"

Lorsque les génériques ont été introduits, ils n'ont volontairement pas été rendus covariants pour les raisons indiquées dans le document cette réponse de Jon Skeet :

Non, un List<Dog> n'est pas un List<Animal> . Considérez ce que vous pouvez faire avec un List<Animal> - vous pouvez y ajouter n'importe quel animal... y compris un chat. Maintenant, pouvez-vous logiquement ajouter un chat à une portée de chiots ? Absolument pas.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

Soudain, vous avez un très chat confus.

La motivation initiale pour rendre les tableaux covariants décrite dans l'article de wikipedia ne s'appliquait pas aux génériques car caractères de remplacement a rendu possible l'expression de la covariance (et de la contravariance), par exemple :

boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);

5 votes

Oui, Arrays permet un comportement polymorphe, cependant, il introduit des exceptions à l'exécution (contrairement aux exceptions à la compilation avec les génériques). ex : Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";

27 votes

C'est exact. Les tableaux ont des types réifiables et jettent ArrayStoreException selon les besoins. Il est clair que cela était considéré comme un compromis valable à l'époque. Comparez avec aujourd'hui : beaucoup considèrent rétrospectivement la covariance des tableaux comme une erreur.

4 votes

Pourquoi "beaucoup" considèrent-ils que c'est une erreur ? C'est bien plus utile que de ne pas avoir de covariance de tableau. Combien de fois avez-vous vu une ArrayStoreException ; elles sont plutôt rares. L'ironie ici est impardonnable imo... parmi les pires erreurs jamais faites en Java, il y a la variance de site d'utilisation aka les wildcards.

37voto

Katona Points 2090

La raison en est que chaque tableau connaît le type de ses éléments pendant l'exécution, alors que la collection générique ne le connaît pas à cause de l'effacement des types.

Par exemple :

String[] strings = new String[2];
Object[] objects = strings;  // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime

Si cela était autorisé avec les collections génériques :

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;  // let's say it is valid
objects.add(12);  // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this

Mais cela poserait des problèmes plus tard lorsque quelqu'un essaierait d'accéder à la liste :

String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String

0 votes

Je pense que la réponse de Paul Bellora est plus appropriée car il commente la raison pour laquelle les tableaux sont covariants. Si les tableaux étaient rendus invariants, alors c'est bien. Vous auriez l'effacement de type avec cela. La principale raison de la propriété d'effacement des types est la compatibilité ascendante, n'est-ce pas ?

0 votes

@user2708477, oui, l'effacement des types a été introduit pour des raisons de compatibilité ascendante. Et oui, ma réponse tente de répondre à la question du titre, pourquoi les génériques sont invariants.

0 votes

Le fait que les tableaux connaissent leur type signifie que, alors que la covariance permet au code de demandez à pour stocker quelque chose dans un tableau où il ne rentrera pas - cela ne signifie pas qu'un tel stockage sera autorisé à avoir lieu. Par conséquent, le niveau de danger introduit par le fait que les tableaux soient covariants est bien moindre que s'ils ne connaissaient pas leurs types.

29voto

Rahul Tripathi Points 1

Peut être este aide:-

Les génériques ne sont pas covariants

Dans le langage Java, les tableaux sont covariants, ce qui signifie que si Integer étend Number (ce qui est le cas), non seulement Integer est également un Number, mais Integer[] est également un Number[] et vous êtes libre de transmettre ou d'attribuer un numéro d'identification. Integer[] où un Number[] est demandé. (Plus formellement, si Number est un supertype de Integer, alors Number[] est un supertype de Integer[] .) On pourrait penser qu'il en va de même pour les types génériques -- que List<Number> est un supertype de List<Integer> et que vous pouvez passer un List<Integer> où un List<Number> est attendu. Malheureusement, ça ne marche pas comme ça.

Il s'avère qu'il y a une bonne raison pour que ça ne marche pas comme ça : Cela casserait la sécurité de type que les génériques sont censés fournir. Imaginez que vous puissiez assigner un List<Integer> à un List<Number> . Le code suivant vous permettrait alors de placer quelque chose qui n'est pas un Integer dans un fichier de type List<Integer> :

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

Parce que ln est un List<Number> l'ajout d'un flotteur semble parfaitement légal. Mais si ln était aliasé avec li alors cela briserait la promesse de sécurité de type implicite dans la définition de li -- qu'il s'agit d'une liste d'entiers, ce qui explique pourquoi les types génériques ne peuvent pas être covariants.

5 votes

Pour les tableaux, vous obtenez un ArrayStoreException au moment de l'exécution.

4 votes

Ma question est la suivante WHY Comme Sotirios l'a mentionné, avec les tableaux, on obtient une exception ArrayStoreException au moment de l'exécution. Si les tableaux étaient invariants, nous pourrions détecter cette erreur au moment de la compilation, n'est-ce pas ?

0 votes

@eagertoLearn : L'une des principales faiblesses sémantiques de Java est que rien dans son système de types ne permet de distinguer "Array qui ne contient rien d'autre que des dérivés de Animal qui ne doit pas accepter d'éléments reçus d'ailleurs" de "Tableau qui ne doit rien contenir d'autre que Animal et doit être prêt à accepter des références fournies par des tiers à l'adresse suivante Animal . Le code qui a besoin du premier devrait accepter un tableau de Cat mais le code qui a besoin de ce dernier ne devrait pas. Si le compilateur pouvait distinguer les deux types, il pourrait fournir une vérification à la compilation. Malheureusement, la seule chose qui les distingue...

4voto

meriton Points 30447

Une caractéristique importante des types paramétriques est la possibilité d'écrire des algorithmes polymorphes, c'est-à-dire des algorithmes qui opèrent sur une structure de données indépendamment de la valeur de son paramètre, tels que Arrays.sort() .

Avec les génériques, cela se fait avec des types joker :

<E extends Comparable<E>> void sort(E[]);

Pour être vraiment utile, les types joker nécessitent une capture joker, et cela nécessite la notion de paramètre de type. Rien de tout cela n'était disponible à l'époque où les tableaux ont été ajoutés à Java, et le fait de rendre les tableaux de type référence covariants permettait une manière bien plus simple d'autoriser les algorithmes polymorphes :

void sort(Comparable[]);

Cependant, cette simplicité a ouvert une faille dans le système des types statiques :

String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException

exigeant une vérification à l'exécution de chaque accès en écriture à un tableau de type référence.

En bref, la nouvelle approche incarnée par les génériques rend le système de types plus complexe, mais aussi plus sûr sur le plan statique, alors que l'ancienne approche était plus simple et moins sûre sur le plan statique. Les concepteurs du langage ont opté pour l'approche la plus simple, ayant des choses plus importantes à faire que de combler une petite faille dans le système de types qui pose rarement des problèmes. Plus tard, lorsque Java a été établi, et que les besoins urgents ont été pris en charge, ils ont eu les ressources nécessaires pour faire les choses correctement pour les génériques (mais le changer pour les tableaux aurait cassé les programmes Java existants).

3voto

supercat Points 25534

Les tableaux sont covariants pour au moins deux raisons :

  • Il est utile que les collections qui contiennent des informations qui ne changeront jamais soient covariantes. Pour qu'une collection de T soit covariante, son backing store doit également être covariant. Bien que l'on puisse concevoir un système immuable T qui n'ont pas utilisé un T[] comme magasin de référence (par exemple, en utilisant un arbre ou une liste chaînée), une telle collection aurait peu de chances d'être aussi performante qu'une collection soutenue par un tableau. On pourrait dire qu'une meilleure façon de fournir des collections immuables covariantes aurait été de définir un type "tableau immuable covariant" qui pourrait utiliser un magasin de sauvegarde, mais autoriser simplement la covariance des tableaux était probablement plus facile.

  • Les tableaux seront fréquemment modifiés par du code qui ne sait pas quel type de chose va s'y trouver, mais qui ne mettra pas dans le tableau ce qui n'a pas été lu dans ce même tableau. Le code de tri en est un bon exemple. D'un point de vue conceptuel, il aurait été possible d'inclure dans les types de tableaux des méthodes permettant d'échanger ou de permuter des éléments (de telles méthodes pourraient être également applicables à n'importe quel type de tableau), ou de définir un objet "manipulateur de tableau" qui contiendrait une référence à un tableau et à un ou plusieurs éléments qui ont été lus à partir de celui-ci, et pourrait inclure des méthodes permettant de stocker les éléments lus précédemment dans le tableau d'où ils proviennent. Si les tableaux n'étaient pas covariants, le code utilisateur n'aurait pas pu définir un tel type, mais le runtime aurait pu inclure certaines méthodes spécialisées.

Le fait que les tableaux soient covariants peut être considéré comme un affreux hack, mais dans la plupart des cas, cela facilite la création de code fonctionnel.

2 votes

The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code. -- bon point

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