Java Primitives versus Objects

oversigt

i denne vejledning viser vi fordele og ulemper ved at bruge Java primitive typer og deres indpakket modstykker.

Java Type System

Java har et todelt type system bestående af primitiver såsom int, boolsk og referencetyper såsom heltal, boolsk. Hver primitiv type svarer til en referencetype.

hvert objekt indeholder en enkelt værdi af den tilsvarende primitive type. Indpakningsklasserne er uforanderlige (så deres tilstand ikke kan ændre sig, når objektet er konstrueret) og er endelige (så vi ikke kan arve fra dem).

under hætten udfører Java en konvertering mellem de primitive og referencetyper, hvis en faktisk type er forskellig fra den deklarerede:

Integer j = 1; // autoboxingint i = new Integer(1); // unboxing

processen med at konvertere en primitiv type til en reference kaldes autoboksning, den modsatte proces kaldes unboksning.

fordele og ulemper

beslutningen, hvilket objekt der skal bruges, er baseret på, hvilken applikationsydelse vi prøver at opnå, hvor meget tilgængelig hukommelse vi har, mængden af tilgængelig hukommelse og hvilke standardværdier vi skal håndtere.

Hvis vi ikke står over for nogen af dem, kan vi ignorere disse overvejelser, selvom det er værd at kende dem.

3.1. Enkelt element hukommelse fodaftryk

bare for reference, de primitive type variabler har følgende indvirkning på hukommelsen:

  • boolean – 1 bit
  • byte – 8 bit
  • kort, char – 16 bit
  • int, float – 32 bit
  • Lang, dobbelt – 64 bit

i praksis kan disse værdier variere afhængigt af implementeringen af den virtuelle maskine. I Oracle ‘ s VM er den boolske type For eksempel kortlagt til int-værdier 0 og 1, så det tager 32 bit, som beskrevet her: Primitive typer og værdier.

variabler af disse typer lever i stakken og fås derfor hurtigt. For detaljerne anbefaler vi vores tutorial om Java-hukommelsesmodellen.referencetyperne er objekter, de bor på bunken og er relativt langsomme at få adgang til. De har en vis overhead vedrørende deres primitive modstykker.

de konkrete værdier af overhead er generelt JVM-specifikke. Her præsenterer vi resultater for en 64-bit virtuel maskine med disse parametre:

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)

for at få et objekts interne struktur kan vi bruge Java-Objektlayoutværktøjet (se vores en anden tutorial om, hvordan du får størrelsen på et objekt).

det viser sig, at en enkelt forekomst af en referencetype på denne JVM optager 128 bit bortset fra lange og dobbelte, der optager 192 bit:

  • Boolean – 128 bit
  • Byte – 128 bit
  • kort, Tegn – 128 bit
  • heltal, Float – 128 bit
  • Lang, Dobbelt – 192 bit
  • ul vi kan se, at en enkelt variabel af boolsk type optager så meget plads som 128 primitive, mens en heltalsvariabel optager så meget plads som fire int-variabler.

    3.2. Hukommelsesfodaftryk for Arrays

    situationen bliver mere interessant, hvis vi sammenligner, hvor meget hukommelse der optager arrays af de pågældende typer.

    når vi opretter arrays med det forskellige antal elementer for hver type, får vi et plot:

    , der viser, at typerne er grupperet i fire familier med hensyn til, hvordan hukommelsen m(s) afhænger af antallet af elementer s i arrayet:

    • lang, dobbelt: m(s) = 128 + 64 s
    • kort, char: m(s) (s) = 128 + 64
    • byte, boolsk: m (s) = 128 + 64
    • resten: m (s) = 128 + 64

    hvor de firkantede parenteser angiver standardloftfunktionen.

    overraskende forbruger arrays af de primitive typer lang og dobbelt mere hukommelse end deres indpakningsklasser lang og dobbelt.

    Vi kan enten se, at enkeltelementarrays af primitive typer næsten altid er dyrere (undtagen lange og dobbelte) end den tilsvarende referencetype.

    3, 3. Ydeevne

    udførelsen af en Java-kode er et ret subtilt problem, det afhænger meget af det udstyr, som koden kører på, på kompilatoren, der kan udføre visse optimeringer, på tilstanden af den virtuelle maskine, på aktiviteten af andre processer i operativsystemet.

    som vi allerede har nævnt, lever de primitive typer i stakken, mens referencetyperne lever i bunken. Dette er en dominerende faktor, der bestemmer, hvor hurtigt objekterne får adgang.

    for at demonstrere, hvor meget operationerne for primitive typer er hurtigere end dem for indpakningsklasser, lad os oprette et fem millioner elementarray, hvor alle elementer er ens bortset fra den sidste; så udfører vi et opslag for det element:

while (!pivot.equals(elements)) { index++;}

og sammenligner udførelsen af denne operation for sagen, når arrayet indeholder variabler af de primitive typer og for sagen, når den indeholder objekter af referencetyperne.

Vi bruger det velkendte JMH benchmarking-værktøj (se vores tutorial om, hvordan du bruger det), og resultaterne af opslagsoperationen kan opsummeres i dette diagram:

selv for en så enkel operation kan vi se, at det kræver mere tid at udføre operationen til indpakningsklasser.

i tilfælde af mere komplicerede operationer som summation, multiplikation eller division, kan forskellen i hastighed skyrocket.

3, 4. Standardværdier

standardværdier for de primitive typer er 0 (i den tilsvarende repræsentation, dvs.0, 0.0d osv.) for numeriske typer, falsk for den boolske type, \u0000 for char-typen. For indpakningsklasserne er standardværdien null.

det betyder, at de primitive typer kun kan erhverve værdier fra deres domæner, mens referencetyperne muligvis erhverver en værdi (null), der på en eller anden måde ikke hører til deres domæner.

selvom det ikke betragtes som en god praksis at forlade variabler uinitialiseret, kan vi nogle gange tildele en værdi efter oprettelsen.

i en sådan situation, når en primitiv type variabel har en værdi, der er lig med dens type standard EN, Skal vi finde ud af, om variablen virkelig er initialiseret.

der er ikke noget sådant problem med en indpakningsklassevariabler, da null-værdien er en ganske tydelig indikation af, at variablen ikke er initialiseret.

anvendelse

som vi har set, er de primitive typer meget hurtigere og kræver meget mindre hukommelse. Derfor vil vi måske foretrække at bruge dem.

på den anden side tillader nuværende Java-sprogspecifikation ikke brug af primitive typer i de parametriserede typer (generics), i Java-samlingerne eller Reflection API.

når vores applikation har brug for Samlinger med et stort antal elementer, bør vi overveje at bruge arrays med så mere “økonomisk” type som muligt, som det er illustreret på plottet ovenfor.

konklusion

Det denne tutorial illustrerede vi, at objekterne i Java er langsommere og har en større hukommelsespåvirkning end deres primitive analoger.

som altid kan kodestykker findes i vores lager på GitHub.

kom i gang med Spring 5 og Spring Boot 2, gennem Learn Spring course:

>>tjek kurset

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *