IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

4. diel - Referenčná dátové typy v Kotlin

V predchádzajúcom cvičení, Riešené úlohy k 3. lekcii OOP v Kotlin, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V minulej lekcii, Riešené úlohy k 3. lekcii OOP v Kotlin , sme si vytvorili svoj prvý poriadny objekt v Kotlin, bola ním hracia kocka. Všetky dátové typy v Kotlin sú tzv. Referenčné. Až doteraz nás to netrápilo, pretože sme pracovali len s číslami alebo texty, čo boli síce aj objekty, ale mali vždy iba jednu hodnotu. Čo sa teraz zmenilo je, že cez jednu premennú pristupujeme hneď k niekoľkým atribútom a dokonca môžeme cez rôzne premenné pristupovať k tomu samému objektu. Je dôležité, aby sme presne vedeli, čo sa vo vnútri programu deje, inak by nás v budúcnosti mohlo všeličo prekvapiť.

Aplikácia (resp. Jej vlákno) má operačným systémom pridelenú pamäť v podobe tzv. Zásobníka (stack). Jedná sa o veľmi rýchlu pamäť s priamym prístupom, jej veľkosť aplikácie nemôže ovplyvniť, prostriedky sú prideľované operačným systémom. Do tejto pamäti Kotlin ukladá len referencie na naše objekty. Druhá časť pamäte, kde sú tieto objekty skutočne uložené, sa nazýva halda. Prácu s referenciami si dnes vyskúšame.

Vytvorme si novú konzolovú aplikáciu a pridajme si k nej jednoduchú triedu, ktorá bude reprezentovať užívateľa nejakého systému. Pre názornosť vypustím komentáre a nebudem riešiť viditeľnosti:

class Uzivatel(var jmeno: String, var vek: Int) {
    override fun toString(): String {
        return jmeno
    }
}

Trieda má 2 verejné atribúty, konštruktor a preťažený toString(), aby sme používateľa mohli jednoducho vypisovať. Do funkcie main() v hlavnom súbore pridajme vytvorení inštancie tejto triedy:

val u: Uzivatel? = Uzivatel("Jan Novák", 28)

Premennú u sme rovno tiež deklarovali ako nullovatelnou, aby sme si referenciu mohli skúsiť aj vyprázdniť. Pozrime sa na aktuálnu situáciu v pamäti:

Zásobník a halda v pamäti počítača - Objektovo orientované programovanie v Kotlin

Všetky premenné Kotlin ukladá do pamäte vlastne na dvakrát, raz v zásobníku a raz v halde. V zásobníku je uložená iba tzv. Referencie, teda odkaz do haldy, kde sa potom nachádza naozajstný objekt.

Zásobník aj halda sa nachádzajú v pamäti RAM. Rozdiel je v prístupe a veľkosti. Halda je prakticky neobmedzená pamäť, ku ktorej je však prístup zložitejšia a tým pádom pomalší. Naopak zásobník je pamäť rýchla, ale veľkostne obmedzená.

Môžete sa pýtať, prečo je to takto urobené. Dôvodov je hneď niekoľko, poďme si niektoré vymenovať:

  1. Miesto vo stacku je obmedzené.
  2. Keď budeme chcieť použiť objekt viackrát (napr. Ho odovzdať ako parameter do niekoľkých metód), nemusíme ho v programe odovzdávať ako kópiu. Odovzdáme iba malý typ s referenciou na objekt namiesto toho, aby sme všeobecne pamäťovo náročný objekt kopírovali. Toto si vzápätí ukážeme.
  3. Pomocou referencií môžeme jednoducho vytvárať štruktúry s dynamickou veľkosťou, napr. Štruktúry podobné poli, do ktorých môžeme za behu vkladať nové prvky. Tie sú na seba navzájom odkazované referenciami, ako reťaz objektov.

Napr. v C ++ je veľký rozdiel medzi pojmom ukazovateľ a referencie. Kotlín žiadne ukazovatele našťastie nemá a používa termín referencie, tie sa paradoxne princípom podobajú skôr ukazovateľom v C ++. Pojmy ukazovateľ a referencie tu spomenuté teda znamenajú referenciu v zmysle Kotlin a nemajú s C ++ nič spoločné.

Založme si 2 premenné typu Uzivatel:

var u: Uzivatel? = Uzivatel("Jan Novák", 28)
var v: Uzivatel? = Uzivatel("Josef Nový", 32)

Situácia v pamäti bude nasledovné:

Referenčné hodnoty v Kotlinu v pamäti počítača - Objektovo orientované programovanie v Kotlin

Teraz skúsme priradiť do premennej u premennú v. Skopíruje sa iba odkaz na objekt, ale objekt máme stále len jeden. V kóde vykonáme teda toto:

