Quel est le problème avec les génériques de Java? [fermé]

49

J'ai vu à plusieurs reprises sur ce site des publications qui dénoncent l'implémentation des génériques par Java. Maintenant, je peux honnêtement dire que je n’ai eu aucun problème à les utiliser. Cependant, je n'ai pas tenté de créer moi-même une classe générique. Alors, quels sont vos problèmes avec le support générique de Java?

Michael K
la source
1
Voir aussi stackoverflow.com/questions/31693/…
Bill Michell, le
comme Clinton Begin (le "gars d'iBatis") a dit: "Ils ne travaillent pas ..."
moucher

Réponses:

54

L'implémentation générique de Java utilise le type effacement . Cela signifie que vos collections génériques fortement typées sont en réalité de type Objectau moment de l'exécution. Cela a des conséquences sur les performances car cela signifie que les types primitifs doivent être encadrés lorsqu'ils sont ajoutés à une collection générique. Bien entendu, les avantages de la correction du type de temps de compilation l'emportent sur la stupidité générale de l'effacement du type et la focalisation obsessionnelle sur la compatibilité ascendante.

ChaosPandion
la source
4
Il suffit d'ajouter que, du fait de l'effacement des types, l'extraction d'informations génériques au moment de l'exécution n'est possible que si vous utilisez quelque chose comme un TypeLiteral google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/…
Jeremy Heiler
43
Ce qui signifie quenew ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
Note à soi-même - pense à un nom
9
@Job non, ce n'est pas. Et C ++ utilise une approche totalement différente de la métaprogrammation appelée modèles. Il utilise le typage de canard et vous pouvez faire des choses utiles comme template<T> T add(T v1, T v2) { return v1->add(v2); }une méthode vraiment générique de créer une fonction qui comporte adddeux choses, peu importe ce que sont ces choses, elles doivent simplement avoir une méthode nommée add()avec un paramètre.
Trinidad
7
@Job c'est du code généré, en effet. Les modèles sont destinés à remplacer le préprocesseur C et, ce faisant, ils devraient être capables de réaliser des astuces très astucieuses et sont en eux-mêmes un langage complet de Turing. Les génériques Java ne sont que du sucre pour les conteneurs sûrs, mais les génériques C # sont meilleurs, mais restent le modèle C ++ du pauvre.
Trinidad
3
@Job AFAIK, les génériques Java ne génèrent pas de classe pour chaque nouveau type générique, ils ajoutent simplement des typecasts au code qui utilise des méthodes génériques, de sorte que ce n'est pas réellement de la méta-programmation OMI. Les modèles C # génèrent un nouveau code pour chaque type générique, c’est-à-dire que si vous utilisez List <int> et List <double> dans le monde de C #, un code serait généré pour chacun d’eux. En comparaison avec les modèles, les génériques C # exigent toujours de savoir quel type de papier vous pouvez l’alimenter. Vous ne pouvez pas implémenter le simple Addexemple que j’ai donné, il est impossible de savoir à l’avance à quelles classes il peut être appliqué, ce qui est un inconvénient.
Trinidad
26

Notez que les réponses déjà fournies se concentrent sur la combinaison du langage Java, de la machine virtuelle Java et de la bibliothèque de classes Java.

Il n’ya rien de mal avec les génériques de Java en ce qui concerne le langage Java. Comme décrit dans Génériques C # vs Java, les génériques Java sont assez bien au niveau de langue 1 .

Ce qui est sous-optimal, c'est que la JVM ne prend pas directement en charge les génériques, ce qui a plusieurs conséquences majeures:

  • les parties de la bibliothèque de classes liées à la réflexion ne peuvent pas afficher toutes les informations de type disponibles dans Java-the-language
  • une pénalité de performance est impliquée

Je suppose que la distinction que je fais est de nature pédante, car le langage Java est très largement compilé avec JVM comme cible et la bibliothèque de classes Java comme bibliothèque principale.

1 À l'exception peut-être des caractères génériques, qui sont supposés rendre l'inférence de type indécidable dans le cas général. C'est une différence majeure entre les génériques C # et Java, qui n'est pas du tout mentionnée très souvent. Merci Antimony.

