Java Language
autoboxing
Zoeken…
Invoering
Autoboxing is de automatische conversie die Java-compiler tussen primitieve typen en hun overeenkomstige objectwikkelklassen uitvoert . Voorbeeld, het converteren van int -> Integer, dubbel -> Dubbel ... Als de conversie de andere kant op gaat, wordt dit unboxing genoemd. Meestal wordt dit gebruikt in collecties die niet anders dan objecten kunnen bevatten, waar primitieve typen boksen nodig is voordat u ze in de collectie plaatst.
Opmerkingen
Autoboxing kan prestatieproblemen veroorzaken wanneer het vaak in uw code wordt gebruikt.
Int en Integer door elkaar gebruiken
Aangezien u generieke typen met hulpprogramma-klassen gebruikt, merkt u vaak dat nummertypen niet erg nuttig zijn wanneer ze als objecttypen worden opgegeven, omdat ze niet gelijk zijn aan hun primitieve tegenhangers.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
Gelukkig kunnen expressies die evalueren tot int
worden gebruikt in plaats van een geheel Integer
wanneer dat nodig is.
for (int i = 0; i < 10; i++)
ints.add(i);
De ints.add(i);
verklaring is gelijk aan:
ints.add(Integer.valueOf(i));
En behoudt eigenschappen van Integer#valueOf
zoals het hebben van dezelfde Integer
objecten in de cache van de JVM wanneer deze zich binnen het bereik voor nummercache bevindt.
Dit geldt ook voor:
-
byte
enByte
-
short
enShort
-
float
enFloat
-
double
enDouble
-
long
enLong
-
char
enCharacter
-
boolean
enBoolean
Voorzichtigheid is echter geboden in onduidelijke situaties. Overweeg de volgende code:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
De java.util.List
-interface bevat zowel een remove(int index)
( List
-interface methode) en een remove(Object o)
(methode overgenomen van java.util.Collection
). In dit geval vindt geen boksen plaats en wordt remove(int index)
aangeroepen.
Nog een voorbeeld van vreemd Java-codegedrag veroorzaakt door autoboxing van gehele getallen met waarden in het bereik van -128
tot 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
Dit gebeurt omdat de operator >=
impliciet intValue()
aanroept die int
retourneert terwijl ==
referenties vergelijkt, niet de int
waarden.
Standaard slaat Java waarden op in bereik [-128, 127]
, dus de operator ==
werkt omdat de Integers
in dit bereik naar dezelfde objecten verwijzen als hun waarden hetzelfde zijn. De maximale waarde van het cachebereik kan worden gedefinieerd met de optie -XX:AutoBoxCacheMax
JVM. Dus als u het programma uitvoert met -XX:AutoBoxCacheMax=1000
, wordt de volgende code true
afgedrukt:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Boolean in if-instructie gebruiken
Vanwege automatisch unboxing kan men een Boolean
in een if
statement:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Dat werkt voor while
, do while
en de voorwaarde in de for
statements ook.
Merk op dat als de Boolean
null
, een NullPointerException
in de conversie wordt gegooid.
Auto-unboxing kan leiden tot NullPointerException
Deze code bestaat uit:
Integer arg = null;
int x = arg;
Maar het crasht tijdens runtime met een java.lang.NullPointerException
op de tweede regel.
Het probleem is dat een primitieve int
geen null
kan hebben.
Dit is een minimalistisch voorbeeld, maar in de praktijk manifesteert het zich vaak in meer verfijnde vormen. De NullPointerException
is niet erg intuïtief en biedt vaak weinig hulp bij het vinden van dergelijke bugs.
Vertrouw op autoboxing en auto-unboxing met zorg, zorg ervoor dat unboxed waarden niet zal hebben null
-waarden tijdens de uitvoering.
Geheugen en computeroverhead bij autoboxing
Autoboxing kan zorgen voor een aanzienlijke geheugenoverhead. Bijvoorbeeld:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
kost doorgaans een aanzienlijke hoeveelheid geheugen (ongeveer 60 kb voor 6 k werkelijke gegevens).
Bovendien vereisen integers in de box meestal extra round-trips in het geheugen, en maken daardoor CPU-caches minder effectief. In het bovenstaande voorbeeld is het geopende geheugen verspreid over vijf verschillende locaties die zich in geheel verschillende gebieden van het geheugen kunnen bevinden: 1. het HashMap
object, 2. het HashMap
Entry[] table
, 3. het Entry
object, 4. de entrys key
object (boksen van de primitieve sleutel), 5. het entrys value
object (boksen van de primitieve waarde).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
boxed
lezen vereist twee geheugentoegangen, toegang tot primitive
slechts één.
Bij het ophalen van gegevens van deze kaart, de schijnbaar onschuldige code
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
is gelijk aan:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
Meestal zorgt de bovenstaande code voor het maken en ophalen van een Integer
object voor elke Map#get(Integer)
bewerking. (Zie opmerking hieronder voor meer informatie.)
Om deze overhead te verminderen, bieden verschillende bibliotheken geoptimaliseerde collecties voor primitieve typen waarvoor geen boksen nodig is. Naast het vermijden van boksen overhead, vereist deze verzameling ongeveer 4x minder geheugen per invoer. Hoewel Java Hotspot mogelijk de autoboxing kan optimaliseren door te werken met objecten op de stapel in plaats van de heap, is het niet mogelijk om de overhead van het geheugen en de resulterende geheugenindirection te optimaliseren.
Java 8-streams hebben ook geoptimaliseerde interfaces voor primitieve gegevenstypen, zoals IntStream
waarvoor geen boksen nodig is.
Opmerking: een typische Java-runtime onderhoudt een eenvoudige cache van Integer
en een ander primitief wrapper-object dat wordt gebruikt door de valueOf
fabrieksmethoden en door autoboxing. Voor Integer
is het standaardbereik van deze cache -128 tot +127. Sommige JVM's bieden een JVM-opdrachtregeloptie voor het wijzigen van de cachegrootte / het bereik.
Verschillende gevallen waarin Integer en int door elkaar kunnen worden gebruikt
Geval 1: tijdens gebruik in plaats van methodeargumenten.
Als een methode een object van wrapper-klasse als argument vereist. Vervolgens kan het argument door een variabele van het respectieve primitieve type worden doorgegeven en vice versa.
Voorbeeld:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Geval 2: Bij het doorgeven van retourwaarden:
Wanneer een methode een variabele van het primitieve type retourneert, kan een object van de overeenkomstige wrapper-klasse worden doorgegeven als de retourwaarde uitwisselbaar en vice versa.
Voorbeeld:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Geval 3: Tijdens het uitvoeren van bewerkingen.
Wanneer bewerkingen op getallen worden uitgevoerd, kunnen de variabele van het primitieve type en het object van de respectieve wrapper-klasse door elkaar worden gebruikt.
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
Valkuil : vergeet niet een waarde van een wrapper-klasse te initialiseren of toe te wijzen.
Bij het gebruik van het wrapper class-object en de primitieve variabele door elkaar heen nooit vergeten of missen om een wrapper class-object te initialiseren of een waarde toe te kennen, anders kan dit tijdens runtime leiden tot een nulaanwijzeruitzondering.
Voorbeeld:
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
}
In het bovenstaande voorbeeld is de waarde van het object niet toegewezen en niet geïnitialiseerd en loopt het programma dus tijdens runtime een uitzondering voor de aanwijzer.