J'ai parfois entendu dire qu'avec les génériques, Java ne faisait pas les choses correctement. (référence la plus proche, ici )
Pardonnez mon inexpérience, mais qu'est-ce qui les aurait améliorés?
J'ai parfois entendu dire qu'avec les génériques, Java ne faisait pas les choses correctement. (référence la plus proche, ici )
Pardonnez mon inexpérience, mais qu'est-ce qui les aurait améliorés?
Réponses:
Mauvais:
List<byte>
est vraiment soutenu par un byte[]
par exemple, et aucune boxe n'est requise)Bien:
Le plus gros problème est que les génériques Java sont une seule chose au moment de la compilation, et vous pouvez le subvertir au moment de l'exécution. C # est loué car il effectue plus de vérification à l'exécution. Il y a de très bonnes discussions dans cet article et des liens vers d'autres discussions.
Class
objets.
Le problème principal est que Java n'a pas réellement de génériques au moment de l'exécution. C'est une fonctionnalité de compilation.
Lorsque vous créez une classe générique en Java, ils utilisent une méthode appelée "Effacement de type" pour supprimer en fait tous les types génériques de la classe et les remplacer essentiellement par Object. La version la plus élevée des génériques est que le compilateur insère simplement des casts vers le type générique spécifié chaque fois qu'il apparaît dans le corps de la méthode.
Cela présente de nombreux inconvénients. L'un des plus grands, à 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 puisse 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 n'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 mélangé), je pense juste que les génériques .Net sont meilleurs.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
peut sembler bizarre / redondant au début mais c'est en fait assez intéressant, du moins dans les contraintes de Java / c'est générique. Les énumérations ont une values()
méthode statique donnant un tableau de leurs éléments typés comme enum, non Enum
, et ce type est déterminé par le paramètre générique, ce qui signifie que vous voulez Enum<T>
. Bien sûr, ce typage n'a de sens que dans le contexte d'un type énuméré, et toutes les énumérations sont des sous-classes de Enum
, donc vous voulez Enum<T extends Enum>
. Cependant, Java n'aime pas mélanger des types bruts avec des génériques, donc Enum<T extends Enum<T>>
par souci de cohérence.
Je vais lancer une opinion vraiment controversée. 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. Dans l'ancien temps, je pouvais déclarer cela simplement comme
Map someMap;
Maintenant, je dois le déclarer comme
Map<String, List<String>> someMap;
Et chaque fois que je la transmets à une méthode, je dois répéter cette longue et longue déclaration une fois de plus. À mon avis, toute cette frappe supplémentaire distrait le développeur et le sort de «la zone». De plus, lorsque le code est rempli de beaucoup de cruauté, il est parfois difficile d'y revenir plus tard et de passer rapidement au crible toute la cruauté pour trouver la logique importante.
Java a déjà la mauvaise réputation d'être l'un des langages les plus verbeux d'usage courant, 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 entier dans une collection qui est censée contenir des chaînes, ou où quelqu'un a essayé de retirer une chaîne d'une collection d'entiers? Au cours de mes 10 années d'expérience dans la création d'applications Java commerciales, cela n'a 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 un bagage bureaucratique supplémentaire.
Maintenant, je vais devenir vraiment controversé. Ce que je vois comme le plus gros problème avec les collections en Java 1.4, c'est la nécessité de typer partout. Je considère ces typecasts comme des crottes extra, verbeuses qui ont beaucoup des mêmes problèmes que les génériques. Donc, 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 () renvoie un Object qui est un supertype de List. Donc, l'attribution ne peut pas être faite sans un typage. 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 à la place permis la conversion implicite d'un supertype vers un sous-type. Laissez les casts incorrects être capturés au moment de l'exécution. Alors j'aurais pu avoir la simplicité de définir
Map someMap;
et plus tard faire
List someList = someMap.get("some key");
toute la cruauté serait partie, et je ne pense vraiment pas que j'introduirais une grande nouvelle source de bogues dans mon code.
Un autre effet secondaire de leur temps de compilation et non d'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 usine 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 lors de l'exécution, et il est maintenant impossible de dire quels types ont été utilisés comme paramètres génériques. Ce dernier entraîne à son tour
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Je souhaite que ce soit un wiki afin que je puisse ajouter à d'autres personnes ... mais ...
Problèmes:
<
? Etend MyObject >
[], mais je ne suis pas autorisé)En ignorant tout le désordre d'effacement de type, les génériques tels que 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. A quel point est-ce fou?
L'introduction de génériques dans Java était une tâche difficile car les architectes essayaient d'équilibrer fonctionnalité, facilité d'utilisation et rétrocompatibilité avec le code hérité. Comme on pouvait s'y attendre, des compromis ont dû être faits.
Certains estiment également que l'implémentation par Java des génériques a augmenté la complexité du langage à un niveau inacceptable (voir " Generics Considered Harmful " de Ken Arnold ). La FAQ générique d' Angelika Langer donne une assez bonne idée de la complexité des choses.
Java n'applique pas les génériques au moment de l'exécution, uniquement 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 à la compilation et sont compilés en code non générique. En C #, le MSIL compilé réel est générique. Cela a d'énormes implications pour les performances, car Java effectue toujours un cast pendant l'exécution. Cliquez ici pour en savoir 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 Neal Gafter intitulé Reified Generics for Java qui dit:
De nombreuses personnes ne sont pas satisfaites des restrictions causées par la manière dont les génériques sont implémentés en Java. Plus précisément, ils ne sont pas satisfaits 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: section de réponse , qui soulignait le point sur la compatibilité de la migration dans les exigences.
L'objectif était de fournir une compatibilité descendante du code source et du code objet, ainsi que la compatibilité de la migration.