Java Primitives versus Objects

1. Oversikt

I denne opplæringen viser vi fordeler og ulemper ved å bruke Java primitive typer og deres innpakket kolleger.

2. Java Type System

Java har et dobbelt system som består av primitiver som int, boolsk og referansetyper som Heltall,Boolsk. Hver primitiv type tilsvarer en referansetype.

Hvert objekt inneholder en enkelt verdi av den tilsvarende primitive typen. De innpakningsklasser er uforanderlige (slik at deres tilstand ikke kan endres når objektet er konstruert) og er endelig (slik at vi ikke kan arve fra dem).

Under panseret utfører Java en konvertering mellom primitive og referansetyper hvis en faktisk type er forskjellig fra den deklarerte:

Heltall j = 1; // autoboxing int i = new Integer (1); // unboxing 

Prosessen med å konvertere en primitiv type til en referanse kalles autoboksing, den motsatte prosessen kalles unboxing.

3. Fordeler og ulemper

Beslutningen hvilket objekt som skal brukes er basert på hvilken applikasjonsytelse vi prøver å oppnå, hvor mye tilgjengelig minne vi har, mengden tilgjengelig minne og hvilke standardverdier vi skal håndtere.

Hvis vi ikke møter noen av disse, kan vi ignorere disse hensynene, men det er verdt å kjenne dem.

3.1. Enkelt element minnefotavtrykk

Bare for referansen har de primitive typevariablene følgende innvirkning på minnet:

  • boolsk - 1 bit
  • byte - 8 biter
  • kort, char - 16 bits
  • int, float - 32 bits
  • lang, dobbel - 64 bits

I praksis kan disse verdiene variere avhengig av implementeringen av Virtual Machine. I Oracles VM blir den boolske typen for eksempel kartlagt til int-verdiene 0 og 1, så det tar 32 biter, som beskrevet her: Primitive Types and Values.

Variabler av disse typene lever i bunken og nås raskt. For detaljer, anbefaler vi vår opplæring om Java-minnemodellen.

Referansetypene er objekter, de lever på dyngen og er relativt tregt å få tilgang til. De har en viss overhead angående sine primitive kolleger.

De konkrete verdiene til overhead er generelt JVM-spesifikke. Her presenterer vi resultater for en 64-biters virtuell maskin med disse parametrene:

java 10.0.1 2018-04-17 Java (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 å få et objekts interne struktur, kan vi bruke Java Object Layout-verktøyet (se vår annen veiledning om hvordan du får størrelsen på et objekt).

Det viser seg at en enkelt forekomst av en referansetype på denne JVM opptar 128 bits bortsett fra Lang og Dobbelt som opptar 192 bits:

  • Boolsk - 128 bits
  • Byte - 128 bits
  • Kort, karakter - 128 bits
  • Heltall, flyt - 128 bits
  • Lang, dobbel - 192 bits

Vi kan se at en enkelt variabel på Boolsk typen opptar like mye plass som 128 primitive, mens en Heltall variabel opptar så mye plass som fire int de.

3.2. Memory Footprint for Arrays

Situasjonen blir mer interessant hvis vi sammenligner hvor mye minne som opptar matriser av de aktuelle typene.

Når vi lager matriser med det forskjellige antall elementer for hver type, får vi et plott:

som viser at typene er gruppert i fire familier med hensyn til hvordan minnet m (s) avhenger av antall elementer s i matrisen:

  • lang, dobbel: m (s) = 128 + 64 s
  • kort, char: m (s) = 128 + 64 [s / 4]
  • byte, boolsk: m (s) = 128 + 64 [s / 8]
  • resten: m (s) = 128 + 64 [s / 2]

der firkantede parenteser betegner standard takfunksjon.

Overraskende nok bruker matriser av de primitive typene lang og dobbel mer minne enn deres omslagsklasser Lang og Dobbelt.

Vi kan se enten det enkeltelementarrayer av primitive typer er nesten alltid dyrere (bortsett fra lange og doble) enn den tilsvarende referansetypen.

3.3. Opptreden

Ytelsen til en Java-kode er ganske subtil, det avhenger veldig av maskinvaren koden kjører på, av kompilatoren som kan utføre visse optimaliseringer, av tilstanden til den virtuelle maskinen, av aktiviteten til andre prosesser i operativsystem.

Som vi allerede har nevnt, lever de primitive typene i stabelen mens referansetypene lever i dyngen. Dette er en dominerende faktor som avgjør hvor raskt gjenstandene får tilgang.

For å demonstrere hvor mye operasjonene for primitive typer er raskere enn for wrapper-klasser, la oss lage et fem millioner elementarray der alle elementene er like bortsett fra den siste; så utfører vi en oppslag for det elementet:

mens (! pivot.equals (elements [index])) {index ++; }

og sammenligne ytelsen til denne operasjonen for saken når matrisen inneholder variabler av de primitive typene, og for saken når den inneholder objekter av referansetypene.

Vi bruker det velkjente JMH benchmarking-verktøyet (se veiledningen vår om hvordan du bruker den), og resultatene av oppslagsoperasjonen kan oppsummeres i dette diagrammet:

Selv for en så enkel operasjon kan vi se at det kreves mer tid å utføre operasjonen for wrapper-klasser.

I tilfelle mer kompliserte operasjoner som summering, multiplikasjon eller divisjon, kan forskjellen i hastighet skyte i været.

3.4. Standardverdier

Standardverdiene for de primitive typene er 0 (i den tilsvarende representasjonen, dvs. 0, 0,0d etc) for numeriske typer, falsk for den boolske typen, \ u0000 for røyetypen. For innpakningsklassene er standardverdien null.

Det betyr at de primitive typene bare kan tilegne seg verdier fra domenene sine, mens referansetypene kan tilegne seg en verdi (null) som i en eller annen forstand ikke tilhører domenene deres.

Selv om det ikke betraktes som en god praksis å la variabler være uinitialisert, kan vi noen ganger tildele en verdi etter opprettelsen.

I en slik situasjon, når en primitiv typevariabel har en verdi som er lik den som standardverdien, bør vi finne ut om variabelen virkelig er initialisert.

Det er ikke noe slikt problem med en wrapper-klasse variabler siden null verdi er en tydelig indikasjon på at variabelen ikke er initialisert.

4. Bruk

Som vi har sett, er de primitive typene mye raskere og krever mye mindre minne. Derfor vil vi kanskje foretrekke å bruke dem.

På den annen side tillater ikke gjeldende Java-språkspesifikasjon bruk av primitive typer i de parametriserte typene (generiske), i Java-samlingene eller Reflection API.

Når søknaden vår trenger samlinger med et stort antall elementer, bør vi vurdere å bruke matriser med en mer “økonomisk” type som mulig, slik det er illustrert på plottet ovenfor.

5. Konklusjon

I denne opplæringen illustrerte vi at objektene i Java er tregere og har større minnepåvirkning enn deres primitive analoger.

Som alltid kan du finne kodebiter i depotet vårt på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found