Java Language
Autoboxing
Recherche…
Introduction
La compilation automatique est la conversion automatique effectuée par le compilateur Java entre les types primitifs et leurs classes d'encapsulation d'objet correspondantes. Exemple, conversion int -> Entier, double -> Double ... Si la conversion est inversée, cela s'appelle unboxing. En général, cela est utilisé dans les collections qui ne peuvent contenir que des objets, où les types primitifs de boxe sont nécessaires avant de les définir dans la collection.
Remarques
La programmation automatique peut avoir des problèmes de performance lorsqu'elle est fréquemment utilisée dans votre code.
Utilisation de int et Integer indifféremment
Lorsque vous utilisez des types génériques avec des classes d'utilitaires, vous pouvez souvent constater que les types de nombres ne sont pas très utiles lorsqu'ils sont spécifiés comme types d'objet, car ils ne sont pas égaux à leurs homologues primitifs.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
Heureusement, les expressions évaluées par int
peuvent être utilisées à la place d'un Integer
lorsque cela est nécessaire.
for (int i = 0; i < 10; i++)
ints.add(i);
Le ints.add(i);
déclaration équivaut à:
ints.add(Integer.valueOf(i));
Et conserve les propriétés d' Integer#valueOf
telles que la mise en cache des mêmes objets Integer
par la JVM lorsqu'elle se trouve dans la plage de mise en cache des nombres.
Cela vaut également pour:
-
byte
etByte
-
short
etShort
-
float
etFloat
-
double
etDouble
-
long
etLong
-
char
etCharacter
-
boolean
etBoolean
Il faut toutefois être prudent dans les situations ambiguës. Considérez le code suivant:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
L'interface java.util.List
contient à la fois une méthode remove(int index)
(méthode d'interface List
) et une remove(Object o)
(méthode héritée de java.util.Collection
). Dans ce cas, aucune boxe n'a lieu et on remove(int index)
.
Un autre exemple de comportement de code Java étrange causé par des entiers à base de boîtes de vitesses automatiques avec des valeurs comprises entre -128
et 127
:
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b); // true
System.out.println(c <= d); // true
System.out.println(c >= d); // true
System.out.println(c == d); // false
Cela se produit car l'opérateur >=
appelle implicitement intValue()
qui renvoie int
alors que ==
compare les références , pas les valeurs int
.
Par défaut, Java met en cache les valeurs dans la plage [-128, 127]
, de sorte que l'opérateur ==
fonctionne car les Integers
dans cette plage font référence aux mêmes objets si leurs valeurs sont identiques. La valeur maximale de la plage pouvant être mise en cache peut être définie avec l'option -XX:AutoBoxCacheMax
JVM. Donc, si vous exécutez le programme avec -XX:AutoBoxCacheMax=1000
, le code suivant s'imprimera true
:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Utilisation de booléen dans l'instruction if
En raison de la désactivation automatique, on peut utiliser un Boolean
dans une instruction if
:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Cela fonctionne pour while
, do while
et l'état dans le for
des déclarations aussi bien.
Notez que si le Boolean
est null
, une NullPointerException
sera lancée dans la conversion.
Unboxing automatique peut conduire à NullPointerException
Ce code compile:
Integer arg = null;
int x = arg;
Mais il se bloquera à l'exécution avec une java.lang.NullPointerException
sur la deuxième ligne.
Le problème est qu'un int
primitif ne peut pas avoir de valeur null
.
C'est un exemple minimaliste, mais dans la pratique, il se manifeste souvent sous des formes plus sophistiquées. Le NullPointerException
n'est pas très intuitif et aide souvent peu à localiser de tels bogues.
En prenant soin de la mise en boîte automatique et de la désencapsulation automatique avec soin, assurez-vous que les valeurs non débrayées n'auront pas de valeurs null
à l'exécution.
Mémoire et surcharge de calcul de l'autoboxing
L’autoboxing peut avoir une surcharge de mémoire importante. Par exemple:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
consommera généralement beaucoup de mémoire (environ 60 kb pour 6 k de données réelles).
De plus, les entiers encadrés nécessitent généralement des allers-retours supplémentaires dans la mémoire, ce qui rend les caches de processeur moins efficaces. Dans l'exemple ci-dessus, la mémoire accédée est répartie sur cinq emplacements différents qui peuvent se trouver dans des régions entièrement différentes de la mémoire: 1. l'objet HashMap
, 2. l'objet Entry[] table
, 3. l'objet Entry
, 4. le entrys key
object (boxing la clé primitive), 5. l'objet entry value
(encadrant la value
primitive).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
La lecture en boxed
nécessite deux accès mémoire, n'accédant qu'à une primitive
.
Lorsque vous obtenez des données de cette carte, le code apparemment innocent
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
est équivalent à:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
En général, le code ci-dessus provoque la création et la récupération de Integer
un objet Integer
pour chaque opération Map#get(Integer)
. (Voir la note ci-dessous pour plus de détails.)
Pour réduire ce surcoût, plusieurs bibliothèques offrent des collections optimisées pour les types primitifs ne nécessitant pas de boxe. En plus d'éviter la surcharge de boxe, ces collectes nécessiteront environ 4 fois moins de mémoire par entrée. Bien que Java Hotspot puisse optimiser l'autoboxing en travaillant avec des objets sur la pile au lieu du tas, il n'est pas possible d'optimiser la surcharge de mémoire et l'indirection de mémoire qui en résulte.
Les flux Java 8 ont également des interfaces optimisées pour les types de données primitifs, tels que IntStream
qui ne nécessitent pas de boxe.
Remarque: un environnement d'exécution Java classique conserve un cache simple d' Integer
et des autres objets wrapper primitifs utilisés par les méthodes factory valueOf
et par la création automatique de boîtes aux lettres. Pour Integer
, la plage par défaut de ce cache est comprise entre -128 et +127. Certaines machines virtuelles Java offrent une option de ligne de commande JVM pour modifier la taille / plage du cache.
Différents cas où Integer et int peuvent être utilisés indifféremment
Cas 1: En utilisant à la place des arguments de méthode.
Si une méthode nécessite un objet de classe wrapper en tant qu'argument.Alors de manière interchangeable, l'argument peut être passé à une variable du type primitif respectif et vice versa.
Exemple:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Cas 2: Lors du passage des valeurs de retour:
Lorsqu'une méthode retourne une variable de type primitive, un objet de la classe wrapper correspondante peut être transmis comme valeur de retour de manière interchangeable et inversement.
Exemple:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Cas 3: lors des opérations.
Chaque fois que l'on effectue des opérations sur des nombres, la variable de type primitif et l'objet de la classe d'encapsulation respective peuvent être utilisés indifféremment.
int i=5;
Integer j=new Integer(7);
int k=i+j;//Is a valid statement
Integer m=i+j;//Is also a valid statement
Piège : N'oubliez pas d'initialiser ou d'attribuer une valeur à un objet de la classe wrapper.
Lorsque vous utilisez un objet de classe wrapper et une variable primitive de manière interchangeable, n'oubliez jamais ou omettez d'initialiser ou d'attribuer une valeur à l'objet de classe wrapper. Sinon, cela peut entraîner une exception de pointeur nul lors de l'exécution.
Exemple:
public class Test{
Integer i;
int j;
public void met()
{j=i;//Null pointer exception
SOP(j);
SOP(i);}
public static void main(String[] args)
{Test t=new Test();
t.go();//Null pointer exception
}
Dans l'exemple ci-dessus, la valeur de l'objet est non affectée et non initialisée. Par conséquent, à l'exécution, le programme s'exécutera avec une exception de pointeur nul. Ainsi, la valeur de l'objet ne devrait jamais être non initialisée et non attribuée.