översikt
i denna handledning visar vi för-och nackdelar med att använda Java primitiva typer och deras inslagna motsvarigheter.
Java type System
Java har ett tvåfaldigt typsystem som består av primitiva som int, booleska och referenstyper som heltal, Booleska. Varje primitiv typ motsvarar en referenstyp.
varje objekt innehåller ett enda värde av motsvarande primitiva typ. Wrapper-klasserna är oföränderliga (så att deras tillstånd inte kan förändras när objektet är konstruerat) och är slutgiltiga (så att vi inte kan ärva från dem).
under huven utför Java en konvertering mellan primitiva och referenstyper om en faktisk typ skiljer sig från den deklarerade:
Integer j = 1; // autoboxingint i = new Integer(1); // unboxing
processen att konvertera en primitiv typ till en referens kallas autoboxing, den motsatta processen kallas unboxing.
fördelar och nackdelar
beslutet vilket objekt som ska användas baseras på vilken applikationsprestanda vi försöker uppnå, hur mycket tillgängligt minne Vi har, mängden tillgängligt minne och vilka standardvärden vi ska hantera.
Om vi inte möter någon av dem kan vi ignorera dessa överväganden men det är värt att känna till dem.
3.1. Single Item Memory Footprint
bara för referensen har de primitiva typvariablerna följande inverkan på minnet:
- boolean – 1 bit
- byte – 8 bitar
- kort, char – 16 bitar
- int, float – 32 bitar
- lång, dubbel – 64 bitar
i praktiken kan dessa värden variera beroende på den virtuella maskinens implementering. I Oracles VM mappas till exempel den booleska typen till int-värden 0 och 1, Så det tar 32 bitar, som beskrivs här: primitiva typer och värden.
variabler av dessa typer bor i stapeln och nås därför snabbt. För detaljerna rekommenderar vi vår handledning om Java-minnesmodellen.
referenstyperna är objekt, de lever på högen och är relativt långsamma att komma åt. De har en viss overhead om sina primitiva motsvarigheter.
de konkreta värdena för overhead är i allmänhet JVM-specifika. Här presenterar vi resultat för en 64-bitars virtuell maskin med dessa parametrar:
java 10.0.1 2018-04-17Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
för att få ett objekts interna struktur kan vi använda Java Object Layout tool (se vår annan handledning om hur du får storleken på ett objekt).
det visar sig att en enda instans av en referenstyp på denna JVM upptar 128 bitar förutom lång och dubbel som upptar 192 bitar:
- Boolean – 128 bitar
- Byte – 128 bitar
- kort, tecken – 128 bitar
- heltal, Float – 128 bitar
- lång, dubbel – 192 bitar
Vi kan se att en enda variabel av boolesk typ upptar så mycket utrymme som 128 primitiva, medan en heltalsvariabel upptar så mycket utrymme som fyra int.
3.2. Minnesavtryck för Arrays
situationen blir mer intressant om vi jämför hur mycket minne upptar arrays av de aktuella typerna.
När vi skapar arrayer med olika antal element för varje typ får vi en plot:
som visar att typerna är grupperade i fyra familjer med avseende på hur minnet m(s) beror på antalet element s i matrisen:
- lång, dubbel: m(s) = 128 + 64 s
- kort, röding: m(s) s) = 128 + 64
- byte, booleska: m(s) = 128 + 64
- resten: m (s) = 128 + 64
där hakparenteserna anger Standard takfunktionen.
överraskande förbrukar arrays av primitiva typer lång och dubbel mer minne än deras wrapper klasser lång och dubbel.
vi kan se antingen att enstaka element av primitiva typer nästan alltid är dyrare (förutom långa och dubbla) än motsvarande referenstyp.
3, 3. Prestanda
prestanda för en Java-kod är en ganska subtil fråga, det beror mycket på hårdvaran som koden körs på, på kompilatorn som kan utföra vissa optimeringar, på den virtuella maskinens tillstånd, på aktiviteten hos andra processer i operativsystemet.
som vi redan har nämnt bor de primitiva typerna i stapeln medan referenstyperna bor i högen. Detta är en dominerande faktor som avgör hur snabbt objekten får nås.
för att visa hur mycket operationerna för primitiva typer är snabbare än de för wrapper-klasser, låt oss skapa en fem miljoner elementarray där alla element är lika med undantag för den sista; sedan utför vi en sökning för det elementet:
while (!pivot.equals(elements)) { index++;}
och jämför prestanda för denna operation för fallet när matrisen innehåller variabler av primitiva typer och för fallet när det innehåller objekt av referenstyperna.
Vi använder det välkända JMH-benchmarkingverktyget (se vår handledning om hur du använder det), och resultaten av uppslagningsoperationen kan sammanfattas i detta diagram:
även för en så enkel operation kan vi se att det krävs mer tid att utföra operationen för wrapper-klasser.
Vid mer komplicerade operationer som summering, multiplikation eller delning kan skillnaden i hastighet skjuta i höjden.
3, 4. Standardvärden
standardvärden för primitiva typer är 0 (i motsvarande representation, dvs 0, 0.0D etc) för numeriska typer, falskt för den booleska typen, \u0000 för rödingstypen. För wrapper-klasserna är standardvärdet null.
det betyder att de primitiva typerna endast kan förvärva värden från sina domäner, medan referenstyperna kan förvärva ett värde (null) som i viss mening inte tillhör deras domäner.
även om det inte anses vara en bra praxis att lämna variabler oinitierade, kan vi ibland tilldela ett värde efter skapandet.
i en sådan situation, när en primitiv typvariabel har ett värde som är lika med dess typ Standard EN, bör vi ta reda på om variabeln verkligen har initialiserats.
det finns inget sådant problem med en wrapper klassvariabler eftersom null-värdet är en ganska tydlig indikation på att variabeln inte har initialiserats.
användning
som vi har sett är de primitiva typerna mycket snabbare och kräver mycket mindre minne. Därför kanske vi föredrar att använda dem.
å andra sidan tillåter nuvarande Java-språkspecifikation inte användning av primitiva typer i parametriserade typer (generika), i Java-samlingarna eller Reflection API.
När vår applikation behöver Samlingar med ett stort antal element, bör vi överväga att använda arrays med så mer ”ekonomisk” typ som möjligt, som det illustreras på diagrammet ovan.
slutsats
det här handledningen illustrerade vi att objekten i Java är långsammare och har en större minneseffekt än deras primitiva analoger.
som alltid finns kodavsnitt i vårt arkiv på GitHub.
Kom igång med Spring 5 och Spring Boot 2, genom Learn Spring course:
>> kolla in kursen