Quand utiliser primitive vs classe en Java?

54

Je vois que Java a Boolean (classe) vs boolean (primitive). De même, il y a un Integer (classe) vs int (primitive). Quelle est la meilleure pratique pour utiliser la version primitive par rapport à la classe? Devrais-je toujours utiliser la version de classe à moins d’avoir une raison spécifique (performance?) De ne pas le faire? Quelle est la manière la plus courante et acceptée d’utiliser chacun?

Casey Patton
la source
Pour moi, ce ne sont pas des classes, ce sont des boîtes. Ils ne sont là que pour que vous puissiez utiliser des primitives lorsque des objets sont requis, c'est-à-dire dans des collections. Vous ne pouvez pas ajouter deux nombres entiers (vous pouvez prétendre, mais vraiment Java est la boxe automatique | déballer les valeurs pour vous).
Stonemetal

Réponses:

47

Dans l'article 5 de Java efficace, Joshua Bloch dit

La leçon est claire: préférez les primitives aux primitives en boîte et faites attention aux renvois automatiques non intentionnels .

Une bonne utilisation des classes consiste à les utiliser comme types génériques (y compris les classes Collection, telles que des listes et des mappes) ou à les transformer en un autre type sans transtypage implicite (par exemple, Integerclasse a des méthodes doubleValue()ou byteValue().

Edit: La raison de Joshua Bloch est:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

Ce programme obtient la bonne réponse, mais il est beaucoup plus lent qu'il ne devrait l'être, en raison d'une erreur typographique à un caractère. La variable sumest déclarée au Longlieu de a long, ce qui signifie que le programme construit environ 2 ^ 31 Longoccurrences inutiles (environ une pour chaque long iajout de la Long sum). Changer la déclaration de somme de Longen longréduit la durée d'exécution de 43 secondes à 6,8 secondes sur ma machine.

m3th0dman
la source
2
Il serait utile que vous énumériez les raisons de Bloch plutôt que de simplement citer sa conclusion!
vaughandroid
@ Baqueta J'ai édité le post. La raison est la performance.
m3th0dman
C'est un peu plus clair, merci. Je me sens justifié d’afficher ma propre réponse maintenant. :)
vaughandroid
Vous pouvez trouver intéressant l’architecture lmax - "Il a fallu un peu plus d’astuce pour atteindre un autre ordre de grandeur. L’équipe LMAX a trouvé plusieurs solutions utiles pour y parvenir. L’une consistait à écrire des implémentations personnalisées des collections Java conçues être amical en cache et prudent avec les ordures. Un exemple en utilisant java primitif est utilisé comme clé de hachage avec une implémentation de Map sauvegardée par un tableau spécialement écrit . "
Cela ressemble à quelque chose que JIT devrait gérer
deFreitas
28

La pratique habituelle consiste à utiliser les primitives, à moins que vous n'utilisiez des génériques (assurez-vous que vous êtes au courant de l' autoboxing et de la déballage !).

Il y a plusieurs bonnes raisons de suivre la convention:

1. Vous évitez les erreurs simples:

Il existe des cas subtils et non intuitifs qui attrapent souvent les débutants. Même des codeurs expérimentés font des erreurs et font parfois ces erreurs (j'espère que cela sera suivi de jurons quand ils débogueront le code et trouveront l'erreur!).

L’erreur la plus courante est d’utiliser a == bau lieu de a.equals(b). Les gens sont habitués à utiliser des a == bprimitives, alors vous pouvez le faire facilement lorsque vous utilisez les wrappers d’objets.

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2. Lisibilité:

Considérez les deux exemples suivants. La plupart des gens diraient que la seconde est plus lisible.

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3. Performance:

Le fait est qu'il est plus lent d'utiliser les wrappers d'objet pour les primitives que de simplement utiliser les primitives. Vous ajoutez le coût de l'instanciation d'objet, des appels de méthode, etc. aux éléments que vous utilisez partout .

Knuth "... disons environ 97% du temps: l'optimisation prématurée est la racine de tout le mal" citation ne s'applique pas vraiment ici. Il parlait Optimisations qui rend plus compliqué le code (ou système) - si vous êtes d' accord avec le point # 2, ceci est une optimisation qui rend le code moins compliqué!

4. C'est la convention:

Si vous faites des choix stylistiques différents pour 99% des autres programmeurs Java, il y a 2 inconvénients:

  • Vous trouverez le code des autres personnes plus difficile à lire. 99% des exemples / tutoriels / etc utilisera des primitives. Chaque fois que vous en lirez un, vous aurez le surcroît de travail cognitif de penser à la manière dont cela ressemblerait dans le style auquel vous êtes habitué.
  • D'autres personnes trouveront votre code plus difficile à lire. Chaque fois que vous posez des questions sur le dépassement de pile, vous devrez passer au crible les réponses / commentaires en demandant "pourquoi n'utilisez-vous pas de primitives?". Si vous ne me croyez pas, il suffit de regarder les batailles que les gens ont sur des choses comme le placement des crochets, qui n'affectent même pas le code généré!

