Pourquoi les collections Java ne peuvent-elles pas stocker directement les types de primitives?


123

Les collections Java stockent uniquement des objets, pas des types primitifs; cependant, nous pouvons stocker les classes wrapper.

Pourquoi cette contrainte?


2
Cette contrainte est nul lorsque vous traitez avec des primitives et que vous souhaitez utiliser des files d'attente pour envoyer et que vos taux d'envoi sont très rapides. Je m'occupe actuellement de ce problème de l'auto-boxe qui prend trop de temps.
JPM

Techniquement, les types primitifs sont des objets (des instances singleton de tels pour être exact), ils ne sont tout simplement pas définis par a class, plutôt par la JVM. L'instruction int i = 1définit un pointeur vers l'instance singleton de l'objet qui définit intdans la JVM, définie sur la valeur 1définie quelque part dans la JVM. Oui, des pointeurs en Java - cela vous est simplement retiré par l'implémentation du langage. Les primitives ne peuvent pas être utilisées comme génériques car les prédicats de langage tous les types génériques doivent être du supertype Object- d'où la raison de la A<?>compilation A<Object>au moment de l'exécution.
Robert E Fry

1
Les types primitifs @RobertEFry ne sont pas des objets en Java, donc tout ce que vous avez écrit sur les instances de singleton et ce genre de choses est fondamentalement faux et déroutant. Je suggère de lire le chapitre «Types, valeurs et variables» de la spécification du langage Java, qui définit ce qu'est un objet: «Un objet (§4.3.1) est une instance créée dynamiquement d'un type de classe ou d'un tableau créé dynamiquement. "
typeracer

Réponses:


99

C'était une décision de conception Java, et que certains considèrent comme une erreur. Les conteneurs veulent des objets et les primitives ne dérivent pas d'Object.

C'est un endroit que les concepteurs .NET ont appris de la JVM et ont implémenté des types de valeur et des génériques tels que la boxe est éliminée dans de nombreux cas. Dans CLR, les conteneurs génériques peuvent stocker des types valeur dans le cadre de la structure de conteneur sous-jacente.

Java a choisi d'ajouter une prise en charge générique à 100% dans le compilateur sans prise en charge de la JVM. La JVM étant ce qu'elle est, ne supporte pas un objet "non-objet". Les génériques Java vous permettent de prétendre qu'il n'y a pas de wrapper, mais vous payez toujours le prix des performances de la boxe. Ceci est IMPORTANT pour certaines classes de programmes.

La boxe est un compromis technique, et je pense que ce sont des détails d'implémentation qui fuient dans la langue. L'autoboxing est un bon sucre syntaxique, mais c'est toujours une pénalité de performance. Si quoi que ce soit, j'aimerais que le compilateur me prévienne lorsqu'il effectue des boîtes automatiques. (Pour autant que je sache, il se peut maintenant, j'ai écrit cette réponse en 2010).

Une bonne explication sur SO sur la boxe: Pourquoi certaines langues ont-elles besoin de boxe et de déballage?

Et la critique des génériques Java: pourquoi certains prétendent que l'implémentation Java des génériques est mauvaise?

Dans la défense de Java, il est facile de regarder en arrière et de critiquer. La JVM a résisté à l'épreuve du temps et constitue une bonne conception à bien des égards.


6
Ce n'est pas une erreur, un compromis soigneusement choisi qui, je pense, a très bien servi Java.
DJClayworth le

13
C'était une erreur suffisante que .NET en a tiré les leçons et a implémenté la boxe automatique dès le début et les génériques au niveau de la machine virtuelle sans surcharge de boxe. La propre tentative de correction de Java n'était qu'une solution au niveau de la syntaxe qui souffre toujours de la performance de la boxe automatique contre pas de boxe du tout. L'implémentation de Java a montré de mauvaises performances avec de grandes structures de données.
codenheim

2
@mrjoltcola: À mon humble avis, l'autoboxing par défaut est une erreur, mais il aurait dû y avoir un moyen d'étiqueter les variables et les paramètres qui devraient automatiquement encadrer les valeurs qui leur sont données. Même maintenant, je pense qu'il devrait y avoir un moyen ajouté pour spécifier que certaines variables ou paramètres ne devraient pas accepter de nouvelles valeurs auto-boxées [par exemple, il devrait être légal de passer des Object.ReferenceEqualsréférences de type Objectqui identifient des entiers encadrés, mais cela ne devrait pas être légal pour passer une valeur entière]. Le déballage automatique de Java est à mon humble avis tout simplement méchant.
supercat

18

Facilite la mise en œuvre. Étant donné que les primitives Java ne sont pas considérées comme des objets, vous devez créer une classe de collection distincte pour chacune de ces primitives (pas de code de modèle à partager).

Vous pouvez le faire, bien sûr, il suffit de voir GNU Trove , Apache Commons Primitives ou HPPC .

