Java Primitive verzus objekty

1. Prehľad

V tomto tutoriáli si ukážeme výhody a nevýhody používania primitívnych typov Java a ich zalomených náprotivkov.

2. Systém typov Java

Java má systém dvojnásobného typu pozostávajúci z primitív ako napr int, boolovský a referenčné typy ako napr Celé číslo,Boolovský. Každý primitívny typ zodpovedá referenčnému typu.

Každý objekt obsahuje jednu hodnotu zodpovedajúceho primitívneho typu. The triedy obalov sú nemenné (aby sa ich stav nemohol zmeniť, akonáhle je objekt skonštruovaný) a sú konečné (aby sme po nich nemohli dediť).

Pod kapotou vykonáva Java prevod medzi primitívnym a referenčným typom, ak sa skutočný typ líši od deklarovaného:

Celé číslo j = 1; // autoboxing int i = new Integer (1); // rozbaľovanie 

Proces prevodu primitívneho typu na referenčný sa nazýva autoboxing, opačný proces sa nazýva unboxing.

3. Klady a zápory

Rozhodnutie, aký objekt sa má použiť, je založené na tom, aký výkon aplikácie sa snažíme dosiahnuť, koľko dostupnej pamäte máme, množstvo dostupnej pamäte a aké predvolené hodnoty by sme mali spracovať.

Ak nebudeme čeliť žiadnemu z nich, môžeme tieto úvahy ignorovať, hoci stojí za to ich poznať.

3.1. Stopa pamäte jednej položky

Len pre porovnanie, premenné primitívneho typu majú na pamäť nasledujúci vplyv:

  • boolean - 1 bit
  • bajt - 8 bitov
  • krátke, char - 16 bitov
  • int, float - 32 bitov
  • dlhý, dvojitý - 64 bitov

V praxi sa tieto hodnoty môžu líšiť v závislosti od implementácie virtuálneho počítača. Napríklad vo VM spoločnosti Oracle je boolovský typ namapovaný na int hodnoty 0 a 1, takže trvá 32 bitov, ako je popísané tu: Primitívne typy a hodnoty.

Premenné týchto typov žijú v zásobníku, a preto sú rýchlo dostupné. Podrobnosti nájdete v našom výučbe o pamäťovom modeli Java.

Referenčné typy sú objekty, žijú na halde a je k nim prístup pomerne pomalý. Majú určitú réžiu, pokiaľ ide o ich primitívne náprotivky.

Konkrétne hodnoty réžie sú všeobecne špecifické pre JVM. Tu uvádzame výsledky pre 64-bitový virtuálny stroj s týmito parametrami:

java 10.0.1 2018-04-17 Java (TM) SE Runtime Environment 18.3 (build 10.0.1 + 10) Java HotSpot (TM) 64-bitový server VM 18.3 (build 10.0.1 + 10, zmiešaný režim)

Na získanie vnútornej štruktúry objektu môžeme použiť nástroj Java Object Layout (pozrite si náš ďalší návod, ako zistiť veľkosť objektu).

Ukazuje sa, že jedna inštancia referenčného typu na tomto JVM zaberá 128 bitov okrem Dlhé a Dvojitý ktoré zaberajú 192 bitov:

  • Boolean - 128 bitov
  • Bajt - 128 bitov
  • Krátky, znak - 128 bitov
  • Celé číslo, float - 128 bitov
  • Dlhý, dvojitý - 192 bitov

Vidíme, že jedna premenná z Boolovský typ zaberá toľko miesta ako 128 primitívnych, zatiaľ čo jeden Celé číslo premenná zaberá toľko miesta ako štyri int tie.

3.2. Pamäťová stopa pre polia

Situácia sa stáva zaujímavejšou, ak porovnáme, koľko pamäte zaberá polia uvažovaných typov.

Keď vytvoríme polia s rôznym počtom prvkov pre každý typ, dostaneme graf:

