J'ai parfois entendu dire qu'avec les génériques, Java ne l'a pas bien compris. (Référence la plus proche, ici )
Pardonnez mon inexpérience, mais qu'est-ce qui aurait pu les améliorer?
Mal:
List<byte>
est vraiment soutenu par un byte[]
par exemple, et aucune boxe n'est requise)Bien:
Le plus gros problème est que Java sont une chose uniquement à la compilation, et vous pouvez la subvertir au moment de l'exécution. C # est loué parce qu'il fait plus de vérification à l'exécution. Il y en a vraiment bonne discussion dans cet article , et il renvoie à d'autres discussions.
Le principal problème est que Java n'a pas réellement de génériques au moment de l'exécution. C'est une fonction de compilation.
Lorsque vous créez une classe générique dans Java, ils utilisent une méthode appelée "Type Erasure" pour réellement supprimer tous les types génériques de la classe et les remplacer essentiellement par Object. La version kilométrique des génériques est que le compilateur insère simplement les transtypages dans le type générique spécifié chaque fois qu'il apparaît dans le corps de la méthode.
Cela a beaucoup d'inconvénients. L'un des plus importants, à mon humble avis, est que vous ne pouvez pas utiliser la réflexion pour inspecter un type générique. Les types ne sont pas réellement génériques dans le code d'octet et ne peuvent donc pas être inspectés en tant que génériques.
Grand aperçu des différences ici: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) conduit à un comportement très étrange. Le meilleur exemple auquel je peux penser est. Présumer:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
puis déclarez deux variables:
MyClass<T> m1 = ...
MyClass m2 = ...
Appelez maintenant getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
Le second a son argument de type générique supprimé par le compilateur car il s'agit d'un type brut (ce qui signifie que le type paramétré n'est pas fourni) même s'il a rien à voir avec le type paramétré.
Je mentionnerai également ma déclaration préférée du JDK:
public class Enum<T extends Enum<T>>
Mis à part le joker (qui est un sac mixte), je pense simplement que les génériques .Net sont meilleurs.
Je vais jeter un avis vraiment controversé. Les génériques compliquent le langage et compliquent le code. Par exemple, disons que j'ai une carte qui mappe une chaîne à une liste de chaînes. Autrefois, je pouvais déclarer cela simplement
Map someMap;
Maintenant, je dois le déclarer comme
Map<String, List<String>> someMap;
Et chaque fois que je le passe dans une méthode, je dois répéter cette longue et longue déclaration. À mon avis, toute cette frappe supplémentaire distrait le développeur et le fait sortir de "la zone". De plus, lorsque le code est rempli de beaucoup de cruches, il est parfois difficile d'y revenir plus tard et de passer rapidement en revue toutes les cruches pour trouver la logique importante.
Java a déjà une mauvaise réputation d'être l'un des langages les plus verbeux couramment utilisés, et les génériques ne font qu'ajouter à ce problème.
Et qu'achetez-vous vraiment pour toute cette verbosité supplémentaire? Combien de fois avez-vous vraiment eu des problèmes lorsque quelqu'un a mis un nombre entier dans une collection qui est censée contenir des chaînes, ou lorsque quelqu'un a essayé de retirer une chaîne d'une collection de nombres entiers? Au cours de mes 10 années d'expérience dans la création d'applications commerciales Java applications, cela n'a tout simplement jamais été une grande source d'erreurs. Donc, je ne suis pas vraiment sûr de ce que vous obtenez pour la verbosité supplémentaire Cela me semble vraiment être un bagage bureaucratique supplémentaire.
Maintenant, je vais devenir vraiment controversé. Ce que je considère comme le plus gros problème avec les collections dans Java 1.4 est la nécessité de transtyper partout. Je vois ces transcriptions comme des suppléments verbeux supplémentaires qui ont beaucoup des mêmes problèmes que les génériques. Donc, pour par exemple, je ne peux pas simplement faire
List someList = someMap.get("some key");
Je dois faire
List someList = (List) someMap.get("some key");
La raison, bien sûr, est que get () retourne un objet qui est un sur-type de List. L'affectation ne peut donc pas être effectuée sans transtypage. Encore une fois, pensez à combien cette règle vous achète vraiment. D'après mon expérience, pas grand-chose.
Je pense que Java aurait été bien mieux si 1) il n'avait pas ajouté de génériques mais 2) avait permis à la place la conversion implicite d'un supertype en un sous-type. Laissez les conversions incorrectes être capturées lors de l'exécution. Ensuite, j'aurais pu avoir la simplicité de définir
Map someMap;
et faire plus tard
List someList = someMap.get("some key");
toute la cruauté aurait disparu, et je ne pense vraiment pas que j'introduirais une grosse nouvelle source de bugs dans mon code.
Un autre effet secondaire d'entre eux étant à la compilation et non à l'exécution est que vous ne pouvez pas appeler le constructeur du type générique. Vous ne pouvez donc pas les utiliser pour implémenter une fabrique générique ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
L'exactitude des génériques Java est vérifiée au moment de la compilation, puis toutes les informations de type sont supprimées (le processus est appelé effacement de type. Ainsi, le générique List<Integer>
sera réduit à son type brut, non générique List
, qui peut contenir des objets de classe arbitraire.
Cela permet d'insérer des objets arbitraires dans la liste au moment de l'exécution, et il est désormais impossible de dire quels types ont été utilisés comme paramètres génériques. Ce dernier se traduit à son tour par
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Ignorant le désordre d'effacement de type entier, les génériques spécifiés ne fonctionnent tout simplement pas.
Cela compile:
List<Integer> x = Collections.emptyList();
Mais c'est une erreur de syntaxe:
foo(Collections.emptyList());
Où foo est défini comme:
void foo(List<Integer> x) { /* method body not important */ }
Ainsi, la vérification d'un type d'expression dépend de son affectation à une variable locale ou à un paramètre réel d'un appel de méthode. C'est fou comme ça?
L'introduction de génériques dans Java a été une tâche difficile parce que les architectes essayaient d'équilibrer la fonctionnalité, la facilité d'utilisation et la compatibilité descendante avec le code hérité. Comme on pouvait s'y attendre, des compromis devaient être faits.
Certains estiment également que la mise en œuvre de Java des génériques a augmenté la complexité du langage à un niveau inacceptable (voir Ken Arnold " Generics Considered Harmful "). La FAQ sur les génériques d'Angelika Langer donne une assez bonne idée de la complexité des choses.
Je souhaite que ce soit un wiki afin que je puisse ajouter à d'autres personnes ... mais ...
Problèmes:
<
? étend MyObject>
[], mais je ne suis pas autorisé)Java n'applique pas les génériques au moment de l'exécution, seulement au moment de la compilation.
Cela signifie que vous pouvez faire des choses intéressantes comme ajouter les mauvais types aux collections génériques.
Les génériques Java sont uniquement au moment de la compilation et sont compilés en code non générique. En C #, le MSIL réellement compilé est générique. Cela a d'énormes implications pour les performances car Java continue d'être lancé pendant l'exécution. Voir ici pour plus .
Si vous écoutez Java Posse # 279 - Entretien avec Joe Darcy et Alex Buckley , ils parlent de ce problème. Cela renvoie également à un article de blog de Neal Gafter intitulé Reified Generics for Java qui dit:
Beaucoup de gens ne sont pas satisfaits des restrictions causées par la façon dont les génériques sont implémentés en Java. Plus précisément, ils sont mécontents que les paramètres de type générique ne soient pas réifiés: ils ne sont pas disponibles au moment de l'exécution. Les génériques sont implémentés à l'aide de l'effacement, dans lequel les paramètres de type générique sont simplement supprimés au moment de l'exécution.
Ce billet de blog fait référence à une entrée plus ancienne, Puzzling Through Erasure: answer section , qui soulignait le point sur la compatibilité de la migration dans le exigences.
L'objectif était de fournir une compatibilité descendante du code source et du code objet, ainsi qu'une compatibilité de migration.