Java Primitives versus Objects

Overview

tässä opetusohjelmassa näytämme Java primitives-tyyppien ja niiden käärittyjen vastineiden käytön hyvät ja huonot puolet.

Java Type System

Javalla on kaksiosainen tyyppijärjestelmä, joka koostuu primitiiveistä kuten int, boolean ja viitetyypeistä kuten Integer, Boolean. Jokainen primitiivinen tyyppi vastaa vertailutyyppiä.

jokainen objekti sisältää yhden vastaavan alkeistyypin arvon. Käärinluokat ovat muuttumattomia (niin, että niiden tila ei voi muuttua, kun objekti on rakennettu) ja ovat lopullisia (niin, että emme voi periä niistä).

hupun alla Java suorittaa muunnoksen primitiivisen tyypin ja viitetyypin välillä, jos todellinen tyyppi on eri kuin ilmoitettu:

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

prosessia primitiivisen tyypin muuntamiseksi referenssityypiksi kutsutaan autoboksingiksi, päinvastaista prosessia unboxingiksi.

plussat ja miinukset

päätös siitä, mitä objektia käytetään, perustuu siihen, mitä sovelluksen suorituskykyä yritämme saavuttaa, kuinka paljon käytettävissä olevaa muistia meillä on, käytettävissä olevan muistin määrä ja mitä oletusarvoja meidän tulisi käsitellä.

Jos emme kohtaa mitään noista, saatamme jättää nämä näkökohdat huomioimatta, vaikka ne kannattaa tuntea.

3, 1. Yhden kohteen muistijälki

pelkästään viitteen osalta primitiivisillä tyyppimuuttujilla on seuraava vaikutus muistiin:

  • boolean – 1 bitti
  • tavu – 8 bittiä
  • lyhyt, char – 16 bittiä
  • int, float – 32 bittiä
  • pitkä, tupla – 64 bittiä

käytännössä nämä arvot voivat vaihdella virtuaalikoneen toteutuksesta riippuen. Esimerkiksi Oraclen VM: ssä Boolen tyyppi on kartoitettu int-arvoihin 0 ja 1, joten siihen tarvitaan 32 bittiä, kuten tässä on kuvattu: primitiiviset tyypit ja arvot.

näiden tyyppien muuttujat elävät pinossa, joten niihin pääsee nopeasti käsiksi. Lisätietoja, suosittelemme opetusohjelma Java muisti malli.

vertailutyypit ovat esineitä, ne elävät kasassa ja niihin on suhteellisen hidas pääsy. Heillä on tietty yleiskuva alkeellisista vastineistaan.

yleiskustannusten konkreettiset arvot ovat yleensä JVM-spesifisiä. Tässä esitämme tulokset 64-bittiselle virtuaalikoneelle näillä parametreilla:

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)

saadaksemme objektin sisäisen rakenteen, saatamme käyttää Java Object Layout-työkalua (katso toinen opetusohjelma Kuinka saada objektin koko).

on käynyt ilmi, että yhdellä viittaustyypin esiintymällä tällä JVM: llä on 128 bittiä, paitsi pitkällä ja kaksinkertaisella, jotka vievät 192 bittiä:

  • Boolean – 128 bittiä
  • Byte – 128 bittiä
  • lyhyt, merkki – 128 bittiä
  • kokonaisluku, Float – 128 bittiä
  • pitkä, Double – 192 bittiä

voimme nähdä, että yksi Boolen tyypin muuttuja vie yhtä paljon tilaa kuin 128 primitiivistä, kun taas yksi kokonaislukumuuttuja vie yhtä paljon tilaa kuin neljä int-muuttujaa.

3, 2. Muistin jalanjälki matriiseille

tilanne muuttuu kiinnostavammaksi, jos vertaamme, kuinka paljon muistia vievät tarkasteltavien tyyppien ryhmät.

kun luomme matriiseja, joissa on eri määrä alkuaineita jokaiselle tyypille, saadaan kuvaaja:

, joka osoittaa, että tyypit on ryhmitelty neljään perheeseen sen suhteen, miten muisti m(s) riippuu joukon alkuaineiden määrästä s:

  • pitkä, kaksinkertainen: m(S) = 128 + 64 s
  • lyhyt, merkki: m(S) = 128 + 64
  • tavu, boolean: M(S) = 128 + 64
  • loput: m (S) = 128 + 64