var u = Uzivatel("Jan Novák", 28)
var v = Uzivatel("Josef Nový", 32)
u = v

V pamäti bude celá situácia vyzerať nasledovne:

Referenčné hodnoty v Kotlinu v pamäti počítača - Objektovo orientované programovanie v Kotlin

Presvedčte sa o tom, aby ste videli, že to naozaj tak je :) Najprv si necháme obe dve premenné vypísať pred a po zmene. Navyše si vypíšeme aj tzv. Hash kód cez metódu hashCode(), aby sme videli, že sa jedná o rôzne objekty. Pretože budeme výpis volať viackrát, napíšem ho trochu úspornejšie. Upravme teda kód na nasledujúce:

// založení proměnných
var u = Uzivatel("Jan Novák", 28)
var v = Uzivatel("Josef Nový", 32)
println("u: $u ${u.hashCode()} ")
println("v: $v ${v.hashCode()}\n")
// přiřazení
u = v
println("u: $u ${u.hashCode()} ")
println("v: $v ${v.hashCode()}\n")
class Uzivatel(var jmeno: String, var vek: Int) {
    override fun toString(): String {
        return jmeno
    }
}

Výstup programu:

u: Jan Novák 460141958
v: Josef Nový 1163157884

u: Josef Nový 1163157884
v: Josef Nový 1163157884

Poďme zmeniť meno používateľa v a podľa našich predpokladov by sa mala zmena prejaviť aj v premennej u. K programu pripíšeme:

v.jmeno = "John Doe"
println("u: $u ${u.hashCode()} ")
println("v: $v ${v.hashCode()}\n")
class Uzivatel(var jmeno: String, var vek: Int) {
    override fun toString(): String {
        return jmeno
    }
}

Zmenili sme objekt v premennej v a znovu vypíšeme u a v:

u: Jan Novák 460141958
v: Josef Nový 1163157884

u: Josef Nový 1163157884
v: Josef Nový 1163157884

u: John Doe 1163157884
v: John Doe 1163157884

Spolu so zmenou v sa zmení aj u, pretože premenné ukazujú na ten istý objekt. Ak sa pýtate, ako vytvoriť naozajstnú kópiu objektu, tak najjednoduchšie je objekt znova vytvoriť pomocou konstruktoru a dať do neho rovnaké dáta. Ďalej môžeme použiť klonovanie, ale o tom zas až niekedy inokedy. Pripomeňme si situáciu v pamäti ešte raz a zamerajme sa na Jána Nováka.

Referenčné hodnoty v Kotlinu v pamäti počítača - Objektovo orientované programovanie v Kotlin

Čo sa s ním stane? "Zožerie" ho tzv. Garbage collector.

garbage collector - Objektovo orientované programovanie v Kotlin

Garbage collector a dynamická správa pamäte

Pamäť môžeme v programoch alokovať staticky, to znamená, že v zdrojovom kóde vopred určíme, koľko jej budeme používať. Ale tiež nemusíme určiť koľko pamäte budeme potrebovať. V tomto prípade hovoríme o dynamickej správe pamäte.

V minulosti, hlavne v časoch jazykov C, Pascal a C ++, sa na tento účel používali tzv. Pointer, čiže priame ukazovatele do pamäte. Napospol to fungovalo tak, že sme si povedali operačnému systému o kus pamäti o určitej veľkosti. On ju pre nás vyhradil a dal nám jej adresu. Na toto miesto v pamäti sme mali pointer, cez ktorý sme s pamäťou pracovali. Problém bol, že nikto nestrážil, čo do pamäti dávame (ukazovateľ smeroval na začiatok vyhradeného priestoru). Keď sme tam dali niečo väčšieho, skrátka sa to rovnako uložilo a prepísala sa dáta za naším priestorom, ktorá patrila napríklad inému programu alebo operačnému systému (v tom prípade by našu aplikáciu OS asi zabil - zastavil). Často sme si však my v pamäti prepísali nejaká ďalšie dáta nášho programu a program sa začal správať chaoticky. Predstavte si, že si uložíte používateľa do poľa a v tej chvíli sa vám zrazu zmení farba užívateľského prostredia, teda niečo, čo s tým vôbec nesúvisí. Hodiny strávite tým, že kontrolujete kód pre zmenu farby, potom zistíte, že je chyba v založenia používateľa, kedy dôjde k pretečeniu pamäte a prepísanie hodnôt farby.

