Pourquoi les gens utilisent-ils encore des types primitifs en Java?

Depuis Java 5, nous avons eu la boxing / unboxing des types primitifs de sorte que int soit enveloppé pour être java.lang.Integer , et ainsi de suite.

Je vois beaucoup de nouveaux projets Java récemment (qui requièrent définitivement un JRE d’au moins la version 5, sinon 6) qui utilise int plutôt que java.lang.Integer , bien qu’il soit beaucoup plus pratique d’utiliser ce dernier, comme cela a déjà été le cas. quelques méthodes d’aide à la conversion en valeurs long et al.

Pourquoi certains utilisent-ils encore des types primitifs en Java? Y a-t-il des avantages tangibles?

Dans Effective Java de Joshua Bloch, élément 5: “Évitez de créer des objects inutiles”, il affiche l’exemple de code suivant:

 public static void main(Ssortingng[] args) { Long sum = 0L; // uses Long, not long for (long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); } 

et cela prend 43 secondes pour courir. Prendre le long dans la primitive le ramène à 6,8 secondes ... Si c'est la raison pour laquelle nous utilisons des primitives.

Le manque d'égalité des valeurs natives est également un problème ( .equals() est assez verbeux comparé à == )

pour biziclop:

 class Biziclop { public static void main(Ssortingng[] args) { System.out.println(new Integer(5) == new Integer(5)); System.out.println(new Integer(500) == new Integer(500)); System.out.println(Integer.valueOf(5) == Integer.valueOf(5)); System.out.println(Integer.valueOf(500) == Integer.valueOf(500)); } } 

Résulte en:

 false false true false 

EDIT Pourquoi (3) retourne true et (4) renvoie false ?

Parce que ce sont deux objects différents. Les 256 entiers les plus proches de zéro [-128; 127] sont mis en cache par la JVM, ils renvoient donc le même object pour ceux-ci. Au-delà de cette plage, cependant, ils ne sont pas mis en cache, un nouvel object est donc créé. Pour rendre les choses plus compliquées, le JLS exige qu'au moins 256 flyweights soient mis en cache. Les implémenteurs JVM peuvent en append s'ils le souhaitent, ce qui signifie qu'ils peuvent s'exécuter sur un système où les 1024 les plus proches sont mis en cache et tous renvoient la valeur true ... #awkward

La boîte automatique peut rendre difficile la détection des NPE

 Integer in = null; ... ... int i = in; // NPE at runtime 

Dans la plupart des situations, l’affectation nulle à in est beaucoup moins évidente que ci-dessus.

Types primitifs:

 int x = 1000; int y = 1000; 

Maintenant, évaluez:

 x == y 

C’est true Pas surprenant Maintenant, essayez les types encadrés:

 Integer x = 1000; Integer y = 1000; 

Maintenant, évaluez:

 x == y 

C’est false Probablement. Dépend de l’exécution. Est-ce une raison suffisante?

Les types en boîte ont de moins bonnes performances et nécessitent plus de mémoire.

Outre les problèmes de performances et de mémoire, j’aimerais proposer un autre problème: l’ interface List serait interrompue sans int .
Le problème est la méthode remove() surchargée ( remove(int) vs. remove(Object) ). remove(Integer) se résoudra toujours à appeler ce dernier, vous ne pouvez donc pas supprimer un élément par index.

D’un autre côté, il y a un écueil lorsque vous essayez d’append et de supprimer un int :

 final int i = 42; final List list = new ArrayList(); list.add(i); // add(Object) list.remove(i); // remove(int) - Ouch! 

Pouvez-vous vraiment imaginer un

  for (int i=0; i<10000; i++) { do something } 

boucle avec java.lang.Integer à la place? Un java.lang.Integer est immuable, donc chaque incrément autour de la boucle créera un nouvel object java sur le tas, plutôt que d'incrémenter simplement l'int sur la stack avec une seule instruction JVM. La performance serait diabolique.

Je ne serais pas du tout d'accord avec le fait qu'il soit préférable d'utiliser java.lang.Integer que int. Au contraire. Autoboxing signifie que vous pouvez utiliser int où vous seriez obligé d'utiliser Integer, et le compilateur Java prend soin d'insérer le code pour créer le nouvel object Integer pour vous. L'autoboxing consiste à vous permettre d'utiliser un int où un entier est attendu, le compilateur insérant la construction d'object appropriée. Il ne supprime ni ne réduit en aucun cas le besoin de l’int int. Avec l'autoboxing, vous obtenez le meilleur des deux mondes. Vous obtenez un Integer créé automatiquement pour vous lorsque vous avez besoin d'un object Java basé sur le tas, et vous obtenez la vitesse et l'efficacité d'un int lorsque vous ne faites que des calculs arithmétiques et locaux.

Les types primitifs sont beaucoup plus rapides:

 int i; i++; 

Integer (tous les nombres et aussi une chaîne) est un type immuable : une fois créé, il ne peut plus être modifié. Si i Integer, i++ créerait un nouvel object Integer, beaucoup plus coûteux en termes de mémoire et de processeur.

Avant tout, l’habitude. Si vous avez codé en Java pendant huit ans, vous accumulez une inertie considérable. Pourquoi changer s’il n’y a pas de raison impérieuse de le faire? Ce n’est pas comme si l’utilisation de primitives en boîte présentait des avantages supplémentaires.

L’autre raison est d’affirmer que null n’est pas une option valide. Il serait inutile et trompeur de déclarer la sum de deux nombres ou une variable de boucle comme Integer .

Il y a aussi l’aspect des performances, alors que la différence de performances n’est pas critique dans de nombreux cas (même si, quand c’est le cas, c’est plutôt mauvais), personne n’aime écrire du code qui pourrait être écrit aussi rapidement. habitué.

Au fait, Smalltalk ne possède que des objects (pas de primitives), et pourtant ils ont optimisé leurs petits entiers (en utilisant tous les 32 bits, seulement 27 ou plus) pour ne pas allouer d’espace, mais simplement utiliser un modèle de bits spécial. De plus, d’autres objects communs (true, false, null) avaient des motifs de bits spéciaux ici.

Ainsi, au moins sur les JVM 64 bits (avec un espace de nom de pointeur de 64 bits), il devrait être possible de ne pas avoir d’objects de type Entier, Caractère, Octet, Court, Booléen, Flottant (et petit Long) par explicite new ...() ), uniquement des motifs de bits spéciaux, qui pourraient être manipulés de manière très efficace par les opérateurs normaux.

