Perché usiamo autoboxing e unboxing in Java?

Autoboxing è la conversione automatica che il compilatore Java effettua tra i tipi primitivi e le corrispondenti classi wrapper degli oggetti. Ad esempio, convertendo un int in un intero, un doppio in un doppio e così via. Se la conversione va dall’altra parte, questo è chiamato unboxing.

Quindi perché ne abbiamo bisogno e perché usiamo autoboxing e unboxing in Java?

È necessario un certo contesto per comprendere appieno la ragione principale alla base di questo.

Primitivi contro classi

Le variabili primitive in Java contengono valori (un numero intero, un numero binario in virgola mobile a precisione doppia, ecc.). Poiché questi valori possono avere lunghezze diverse , le variabili che li contengono possono anche avere lunghezze diverse (considerare float contro double ).

D’altra parte, le variabili di class contengono riferimenti a istanze. I riferimenti vengono in genere implementati come puntatori (o qualcosa di molto simile ai puntatori) in molte lingue. Queste cose hanno tipicamente le stesse dimensioni, indipendentemente dalle dimensioni delle istanze a cui si riferiscono ( Object , String , Integer , ecc.).

Questa proprietà delle variabili di class rende i riferimenti che contengono intercambiabili (in una certa misura). Questo ci permette di fare ciò che chiamiamo sostituzione : in senso lato, per usare un’istanza di un tipo particolare come un’istanza di un altro tipo correlato (usa una String come Object , ad esempio).

Le variabili primitive non sono intercambiabili nello stesso modo, né l’una con l’altra, né con l’ Object . La ragione più ovvia per questo (ma non l’unico motivo) è la loro differenza di dimensioni. Ciò rende scomodi i tipi primitivi in ​​questo senso, ma ne abbiamo ancora bisogno nella lingua (per ragioni che si riducono principalmente alle prestazioni).

Generici e cancellazione di tipi

I tipi generici sono tipi con uno o più parametri di tipo (il numero esatto è chiamato generalità ). Ad esempio, la List definizioni di tipo generico List ha un parametro di tipo T , che può essere Object (producendo un tipo concreto List ), String ( List ), Integer ( List ) e così via .

I tipi generici sono molto più complicati di quelli non generici. Quando sono stati introdotti in Java (dopo la sua versione iniziale), per evitare di apportare modifiche radicali alla JVM e possibilmente compromettere la compatibilità con i binari più vecchi, i creatori di Java hanno deciso di implementare tipi generici nel modo meno invasivo: tutti i tipi concreti di List sono, infatti, compilati per (l’equivalente binario di) List (per altri tipi, il limite può essere qualcosa di diverso da Object , ma si ottiene il punto). Le informazioni sui parametri di tipo aritico e di tipo generico vengono perse in questo processo , motivo per cui la chiamiamo cancellazione di tipo .

Mettendo insieme i due

Ora il problema è la combinazione delle realtà di cui sopra: se List diventa List in tutti i casi, allora T deve sempre essere un tipo che può essere assegnato direttamente ad Object . Tutto il resto non può essere permesso. Dato che, come abbiamo detto prima, int , float e double non sono intercambiabili con Object , non ci può essere un List , List o List (a meno che non esista un’implementazione significativamente più complicata di generici la JVM).

Ma Java offre tipi come Integer , Float e Double che avvolgono queste primitive in istanze di class, rendendole effettivamente sostituibili come Object , consentendo così ai tipi generici di lavorare indirettamente anche con le primitive (perché si può avere List , List , List e così via).

Il processo di creazione di un Integer da un int , un Float da un float e così via, è chiamato boxing . Il contrario è chiamato unboxing . Dato che è necessario eseguire il box delle primitive ogni volta che si desidera utilizzarle come Object è scomodo, ci sono casi in cui la lingua lo fa automaticamente – si chiama autoboxing .

Auto Boxing è usato per convertire i tipi di dati primitivi nei loro oggetti di class wrapper. La class wrapper fornisce un’ampia gamma di funzioni da eseguire sui tipi primitivi. L’esempio più comune è:

 int a = 56; Integer i = a; // Auto Boxing 

È necessario perché i programmatori sono in grado di scrivere direttamente codice e JVM si prenderà cura del Boxing e dell’Unboxing.

Auto Boxing è utile anche quando lavoriamo con i tipi java.util.Collection. Quando vogliamo creare una raccolta di tipi primitivi non possiamo creare direttamente una raccolta di un tipo primitivo, possiamo creare solo collezioni di oggetti. Per esempio :

 ArrayList al = new ArrayList(); // not supported ArrayList al = new ArrayList(); // supported al.add(45); //auto Boxing 

Classi wrapper

Ciascuno degli 8 tipi primitivi di Java (byte, short, int, float, char, double, boolean, long) hava una class Wrapper separata associata a loro. Queste classi Wrapper hanno metodi predefiniti per la preformazione di operazioni utili su tipi di dati primitivi.