Keď sme naopak nejaký objekt prestali používať, museli sme po ňom miesto sami uvoľniť. Ak sme to neurobili, pamäť ostala blokovaná. Pokiaľ toto robíme napr. V nejakej metóde a zabudneme pamäť uvoľňovať, naše aplikácie začne padať, prípadne zasekne celý operačný systém. Takáto chyba sa opäť zle hľadá, prečo program prestane po niekoľkých hodinách fungovať? Kde tú chybu v niekoľkých tisícoch riadkov kódu vôbec hľadať? Nemáme jedinú stopu, nemôžeme sa ničoho chytiť, musíme prejsť celý program riadok po riadku alebo začať preskúmavať pamäť počítača, ktorá je v binárce. Brrr. Podobný problém nastane, keď si niekde pamäť uvoľníme a následne pointer opäť použijeme (zabudneme, že je uvoľnený, to sa môže ľahko stať), povedie niekam, kde je už uloženého niečo iné a tieto dáta budú opäť prepísané. Povedie to k nekontrolovanému správanie našej aplikácie a môže to dopadnúť aj takto:

Blue Screen Of Death – BSOD vo Windows - Objektovo orientované programovanie v Kotlin

Môj kolega raz povedal: "Ľudský mozog sa nedokáže starať ani o správu pamäte vlastné, nieto aby riešil memory management programu." Mal samozrejme pravdu, až na malú skupinu géniov ľudí prestalo baviť riešiť neustále a nezmyselné chyby. Za cenu mierneho zníženia výkonu vznikli riadené jazyky (managed) s tzv. Garbage collector, jedným z nich je aj Kotlin a Swift. C ++ sa samozrejme naďalej používa, ale len na špecifické programy, napr. Časti operačného systému alebo 3D enginy komerčných hier, kde je potreba z počítača dostať maximálny výkon. Na 99% všetkých ostatných aplikácií sa hodia Kotlin, hlavne kvôli automatické správe pamäte.

garbage collector - Objektovo orientované programovanie v Kotlin
Garbage collector je vlastne program, ktorý beží paralelne s našou aplikáciou, v samostatnom vlákne. Občas sa spustí a pozrie sa, na ktoré objekty už v pamäti nevedú žiadne referencie. Tie potom odstráni. Strata výkonu je minimálna a značne to zníži percento samovrážd programátorov, ladiacich po večeroch rozbité pointera. Zapnutie GC môžeme dokonca z kódu ovplyvniť, aj keď to nie je v 99% prípadov vôbec potrebné. Pretože je jazyk riadený a nepracujeme s priamymi Pointer, nie je vôbec možné pamäť nejako narušiť, nechať ju pretiecť a podobne, interpret sa o pamäť automaticky stará.

Hodnota null

Z lekcie Typový systém: Null safety v Kotlin by ste už mali aspoň pasívne poznať kľúčové slovo null. Premenné v Kotlin, ktoré sú nullovatelné, môžu nadobúdať navyše špeciálny hodnoty null. null označuje, že referencie neukazuje na žiadne dáta. Keď nastavíme premennú v na null, zrušíme iba referenciu. Pokiaľ na náš objekt existuje ešte nejaká referencie, bude aj naďalej existovať. Ak nie, bude uvoľnený GC. Zmeňme ešte posledná riadky nášho programu na:

v = null
println("u: $u ${u?.hashCode()} ")
println("v: $v ${v?.hashCode()}\n")

Výstup programu:

u: Jan Novák 666988784
v: Josef Nový 1414644648

u: Josef Nový 1414644648
v: Josef Nový 1414644648

u: John Doe 1414644648
v: John Doe 1414644648

u: John Doe 1414644648
v: null null

Vidíme, že objekt stále existuje a ukazuje na neho premenná u, v premennej v už nie je referencie. null sa občas využíva ako vo vnútri Kotlinu (aj keď môže spôsobiť chyby), tak v databázach. K referenčným typom sa ešte raz vrátime. V budúcej lekcii, Kvíz - Úvod, konštruktory, metódy, dátové typy v Kotlin OOP , si zas niečo praktické naprogramujeme, nech si vedomosti zažijeme. Prezradím, že pôjde o objekt bojovníka do našej arény. To je zatiaľ všetko :)

V nasledujúcom kvíze, Kvíz - Úvod, konštruktory, metódy, dátové typy v Kotlin OOP, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 26x (9.6 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin

 

Predchádzajúci článok
Riešené úlohy k 3. lekcii OOP v Kotlin
Všetky články v sekcii
Objektovo orientované programovanie v Kotlin
Preskočiť článok
(neodporúčame)
Kvíz - Úvod, konštruktory, metódy, dátové typy v Kotlin OOP
Článok pre vás napísal Samuel Kodytek
Avatar
Užívateľské hodnotenie:
1 hlasov
Autor se věnuje všem jazykům okolo JVM. Rád pomáhá lidem, kteří se zajímají o programování. Věří, že všichni mají šanci se naučit programovat, jen je potřeba prorazit tu bariéru, který se říká lenost.
Aktivity