Quelles sont les différences entre les génériques en C # et Java… et les modèles en C ++? [fermé]

203

J'utilise principalement Java et les génériques sont relativement nouveaux. Je continue de lire que Java a pris la mauvaise décision ou que .NET a de meilleures implémentations, etc., etc.

Alors, quelles sont les principales différences entre C ++, C #, Java dans les génériques? Avantages / inconvénients de chacun?

pek
la source

Réponses:

364

Je vais ajouter ma voix au bruit et essayer de clarifier les choses:

Les génériques C # vous permettent de déclarer quelque chose comme ça.

List<Person> foo = new List<Person>();

puis le compilateur vous empêchera de mettre des choses qui ne sont pas Persondans la liste.
Dans les coulisses, le compilateur C # met juste List<Person>dans le fichier dll .NET, mais au moment de l'exécution, le compilateur JIT va et construit un nouvel ensemble de code, comme si vous aviez écrit une classe de liste spéciale juste pour contenir des personnes - quelque chose comme ListOfPerson.

L'avantage de cela est que cela le rend très rapide. Il n'y a pas de casting ou tout autre truc, et parce que la dll contient les informations dont il s'agit d'une liste Person, un autre code qui l'examine plus tard en utilisant la réflexion peut dire qu'il contient des Personobjets (vous obtenez donc intellisense, etc.).

L'inconvénient est que l'ancien code C # 1.0 et 1.1 (avant d'ajouter des génériques) ne comprend pas ces nouveaux List<something>, vous devez donc reconvertir manuellement les choses en vieux Listpour les interagir avec elles. Ce n'est pas un gros problème, car le code binaire C # 2.0 n'est pas rétrocompatible. La seule fois où cela se produira, c'est si vous mettez à niveau un ancien code C # 1.0 / 1.1 vers C # 2.0

Les génériques Java vous permettent de déclarer quelque chose comme ça.

ArrayList<Person> foo = new ArrayList<Person>();

En surface, il a la même apparence, et il l'est en quelque sorte. Le compilateur vous empêchera également de mettre des choses qui ne sont pas Persondans la liste.

La différence est ce qui se passe dans les coulisses. Contrairement à C #, Java ne va pas créer un spécial ListOfPerson- il utilise simplement l'ancien simple ArrayListqui a toujours été en Java. Lorsque vous sortez les choses du tableau, la Person p = (Person)foo.get(1);danse de casting habituelle doit encore être faite. Le compilateur vous permet d'économiser les pressions sur les touches, mais la vitesse de frappe / casting est toujours engagée comme elle l'a toujours été.
Quand les gens mentionnent "Type Erasure", c'est de cela qu'ils parlent. Le compilateur insère les transtypages pour vous, puis «efface» le fait qu'il est censé être une liste de Personnon seulementObject

L'avantage de cette approche est que l'ancien code qui ne comprend pas les génériques n'a pas à s'en soucier. Il s'agit toujours du même vieux ArrayListqu'il l'a toujours fait. Ceci est plus important dans le monde java car ils voulaient prendre en charge la compilation de code à l'aide de Java 5 avec des génériques, et le faire fonctionner sur les anciennes JVM 1.4 ou précédentes, avec lesquelles Microsoft a délibérément décidé de ne pas s'embêter.