À moins que vous n'ayez de très grandes collections, la surcharge des wrappers n'a pas suffisamment d'importance pour que les gens s'en soucient (et lorsque vous avez de très grandes collections primitives, vous voudrez peut-être consacrer l'effort à utiliser / construire une structure de données spécialisée pour eux. ).


11

C'est une combinaison de deux faits:

  • Les types primitifs Java ne sont pas des types de référence (par exemple, an intn'est pas un Object)
  • Java fait des génériques en utilisant l'effacement de type des types de référence (par exemple, a List<?>est vraiment un List<Object>au moment de l'exécution)

Étant donné que ces deux conditions sont vraies, les collections Java génériques ne peuvent pas stocker directement les types primitifs. Pour plus de commodité, la mise en boîte automatique est introduite pour permettre aux types primitifs d'être automatiquement encadrés en tant que types de référence. Ne vous y trompez pas, cependant, les collections stockent toujours des références d'objets.

Cela aurait-il pu être évité? Peut-être.

  • Si un intest unObject , il n'y a aucun besoin de type de boîte.
  • Si les génériques ne sont pas effectués à l'aide de l'effacement de type, des primitives auraient pu être utilisées pour les paramètres de type.

8

Il y a le concept d' auto-boxing et d'auto-unboxing. Si vous essayez de stocker un intdans un, List<Integer>le compilateur Java le convertira automatiquement en fichier Integer.


1
La boxe automatique a été introduite dans Java 1.5 avec les génériques.
Jeremy

1
Mais c'est une chose de temps de compilation; sucre de syntaxe sans avantage de performance. Les boîtes automatiques du compilateur Java, d'où la pénalité des performances par rapport aux implémentations de VM telles que .NET, qui sont génériques n'impliquent pas de boxe.
codenheim

1
@mrjoltcola: Quel est votre point? Je partageais simplement des faits, je ne faisais pas d'argumentation.
Jeremy

3
Mon point est qu'il est important de souligner la différence entre la syntaxe et les performances. Je considère également mes commentaires comme un partage des faits, pas des arguments. Merci.
codenheim

2

Ce n'est pas vraiment une contrainte, n'est-ce pas?

Considérez si vous souhaitez créer une collection contenant des valeurs primitives. Comment écririez-vous une collection qui peut stocker soit int, soit float ou char? Très probablement, vous vous retrouverez avec plusieurs collections, vous aurez donc besoin d'une intlist et d'une charlist, etc.

Tirant parti de la nature orientée objet de Java lorsque vous écrivez une classe de collection, il peut stocker n'importe quel objet, vous n'avez donc besoin que d'une seule classe de collection. Cette idée, le polymorphisme, est très puissante et simplifie grandement la conception des bibliothèques.


7
"Comment écririez-vous une collection qui peut stocker soit int, soit float ou char?" - Avec des génériques / modèles correctement implémentés comme les autres langages qui ne paient pas la peine de prétendre que tout est un objet.
codenheim

Je n'ai presque jamais eu en six ans de Java voulu stocker une collection de primitives. Même dans les rares cas où j'aurais pu le souhaiter, les coûts de temps et d'espace supplémentaires liés à l'utilisation des objets de référence ont été négligeables. En particulier, je trouve que beaucoup de gens pensent vouloir Map <int, T>, oubliant qu'un tableau fera très bien cette astuce.
DJClayworth le

2
@DJClayworth Cela ne fonctionne bien que si les valeurs clés ne sont pas rares. Bien sûr, vous pouvez utiliser un tableau auxiliaire pour garder une trace des clés, mais cela a ses propres problèmes: un accès relativement efficace nécessiterait de garder les deux tableaux triés en fonction de l'ordre des clés pour permettre la recherche binaire, qui à son tour effectuerait l'insertion et la suppression. inefficace sauf si l'insertion / suppression est structurée de telle sorte que les éléments insérés sont susceptibles de se retrouver là où un élément précédemment supprimé était et / ou que certains tampons sont intercalés dans les tableaux, etc. Il y a des ressources disponibles, mais ce serait bien d'avoir dans Java lui-même.
JAB

@JAB En fait, cela fonctionne très bien si les clés sont rares, il a juste besoin de plus de mémoire que si elles ne sont pas rares. Et si ces clés sont rares, cela implique qu'il n'y en a pas beaucoup et que l'utilisation d'Integer comme clé fonctionne bien. Utilisez l'approche qui nécessite le moins de mémoire. Ou ce que vous ressentez si vous ne vous souciez pas.
DJClayworth

0

Je pense que nous pourrions voir des progrès dans cet espace dans le JDK éventuellement dans Java 10 basé sur ce JEP - http://openjdk.java.net/jeps/218 .

Si vous souhaitez éviter de boxer les primitives dans les collections d'aujourd'hui, il existe plusieurs alternatives tierces. En plus des options tierces mentionnées précédemment, il existe également des collections Eclipse , FastUtil et Koloboke .

Une comparaison de cartes primitives a également été publiée il y a quelque temps avec le titre: Large HashMap overview: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove . La bibliothèque GS Collections (Goldman Sachs) a été migrée vers Eclipse Foundation et s'appelle désormais Eclipse Collections.


0

La principale raison est la stratégie de conception java. ++ 1) les collections nécessitent des objets pour la manipulation et les primitives ne sont pas dérivées de l'objet, ce peut donc être l'autre raison. 2) Les types de données primitifs Java ne sont pas des types de référence par exemple. int n'est pas un objet.

Surpasser:-

nous avons le concept d'auto-boxing et d'auto-unboxing. donc si vous essayez de stocker des types de données primitifs, le compilateur le convertira automatiquement en objet de cette classe de données primitive.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.