Je ne peux pas croire que personne n’a mentionné ce que je pense être la raison la plus importante: “int” est tellement plus facile à taper que “Integer”. Je pense que les gens sous-estiment l’importance d’une syntaxe concise. Les performances ne sont pas vraiment une raison pour les éviter car la plupart du temps, quand on utilise des nombres, on les indexe et les incrémenter et les comparer ne coûtent rien dans une boucle non sortingviale (que vous utilisiez int ou Integer).

L’autre raison donnée était que vous pouviez obtenir des NPE, mais c’est extrêmement facile à éviter avec les types boxed (et il est garanti d’être évité tant que vous les initialisez toujours à des valeurs non nulles).

L’autre raison était que (new Long (1000)) == (new Long (1000)) est faux, mais c’est juste une autre façon de dire que “.equals” n’a pas de support syntaxique pour les types boxed (contrairement aux opérateurs <,> , =, etc), nous revenons donc à la raison de la “syntaxe plus simple”.

Je pense que l’exemple de boucle non primitif de Steve Yegge illustre très bien mon propos: http://sites.google.com/site/steveyegge2/language-sortingckery-and-ejb

Pensez-y: à quelle fréquence utilisez-vous des types de fonctions dans des langages qui ont une bonne syntaxe (comme tout langage fonctionnel, python, ruby ​​et même C) par rapport à java où vous devez les simuler avec des interfaces telles que Runnable et Callable classes sans nom.

Quelques raisons pour ne pas se débarrasser des primitives:

  • Compatibilité descendante

Si elle est éliminée, tous les anciens programmes ne fonctionneront même pas.

  • Réécriture de la JVM.

La JVM entière devrait être réécrite pour supporter cette nouvelle chose.

  • Plus grande empreinte mémoire.

Vous devez stocker la valeur et la référence, qui utilise plus de mémoire. Si vous disposez d’un grand nombre d’octets, l’utilisation d’ byte est nettement plus petite que celle des Byte .

  • Problèmes de pointeur nul.

Déclarer int i puis faire des trucs avec i n’entraînerait aucun problème, mais déclarer Integer i et en faire autant entraînerait un NPE.

  • Problèmes d’égalité

Considérez ce code:

 Integer i1 = 5; Integer i2 = 5; i1 == i2; // Currently would be false. 

Serait faux Les opérateurs devraient être surchargés, ce qui entraînerait une réécriture majeure.

  • Lent

Les wrappers d’objects sont nettement plus lents que leurs homologues primitifs.

En plus de ce que d’autres ont dit, les variables locales primitives ne sont pas allouées depuis le tas, mais sur la stack. Mais les objects sont alloués depuis le tas et doivent donc être vidés.

Les objects sont beaucoup plus lourds que les types primitifs, donc les types primitifs sont beaucoup plus efficaces que les instances des classes wrapper.

Les types primitifs sont très simples: par exemple, un int de 32 bits prend exactement 32 bits en mémoire et peut être manipulé directement. Un object Integer est un object complet qui, comme tout object, doit être stocké sur le tas et accessible uniquement via une référence (pointeur). Il est également probable qu’il occupe plus de 32 bits (4 octets) de mémoire.