Normalement, je listerais quelques contre-points, mais honnêtement, je ne vois aucune bonne raison de ne pas aller à la convention ici!

vaughandroid
la source
2
Ne suggère pas aux gens de comparer des objets avec ==. Les objets doivent être comparés avec equals().
Tulains Córdova
2
@ user61852 Je ne suggérais pas cela comme une chose à faire, mais comme une erreur courante! Devrais-je rendre cela plus clair?
Vaughandroid
Oui, vous ne mentionnez pas que les objets doivent être comparés avec equals()... vous leur donnez une solution de contournement afin que la comparaison d'objets avec ==les résultats attendus.
Tulains Córdova
Bon point. Changé.
Vaughandroid
J'ai ajouté equals()au deuxième extrait de code et changé mon vote.
Tulains Córdova
12

D'habitude je vais avec les primitives. Cependant, une particularité d'utiliser des classes comme Integeret Booleanest la possibilité d'assigner nullà ces variables. Bien sûr, cela signifie que vous devez faire des nullvérifications tout le temps, mais il vaut mieux obtenir une exception NullPointerException que d'avoir des erreurs de logique dues à l'utilisation d'une variable intou d'une booleanvariable qui n'a pas été initialisée correctement.

Bien sûr, depuis Java 8, vous pouvez (et devriez probablement) aller plus loin et Integervous pouvez, au lieu de, par exemple, utiliser Optional<Integer>des variables qui peuvent ou non avoir une valeur.

De plus, cela introduit la possibilité d'utiliser nullpour attribuer à ces variables une valeur " inconnue " ou " générique ". Cela peut être pratique dans certaines situations, par exemple dans la logique ternaire . Vous pouvez également vérifier si un objet donné correspond à un modèle. dans ce cas, vous pouvez utiliser nullles variables du modèle pouvant avoir une valeur quelconque dans l'objet.

tobias_k
la source
2
(Ce n'était pas mon avis négatif, mais ...) Java détecte déjà les variables non initialisées et ne vous laissera pas lire une variable jusqu'à ce que chaque chemin de code menant à son utilisation ait définitivement attribué une valeur. Donc, vous ne tirez pas grand profit de la possibilité d’affecter nullune valeur par défaut. Au contraire: vous feriez mieux de ne pas "initialiser" la variable du tout. La définition de toute valeur par défaut, même null, ferme le compilateur ... mais l'empêche également de détecter l'absence d' affectation utile le long de tous les chemins de code. Ainsi, une erreur que le compilateur aurait pu intercepter se glisse dans l'exécution.
cHao
@ cHao Mais que se passe-t - il s'il n'y a pas de valeur par défaut raisonnable pour initialiser la variable? Vous pouvez le définir sur 0.0, ou -1, ou Integer.MAX_VALUE, ou False, mais au final, vous ne savez pas s'il s'agit de la valeur par défaut ou d'une valeur réelle affectée à cette variable. Dans les cas où cela est important, avoir une nullvaleur peut être plus clair.
tobias_k
Ce n'est pas plus clair, c'est juste plus facile de dire au compilateur de ne pas vous avertir d'une logique peu claire jusqu'à ce qu'un bogue se soit déjà propagé. : P Dans les cas où il n'y a pas de valeur par défaut raisonnable, n'initialisez pas la variable. Ne le définissez qu'une fois que vous avez une valeur raisonnable à y mettre. Cela permet à Java de vous arrêter lors de la compilation si votre valeur n'est peut-être pas définie correctement.
cHao
@ cHao Je voulais dire, il peut y avoir des cas où vous ne pouvez pas initialiser la variable et que vous devez la gérer au moment de l'exécution. Dans de tels cas, un "défaut" comme "null", qui peut clairement être identifié comme étant le défaut, ou "non initié", pourrait être préférable à toute initialisation à la compilation qui pourrait également être une valeur valide.
tobias_k
Avez-vous un tel cas en tête? Les cas auxquels je peux penser se situent aux limites de l'interface (par exemple: en tant que type de paramètre ou de retour) ... mais même là, ils seraient bien meilleurs s'ils étaient encapsulés. (Les personnes nues nullprésentent toute une série de problèmes, y compris une paranoïa nulle.) Dans une fonction, une variable qui peut en réalité ne pas être initialisée au moment de l’utilisation indique généralement des cas non couverts. (L'analyse d'attribution définie est simpliste, donc des faux positifs sont possibles. Mais vous pouvez souvent les résoudre en simplifiant la logique, alors.)
cHao
2

En termes simples:

Vous utilisez les wrappers lorsque vous devez ajouter des éléments aux collections.

Les collections ne peuvent pas contenir de primitives.

Tulains Córdova
la source
0

Comme le mentionne m3th0dman, les fonctionnalités Java sont automatiques. Pensez au niveau le plus bas possible et vous verrez que la valeur primitive de la sélection automatique (entrée ou sortie) implique des cycles d'horloge utilisés dans certaines tâches inutiles si vous utilisez des types de données natifs autour de votre application.

En règle générale, vous devriez essayer d'utiliser des types de données natifs autant que possible.

Silvarion
la source