En Java, la différence entre `int` et `Integer` paraît simple au premier regard, mais elle change vite la manière d’écrire des boucles, de manipuler des collections et d’éviter des bugs silencieux. Le premier est rapide et compact; le second ajoute la souplesse d’un objet, avec des cas d’usage précis et quelques pièges bien connus. Je vais clarifier ce que fait la classe enveloppe, quand elle est utile, comment convertir proprement des valeurs et quels réflexes adoptent les équipes qui gardent leur code lisible en 2026.
L’essentiel à retenir
- `int` est un type primitif sur 32 bits signé; sa plage va de -2 147 483 648 à 2 147 483 647.
- `Integer` est la version objet de `int`; elle sert surtout dans les API qui attendent des objets, comme les collections génériques.
- `parseInt` renvoie un primitif, `valueOf` renvoie un objet.
- `==` n’est pas un bon test pour comparer deux wrappers; utilisez `equals` ou comparez des valeurs primitives.
- Le `null` et l’autoboxing peuvent déclencher des `NullPointerException` à des endroits qui semblent anodins.
- En code sensible à la performance, j’évite le boxing inutile et je préfère les types primitifs ou les API spécialisées.
Comprendre la différence entre `int` et `Integer`
Le type `int` stocke directement une valeur numérique. Il est simple, rapide et très bien adapté aux compteurs, aux indices de tableau, aux tailles et à tout ce qui relève du calcul pur. `Integer`, lui, est l’objet qui enveloppe cette valeur: la documentation officielle de Java le décrit comme la classe wrapper de `int`, avec un champ interne de type `int`, des constantes utiles et plusieurs méthodes de conversion.
Le point à retenir est simple: `Integer` ne remplace pas `int`; il répond à un besoin différent. En pratique, la classe `java.lang.Integer` apporte surtout trois choses utiles: la possibilité de travailler avec un objet, la compatibilité avec les API génériques, et des méthodes comme `parseInt`, `valueOf`, `decode` ou `compareTo`. Elle expose aussi des constantes concrètes comme `SIZE` à 32 bits et `BYTES` à 4, ce qui rappelle bien qu’on reste sur un entier signé classique.
| Critère | `int` | `Integer` | Impact pratique |
|---|---|---|---|
| Nature | Type primitif | Objet de type wrapper | `int` est plus direct; `Integer` s’intègre mieux aux API objet |
| Valeur nulle | Impossible | Possible | `Integer` peut représenter une valeur absente, mais il faut gérer `null` |
| Mémoire et vitesse | Très compact | Plus lourd, car objet | Le boxing ajoute du coût et peut peser dans les boucles chaudes |
| Comparaison | `==` compare la valeur | `==` compare la référence | `equals` est généralement le bon choix pour les wrappers |
| Conversion | Nécessite un parsing ou un cast adapté | Dispose de méthodes dédiées | `parseInt`, `valueOf`, `decode` simplifient la lecture et l’écriture |
| Usage typique | Calcul, compteur, index | Collections, champs nullable, API objet | Le choix dépend surtout du contexte, pas d’une préférence théorique |
Deux détails méritent une attention particulière en revue de code. D’abord, `Integer.valueOf(int)` met en cache les valeurs de -128 à 127, et peut en cacher d’autres; je n’utilise donc jamais l’identité d’objet comme signal métier. Ensuite, les constructeurs `Integer(int)` et `Integer(String)` sont déconseillés: la voie normale passe par `valueOf` ou par le type primitif quand l’objet n’apporte rien. C’est ce qui fait la différence entre un code simplement correct et un code qui reste stable à long terme.
Pour décider vite entre les deux, je regarde ensuite les contextes concrets où chacun devient le bon choix.

