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>();
Java SE 7
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 e Byte
  • short e Short
  • float e Float
  • double e Double
  • long e Long
  • char e Character
  • boolean e Boolean

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.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow