web-dev-qa-db-fra.com

Pourquoi certains prétendent-ils que la mise en œuvre de Java des génériques est mauvaise?

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?

110
sdellysse

Mal:

  • Les informations de type sont perdues au moment de la compilation, donc au moment de l'exécution, vous ne pouvez pas dire de quel type il est "censé" être
  • Ne peut pas être utilisé pour les types de valeur (c'est un gros problème - dans .NET a List<byte> est vraiment soutenu par un byte[] par exemple, et aucune boxe n'est requise)
  • Syntaxe pour appeler les méthodes génériques suce (IMO)
  • La syntaxe des contraintes peut prêter à confusion
  • Le joker est généralement déroutant
  • Diverses restrictions dues à ce qui précède - casting etc.

Bien:

  • Le caractère générique permet de spécifier la covariance/contravariance du côté appelant, ce qui est très soigné dans de nombreuses situations
  • C'est mieux que rien!
141
Jon Skeet

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.

26
Paul Tomblin

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

17
JaredPar
  1. Implémentation du runtime (ie pas d'effacement de type);
  2. La capacité d'utiliser des types primitifs (ceci est lié à (1));
  3. Bien que le caractère générique soit utile, la syntaxe et savoir quand l'utiliser est quelque chose qui frappe beaucoup de gens. et
  4. Aucune amélioration des performances (en raison de (1); Java sont du sucre syntaxique pour les objets castingi).

(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.

14
cletus

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.

9
Clint Miller

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 ++

7
jdkoftinoff

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");
6
Anton Gogolev

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?

5
Nat

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.

4
Zach Scrivena

Je souhaite que ce soit un wiki afin que je puisse ajouter à d'autres personnes ... mais ...

Problèmes:

  • Effacement de type (pas de disponibilité d'exécution)
  • Pas de support pour les types primitifs
  • Incompatibilité avec les annotations (elles ont toutes deux été ajoutées en 1.5, je ne sais toujours pas pourquoi les annotations n'autorisent pas les génériques à part précipiter les fonctionnalités)
  • Incompatibilité avec les tableaux. (Parfois, je veux vraiment faire quelque chose comme Class<? étend MyObject> [], mais je ne suis pas autorisé)
  • Syntaxe et comportement des caractères génériques bizarres
  • Le fait que le support générique soit incohérent entre les classes Java. Ils l'ont ajouté à la plupart des méthodes de collections, mais de temps en temps, vous rencontrez une instance où elle n'est pas là.
4
Laplie Anderson

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.

3
Powerlord

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 .

2
Szymon Rozga

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.

2
Kevin Hakanson