, jossa hakasulkeet tarkoittavat vakiokattofunktiota.

yllättäen alkeistyyppien pitkät ja kaksinkertaiset ryhmät kuluttavat enemmän muistia kuin niiden kääreluokat pitkät ja kaksinkertaiset.

voidaan nähdä joko, että alkeellisten tyyppien yksielementtiset ryhmät ovat lähes aina kalliimpia (paitsi pitkät ja kaksinkertaiset) kuin vastaava vertailutyyppi.

3, 3. Suorituskyky

Java-koodin suorituskyky on varsin hienovarainen kysymys, se riippuu hyvin paljon laitteistosta, jolla koodi toimii, kääntäjästä, joka saattaa suorittaa tiettyjä optimointeja, virtuaalikoneen tilasta, käyttöjärjestelmän muiden prosessien aktiivisuudesta.

kuten olemme jo maininneet, primitiiviset tyypit elävät kasassa, kun taas viitetyypit elävät kasassa. Tämä on hallitseva tekijä, joka määrittää, kuinka nopeasti objektit saada käyttää.

osoittaaksemme, kuinka paljon primitiivisten tyyppien operaatiot ovat nopeampia kuin kääreluokkien, luodaan viiden miljoonan alkuaineen ryhmä, jossa kaikki elementit ovat yhtä suuret viimeistä lukuun ottamatta; sitten suoritamme tälle alkuaineelle haun:

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

ja vertaamme tämän operaation suorituskykyä silloin, kun joukko sisältää primitiivisten tyyppien muuttujia, ja silloin, kun se sisältää referenssityyppien objekteja.

käytämme tunnettua JMH-vertailutyökalua (katso tutoriaalimme sen käytöstä), ja tähystysoperaation tulokset voi tiivistää tähän kaavioon:

näinkin yksinkertaisessa operaatiossa huomaamme, että kääreluokkien operointiin tarvitaan enemmän aikaa.

monimutkaisemmissa operaatioissa, kuten yhteen -, kerto-tai jakolaskussa, nopeusero saattaa kasvaa huimasti.

3, 4. Oletusarvot

Alkeistyyppien oletusarvot ovat 0 (vastaavassa esityksessä eli 0, 0.0d etc) numeerisille tyypeille, false Boolen tyypille, \u0000 char-tyypille. Kääreluokkien oletusarvo on nolla.

se tarkoittaa, että alkeistyypit voivat saada arvoja vain verkkotunnuksistaan, kun taas viitetyypit saattavat saada arvon (null), joka ei jossain mielessä kuulu niiden verkkotunnuksiin.

vaikka ei pidäkään hyvänä käytäntönä jättää muuttujia aloittamatta, joskus saatamme antaa arvon sen luomisen jälkeen.

tällaisessa tilanteessa, kun primitiivisen tyypin muuttujan arvo on yhtä suuri kuin sen tyypin oletusarvo, pitäisi selvittää, onko muuttuja todella alustettu.

tällaista ongelmaa ei ole wrapper-luokan muuttujissa, sillä nollaluku on melko selvä osoitus siitä, että muuttujaa ei ole alustettu.

käyttö

kuten olemme nähneet, alkeelliset tyypit ovat paljon nopeampia ja vaativat paljon vähemmän muistia. Siksi voisimme mieluummin käyttää niitä.

toisaalta nykyinen Java-kielimäärittely ei salli primitiivisten tyyppien käyttöä parametrisoiduissa tyypeissä (generics), Java-kokoelmissa tai Reflection API: ssa.

kun sovelluksemme tarvitsee kokoelmia, joissa on suuri määrä elementtejä, meidän tulisi harkita mahdollisimman ”taloudellisemman” tyypin taulukoiden käyttöä, kuten yllä olevasta kuviosta käy ilmi.

johtopäätös

se tämä opetusohjelma havainnollisti, että javan objektit ovat hitaampia ja niillä on suurempi muistivaikutus kuin alkeellisilla analogeillaan.

kuten aina, koodinpätkiä löytyy arkistostamme Githubista.

Aloita kevät 5 ja kevät Boot 2, läpi Learn Spring-kurssin:

>> tsekkaa kurssi

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *