Java Language
autoboxing
Sök…
Introduktion
Autoboxing är den automatiska omvandlingen som Java-kompilatorn gör mellan primitiva typer och deras motsvarande objektomslagsklasser. Exempel, konvertering av int -> heltal, dubbel -> dubbel ... Om konverteringen går åt andra hållet kallas detta unboxing. Vanligtvis används detta i samlingar som inte kan innehålla andra än objekt, där primitiva typer av boxning behövs innan de ställs in i samlingen.
Anmärkningar
Autoboxing kan ha prestandaproblem när de används ofta i din kod.
Använd int och heltal utbytbart
När du använder generiska typer med verktygsklasser kan du ofta upptäcka att numretyper inte är till stor hjälp när de anges som objekttyper, eftersom de inte är lika med deras primitiva motsvarigheter.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
Lyckligtvis kan uttryck som utvärderar till int
användas i stället för ett Integer
när det behövs.
for (int i = 0; i < 10; i++)
ints.add(i);
ints.add(i);
uttalande motsvarar:
ints.add(Integer.valueOf(i));
Och behåller egenskaper från Integer#valueOf
som att ha samma Integer
objekt cachade av JVM när det ligger inom cacheintervallet.
Detta gäller också för:
-
byte
ochByte
-
short
ochShort
-
float
ochFloat
-
double
ochDouble
-
long
ochLong
-
char
ochCharacter
-
boolean
ochBoolean
Man måste dock vara försiktig i tvetydiga situationer. Tänk på följande kod:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
Den java.util.List
gränssnittet innehåller både en remove(int index)
( List
gränssnittsmetoden) och en remove(Object o)
(metod ärvs från java.util.Collection
). I detta fall sker ingen boxning och kallas remove(int index)
.
Ytterligare ett exempel på konstigt Java-kodbeteende orsakat av autoboxande heltal med värden i området från -128
till 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
Detta händer eftersom >=
operatör implicit kallar intValue()
som returnerar int
medan ==
jämför referenser , inte int
värdena.
Som standard cachar Java värden i intervallet [-128, 127]
, så operatören ==
fungerar eftersom Integers
i detta intervall hänvisar till samma objekt om deras värden är desamma. Det maximala värdet för det cachebara området kan definieras med -XX:AutoBoxCacheMax
JVM-alternativet. Så om du kör programmet med -XX:AutoBoxCacheMax=1000
kommer följande kod att skrivas ut true
:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Använda Boolean i if-uttalande
På grund av automatisk avbokning kan man använda en Boolean
i ett if
uttalande:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Det fungerar en while
, do while
och villkoret i for
uttalanden också.
Observera att om Boolean
är null
kommer en NullPointerException
att kastas i konverteringen.
Auto-unboxing kan leda till NullPointerException
Den här koden sammanställer:
Integer arg = null;
int x = arg;
Men det kraschar vid körning med en java.lang.NullPointerException
på den andra raden.
Problemet är att en primitiv int
inte kan ha ett null
.
Detta är ett minimalistiskt exempel, men i praktiken manifesteras det ofta i mer sofistikerade former. NullPointerException
är inte särskilt intuitiv och är ofta liten hjälp för att hitta sådana buggar.
Lita på autoboxing och auto-unboxing med försiktighet, se till att värden för unboxed inte har null
vid körning.
Memory and Computational Overhead of Autoboxing
Autoboxning kan komma att ha en betydande minneskostnad. Till exempel:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
kommer vanligtvis att konsumera en betydande mängd minne (cirka 60 kb för 6 k faktiska data).
Dessutom kräver boxade heltal vanligtvis ytterligare rundturer i minnet, och gör därför CPU-cachar mindre effektiva. I exemplet ovan sprids det HashMap
minnet ut till fem olika platser som kan vara i helt olika regioner i minnet: 1. HashMap
objektet, 2. kartans Entry[] table
-tabellobjekt, 3. Entry
objektet, 4. entrys key
object (boxning av primitiv nyckel), 5. entrys value
object (boxning av primitivt värde).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
boxed
läsa boxed
krävs två minnesåtkomstar, endast åtkomst till primitive
.
När du hämtar data från den här kartan, den till synes oskyldiga koden
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
är ekvivalent med:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
Vanligtvis orsakar ovanstående kod skapandet och skräpsamlingen av ett Integer
objekt för varje Map#get(Integer)
-operation. (Se not nedan för mer information.)
För att minska detta overhead erbjuder flera bibliotek optimerade samlingar för primitiva typer som inte kräver boxning. Förutom att undvika boxningskostnaderna kräver dessa samlingar cirka 4x mindre minne per post. Medan Java Hotspot kanske kan optimera autoboxningen genom att arbeta med objekt på stacken istället för högen, är det inte möjligt att optimera minnesomkostningen och resultera i indirekt minne.
Java 8-strömmar har också optimerade gränssnitt för primitiva datatyper, till exempel IntStream
som inte kräver boxning.
Obs: en typisk Java-runtime upprätthåller en enkel cache för Integer
och andra primitiva omslagobjekt som används av valueOf
fabriksmetoderna och genom autoboxing. För Integer
är standardintervallet för denna cache -128 till +127. Vissa JVM: er har ett JVM-kommandoradsalternativ för att ändra cache-storlek / intervall.
Olika fall när heltal och int kan användas omväxlande
Fall 1: När det används i stället för metodargument.
Om en metod kräver ett objekt av omslagsklass som argument. Därefter kan argumentet omväxlande ges en variabel av respektive primitivtyp och vice versa.
Exempel:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Fall 2: När du returnerar värden:
När en metod returnerar en primitiv typvariabel, kan ett objekt i motsvarande omslagsklass passeras som retursvärdet utbytbart och vice versa.
Exempel:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Fall 3: När du utför operationer.
Varje gång man utför operationer på siffror kan variabeln av primitivtyp och objekt i respektive omslagsklass användas omväxlande.
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
Fallgrop : Kom ihåg att initialisera eller tilldela ett värde till ett objekt i omslagsklassen.
När du använder omslagsklassobjekt och primitiv variabel omväxlande kan du aldrig glömma eller missa att initialisera eller tilldela ett värde till omslagsklassobjektet annars kan det leda till undantag från nollpekaren vid körning.
Exempel:
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
}
I exemplet ovan är värdet på objektet inte tilldelat och uninitialiserat och därför kommer programmet under körning att undvika nollpekareundantag. Så klart från exemplet ovan bör värdet på objekt aldrig lämnas oinitialiserat och otilldelat.