Primitives Java vs Objets
1. Vue d'ensemble
Dans ce didacticiel, nous montrons les avantages et les inconvénients de l’utilisation des types primitifs Java et de leurs équivalents encapsulés.
2. Système de type Java
Java a un système de type à deux volets composé de primitives telles queint,boolean et de types de référence tels queInteger,Boolean. Chaque type de primitive correspond à un type de référence.
Chaque objet contient une valeur unique du type primitif correspondant. Leswrapper classes are immutable (pour que leur état ne puisse pas changer une fois l’objet construit) et sont définitifs (pour que nous ne puissions pas en hériter).
Sous le capot, Java effectue une conversion entre les types primitif et référence si un type réel est différent de celui déclaré:
Integer j = 1; // autoboxing
int i = new Integer(1); // unboxing
Le processus de conversion d'un type primitif en un type de référence s'appelle la substitution automatique, le processus opposé s'appelle unboxing.
3. Avantages et inconvénients
Le choix de l'objet à utiliser dépend des performances de l'application que nous essayons d'atteindre, de la quantité de mémoire disponible, de la quantité de mémoire disponible et des valeurs par défaut que nous devons gérer.
Si nous ne sommes confrontés à aucun de ces problèmes, nous pouvons ignorer ces considérations, même s’il vaut la peine de les connaître.
3.1. Empreinte mémoire unique
Juste pour référence, lesprimitive type variables ont l'impact suivant sur la mémoire:
-
booléen - 1 bit
-
octet - 8 bits
-
court, caractère - 16 bits
-
int, float - 32 bits
-
long, double - 64 bits
En pratique, ces valeurs peuvent varier en fonction de l'implémentation de la machine virtuelle. Dans la VM d'Oracle, le type booléen, par exemple, est mappé aux valeurs int 0 et 1, donc il prend 32 bits, comme décrit ici:Primitive Types and Values.
Les variables de ces types résident dans la pile et sont donc rapidement accessibles. Pour plus de détails, nous recommandons nostutorial sur le modèle de mémoire Java.
Les types de référence sont des objets, ils vivent sur le tas et sont relativement lents à accéder. Ils ont un certain surcoût concernant leurs homologues primitifs.
Les valeurs concrètes de la surcharge sont en général spécifiques à la machine virtuelle Java. Ici, nous présentons les résultats pour une machine virtuelle 64 bits avec ces paramètres:
java 10.0.1 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
Pour obtenir la structure interne d'un objet, nous pouvons utiliser l'outilJava Object Layout (voir notre autretutorial pour savoir comment obtenir la taille d'un objet).
Il s'avère qu'une seule instance d'un type de référence sur cette JVM occupe 128 bits sauf pourLong etDouble qui occupent 192 bits:
-
Booléen - 128 bits
-
Octet - 128 bits
-
Court, caractère - 128 bits
-
Entier, Float - 128 bits
-
Double long - 192 bits
Nous pouvons voir qu'une seule variable de typeBoolean occupe autant d'espace que 128 variables primitives, tandis qu'une variableInteger occupe autant d'espace que quatre variablesint.
3.2. Empreinte mémoire pour les tableaux
La situation devient plus intéressante si on compare la quantité de mémoire occupée par des tableaux des types considérés.
Lorsque nous créons des tableaux avec le nombre d'éléments différents pour chaque type, nous obtenons un tracé:
cela démontre que les types sont regroupés en quatre familles en fonction de la façon dont la mémoirem(s) dépend du nombre d'éléments s du tableau:
-
long, double: m (s) = 128 + 64 s
-
court, car: m (s) = 128 + 64 [s / 4]
-
octet, booléen: m (s) = 128 + 64 [s / 8]
-
le reste: m (s) = 128 + 64 [s / 2]
où les crochets indiquent la fonction de plafond standard.
De manière surprenante, les tableaux des types primitifs long et double consomment plus de mémoire que leurs classes wrapperLong etDouble.
On peut voir non plus quesingle-element arrays of primitive types are almost always more expensive (except for long and double) than the corresponding reference type.
3.3. Performance
La performance d’un code Java est une question assez subtile, elle dépend beaucoup du matériel sur lequel le code est exécuté, du compilateur qui peut effectuer certaines optimisations, de l’état de la machine virtuelle, de l’activité des autres processus du processus. système opérateur.
Comme nous l'avons déjà mentionné, les types primitifs résident dans la pile, tandis que les types de référence résident dans le tas. C'est un facteur dominant qui détermine la rapidité d'accès aux objets.
Pour démontrer à quel point les opérations pour les types primitifs sont plus rapides que celles des classes wrapper, créons un tableau de cinq millions d’éléments dans lequel tous les éléments sont égaux à l’exception du dernier; puis nous effectuons une recherche pour cet élément:
while (!pivot.equals(elements[index])) {
index++;
}
et comparez les performances de cette opération pour le cas où le tableau contient des variables des types primitifs et pour le cas où il contient des objets des types de référence.
Nous utilisons le célèbre outil d'analyse comparativeJMH (voir notretutorial pour savoir comment l'utiliser), et les résultats de l'opération de recherche peuvent être résumés dans ce graphique:
Même pour une opération aussi simple, nous pouvons voir qu’il faut plus de temps pour effectuer l’opération pour les classes wrapper.
Dans le cas d'opérations plus complexes telles que la sommation, la multiplication ou la division, la différence de vitesse peut monter en flèche.
3.4. Les valeurs par défaut
Les valeurs par défaut des types primitifs sont0 (dans la représentation correspondante, i.e. 0,0.0d etc) pour les types numériques,false pour le type booléen,