Java primitívek és objektumok

1. Áttekintés

Ebben az oktatóanyagban bemutatjuk a Java primitív típusok és burkolt társaik használatának előnyeit és hátrányait.

2. Java típusú rendszer

A Java kétszeres típusú rendszerrel rendelkezik, amely primitívekből áll, mint pl int, logikai és referenciatípusok, például Egész szám,Logikai. Minden primitív típus megfelel egy referencia típusnak.

Minden objektum egyetlen, a megfelelő primitív típusú értéket tartalmaz. A burkoló osztályok megváltoztathatatlanok (hogy az állapotuk az objektum felépítése után ne változhasson) és véglegesek (hogy ne örököljünk tőlük).

A burkolat alatt a Java átalakítást hajt végre a primitív és a referencia típus között, ha egy tényleges típus eltér a deklarált típustól:

Egész j = 1; // autoboxing int i = új egész szám (1); // kicsomagolás 

A primitív típus referenciaként való átalakításának folyamatát autoboxolásnak, az ellenkezőjét kibontásnak nevezzük.

3. Érvek és ellenérvek

Az eldöntendő objektum alapja az, hogy milyen alkalmazási teljesítményt próbálunk elérni, mennyi szabad memóriánk van, mennyi memória áll rendelkezésre és milyen alapértelmezett értékeket kell kezelnünk.

Ha ezek egyikével sem nézünk szembe, figyelmen kívül hagyhatjuk ezeket a szempontokat, bár érdemes megismerni őket.

3.1. Egy elem memória lábnyoma

Csak a referencia kedvéért a primitív típusú változók a következő hatást gyakorolják a memóriára:

  • logikai - 1 bit
  • bájt - 8 bit
  • rövid, char - 16 bit
  • int, úszó - 32 bit
  • hosszú, dupla - 64 bit

A gyakorlatban ezek az értékek a Virtual Machine megvalósításától függően változhatnak. Az Oracle virtuális gépében például a logikai típust az 0 és 1 int értékekhez hozzárendelik, tehát 32 bit kell hozzá, az itt leírtak szerint: Primitív típusok és értékek.

Az ilyen típusú változók a veremben élnek, és ezért gyorsan elérhetők. A részletekhez javasoljuk a Java memóriamodell bemutatónkat.

A referencia típusok objektumok, a kupacon élnek és viszonylag lassan férnek hozzá. Bizonyos általános költségek vannak primitív társaikkal kapcsolatban.

A rezsi konkrét értékei általában JVM-specifikusak. Itt egy 64 bites virtuális gép eredményeit mutatjuk be a következő paraméterekkel:

java 10.0.1 2018-04-17 Java (TM) SE Futásidejű környezet 18.3 (10.0.1 + 10 build) Java HotSpot (TM) 64 bites kiszolgáló virtuális gép 18.3 (10.0.1 + 10 build, vegyes mód)

Az objektum belső struktúrájának megszerzéséhez használhatjuk a Java Object Layout eszközt (lásd egy másik oktatóanyagunkat az objektum méretének meghatározásáról).

Kiderült, hogy ezen a JVM-en egyetlen referencia típusú példány 128 bitet foglal el, kivéve Hosszú és Kettős amelyek 192 bitet foglalnak el:

  • Boolean - 128 bit
  • Bájt - 128 bit
  • Rövid, karakter - 128 bit
  • Egész, úszó - 128 bit
  • Hosszú, dupla - 192 bit

Láthatjuk, hogy egyetlen változója Logikai típus annyi helyet foglal el, mint 128 primitív, míg egy Egész szám a változó négy helyet foglal el int azok.

3.2. A memória lábnyoma tömbökhöz

Érdekesebbé válik a helyzet, ha összehasonlítjuk, hogy mennyi memória foglalja el a vizsgált típusú tömböket.

Amikor minden típushoz különféle számú elemet tartalmazó tömböket hozunk létre, akkor egy diagramot kapunk:

