3. diel - Hracia kocka v Jave - Zapuzdrenie, konštruktor a Random
V predchádzajúcom cvičení, Riešené úlohy k 2. lekcii OOP v Jave, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V dnešnom tutoriáli začneme pracovať na sľúbenej aréne, v ktorej budú proti sebe bojovať dvaja bojovníci. Boj bude ťahový (na preskáčku) a bojovník vždy druhému uberie život na základe sily jeho útoku a obrany druhého bojovníka. Simulujeme v podstate stolnú hru, budeme teda simulovať aj hraciu kocku, ktorá dodá hre prvok náhodnosti. Začnime zvoľna a vytvorme si dnes práve túto hraciu kocku. Zároveň sa naučíme ako definovať vlastný konštruktor.
Vytvorenie projektu
Vytvorme si nový projekt a pomenujme ho ArenaFight
. K projektu
si pridajme novú triedu s názvom RollingDie
. Zamyslime sa nad
atribútmi, ktoré kocke dáme. Iste by sa hodilo, keby sme si mohli zvoliť
počet stien kocky (klasicky 6 alebo 10 stien, ako je zvykom pri tomto type
hier). Ďalej bude kocka potrebovať tzv. generátor náhodných čísel. Ten
nám samozrejme poskytne Java, ktorá na tieto účely obsahuje triedu
Random
. Aby sme ju mohli používať, musíme si triedu
java.util.Random
naimportovať. Import napíšeme hore, ako sme
zvyknutí z používania importu pre Scanner
. Naša trieda bude
mať teraz 2 atribúty:
sidesCount
typuint
arandom
typuRandom
, kde bude náhodný generátor.
Zapuzdrenie
Minule sme kvôli jednoduchosti nastavovali všetky atribúty našej triedy
ako public
, teda ako verejne prístupné. Väčšinou sa však
skôr nechce, aby sa dali zvonku modifikovať a používa sa modifikátor
private
. Atribút je potom viditeľný len vo vnútri triedy a
zvonku sa Java tvári, že vôbec neexistuje. Pri návrhu triedy teda nastavíme
všetko na private
a v prípade, že niečo bude naozaj potrebné
vystaviť, použijeme modifikátor public
. Naša trieda teraz
vyzerá takto:
import java.util.Random; /** * Class representing a die for a board game */ public class RollingDie { /** * Random number generator */ private Random random; /** * Number of sides that the die has */ private int sidesCount;
Pozrime sa na tento riadok:
private Random random;
Ten nám hovorí: nastav ako privátny (neverejný) atribút
random
dátového typu Random
. Uložíme tak do
atribútu celú triedu Random
a môžeme v triede v akejkoľvek
metóde volať napríklad metódu random.nextInt()
, bez toho aby
sme tam znovu zakladali premennú.
Konštruktory
Až doposiaľ sme nevedeli zvonku nastaviť iné atribúty ako
public
, pretože napr. private
nie sú zvonku
viditeľné. Už sme si hovorili niečo málo o konštruktore objektu. Je to
metóda, ktorá sa zavolá vo chvíli vytvorenia inštancie
objektu. Slúži samozrejme na nastavenie vnútorného stavu objektu a
na vykonanie prípadnej inicializácie. Inštanciu kocky by sme teraz v súbore
ArenaFight.java
vytvorili takto:
RollingDie die = new RollingDie();
Práve slovo RollingDie()
je konštruktor. Pretože v našej
triede RollingDie
žiadny konštruktor nie je, Java si dogeneruje
prázdnu metódu. My si však teraz konštruktor do triedy pridáme. Deklaruje
sa ako metóda, ale nemá návratový typ a musí mať
rovnaký názov ako je názov triedy (začína teda, na rozdiel
od ostatných metód, veľkým písmenom), v našom prípade teda
RollingDie
. V konštruktore nastavíme počet stien na pevnú
hodnotu a vytvoríme inštanciu triedy Random
. Konštruktor bude
vyzerať nasledovne:
/** * Creates an instance of a new rolling die */ public RollingDie() { sidesCount = 6; random = new Random(); }
Ak kocku teraz vytvoríme, bude mať atribút sidesCount
hodnotu
6
a v atribúte random
bude vytvorená inštancia
generátora náhodných čísel. Vypíšme si počet stien do konzoly, nech
vidíme, že tam hodnota naozaj je. Nie je dobré atribút nastaviť na
public
, pretože nebudeme chcieť, aby nám niekto mohol už pri
vytvorenej kocke meniť počet stien. Pridáme do triedy teda metódu
getSidesCount()
, ktorá nám vráti hodnotu atribútu
sidesCount
. Docielili sme tým v podstate to, že je atribút
read-only (atribút nie je viditeľný a možno ho iba čítať
metódou, zmeniť ho nemožno). Nová metóda bude vyzerať asi takto:
/** * Returns the number of sides the die has * @return Number of sides the die has */ public int getSidesCount() { return sidesCount; }
Presuňme sa do súboru ArenaFight.java
a vyskúšajme si
vytvoriť kocku a vypísať počet stien:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie die = new RollingDie(); // the constructor is called right here System.out.println(die.getSidesCount()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public int getSidesCount() { return sidesCount; } } {/JAVA_OOP}
Výstup:
Konzolová aplikácia
6
Vidíme, že sa konštruktor naozaj zavolal. My by sme ale chceli, aby sme
mohli pri každej kocke pri vytvorení špecifikovať, koľko stien budeme
potrebovať. Prejdeme do triedy RollingDie.java
a dáme teda
konštruktoru parameter:
public RollingDie(int aSidesCount) { sidesCount = aSidesCount; random = new Random(); }
Všimnite si, že sme pred názov parametra metódy pridali znak
a
, pretože inak by mal rovnaký názov ako atribút a Javu by to
zmiatlo. Vráťme sa do súboru ArenaFight.java
a zadajte tento
parameter do konstruktoru:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie die = new RollingDie(10); // a constructor with a parameter 10 is called System.out.println(die.getSidesCount()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie(int aSidesCount) { sidesCount = aSidesCount; random = new Random(); } public int getSidesCount() { return sidesCount; } } {/JAVA_OOP}
Výstup:
Konzolová aplikácia
10
Všetko funguje, ako sme očakávali. Java nám už v tejto chvíli
nevygeneruje prázdny (tzv. bezparametrický konštruktor), takže kocku bez
parametra vytvoriť nemožno. My to však môžeme umožniť, vytvorme si
ďalší konštruktor a tentoraz bez parametra. V ňom nastavíme počet stien
na 6, pretože takú hodnotu asi užívateľ našej triedy pri kocke očakáva
ako východiskový. Prejdeme teda späť do súboru RollingDie.java
a vytvoríme konštruktor bez parametra:
public RollingDie() { sidesCount = 6; random = new Random(); }
Trieda RollingDie
má teda teraz dvoch konštruktorov.
Skúsme si teraz vytvoriť 2 inštancie kocky, každú iným konštruktorom v
súbore ArenaFight.java
:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); System.out.println(sixSided.getSidesCount()); System.out.println(tenSided.getSidesCount()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int aSidesCount) { sidesCount = aSidesCount; random = new Random(); } public int getSidesCount() { return sidesCount; } } {/JAVA_OOP}
Výstup:
Konzolová aplikácia
6
10
Jave nevadí, že máme dve metódy s rovnakým názvom, pretože ich
parametre sú rôzne. Hovoríme o tom, že metóda RollingDie()
(teda tu konštruktor) má preťaženie (overload). To môžeme
využívať aj pri všetkých ďalších metódach, nielen pri konštruktoroch.
IDE nám prehľadne ponúka všetky preťaženia metódy vo chvíli, keď
zadáme jej názov. V ponuke vidíme naše 2 konštruktory:
Veľa metód v Jave má hneď niekoľko preťažení, skúste sa pozrieť
napr. na metódu indexOf()
na triede String
. Je dobré
si u metód prejsť ich preťaženie, aby ste neprogramovali niečo, čo už
niekto urobil pred vami.
Ukážeme si ešte, ako ide obísť nepraktický názov atribútu u
parametrického konštruktora (v našom prípade aSidesCount
) a
potom konštruktory opustíme. Problém je samozrejme v tom, že keď
napíšeme:
public RollingDie(int sidesCount) { sidesCount = sidesCount; random = new Random(); }
Java nevie, ktorú z premenných myslíme, či parameter alebo atribút. V
tomto prípade priraďujeme do parametra znovu ten istý parameter. IDE nás na
túto skutočnosť dokonca upozorní. Vnútri triedy sa máme možnosť
odkazovať na jej inštanciu, je uložená v premennej this
.
Využitie si môžeme predstaviť napr. keby kocka mala metódu
giveToPlayer(Player player)
a tam by volala
player.pickUpDie(this)
. Tu by sme hráči pomocou referenčnej
premennej this
odovzdali seba samého, teda tú konkrétnu kocku, s
ktorou pracujeme. My sa tým tu nebudeme zaťažovať, ale využijeme odkaz na
inštanciu pri nastavovaní atribútu:
public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); }
Pomocou premennej this
sme špecifikovali, že ľavá premenná
sidesCount
patrí inštancii, pravú Java chápe ako z parametra.
Máme teda dva konštruktory, ktoré nám umožňujú tvoriť rôzne hracie
kocky. Prejdime ďalej.
Náhodné čísla
Definujme na kocke metódu roll()
, ktorá nám vráti náhodné
číslo od 1
do počtu stien. Je to veľmi jednoduché, metóda
bude public
(pôjde volať zvonku triedy) a nebude mať žiadny
parameter. Návratová hodnota bude typu int
. Náhodné číslo
získame tak, že na generátore random
zavoláme metódu
nextInt()
. Tá má dve preťaženia:
nextInt()
- Variant bez parametra vracia náhodné číslo v celom rozsahu dátového typuint
, pre úplnosť teda konkrétne od-2147483648
do2147483647
.nextInt(To)
- Vracajú nezáporné čísla menšie ako medzaTo
. Napríkladrandom.nextInt(100)
teda vráti číslo od0
do99
.
Na naše účely sa najlepšie hodí druhé preťaženie, píšeme do súboru
RollingDie.java
teda:
/** * Rolls a die * @return A number from 1 to sides count */ public int roll() { return random.nextInt(sidesCount) + 1; }
Dajte si pozor, aby ste netvorili generátor náhodných čísel v metóde,
ktorá má náhodné číslo vracať, teda že by sa pre každé náhodné
číslo vytvoril nový generátor. Výsledné čísla potom nie sú takmer
náhodné alebo dokonca vôbec. Vždy si vytvorte jednu zdieľanú inštanciu
generátora (napr. do privátneho atribútu pomocou konštruktora) a na tej
potom metódu nextInt()
volajte.
Prekrývanie metódy
toString()
Kocka je takmer hotová, ukážme si ešte jednu užitočnú metódu, ktorú
ju pridáme a ktorú budeme hojne používať aj vo väčšine našich
ďalších objektov. Reč je o metóde toString()
, o ktorej sme sa
už zmienili a ktorú obsahuje každý objekt, teda aj teraz
naša kocka. Metóda je určená na to, aby vrátila tzv. textovú
reprezentáciu inštancie. Hodí sa vo všetkých prípadoch, keď si
inštanciu potrebujeme vypísať alebo s ňou pracovať ako s textom. Túto
metódu majú napr. aj čísla. Už vieme, že v Jave funguje implicitná
konverzia, akonáhle teda budeme chcieť do konzoly vypísať objekt, Java na
ňom zavolá metódu toString()
a vypíše jej výstup. Ak si
robíme vlastnú triedu, mali by sme zvážiť, či sa nám takáto metóda
nehodí. Nikdy by sme si nemali robiť vlastnú metódu, napr.
niečo ako print()
(čo sme používali doteraz), keď máme v Jave
pripravenú cestu, ako toto riešiť. U kocky nemá metóda
toString()
vyšší zmysel, ale u bojovníka bude určite vracať
jeho meno. My si ju ku kocke aj tak pridáme, bude vypisovať, že sa jedná o
kocku a vráti aj počet stien. Najprv si skúsme vypísať do konzoly našu
inštanciu kocky:
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); System.out.println(sixSided); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int getSidesCount() { return sidesCount; } public int roll() { return random.nextInt(sidesCount) + 1; } } {/JAVA_OOP}
Do konzoly sa vypíše iba cesta k našej triede, teda
com.ictdemy.RollingDie
a tzv. hash kód objektu. V mojom prípade
bol vypísaný tento reťazec:
Konzolová aplikácia
com.ictdemy.RollingDie@7c1c8c58
Metódu toString()
už jednoducho nedefinujeme, ale pretože už
existuje, musíme ju prepísať, resp. prekryť. Tým sa opäť
nebudeme teraz podrobne zaoberať, ale chcem, aby sme už teraz vedeli metódu
toString()
používať. Pre prehľadné prekrytie označíme
metódu anotácií @Override
:
{JAVA_OOP} import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int getSidesCount() { return sidesCount; } public int roll() { return random.nextInt(sidesCount) + 1; } /** * Returns a textual representation of our die * @return Textual representation of the die */ @Override public String toString() { return String.format("Rolling die with %s sides", sidesCount); } } {/JAVA_OOP}
{JAVA_OOP} {JAVA_MAIN_BLOCK} RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); System.out.println(sixSided); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
Teraz opäť skúsime do konzoly vypísať priamo inštanciu kocky.
Výstup:
Konzolová aplikácia
Rolling die with 6 sides
Ešte si naše kocky vyskúšame. Skúsime si v programe s našimi dvoma kockami v cykloch hádzať a pozrieme sa, či fungujú tak, ako sa očakáva:
{JAVA_OOP} {JAVA_MAIN_BLOCK} // Create instances RollingDie sixSided = new RollingDie(); RollingDie tenSided = new RollingDie(10); // Rolls the 6-sided die System.out.println(sixSided); for (int i = 0; i < 10; i++) { System.out.print(sixSided.roll() + " "); } // Rolls the 10-sided die System.out.println("\n\n" + tenSided); for (int i = 0; i < 10; i++) { System.out.print(tenSided.roll() + " "); } {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int getSidesCount() { return sidesCount; } public int roll() { return random.nextInt(sidesCount) + 1; } @Override public String toString() { return String.format("Rolling die with %s sides", sidesCount); } } {/JAVA_OOP}
Výstup môže vyzerať nejako takto:
Konzolová aplikácia
Rolling die with 6 sides
3 6 6 1 6 3 6 2 6 3
Rolling die with 10 sides
5 9 9 2 10 4 9 3 10 5
Máme hotovú celkom peknú a nastaviteľnú triedu, ktorá reprezentuje hraciu kocku. Bude sa nám hodiť v našej aréne, ale môžete ju použiť aj kdekoľvek inde. Vidíme, ako OOP umožňuje znovupoužívať komponenty.
V nasledujúcom cvičení, Riešené úlohy k 3. lekcii OOP v Jave, si precvičíme 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é 14x (3.13 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java