Roman Starkov
la source
14
La JVM n'a pas besoin de prendre en charge les génériques. Ce serait la même chose que de dire que C ++ n'a pas de modèles car la machine réelle ix86 ne supporte pas les modèles, ce qui est faux. Le problème est l'approche adoptée par le compilateur pour implémenter les types génériques. Et encore une fois, les modèles C ++ n’imposent aucune pénalité au moment de l’exécution, aucune réflexion n’est faite, je suppose que la seule raison pour laquelle il serait nécessaire de le faire en Java est tout simplement une conception de langage médiocre.
Trinidad
2
@Trinidad mais je n'ai pas dit que Java n'avait pas de génériques, donc je ne vois pas en quoi c'est pareil. Et oui, la JVM n'a pas besoin de les prendre en charge, tout comme C ++ n'a pas besoin d'optimiser le code. Mais ne pas l'optimiser compterait certainement comme "quelque chose qui cloche".
Roman Starkov
3
Ce que j’ai soutenu, c’est que la JVM n’a pas besoin de prendre en charge directement les génériques pour leur permettre d’utiliser la réflexion sur eux. D'autres l'ont fait pour que cela puisse être fait, le problème est que Java ne génère pas de nouvelles classes à partir de génériques, pas de réification.
Trinidad
4
@Trinidad: en C ++, en supposant que le compilateur dispose de suffisamment de temps et de mémoire, les modèles peuvent être utilisés pour faire tout ce que l'on peut faire avec les informations disponibles au moment de la compilation (puisque la compilation des modèles est Turing-complete), mais aucun moyen de créer des modèles en utilisant des informations qui ne sont pas disponibles avant l'exécution du programme. En .net, en revanche, il est possible pour un programme de créer des types basés sur des entrées; il serait assez facile d'écrire un programme dans lequel le nombre de types différents pouvant être créés dépasse le nombre d'électrons dans l'univers.
Supercat
2
@Trinidad: Évidemment, aucune exécution du programme ne pourrait créer tous ces types (ni même une fraction infime d'une fraction infinitésimale), mais le fait est que cela n'est pas nécessaire. Il suffit de créer des types qui sont réellement utilisés. On pourrait avoir un programme qui pourrait accepter n'importe quel nombre arbitraire (par exemple 8675309) et en créer un type unique, avec Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>des membres différents de tout autre type. En C ++, tous les types pouvant être générés à partir de n'importe quelle entrée doivent être générés au moment de la compilation.
Supercat
13

La critique habituelle est le manque de réification. C'est-à-dire que les objets au moment de l'exécution ne contiennent pas d'informations sur leurs arguments génériques (bien que les informations soient toujours présentes sur les champs, la méthode, les constructeurs et la classe étendue et les interfaces). Cela signifie que vous pouvez lancer, par exemple, ArrayList<String>pour List<File>. Le compilateur vous donnera des avertissements, mais il vous avertira également si vous ArrayList<String>assignez un attribut à une Objectréférence et que vous le transmettez ensuite à List<String>. Les avantages sont que, avec les génériques, vous ne devriez probablement pas faire le casting, la performance est meilleure sans les données inutiles et, bien sûr, la compatibilité ascendante.

Certaines personnes se plaignent de l'impossibilité de surcharger sur la base d'arguments génériques ( void fn(Set<String>)et void fn(Set<File>)). Vous devez utiliser de meilleurs noms de méthodes. Notez que cette surcharge ne nécessiterait pas de réification, car la surcharge est un problème statique, au moment de la compilation.

Les types primitifs ne fonctionnent pas avec les génériques.

Les caractères génériques et les limites sont assez compliqués. Ils sont très utiles. Si Java préférait l'immuabilité et les interfaces tell-not-ask, les déclarations génériques du côté de la déclaration plutôt que du côté de l'utilisation seraient plus appropriées.

Tom Hawtin - tackline
la source
"Notez que cette surcharge ne nécessiterait pas de réification, car la surcharge est un problème statique, au moment de la compilation." N'est-ce pas? Comment cela fonctionnerait-il sans réification? La machine virtuelle Java ne devrait-elle pas savoir au moment de l'exécution quel type d'ensemble vous avez afin de savoir quelle méthode appeler?
MatrixFrog
2
@MatrixFrog Non. Comme je l'ai dit, la surcharge est un problème de compilation. Le compilateur utilise le type statique de l'expression pour sélectionner une surcharge particulière, qui est ensuite la méthode écrite dans le fichier de classe. Si vous en avez, Object cs = new char[] { 'H', 'i' }; System.out.println(cs);vous obtenez un non-sens imprimé. Modifier le type de csà char[]et vous obtiendrez Hi.
Tom Hawtin - tackline le
10

Les génériques Java craignent parce que vous ne pouvez pas faire ce qui suit:

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

Vous n'avez pas besoin de couper toutes les classes du code ci-dessus pour que cela fonctionne comme il se doit si les génériques étaient en fait des paramètres de classe génériques.

davidk01
la source
Vous n'avez pas besoin de boucher chaque classe. Vous devez simplement ajouter une classe de fabrique appropriée et l'injecter dans AsyncAdapter. (Et fondamentalement, il ne s'agit pas de génériques, mais du fait que les constructeurs ne sont pas hérités en Java).
Peter Taylor
13
@PeterTaylor: Une classe d'usine appropriée met tout simplement le passe-partout dans un autre bordel caché et ne résout toujours pas le problème. Maintenant, chaque fois que je veux instancier une nouvelle instance de, Parserj'ai besoin de rendre le code d'usine encore plus compliqué. Tandis que si "générique" signifiait vraiment générique, il n'y aurait aucun besoin d'usines ni de tous les autres non-sens qui s'appellent des modèles de conception. C'est l'une des nombreuses raisons pour lesquelles je préfère travailler dans des langages dynamiques.
davidk01
2
Parfois similaire en C ++, où vous ne pouvez pas transmettre l'adresse d'un constructeur, lorsque vous utilisez templates + virtuals - vous pouvez simplement utiliser un foncteur d'usine à la place.
Mark K Cowan
Java craint parce que vous ne pouvez pas écrire "proptected" avant le nom d'une méthode? Pauvre toi. S'en tenir à des langues dynamiques. Et soyez particulièrement prudent avec Haskell.
fdreger
5
  • les avertissements du compilateur pour les paramètres génériques manquants qui ne servent à rien rendent le langage inutilement bavard, par exemple public String getName(Class<?> klazz){ return klazz.getName();}

  • Les génériques ne jouent pas bien avec les tableaux

  • Les informations de type perdu font de la réflexion un gâchis de fonderie et de ruban adhésif.

Steve B.
la source
Je me fâche quand je reçois un avertissement pour utiliser HashMapau lieu de HashMap<String, String>.
Michael K
1
@Michael mais cela devrait en fait être utilisé avec des génériques ...
Alternative
Le problème de la matrice va à la réifiabilité. Voir le livre Java Generics (Alligator) pour une explication.
ncmathsadist
5

Je pense que d'autres réponses l'ont dit dans une certaine mesure, mais pas très clairement. L'un des problèmes avec les génériques est la perte des types génériques au cours du processus de réflexion. Donc par exemple:

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

Malheureusement, les types retournés ne peuvent pas vous dire que les types génériques d'arr sont String. C'est une différence subtile, mais c'est important. Puisque arr est créé à l'exécution, les types génériques sont effacés à l'exécution, de sorte que vous ne pouvez pas vous en assurer. Comme certains l'ont dit, cela ArrayList<Integer>ressemble au ArrayList<String>point de vue de la réflexion.

Cela n’a peut-être pas d'importance pour l'utilisateur de Generics, mais supposons que nous voulions créer un cadre sophistiqué qui utilise la réflexion pour déterminer de façon compliquée la manière dont l'utilisateur a déclaré les types génériques concrets d'une instance.

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

Disons que nous voulions qu'une usine générique crée une instance de MySpecialObjectcar c'est le type générique concret que nous avons déclaré pour cette instance. La classe Well Factory ne peut pas s’interroger sur le type concret déclaré pour cette instance car Java les a effacés.

Vous pouvez le faire avec les génériques .Net, car au moment de l'exécution, l'objet sait que ce sont des types génériques, car le compilateur l'a intégré au binaire. Avec des effacements, Java ne peut pas faire cela.

chubbsondubs
la source
1

Je pourrais dire quelques bonnes choses sur les génériques, mais ce n'était pas la question. Je pourrais me plaindre qu'ils ne sont pas disponibles au moment de l'exécution et ne fonctionnent pas avec les tableaux, mais cela a été mentionné.

Un gros désagrément psychologique: je me trouve parfois dans des situations où les génériques ne peuvent fonctionner. (Les tableaux sont l'exemple le plus simple.) Et je ne peux pas savoir si les génériques ne peuvent pas faire le travail ou si je suis simplement stupide. Je déteste ça. Quelque chose comme les génériques devrait toujours fonctionner. Chaque fois que je ne peux pas faire ce que je veux en utilisant Java-the-language, je sais que le problème est moi, et je sais que si je continue à pousser, je finirai par y arriver. Avec les génériques, si je deviens trop persistant, je peux perdre beaucoup de temps.

Mais le vrai problème est que les génériques ajoutent trop de complexité pour un gain insuffisant. Dans les cas les plus simples, cela peut m'empêcher d'ajouter une pomme à une liste contenant des voitures. Bien. Mais sans génériques, cette erreur déclencherait une exception ClassCastException très rapide au moment de l'exécution, avec une perte de temps minime. Si j'ajoute une voiture avec un siège enfant avec un enfant, ai-je besoin d'un avertissement à la compilation indiquant que la liste ne concerne que les voitures avec des sièges enfants contenant des bébés chimpanzés? Une liste d'instances d'objets simples commence à apparaître comme une bonne idée.

Le code générique peut contenir beaucoup de mots et de caractères, prendre beaucoup d’espace supplémentaire et prendre beaucoup plus de temps à lire. Je peux passer beaucoup de temps à faire fonctionner correctement tout ce code supplémentaire. Quand cela se produit, je me sens incroyablement malin. J'ai également perdu plusieurs heures ou plus de mon temps et je me demande si quelqu'un d'autre sera capable de comprendre le code. J'aimerais pouvoir le confier à des personnes moins intelligentes que moi ou qui ont moins de temps à perdre.

Par contre (je suis un peu fatigué et je ressens le besoin de trouver un peu d’équilibre), c’est bien quand on utilise des collections et des cartes simples d’avoir des ajouts, des mises et des vérifications lorsqu’il est écrit, et cela n’ajoute généralement pas beaucoup. complexité du code ( si quelqu'un d' autre écrit la collection ou la carte) . Et Java est meilleur que C #. Il semble qu'aucune des collections C # que je souhaite utiliser ne gère jamais les génériques. (J'avoue que j'ai des goûts étranges dans les collections.)

RalphChapin
la source
Belle coup de gueule. Cependant, il existe de nombreuses bibliothèques / structures qui pourraient exister sans génériques, par exemple Guice, Gson, Hibernate. Et les génériques ne sont pas si difficiles une fois que vous vous y êtes habitués. Taper et lire les arguments de type est un véritable PITA; ici val aide si vous pouvez utiliser.
Maaartinus
1
@ Maaartinus: Cela fait un moment. Aussi OP a demandé des "problèmes". Je trouve les médicaments génériques extrêmement utiles et les aime beaucoup - beaucoup plus maintenant qu’alors. Cependant, si vous écrivez votre propre collection, ils sont très difficiles à apprendre. Et leur utilité s'estompe lorsque le type de votre collection est déterminé au moment de l'exécution - lorsque la collection dispose d'une méthode vous indiquant la classe de ses entrées. À ce stade, les génériques ne fonctionnent pas et votre IDE produira des centaines d'avertissements sans signification. 99,99% de la programmation Java n'implique pas cela. Mais je juste eu beaucoup de mal avec cela quand je l' ai écrit ci - dessus.
RalphChapin
Étant à la fois développeur Java et C #, je me demande pourquoi préféreriez-vous les collections Java?
Ivaylo Slavov
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.Cela dépend vraiment du temps d’exécution qui s’écoule entre le démarrage du programme et la frappe de la ligne de code en question. Si un utilisateur signale l'erreur et qu'il vous faut plusieurs minutes pour le reproduire (ou, dans le pire des cas, plusieurs heures ou même plusieurs jours), la vérification au moment de la compilation commence de mieux en mieux ...
Mason Wheeler