ez azt mutatja, hogy a típusok négy családba vannak csoportosítva az emlékezet módja szempontjából Kisasszony) a tömb elemeinek számától függ:

  • hosszú, kettős: m (s) = 128 + 64 s
  • rövid, char: m (s) = 128 + 64 [s / 4]
  • bájt, logikai érték: m (s) = 128 + 64 [s / 8]
  • a többi: m (s) = 128 + 64 [s / 2]

ahol a szögletes zárójel jelöli a szokásos mennyezeti funkciót.

Meglepő módon a primitív hosszú és kettős típusú tömbök több memóriát fogyasztanak, mint a burkolóosztályaik Hosszú és Kettős.

Vagy ezt láthatjuk A primitív egyelemű tömbök szinte mindig drágábbak (a hosszú és a kettős kivételével), mint a megfelelő referencia típus.

3.3. Teljesítmény

A Java-kód teljesítménye meglehetősen finom kérdés, nagyon függ a hardvertől, amelyen a kód fut, az optimalizálást esetleg végrehajtó fordítótól, a virtuális gép állapotától, a folyamat többi folyamatától. operációs rendszer.

Mint már említettük, a primitív típusok a veremben élnek, míg a referencia típusok a kupacban élnek. Ez egy domináns tényező, amely meghatározza, hogy az objektumok milyen gyorsan érhetők el.

Annak bemutatásához, hogy a primitív típusok műveletei mennyivel gyorsabbak, mint a burkoló osztályokéi, hozzunk létre egy ötmilliós elemtömböt, amelyben az összes elem egyenlő az utolsó kivételével; akkor keresést hajtunk végre az elemre:

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

és hasonlítsa össze ennek a műveletnek a teljesítményét abban az esetben, ha a tömb primitív típusú változókat tartalmaz, és arra az esetre, amikor a referencia típusú objektumokat tartalmazza.

Használjuk a jól ismert JMH benchmarking eszközt (lásd a bemutatónkat a használatáról), és a keresési művelet eredményeit ebben a táblázatban foglalhatjuk össze:

Még egy ilyen egyszerű műveletnél is láthatjuk, hogy több időre van szükség a művelet végrehajtásához burkoló osztályoknál.

Bonyolultabb műveletek, például összegzés, szorzás vagy osztás esetén a sebességkülönbség az egekbe szökhet.

3.4. Alapértelmezett értékek

A primitív típusok alapértelmezett értékei: 0 (a megfelelő ábrázolásban, azaz 0, 0,0d stb.) numerikus típusoknál, hamis a logikai típushoz, \ u0000 a char típusra. A burkoló osztályok esetében az alapértelmezett érték nulla.

Ez azt jelenti, hogy a primitív típusok csak a tartományaikból szerezhetnek értékeket, míg a referencia típusok értéket (nulla), amely bizonyos értelemben nem tartozik a tartományaikhoz.

Bár nem tekinthető helyes gyakorlatnak a változók inicializálatlan hagyása, néha előfordulhat, hogy létrehozás után hozzárendelünk egy értéket.

Ilyen helyzetben, amikor egy primitív típusú változó értéke megegyezik az alapértelmezettel, meg kell találnunk, hogy a változó valóban inicializált-e.

A wrapper osztály változókkal nincs ilyen probléma a nulla Az érték egyértelműen jelzi, hogy a változót nem inicializálták.

4. Használat

Mint láttuk, a primitív típusok sokkal gyorsabbak és sokkal kevesebb memóriát igényelnek. Ezért érdemes lehet jobban használni őket.

Másrészt a jelenlegi Java nyelvi specifikáció nem teszi lehetővé a primitív típusok használatát a paraméterezett típusokban (generikus), a Java gyűjteményekben vagy a Reflection API-ban.

Ha alkalmazásunknak nagy számú elemet tartalmazó gyűjteményekre van szüksége, akkor fontolóra kell venni a lehető leggazdaságosabb típusú tömbök használatát, ahogyan ez a fenti ábrán látható.

5. Következtetés

Ebben az oktatóanyagban azt illusztráltuk, hogy a Java objektumok lassabbak és nagyobb memóriahatással bírnak, mint primitív analógjaik.

Mint mindig, a kódrészletek is megtalálhatók a GitHub tárolójában.