web-dev-qa-db-fra.com

Quel est le problème avec les génériques Java?

J'ai vu à plusieurs reprises sur ce site des articles qui dénoncent l'implémentation Java des génériques. Maintenant, je peux honnêtement dire que je n'ai eu aucun problème avec leur utilisation. Cependant, je n'ai pas essayé 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?

50
Michael K

L'implémentation générique de Java utilise effacement de type . Cela signifie que vos collections génériques fortement typées sont en fait de type Object au moment de l'exécution. Cela a certaines considérations de performances car cela signifie que les types primitifs doivent être encadrés lorsqu'ils sont ajoutés à une collection générique. Bien sûr, les avantages de la correction de type de temps de compilation l'emportent sur la stupidité générale de l'effacement de type et la concentration obsessionnelle sur la compatibilité descendante.

56
ChaosPandion

Notez que les réponses déjà fournies se concentrent sur la combinaison de Java-the-language, JVM et de la bibliothèque de classes Java Java.

Il n'y a rien de mal avec les génériques de Java en ce qui concerne Java-the-language. Comme décrit dans C # vs Java generics , les génériques de Java sont assez bien au niveau du langage1.

Ce qui n'est pas 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 liées à la réflexion de la bibliothèque de classes ne peuvent pas exposer les informations de type complet qui étaient disponibles en langage Java
  • une certaine pénalité de performance est impliquée

Je suppose que la distinction que je fais est pédante car Java-le-langage est compilé de manière omniprésente avec JVM comme cible et Java Class Library comme bibliothèque de base).

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

26
Roman Starkov

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 convertir, par exemple, ArrayList<String> En List<File>. Le compilateur vous donnera des avertissements, mais il vous avertira également si vous prenez une ArrayList<String> L'affectez à une référence Object puis la convertissez en List<String>. Les avantages sont qu'avec les génériques, vous ne devriez probablement pas faire le casting, les performances sont meilleures sans les données inutiles et, bien sûr, la compatibilité descendante.

Certaines personnes se plaignent que vous ne pouvez pas surcharger sur la base d'arguments génériques (void fn(Set<String>) et void fn(Set<File>)). Vous devez plutôt utiliser de meilleurs noms de méthode. 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ère l'immuabilité et les interfaces dites-ne pas demander, alors les génériques côté déclaration plutôt que côté utilisation seraient plus appropriés.

13

Les génériques Java sont nuls 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'auriez pas besoin de massacrer chaque classe dans le code ci-dessus pour le faire fonctionner comme il se doit si les génériques étaient en fait des paramètres de classe génériques.

10
davidk01
  • les avertissements du compilateur pour les paramètres génériques manquants qui ne servent à rien rendent le langage verbeux, par exemple public String getName(Class<?> klazz){ return klazz.getName();}

  • Génériques ne jouez pas à Nice avec des tableaux

  • Les informations de type perdues font de la réflexion un gâchis de coulée et de ruban adhésif.

5
Steve B.

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

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

Malheureusement, les types renvoyé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. Parce que arr est créé au moment de l'exécution, les types génériques sont effacés au moment de l'exécution, vous ne pouvez donc pas le comprendre. Comme certains l'ont dit ArrayList<Integer> ressemble à ArrayList<String> du point de vue de la réflexion.

Cela peut ne pas avoir d'importance pour l'utilisateur de Generics, mais disons que nous voulions créer un cadre sophistiqué qui utilise la réflexion pour comprendre des choses fantaisistes sur la façon 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 fabrique générique crée une instance de MySpecialObject parce que 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 pour trouver le type concret déclaré pour cette instance car Java les a effacés.

Dans les génériques de .Net, vous pouvez le faire, car au moment de l'exécution, l'objet sait qu'il s'agit de types génériques, car le compilateur l'a respecté dans le binaire. Avec les effacements Java ne peut pas faire cela.

5
chubbsondubs

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

Une grande gêne psychologique: Je me retrouve parfois dans des situations où les génériques ne peuvent pas fonctionner. (Les tableaux étant 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 juste 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 poussant, j'y arriverai finalement. 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 trop peu de gain. Dans les cas les plus simples, cela peut m'empêcher d'ajouter un Apple à une liste contenant des voitures. Très bien. Mais sans génériques, cette erreur entraînerait une exception ClassCastException très rapide au moment de l'exécution avec peu de temps perdu. Si je ajouter une voiture avec un siège enfant avec un enfant dedans, ai-je besoin d'un avertissement au moment de la compilation que la liste est uniquement pour les voitures avec des sièges enfants qui contiennent des bébés chimpanzés? Une liste d'instances d'objets simples commence à ressembler à une bonne idée.

Le code générique peut avoir beaucoup de mots et de caractères et 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 c'est le cas, je me sens incroyablement intelligent. J'ai également perdu plusieurs heures ou plus de mon propre temps et je dois me demander si quelqu'un d'autre pourra jamais comprendre le code. J'aimerais pouvoir le remettre pour maintenance à des gens qui sont soit moins intelligents que moi ou qui ont moins de temps à perdre.

D'un autre côté (je me suis un peu enroulé là-bas et je ressens le besoin de fournir un certain équilibre), c'est bien quand on utilise des collections et des cartes simples pour avoir des ajouts, des mises et des vérifications lors de l'écriture, et cela n'ajoute généralement pas beaucoup de complexité au code ( si quelqu'un autre écrit la collection ou la carte) . Et Java est meilleur dans ce domaine que C #. Il semble qu'aucune des collections C # que je souhaite utiliser ne gère les génériques. (J'avoue avoir des goûts étranges dans les collections.)

1
RalphChapin