ktorá demonštruje, že typy sú zoskupené do štyroch rodín, pokiaľ ide o spôsob pamäti pani) závisí od počtu prvkov s poľa:

  • dlhý, dvojitý: m (s) = 128 + 64 s
  • krátke, znak: m (s) = 128 + 64 [s / 4]
  • bajt, logická hodnota: m (s) = 128 + 64 [s / 8]
  • zvyšok: m (s) = 128 + 64 [s / 2]

kde hranaté zátvorky označujú štandardnú funkciu stropu.

Polia primitívnych typov dlhé a dvojité prekvapivo spotrebúvajú viac pamäte ako ich triedy obálky Dlhé a Dvojitý.

Môžeme vidieť buď to jednoprvkové polia primitívnych typov sú takmer vždy drahšie (okrem dlhých a dvojitých) ako zodpovedajúci referenčný typ.

3.3. Výkon

Výkon kódu Java je pomerne jemný problém, veľmi závisí od hardvéru, na ktorom kód beží, od kompilátora, ktorý môže vykonávať určité optimalizácie, od stavu virtuálneho stroja, od aktivity ostatných procesov v serveri. operačný systém.

Ako sme už uviedli, primitívne typy žijú v zásobníku, zatiaľ čo referenčné typy žijú v halde. Toto je dominantný faktor, ktorý určuje, ako rýchlo sa k objektom dostane.

Aby sme demonštrovali, o koľko sú operácie pre primitívne typy rýchlejšie ako pre triedy wrapper, vytvorme päťmiliónové pole prvkov, v ktorom sú všetky prvky rovnaké okrem posledného; potom vykonáme vyhľadávanie tohto prvku:

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

a porovnaj výkon tejto operácie pre prípad, keď pole obsahuje premenné primitívnych typov a pre prípad, keď obsahuje objekty referenčných typov.

Používame známy benchmarkingový nástroj JMH (pozrite si náš návod, ako ho používať) a výsledky vyhľadávacej operácie môžeme zhrnúť do tejto tabuľky:

Aj pre takúto jednoduchú operáciu vidíme, že vykonanie operácie pre triedy súhrnných programov si vyžaduje viac času.

V prípade komplikovanejších operácií, ako je súčet, násobenie alebo delenie, môže rozdiel v rýchlosti raketovo stúpať.

3.4. Základné hodnoty

Predvolené hodnoty primitívnych typov sú 0 (v zodpovedajúcom zastúpení, t.j. 0, 0,0 d atď.) pre číselné typy, nepravdivé pre boolovský typ, \ u0000 pre typ char. Pre triedy obálky je predvolená hodnota nulový.

To znamená, že primitívne typy môžu nadobúdať hodnoty iba zo svojich domén, zatiaľ čo referenčné typy môžu nadobúdať hodnotu (nulový), ktorý v istom zmysle nepatrí do ich domén.

Aj keď sa nepovažuje za dobrý postup ponechať premenné neinicializované, niekedy im môžeme priradiť hodnotu po jej vytvorení.

V takejto situácii, keď má premenná primitívneho typu hodnotu, ktorá sa rovná jej predvolenej hodnote, mali by sme zistiť, či bola premenná skutočne inicializovaná.

Od premennej triedy nie je taký problém s premennými triedy wrapper nulový hodnota je evidentným znakom toho, že premenná nebola inicializovaná.

4. Použitie

Ako sme videli, primitívne typy sú oveľa rýchlejšie a vyžadujú oveľa menej pamäte. Preto by sme mali chcieť uprednostniť ich použitie.

Na druhej strane súčasná špecifikácia jazyka Java neumožňuje použitie primitívnych typov v parametrizovaných typoch (generických), v zbierkach Java alebo v rozhraní Reflection API.

Keď naša aplikácia vyžaduje kolekcie s veľkým počtom prvkov, mali by sme zvážiť použitie polí s čo najekonomickejším typom, ako je to znázornené na obrázku vyššie.

5. Záver

V tomto výučbe sme ilustrovali, že objekty v Jave sú pomalšie a majú väčší vplyv na pamäť ako ich primitívne analógy.

Útržky kódu ako vždy nájdete v našom úložisku na GitHub.