Uso delle classi wrapper

 String s = "45"; int a = Integer.parseInt(s); // sets the value of a to 45. 

Ci sono molte funzioni utili fornite dalle classi Wrapper. Dai un’occhiata ai documenti java qui

Unboxing è l’opposto di Auto Boxing in cui convertiamo l’object della class wrapper al suo tipo primitivo. Questo viene fatto automaticamente da JVM in modo che possiamo usare le classi wrapper per certe operazioni e poi riconvertirle in tipi primitivi in ​​quanto le primitive danno come risultato un’elaborazione più veloce. Per esempio :

 Integer s = 45; int a = s; auto UnBoxing; 

In caso di collezioni che funzionano con oggetti, viene utilizzato solo l’annullamento automatico. Ecco come :

 ArrayList al = new ArrayList(); al.add(45); int a = al.get(0); // returns the object of Integer . Automatically Unboxed . 

I tipi primitivi (non object) hanno la giustificazione in efficienza.

I tipi primitivi int, boolean, double sono dati immediati, mentre Object s sono riferimenti. Quindi campi (o variabili)

 int i; double x; Object s; 

avrebbe bisogno di memoria locale 4 + 8 + 8? dove per l’object è memorizzato solo il riferimento (indirizzo) alla memoria.

Usando i wrapper di oggetti Integer, Double e altri, si introdurrebbe un riferimento indiretto a qualche istanza Integer / Double nella memoria heap.

Perché la boxe è necessaria?

Questa è una questione di portata relativa. In un futuro java si prevede di avere un ArrayList , sollevando tipi primitivi.

Risposta: per ora un ArrayList funziona solo per Object, riservando spazio per un riferimento a un object e gestendo allo stesso modo la garbage collection. Quindi i tipi generici sono oggetti bambini. Quindi, se si voleva un ArrayList di valori in virgola mobile, era necessario avvolgere un doppio in un object Double.

Qui Java differisce dal tradizionale C ++ con i suoi modelli: classi C ++ vector, vector creerebbe due prodotti di compilazione. Il design Java è andato ad avere una ArrayList.class, non avendo bisogno per ogni tipo di parametro di un nuovo prodotto compilato.

Quindi senza il boxing su Object si dovrebbe compilare classi per ogni occorrenza di un tipo di parametro. In concreto: ogni raccolta o class contenitore necessiterebbe di una versione per Object, int, double, boolean. La versione per Object gestiva tutte le classi child.

In realtà, la necessità di tale diversificazione esisteva già in Java SE per IntBuffer, CharBuffer, DoubleBuffer, … che operano su int, char, double. È stato risolto in modo hacky generando queste fonti da una comune.

A partire da JDK 5, java ha aggiunto due importanti funzioni: autoboxing e autounboxing. AutoBoxing è il processo per il quale un tipo primitivo viene automaticamente incapsulato nel wrapper equivalente ogni volta che tale object è necessario. Non è necessario build un object in modo esplicito. Auto-unboxing è il processo tramite il quale il valore di un object incapsulato viene automaticamente estratto da un type wrapper quando il suo valore è richiesto. Non è necessario chiamare un metodo come intValue () o doubleValue () .

L’aggiunta di autoboxing e auto-unboxing semplifica enormemente la scrittura degli algoritmi , eliminando l’esca manualmente dalla boxe e dall’unboxing dei valori. È anche utile evitare errori . È anche molto importante per i generici , che operano solo sugli oggetti. Infine, l’autoboxing facilita il lavoro con Collections Framework .

perché abbiamo (dis) la boxe?

rendere il codice di scrittura dove mescoliamo le primitive e le loro alternative Object Oriented (OO) più comode / meno dettagliate.

perché abbiamo i primitivi e le loro alternative OO?

i tipi primitivi non sono classi (a differenza di C #), quindi non sono sottoclassi di Object e non possono essere sovrascritti.

abbiamo primitive come int per motivi di prestazioni, e le alternative Object come Integer per i benefici della programmazione OO, e come punto minore, per avere una buona posizione per costanti e metodi di utilità (Integer.MAX_VALUE e Integer.toString(int) ) .

I benefici OO sono visibili più facilmente con Generics ( List ), ma non sono limitati a questo, ad esempio:

 Number getMeSome(boolean wantInt) { if (wantInt) { return Integer.MAX_VALUE; } else { return Long.MAX_VALUE; } } 

Perché sono tipi diversi e per comodità. La prestazione è probabilmente la ragione per avere tipi primitivi.

Alcune strutture dati possono accettare solo oggetti, nessun tipo primitivo.

Esempio: la chiave in una HashMap.

Vedi questa domanda per ulteriori: HashMap e int come chiave

Ci sono altri buoni motivi, come un campo “int” in un database, che potrebbe essere anche NULL. Un int in Java non può essere nullo; un riferimento intero può. Autoboxing e unboxing forniscono una possibilità per evitare di scrivere codice estraneo nelle conversioni avanti e indietro.