L'inconvénient est le coup de vitesse que j'ai mentionné précédemment, et aussi parce qu'il n'y a pas de ListOfPersonpseudo-classe ou quelque chose comme ça dans les fichiers .class, du code qui le regarde plus tard (avec réflexion, ou si vous le retirez d'une autre collection où il a été converti Objectou ainsi de suite) ne peut en aucune façon dire qu'il est censé être une liste contenant uniquement Personet pas simplement une autre liste de tableaux.

Les modèles C ++ vous permettent de déclarer quelque chose comme ça

std::list<Person>* foo = new std::list<Person>();

Cela ressemble à des génériques C # et Java, et il fera ce que vous pensez qu'il devrait faire, mais en coulisses, des choses différentes se produisent.

Il a le plus en commun avec les génériques C # dans la mesure où il est spécial pseudo-classesplutôt que de simplement jeter les informations de type comme Java, mais c'est une toute autre marmite de poisson.

C # et Java produisent tous deux une sortie conçue pour les machines virtuelles. Si vous écrivez du code qui contient une Personclasse, dans les deux cas, certaines informations sur une Personclasse iront dans le fichier .dll ou .class, et la JVM / CLR s'en chargera.

C ++ produit du code binaire x86 brut. Tout n'est pas un objet et aucune machine virtuelle sous-jacente n'a besoin de connaître une Personclasse. Il n'y a pas de boxe ou de déballage, et les fonctions n'ont pas à appartenir à des classes, ni à rien d'autre.

Pour cette raison, le compilateur C ++ n'impose aucune restriction sur ce que vous pouvez faire avec les modèles - essentiellement tout code que vous pourriez écrire manuellement, vous pouvez obtenir des modèles à écrire pour vous.
L'exemple le plus évident est d'ajouter des choses:

En C # et Java, le système générique doit savoir quelles méthodes sont disponibles pour une classe, et il doit le transmettre à la machine virtuelle. La seule façon de le dire est de coder en dur la classe réelle ou d'utiliser des interfaces. Par exemple:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

Ce code ne sera pas compilé en C # ou Java, car il ne sait pas que le type Tfournit réellement une méthode appelée Name (). Vous devez le dire - en C # comme ceci:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

Et puis vous devez vous assurer que les choses que vous transmettez à addNames implémentent l'interface IHasName et ainsi de suite. La syntaxe java est différente ( <T extends IHasName>), mais elle souffre des mêmes problèmes.

Le cas «classique» de ce problème essaie d'écrire une fonction qui fait cela

string addNames<T>( T first, T second ) { return first + second; }

Vous ne pouvez pas réellement écrire ce code car il n'y a aucun moyen de déclarer une interface avec la +méthode qu'il contient. Vous échouez.

C ++ ne souffre d'aucun de ces problèmes. Le compilateur ne se soucie pas de transmettre des types à n'importe quelle machine virtuelle - si vos deux objets ont une fonction .Name (), il se compilera. S'ils ne le font pas, ce ne sera pas le cas. Facile.

Alors voilà :-)

Orion Edwards
la source
8
Le pseudoclasess généré pour les types de références en C # partage la même implémentation, vous n'aurez donc pas exactement ListOfPeople. Consultez blogs.msdn.com/ericlippert/archive/2009/07/30/…
Piotr Czapla
4
Non, vous ne pouvez pas compiler du code Java 5 à l'aide de génériques et le faire exécuter sur d'anciennes machines virtuelles 1.4 (au moins le Sun JDK ne l'implémente pas. Certains outils tiers le font.) Ce que vous pouvez faire est d'utiliser des JAR 1.4 précédemment compilés à partir de Code 1.5 / 1.6.
finnw
4
Je m'oppose à la déclaration selon laquelle vous ne pouvez pas écrire int addNames<T>( T first, T second ) { return first + second; }en C #. Le type générique peut être limité à une classe au lieu d'une interface, et il existe un moyen de déclarer une classe avec l' +opérateur qu'elle contient.
Mashmagar
4
@AlexanderMalakhov c'est exprès non idiomatique. Le but n'était pas d'éduquer sur les idiomes du C ++, mais d'illustrer comment le même morceau de code est géré différemment par chaque langage. Cet objectif aurait été plus difficile à atteindre si le code était différent
Orion Edwards
3
@phresnel Je suis d'accord en principe, mais si j'avais écrit cet extrait en C ++ idiomatique, il serait beaucoup moins compréhensible pour les développeurs C # / Java, et donc (je crois) aurait fait un pire travail pour expliquer la différence. Soyons d'accord pour être en désaccord sur celui-ci :-)
Orion Edwards
61

C ++ utilise rarement la terminologie «générique». Au lieu de cela, le mot «modèles» est utilisé et est plus précis. Les modèles décrivent une technique pour réaliser une conception générique.

Les modèles C ++ sont très différents de ce que C # et Java implémentent pour deux raisons principales. La première raison est que les modèles C ++ autorisent non seulement les arguments de type à la compilation mais aussi les arguments de valeur const à la compilation: les modèles peuvent être donnés sous forme d'entiers ou même de signatures de fonction. Cela signifie que vous pouvez faire des choses assez funky au moment de la compilation, par exemple des calculs:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Ce code utilise également l'autre caractéristique distinctive des modèles C ++, à savoir la spécialisation des modèles. Le code définit un modèle de classe, productqui a un argument de valeur. Il définit également une spécialisation pour ce modèle qui est utilisée chaque fois que l'argument est évalué à 1. Cela me permet de définir une récursivité sur les définitions de modèle. Je crois que cela a été découvert pour la première fois par Andrei Alexandrescu .

La spécialisation des modèles est importante pour C ++ car elle permet des différences structurelles dans les structures de données. Les modèles dans leur ensemble sont un moyen d'unifier une interface entre plusieurs types. Cependant, bien que cela soit souhaitable, tous les types ne peuvent pas être traités de manière égale à l'intérieur de l'implémentation. Les modèles C ++ en tiennent compte. C'est à peu près la même différence que la POO fait entre l'interface et l'implémentation avec le remplacement des méthodes virtuelles.

Les modèles C ++ sont essentiels pour son paradigme de programmation algorithmique. Par exemple, presque tous les algorithmes pour les conteneurs sont définis comme des fonctions qui acceptent le type de conteneur comme type de modèle et les traitent uniformément. En fait, ce n'est pas tout à fait vrai: C ++ ne fonctionne pas sur les conteneurs, mais plutôt sur les plages définies par deux itérateurs, pointant vers le début et derrière la fin du conteneur. Ainsi, l'ensemble du contenu est circonscrit par les itérateurs: begin <= elements <end.

L'utilisation d'itérateurs au lieu de conteneurs est utile car elle permet d'opérer sur des parties d'un conteneur plutôt que sur l'ensemble.

Une autre caractéristique distinctive de C ++ est la possibilité de spécialisation partielle pour les modèles de classe. Ceci est quelque peu lié à la correspondance de motifs sur les arguments dans Haskell et dans d'autres langages fonctionnels. Par exemple, considérons une classe qui stocke des éléments:

template <typename T>
class Store {  }; // (1)

Cela fonctionne pour tout type d'élément. Mais disons que nous pouvons stocker des pointeurs plus efficacement que les autres types en appliquant une astuce spéciale. Nous pouvons le faire en nous spécialisant partiellement pour tous les types de pointeurs:

template <typename T>
class Store<T*> {  }; // (2)

Maintenant, chaque fois que nous instancions un modèle de conteneur pour un type, la définition appropriée est utilisée:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
Konrad Rudolph
la source
J'ai parfois souhaité que la fonction générique dans .net puisse permettre que des choses autres que les types soient utilisées comme clés. Si les tableaux de type valeur faisaient partie du Framework (je suis surpris qu'ils ne le soient pas, en quelque sorte, étant donné la nécessité d'interagir avec les anciennes API qui incorporent des tableaux de taille fixe dans les structures), il serait utile de déclarer un classe qui contenait quelques éléments individuels, puis un tableau de type valeur dont la taille était un paramètre générique. En l'état, le plus proche peut être d'avoir un objet de classe qui contient les éléments individuels et contient également une référence à un objet distinct contenant le tableau.
supercat
@supercat Si vous interagissez avec une API héritée, l'idée est d'utiliser le marshalling (qui peut être annoté via des attributs). Le CLR n'a pas de tableaux de taille fixe de toute façon, donc avoir des arguments de modèle non-type ne serait d'aucune utilité ici.
Konrad Rudolph
Je suppose que ce que je trouve déroutant, c'est qu'il semblerait que les tableaux de type valeur de taille fixe n'aient pas dû être difficiles, et cela aurait permis à de nombreux types de données d'être rassemblés par référence plutôt que par valeur. Bien que marshal-by-value puisse être utile dans les cas qui ne peuvent vraiment pas être gérés d'une autre manière, je considérerais marshal-by-ref comme supérieur dans presque tous les cas où il est utilisable, permettant ainsi à ces cas d'inclure des structures avec des valeurs fixes -les tableaux de taille auraient semblé une fonctionnalité utile.
supercat
BTW, une autre situation où des paramètres génériques non-types seraient utiles serait avec des types de données qui représentent des quantités dimensionnées. On pourrait inclure des informations dimensionnelles dans des instances qui représentent des quantités, mais avoir de telles informations dans un type permettrait de spécifier qu'une collection est censée contenir des objets représentant une unité dimensionnelle particulière.
supercat
35

Anders Hejlsberg lui-même a décrit les différences ici " Génériques en C #, Java et C ++ ".

jfs
la source
j'aime vraiment cette interview. cela montre clairement aux non-c # comme moi ce qui se passe avec les génériques c #.
Johannes Schaub - litb
18

Il y a déjà beaucoup de bonnes réponses sur ce que sont les différences, alors permettez - moi de donner une perspective légèrement différente et ajouter le pourquoi .

Comme cela a déjà été expliqué, la principale différence est l' effacement des types , c'est-à-dire le fait que le compilateur Java efface les types génériques et qu'ils ne se retrouvent pas dans le bytecode généré. Cependant, la question est: pourquoi quelqu'un ferait-il cela? Ça n'a pas de sens! Ou alors?

Eh bien, quelle est l'alternative? Si vous n'implémentez pas de génériques dans la langue, où les implémentez-vous? Et la réponse est: dans la machine virtuelle. Ce qui rompt la compatibilité descendante.

L'effacement de type, d'autre part, vous permet de mélanger des clients génériques avec des bibliothèques non génériques. En d'autres termes: le code qui a été compilé sur Java 5 peut toujours être déployé sur Java 1.4.

Microsoft, cependant, a décidé de rompre la compatibilité descendante pour les génériques. C'est pourquoi les génériques .NET sont "meilleurs" que les génériques Java.

Bien sûr, Sun n'est ni idiot ni lâche. La raison pour laquelle ils "se sont dégonflés", c'est que Java était beaucoup plus ancien et plus répandu que .NET lorsqu'ils ont introduit les génériques. (Ils ont été introduits à peu près en même temps dans les deux mondes.) Rompre la compatibilité descendante aurait été une énorme douleur.

En d'autres termes: en Java, les génériques font partie du langage (ce qui signifie qu'ils s'appliquent uniquement à Java, pas aux autres langages), en .NET, ils font partie de la machine virtuelle (ce qui signifie qu'ils s'appliquent à tous les langages, pas juste C # et Visual Basic.NET).

Comparez cela avec les fonctionnalités .NET comme LINQ, les expressions lambda, l'inférence de type de variable locale, les types anonymes et les arborescences d'expression: ce sont toutes des fonctionnalités de langage . C'est pourquoi il existe de subtiles différences entre VB.NET et C #: si ces fonctionnalités faisaient partie de la machine virtuelle, elles seraient les mêmes dans tous les langages. Mais le CLR n'a pas changé: il est toujours le même dans .NET 3.5 SP1 que dans .NET 2.0. Vous pouvez compiler un programme C # qui utilise LINQ avec le compilateur .NET 3.5 et toujours l'exécuter sur .NET 2.0, à condition que vous n'utilisiez aucune bibliothèque .NET 3.5. Ce serait pas travailler avec des génériques et .NET 1.1, mais il ne fonctionne avec Java et Java 1.4.

Jörg W Mittag
la source
3
LINQ est principalement une fonctionnalité de bibliothèque (bien que C # et VB aient également ajouté du sucre de syntaxe à côté). Tout langage qui cible le 2.0 CLR peut tirer pleinement parti de LINQ simplement en chargeant l'assembly System.Core.
Richard Berg
Ouais, désolé, j'aurais dû être plus clair. LINQ. Je faisais référence à la syntaxe de la requête, pas aux opérateurs de requête standard monadiques, aux méthodes d'extension LINQ ou à l'interface IQueryable. Évidemment, vous pouvez utiliser ceux de n'importe quel langage .NET.
Jörg W Mittag
1
Je pense à une autre option pour Java. Même Oracle ne veut pas rompre la compatibilité ascendante, il peut toujours faire un truc du compilateur pour éviter l'effacement des informations de type. Par exemple, ArrayList<T>peut être émis comme un nouveau type nommé en interne avec un Class<T>champ statique (masqué) . Tant que la nouvelle version de la bibliothèque générique a été déployée avec le code 1.5+ octets, elle pourra s'exécuter sur des JVM 1.4.
Earth Engine
14

Suivi de ma publication précédente.

Les modèles sont l'une des principales raisons pour lesquelles C ++ échoue si abyssalement chez intellisense, quel que soit l'IDE utilisé. En raison de la spécialisation des modèles, l'EDI ne peut jamais être vraiment sûr si un membre donné existe ou non. Considérer:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

Maintenant, le curseur est à la position indiquée et il est sacrément difficile pour l'IDE de dire à ce point si, et quoi, les membres aont. Pour d'autres langages, l'analyse serait simple mais pour C ++, un peu d'évaluation est nécessaire au préalable.

Ça s'empire. Et si my_int_typeétaient également définis dans un modèle de classe? Maintenant, son type dépendrait d'un autre argument de type. Et ici, même les compilateurs échouent.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

Après un peu de réflexion, un programmeur conclurait que ce code est le même que ci-dessus: se Y<int>::my_typerésout à int, bdevrait donc être du même type que a, non?

Faux. Au point où le compilateur essaie de résoudre cette instruction, il ne le sait Y<int>::my_typepas encore! Par conséquent, il ne sait pas qu'il s'agit d'un type. Ce pourrait être autre chose, par exemple une fonction membre ou un champ. Cela peut donner lieu à des ambiguïtés (mais pas dans le cas présent), donc le compilateur échoue. Nous devons lui dire explicitement que nous nous référons à un nom de type:

X<typename Y<int>::my_type> b;

Maintenant, le code se compile. Pour voir comment les ambiguïtés découlent de cette situation, considérez le code suivant:

Y<int>::my_type(123);

Cette instruction de code est parfaitement valide et indique à C ++ d'exécuter l'appel de fonction à Y<int>::my_type. Cependant, s'il my_typene s'agit pas d'une fonction mais plutôt d'un type, cette instruction serait toujours valide et effectuerait une conversion spéciale (la conversion de style fonction) qui est souvent une invocation de constructeur. Le compilateur ne peut pas dire ce que nous voulons dire, nous devons donc lever l'ambiguïté ici.

Konrad Rudolph
la source
2
Je suis plutôt d'accord. Il y a cependant de l'espoir. Le système de saisie semi-automatique et le compilateur C ++ doivent interagir très étroitement. Je suis sûr que Visual Studio n'aura jamais une telle fonctionnalité, mais des choses pourraient se produire dans Eclipse / CDT ou un autre IDE basé sur GCC. ESPÉRER ! :)
Benoît
6

Java et C # ont tous deux introduit des génériques après leur première sortie en langage. Cependant, il existe des différences dans la façon dont les bibliothèques de base ont changé lors de l'introduction des génériques. Les génériques de C # ne sont pas seulement la magie du compilateur et il n'était donc pas possible de générer des classes de bibliothèque existantes sans rompre la compatibilité descendante.

Par exemple, en Java, le Framework de collections existant a été complètement générique . Java n'a pas à la fois une version générique et une version non générique héritée des classes de collections. À certains égards, cela est beaucoup plus propre - si vous devez utiliser une collection en C #, il y a vraiment très peu de raisons d'aller avec la version non générique, mais ces classes héritées restent en place, encombrant le paysage.

Une autre différence notable est les classes Enum en Java et C #. L'énumération de Java a cette définition quelque peu tortueuse:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(voir l' explication très claire d'Angelika Langer pour savoir exactement pourquoi il en est ainsi. Essentiellement, cela signifie que Java peut donner un accès sécurisé de type à partir d'une chaîne à sa valeur Enum:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

Comparez cela à la version de C #:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

Comme Enum existait déjà en C # avant l'introduction des génériques dans le langage, la définition ne pouvait pas changer sans casser le code existant. Ainsi, comme les collections, il reste dans les bibliothèques principales dans cet état hérité.

serg10
la source
Même les génériques de C # ne sont pas seulement la magie du compilateur, le compilateur peut faire plus de magie pour générer la bibliothèque existante. Il n'y a aucune raison pour laquelle ils ont besoin de renommer ArrayListpour List<T>et le mettre dans un nouvel espace de noms. Le fait est que s'il y avait une classe apparaissant dans le code source car ArrayList<T>elle deviendrait un nom de classe généré par le compilateur différent dans le code IL, il n'y aurait donc aucun conflit de nom.
Earth Engine
4

11 mois de retard, mais je pense que cette question est prête pour certains trucs Java Wildcard.

Il s'agit d'une fonctionnalité syntaxique de Java. Supposons que vous ayez une méthode:

public <T> void Foo(Collection<T> thing)

Et supposons que vous n'ayez pas besoin de faire référence au type T dans le corps de la méthode. Vous déclarez un nom T et vous ne l'utilisez qu'une seule fois, alors pourquoi devriez-vous penser à un nom pour lui? Au lieu de cela, vous pouvez écrire:

public void Foo(Collection<?> thing)

Le point d'interrogation demande au compilateur de prétendre que vous avez déclaré un paramètre de type nommé normal qui ne doit apparaître qu'une fois à cet endroit.

Il n'y a rien que vous puissiez faire avec des caractères génériques que vous ne pouvez pas faire avec un paramètre de type nommé (c'est ainsi que ces choses sont toujours faites en C ++ et C #).

Daniel Earwicker
la source
2
Encore 11 mois de retard ... Il y a des choses que vous pouvez faire avec des caractères génériques Java que vous ne pouvez pas avec des paramètres de type nommé. Vous pouvez le faire en Java: class Foo<T extends List<?>>et utiliser, Foo<StringList>mais en C #, vous devez ajouter ce paramètre de type supplémentaire: class Foo<T, T2> where T : IList<T2>et utiliser le maladroit Foo<StringList, String>.
R. Martinho Fernandes
1

La plus grande plainte est l'effacement du type. En cela, les génériques ne sont pas appliqués lors de l'exécution. Voici un lien vers quelques documents Sun sur le sujet .

Les génériques sont implémentés par effacement de type: les informations de type générique ne sont présentes qu'au moment de la compilation, après quoi elles sont effacées par le compilateur.

Matt Cummings
la source
1

Les modèles C ++ sont en réalité beaucoup plus puissants que leurs homologues C # et Java car ils sont évalués au moment de la compilation et prennent en charge la spécialisation. Cela permet la méta-programmation de modèles et rend le compilateur C ++ équivalent à une machine Turing (c'est-à-dire que pendant le processus de compilation, vous pouvez calculer tout ce qui est calculable avec une machine Turing).

Sur Freund
la source
1

En Java, les génériques sont uniquement au niveau du compilateur, vous obtenez donc:

a = new ArrayList<String>()
a.getClass() => ArrayList

Notez que le type de 'a' est une liste de tableaux, pas une liste de chaînes. Ainsi, le type d'une liste de bananes serait égal à () une liste de singes.

Pour ainsi dire.

izb
la source
1

On dirait, entre autres propositions très intéressantes, qu'il y en a une sur le raffinement des génériques et la rupture de la rétrocompatibilité:

Actuellement, les génériques sont implémentés à l'aide de l'effacement, ce qui signifie que les informations de type générique ne sont pas disponibles au moment de l'exécution, ce qui rend une sorte de code difficile à écrire. Les génériques ont été implémentés de cette façon pour prendre en charge la rétrocompatibilité avec les anciens codes non génériques. Les génériques réifiés rendraient les informations de type générique disponibles au moment de l'exécution, ce qui romprait le code non générique hérité. Cependant, Neal Gafter a proposé de rendre les types réifiables uniquement s'ils sont spécifiés, afin de ne pas rompre la compatibilité descendante.

à l'article d'Alex Miller sur les propositions Java 7

pek
la source
0

NB: Je n'ai pas assez de points pour commenter, alors n'hésitez pas à déplacer ceci en tant que commentaire pour une réponse appropriée.

Contrairement à la croyance populaire, dont je ne comprends jamais d'où il vient, .net a mis en œuvre de vrais génériques sans rompre la compatibilité descendante, et ils ont consacré des efforts explicites à cela. Vous n'avez pas besoin de changer votre code .net 1.0 non générique en génériques juste pour être utilisé dans .net 2.0. Les listes génériques et non génériques sont toujours disponibles dans .Net Framework 2.0 jusqu'à 4.0, exactement pour rien d'autre que pour des raisons de compatibilité descendante. Par conséquent, les anciens codes qui utilisaient toujours ArrayList non générique continueront de fonctionner et utiliseront la même classe ArrayList qu'avant. La compatibilité du code en amont est toujours maintenue depuis la version 1.0 jusqu'à maintenant ... Donc, même dans .net 4.0, vous avez toujours la possibilité d'utiliser n'importe quelle classe non générique de 1.0 BCL si vous choisissez de le faire.

Je ne pense donc pas que java doive rompre la compatibilité descendante pour prendre en charge les vrais génériques.

Sheepy
la source
Ce n'est pas le genre de compatibilité ascendante dont les gens parlent. L'idée est la compatibilité descendante pour le runtime : le code écrit à l'aide de génériques dans .NET 2.0 ne peut pas être exécuté sur des versions plus anciennes du .NET Framework / CLR. De même, si Java introduisait de "vrais" génériques, le code Java plus récent ne pourrait pas s'exécuter sur des machines virtuelles Java plus anciennes (car cela nécessite d'interrompre les modifications du bytecode).
tzaman
C'est .net, pas des génériques. Nécessite toujours une recompilation pour cibler une version CLR spécifique. Il y a la compatibilité du bytecode, il y a la compatibilité du code. Et aussi, je répondais spécifiquement concernant la nécessité de convertir l'ancien code qui utilisait l'ancienne liste pour utiliser la nouvelle liste générique, ce qui n'est pas vrai du tout.
Sheepy
1
Je pense que les gens parlent de compatibilité ascendante . C'est-à-dire le code .net 2.0 à exécuter sur .net 1.1, qui se cassera car le runtime 1.1 ne sait rien de la "pseudo-classe" 2.0. Ne faudrait-il pas alors que "java n'implémente pas de vrai générique parce qu'ils veulent maintenir la compatibilité ascendante"? (plutôt que vers l'arrière)
Sheepy
Les problèmes de compatibilité sont subtils. Je ne pense pas que le problème était que l'ajout de "vrais" génériques à Java affecterait tous les programmes qui utilisent des versions plus anciennes de Java, mais plutôt que le code qui utilisait des génériques "nouveaux améliorés" aurait du mal à échanger ces objets avec du code plus ancien qui ne savait rien des nouveaux types. Supposons, par exemple, qu'un programme ait un ArrayList<Foo>fichier qu'il souhaite passer à une méthode plus ancienne qui est censée remplir un ArrayListavec des instances de Foo. Si un ArrayList<foo>n'est pas un ArrayList, comment faire pour que ça marche?
supercat