Cela dit, le fait que Java fasse la distinction entre les types primitifs et non primitifs est également un signe de l’âge du langage de programmation Java. Les nouveaux langages de programmation n’ont pas cette distinction. le compilateur d’un tel langage est assez intelligent pour déterminer lui-même si vous utilisez des valeurs simples ou des objects plus complexes.

Par exemple, en Scala, il n’y a pas de types primitifs; il existe une classe Int pour les entiers, et Int est un object réel (que vous pouvez utiliser, etc.). Lorsque le compilateur comstack votre code, il utilise des ints primitifs en arrière-plan, donc utiliser Int est aussi efficace que d’utiliser un primitif int en Java.

Il est difficile de savoir quels types d’optimisations se produisent sous les couvertures.

Pour une utilisation locale, lorsque le compilateur dispose de suffisamment d’informations pour effectuer des optimisations excluant la possibilité de la valeur NULL, je m’attends à ce que les performances soient identiques ou similaires .

Cependant, les tableaux de primitives sont apparemment très différents des collections de primitives encadrées. Cela a du sens, car très peu d’optimisations sont possibles au sein d’une collection.

De plus, Integer a une surcharge logique beaucoup plus élevée que int : vous devez maintenant vous soucier de savoir si ou non int a = b + c; jette une exception.

J’utiliserais autant que possible les primitives et je m’appuierais sur les méthodes d’usine et l’autoboxing pour obtenir les types de boîtes les plus sémantiquement puissants lorsqu’elles sont nécessaires.

 int loops = 100000000; long start = System.currentTimeMillis(); for (Long l = new Long(0); l 

Millisecondes sockets pour boucler les temps "100000000" autour de la longueur: 468

Millisecondes sockets pour boucler 'fois00 fois fois autour de long: 31

Sur une note de côté, cela ne me dérangerait pas de voir quelque chose comme ça trouver son chemin dans Java.

 Integer loop1 = new Integer(0); for (loop1.lessThan(1000)) { ... } 

Où la boucle for incrémente automatiquement loop1 de 0 à 1000 ou

 Integer loop1 = new Integer(1000); for (loop1.greaterThan(0)) { ... } 

Où la boucle for décrémente automatiquement loop1 1000 à 0.

Les types primitifs ont de nombreux avantages:

  • Code plus simple à écrire
  • Les performances sont meilleures puisque vous n’instanciez pas un object pour la variable
  • Comme ils ne représentent pas une référence à un object, il n’est pas nécessaire de rechercher les valeurs NULL
  • Utilisez des types primitifs sauf si vous avez besoin de tirer parti des fonctionnalités de boxe.

Je suis d’accord avec les réponses précédentes, l’utilisation d’objects primitifs peut être coûteuse. Toutefois, si les performances ne sont pas essentielles dans votre application, vous évitez les dépassements lors de l’utilisation d’objects. Par exemple:

 long bigNumber = Integer.MAX_VALUE + 2; 

La valeur de bigNumber est -2147483647 et vous vous attendez à ce qu’elle soit 2147483649. Il s’agit d’un bogue dans le code qui serait corrigé en procédant comme suit:

 long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now. 

Et bigNumber serait 2147483649. Ces types de bogues sont parfois faciles à ignorer et peuvent conduire à des comportements ou des vulnérabilités inconnus (voir CWE-190 ).

Si vous utilisez des objects wrapper, le code équivalent ne sera pas compilé.

 Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling 

Il est donc plus facile d’arrêter ces types de problèmes en utilisant des objects wrapper primitifs.

Votre question a déjà reçu une réponse, que je réponds simplement pour append un peu plus d’informations non mentionnées auparavant.

  1. Vous avez besoin de primitives pour effectuer des opérations mathématiques
  2. Primitives prend moins de mémoire comme répondu ci-dessus et plus performant

Vous devriez demander pourquoi le type de classe / object est requirejs

La raison d’être de type d’object est de nous faciliter la vie lorsque nous traitons avec des collections. Les primitives ne peuvent pas être ajoutées directement à List / Map, mais vous devez écrire une classe wrapper. Readymade Integer genre de classes vous aide ici et il a de nombreuses méthodes utilitaires comme Integer.pareseInt (str)

Parce que JAVA effectue toutes les opérations mathématiques dans les types primitifs. Considérez cet exemple:

 public static int sumEven(List li) { int sum = 0; for (Integer i: li) if (i % 2 == 0) sum += i; return sum; } 

Ici, les opérations de rappel et unaire plus ne peuvent pas être appliquées sur le type Entier (Référence), le compilateur effectue la désencapsulation et effectue les opérations.

Donc, assurez-vous du nombre d’opérations d’autoboxing et de unboxing effectuées dans le programme java. Depuis, il faut du temps pour effectuer ces opérations.

En règle générale, il est préférable de conserver les arguments de type Référence et le résultat du type primitif.