Quand je choisis le type primitif et quand je garde le wrapper
Dans la majorité des calculs, je pars sur `int` sans hésiter. C’est le bon réflexe pour les boucles serrées, les indices, les tailles, les scores, les compteurs et les opérations bit à bit. Dès que la valeur n’a pas besoin d’exister comme objet, le primitif garde le code plus simple et plus rapide.
Je garde `int` quand la valeur est purement numérique
- Compteurs de lignes, de clics ou d’événements.
- Indices dans les tableaux et les listes.
- Calculs dans une boucle intensive ou un traitement de masse.
- Tailles, bornes, durées et métriques techniques.
Lire aussi : PrintWriter en Java - Écrivez du texte sans pièges !
Je prends `Integer` quand l’objet a une vraie utilité
- Valeur nullable dans une base de données, un JSON ou une API métier.
- Collections génériques comme `List
` ou `Map `. - Interfaces ou frameworks qui attendent un objet, pas un primitif.
- Cas où l’absence de valeur a un sens métier et doit se distinguer de `0`.
Si l’absence de valeur doit être explicite, je regarde souvent `OptionalInt` avant de choisir `Integer`. C’est plus clair qu’un `null` dispersé dans le code, surtout quand on veut éviter de confondre “pas de donnée” et “zéro”. Et si la valeur peut dépasser 2,1 milliards, je ne force pas `int`: je passe directement à `long`, ce qui évite une mauvaise base de départ.
Une fois le bon type choisi, la vraie difficulté devient la conversion d’entrée, parce que c’est là que les erreurs arrivent le plus souvent.
Convertir des chaînes et des nombres sans se tromper
Le trio à connaître est simple. `parseInt` transforme une chaîne en `int`, `valueOf` transforme une chaîne ou un entier en `Integer`, et `decode` lit aussi des formes comme `0x2A`, `#2A` ou `010` selon le préfixe. Le bon choix dépend surtout de ce que vous voulez obtenir à la sortie: un primitif pour calculer, un objet pour stocker ou transmettre.
String raw = "42";
int age = Integer.parseInt(raw.trim());
Integer boxedAge = Integer.valueOf(raw.trim());
int hex = Integer.decode("0x2A");
int binary = Integer.parseInt("1010", 2);
String text = Integer.toString(age);
int backToPrimitive = boxedAge.intValue();Je nettoie presque toujours l’entrée avec `trim()` avant de parser, parce que `Integer.parseInt` n’accepte pas les espaces parasites. Si je lis une valeur issue d’un fichier de configuration, d’un formulaire ou d’un paramètre HTTP, je traite la chaîne comme non fiable et je capture `NumberFormatException` au bon endroit. Pour des traitements plus avancés, la surcharge `parseInt(CharSequence, beginIndex, endIndex, radix)` est utile, car elle peut éviter une copie intermédiaire lorsqu’on travaille sur une sous-chaîne ou un buffer.
La conversion est mécanique; le vrai terrain miné, ce sont les comparaisons, l’autoboxing et les effets de bord qui suivent.
Les erreurs qui reviennent le plus souvent
Quand je relis du code Java, je retrouve toujours les mêmes pièges autour des wrappers. Ils sont discrets, parce qu’ils ne cassent pas toujours tout de suite, mais ils finissent par produire des comportements incohérents ou des exceptions difficiles à relier à leur cause.
| Symptôme | Pourquoi ça arrive | Correction pragmatique |
|---|---|---|
| `==` donne un résultat surprenant | Avec `Integer`, `==` compare la référence, pas la valeur | Utiliser `equals` ou convertir en primitif avant comparaison |
| `NullPointerException` pendant un calcul | L’autounboxing essaie de transformer `null` en `int` | Valider la donnée avant l’opération ou éviter `null` |
| `NumberFormatException` au parsing | La chaîne contient des espaces, des symboles ou dépasse la plage de `int` | Nettoyer, valider et gérer l’erreur explicitement |
| Code ancien avec `new Integer(...)` | Le constructeur est déconseillé et n’apporte rien ici | Passer à `valueOf` ou à `parseInt` selon le besoin |
| Synchronisation sur un `Integer` | La classe est value-based; la doc déconseille cet usage | Utiliser un verrou dédié, pas une instance wrapper |
Le cas de l’autounboxing mérite une vigilance particulière. Un code comme `Integer qty = null; int total = qty + 1;` paraît banal, mais il provoque une exception au moment où Java essaie d’extraire la valeur primitive. C’est typiquement le genre de bug qui passe un peu trop facilement en revue de code si on ne regarde pas la sémantique du `null`. Je préfère donc faire ce contrôle le plus tôt possible, plutôt que de compter sur le compilateur ou sur la chance.
Ces bugs sont agaçants, mais le coût du boxing compte aussi quand le volume augmente ou que le traitement devient sensible à la latence.
Performance, mémoire et code moderne
Chaque `Integer` est un objet. Cela veut dire allocation, référence, et potentiellement pression supplémentaire sur le ramasse-miettes. Dans une application de taille moyenne, ce n’est pas dramatique; dans une boucle chaude, un pipeline de données ou un traitement de volume, la différence devient visible. La règle que j’applique est stricte: je n’ajoute du boxing que si le contexte l’exige vraiment.
- Je garde `int[]` quand je n’ai besoin que d’un stockage numérique compact.
- Je privilégie `IntStream` plutôt que `Stream
` pour les traitements numériques intensifs. - Je laisse `List
` ou `Map ` quand l’API objet est nécessaire, mais je garde un œil sur les volumes. - Je remplace les conversions implicites répétées par des primitives quand la boucle tourne souvent.
- Je préfère `Integer.valueOf` à `new Integer(...)`, parce que la fabrique statique est plus cohérente et profite du cache interne.
La documentation Oracle rappelle d’ailleurs un principe simple: l’autoboxing sert surtout à combler le décalage entre primitifs et références, pas à supprimer la différence entre les deux. C’est une bonne boussole mentale. Si l’algorithme est vraiment numérique, je reste sur les primitives. Si je suis obligé de parler à une API objet, j’accepte le wrapper, mais je le fais consciemment.
À partir de là, j’applique une règle de lecture très stable en revue de code, et elle suffit dans la plupart des projets Java.
La règle simple que je garde en revue de code
Je pars toujours du primitif par défaut, puis je bascule vers `Integer` seulement si l’API, la sérialisation ou la présence d’une valeur nulle l’exigent réellement. Ce réflexe réduit les conversions inutiles, les comparaisons ambiguës et les surprises de l’autoboxing. Il rend aussi les intentions plus lisibles pour le prochain développeur qui ouvrira le fichier.
- Si la valeur est purement numérique, je garde `int`.
- Si la valeur peut manquer, je regarde d’abord `OptionalInt`, puis `Integer` si le contexte l’impose.
- Si je dois lire une chaîne, je prends `parseInt` pour un primitif et `valueOf` seulement si j’ai besoin d’un objet.
- Si je traite des volumes élevés, je limite les conversions implicites et je préfère les API primitives.
- Si la valeur peut dépasser 2,1 milliards, je passe directement à `long` plutôt que de forcer `int`.
Ce cadre suffit dans la plupart des bases Java: il garde le code simple, réduit les bugs de boxing et laisse la classe `Integer` à sa vraie place, celle d’un outil de bord, pas d’un réflexe par défaut.