Java Language
autoboxing
Ricerca…
introduzione
Autoboxing è la conversione automatica effettuata dal compilatore Java tra i tipi primitivi e le corrispondenti classi wrapper degli oggetti. Esempio, convertendo int -> Integer, double -> Double ... Se la conversione va diversamente, viene chiamato unboxing. In genere, questo viene utilizzato in raccolte che non possono contenere oggetti diversi da Oggetti, in cui sono necessari i tipi di boxing primitivi prima di impostarli nella raccolta.
Osservazioni
Autoboxing può avere problemi di prestazioni se usato frequentemente nel tuo codice.
Utilizzando int e Integer in modo intercambiabile
Poiché si utilizzano tipi generici con classi di utilità, è possibile che i tipi di numeri non siano molto utili quando vengono specificati come tipi di oggetto, in quanto non sono uguali alle loro controparti primitive.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
Fortunatamente, le espressioni che valutano in int
possono essere utilizzate al posto di un Integer
quando è necessario.
for (int i = 0; i < 10; i++)
ints.add(i);
Il ints.add(i);
la dichiarazione è equivalente a:
ints.add(Integer.valueOf(i));
E conserva le proprietà dal valore Integer#valueOf
come se avesse gli stessi oggetti Integer
memorizzati nella cache da JVM quando si trova nell'intervallo di memorizzazione nella cache dei numeri.
Questo vale anche per:
-
byte
eByte
-
short
eShort
-
float
eFloat
-
double
eDouble
-
long
eLong
-
char
eCharacter
-
boolean
eBoolean
Bisogna fare attenzione, tuttavia, in situazioni ambigue. Considera il seguente codice:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
L'interfaccia java.util.List
contiene sia un remove(int index)
(metodo di interfaccia List
) che un remove(Object o)
(metodo ereditato da java.util.Collection
). In questo caso non avviene la boxe e si remove(int index)
.
Un altro esempio di strano comportamento del codice Java causato da autoboxing Interi con valori nell'intervallo da -128
a 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
Ciò accade perché >=
operatore chiama implicitamente intValue()
che restituisce int
mentre ==
confronta i riferimenti , non i valori int
.
Per impostazione predefinita, Java memorizza nella cache i valori nell'intervallo [-128, 127]
, quindi l'operatore ==
funziona perché i Integers
di questo intervallo fanno riferimento agli stessi oggetti se i loro valori sono uguali. Il valore massimo dell'intervallo memorizzabile nella cache può essere definito con -XX:AutoBoxCacheMax
opzione -XX:AutoBoxCacheMax
JVM. Quindi, se esegui il programma con -XX:AutoBoxCacheMax=1000
, il seguente codice verrà stampato true
:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Usando l'istruzione booleana in if
A causa dell'auto-unboxing, si può usare un Boolean
in un'istruzione if
:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Questo funziona per while
, do while
e la condizione nelle istruzioni for
pure.
Nota che, se il Boolean
è null
, verrà NullPointerException
una NullPointerException
nella conversione.
L'unbox automatico può portare a NullPointerException
Questo codice compila:
Integer arg = null;
int x = arg;
Ma si romperà in fase di esecuzione con una java.lang.NullPointerException
sulla seconda riga.
Il problema è che un int
primitivo non può avere un valore null
.
Questo è un esempio minimalista, ma nella pratica si manifesta spesso in forme più sofisticate. La NullPointerException
non è molto intuitiva e spesso è di scarso aiuto nell'individuazione di tali bug.
Affidati alla funzione di autoboxing e auto-unboxing con attenzione, assicurati che i valori non condivisi non abbiano valori null
in fase di runtime.
Memoria e overhead computazionale di Autoboxing
Autoboxing può arrivare a un sovraccarico di memoria sostanziale. Per esempio:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
in genere consuma una notevole quantità di memoria (circa 60kb per 6k di dati effettivi).
Inoltre, gli interi in scatola di solito richiedono ulteriori round trip nella memoria, e quindi rendono meno efficaci le cache della CPU. Nell'esempio sopra, la memoria a cui si accede è distribuita in cinque posizioni diverse che possono trovarsi in regioni completamente diverse della memoria: 1. l'oggetto HashMap
, 2. l'oggetto della Entry[] table
della mappa, 3. l'oggetto Entry
, 4. il affida l'oggetto key
(inscatola la chiave primitiva), 5. l'oggetto value
entrys (che inscatola il valore primitivo).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
La lettura in boxed
richiede due accessi alla memoria, l'accesso alla primitive
solo uno.
Quando si ricevono dati da questa mappa, il codice apparentemente innocente
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
è equivalente a:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
In genere, il codice precedente causa la creazione e la garbage collection di un oggetto Integer
per ogni operazione Map#get(Integer)
. (Vedi la nota sotto per maggiori dettagli.)
Per ridurre questo overhead, diverse librerie offrono raccolte ottimizzate per tipi primitivi che non richiedono il pugilato. Oltre a evitare l'overhead di boxe, questa raccolta richiederà circa 4 volte meno memoria per voce. Mentre Java Hotspot può essere in grado di ottimizzare l'autoboxing lavorando con gli oggetti nello stack anziché con l'heap, non è possibile ottimizzare il sovraccarico della memoria e la conseguente deduzione della memoria.
Gli stream Java 8 hanno anche interfacce ottimizzate per tipi di dati primitivi, come IntStream
che non richiede il pugilato.
Nota: un tipico runtime Java mantiene una semplice cache di Integer
e di un altro oggetto wrapper primitivo utilizzato dai metodi valueOf
factory e autoboxing. Per Integer
, l'intervallo predefinito di questa cache è compreso tra -128 e +127. Alcune JVM forniscono un'opzione della riga di comando JVM per modificare la dimensione / intervallo della cache.
Casi diversi Quando Integer e int possono essere utilizzati in modo intercambiabile
Caso 1: durante l'utilizzo al posto degli argomenti del metodo.
Se un metodo richiede un oggetto di classe wrapper come argomento. Quindi l'argomento può essere passato una variabile del rispettivo tipo primitivo e viceversa.
Esempio:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Caso 2: durante il passaggio dei valori di ritorno:
Quando un metodo restituisce una variabile di tipo primitivo, un oggetto della corrispondente classe wrapper può essere passato come valore di ritorno in modo intercambiabile e viceversa.
Esempio:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Caso 3: durante l'esecuzione delle operazioni.
Ogni volta che si eseguono operazioni su numeri, la variabile del tipo primitivo e l'oggetto della rispettiva classe wrapper possono essere usati in modo intercambiabile.
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
Trappola : ricorda di inizializzare o assegnare un valore a un oggetto della classe wrapper.
Mentre si usa l'oggetto classe wrapper e la variabile primitiva intercambiabilmente non si dimentica o manca di inizializzare o assegnare un valore all'oggetto classe wrapper altrimenti può portare all'eccezione del puntatore nullo in fase di runtime.
Esempio:
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
}
Nell'esempio sopra, il valore dell'oggetto non è assegnato e non è inizializzato e quindi in fase di esecuzione il programma verrà eseguito in un'eccezione di puntatore nullo. Pertanto, come illustrato nell'esempio precedente, il valore dell'oggetto non deve mai essere non inizializzato e non assegnato.