Lekce 3 - Hrací kostka v Javě - Zapouzdření, konstruktor a Random
V předešlém cvičení, Řešené úlohy k 2. lekci OOP v Javě, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V dnešním tutoriálu začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat dva bojovníci. Boj bude tahový (na přeskáčku) a bojovník vždy druhému ubere život na základě síly jeho útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat i hrací kostku, která dodá hře prvek náhodnosti. Začněme zvolna a vytvořme si dnes právě tuto hrací kostku. Zároveň se naučíme jak definovat vlastní konstruktor.
Vytvoření projektu
Vytvořme si nový projekt a pojmenujme ho TahovyBoj
. K projektu
si přidejme novou třídu s názvem Kostka
. Zamysleme se nad
atributy, které kostce dáme. Jistě by se hodilo, kdybychom si mohli zvolit
počet stěn kostky (klasicky 6 nebo 10 stěn, jak je zvykem u tohoto typu her).
Dále bude kostka potřebovat tzv. generátor náhodných čísel. Ten nám
samozřejmě poskytne Java, která k těmto účelům obsahuje třídu
Random
. Abychom ji mohli používat, musíme si třídu
java.util.Random
naimportovat. Import napíšeme nahoru, jak jsme
zvyklí z používání importu pro Scanner
. Naše třída bude
mít nyní 2 atributy:
pocetSten
typuint
arandom
typuRandom
, kde bude náhodný generátor.
Zapouzdření
Minule jsme kvůli jednoduchosti nastavovali všechny atributy naší třídy
jako public
, tedy jako veřejně přístupné. Většinou se však
spíše nechce, aby se daly zvenčí modifikovat a používá se modifikátor
private
. Atribut je poté viditelný jen uvnitř třídy a zvenčí
se Java tváří, že vůbec neexistuje. Při návrhu třídy tedy nastavíme
vše na private
a v případě, že něco bude opravdu potřeba
vystavit, použijeme modifikátor public
. Naše třída nyní
vypadá takto:
import java.util.Random; /** * Třída reprezentuje hrací kostku */ public class Kostka { /** * Generátor náhodných čísel */ private Random random; /** * Počet stěn kostky */ private int pocetSten;
Řádek:
private Random random;
nám říká: nastav jako privátní (neveřejný) atribut
random
datového typu Random
. Uložíme tak do
atributu celou třídu Random
a můžeme ve třídě v jakékoliv
metodě volat například metodu random.nextInt()
, aniž bychom tam
znovu zakládali proměnnou.
Konstruktory
Až doposud jsme neuměli zvenčí nastavit jiné atributy než
public
, protože např. private
nejsou zvenčí
viditelné. Již jsme si říkali něco málo o konstruktoru objektu. Je to
metoda, která se zavolá ve chvíli vytvoření instance
objektu. Slouží samozřejmě k nastavení vnitřního stavu objektu a
k provedení případné inicializace. Instanci kostky bychom nyní v souboru
TahovyBoj.java
vytvořili takto:
Kostka kostka = new Kostka();
Právě slovo Kostka()
je konstruktor. Protože v naší
třídě Kostka
žádný konstruktor není, Java si dogeneruje
prázdnou metodu. My si však nyní konstruktor do třídy přidáme. Deklaruje
se jako metoda, ale nemá návratový typ a musí mít
stejný název jako je název třídy (začíná tedy, na
rozdíl od ostatních metod, velkým písmenem), v našem případě tedy
Kostka
. V konstruktoru nastavíme počet stěn na pevnou hodnotu a
vytvoříme instanci třídy Random
. Konstruktor bude vypadat
následovně:
/** * Vytvoří novou instanci hrací kostky */ public Kostka() { pocetSten = 6; random = new Random(); }
Pokud kostku nyní vytvoříme, bude mít atribut pocetSten
hodnotu 6
a v atributu random
bude vytvořená
instance generátoru náhodných čísel. Vypišme si počet stěn do konzole,
ať vidíme, že tam hodnota opravdu je. Není dobré atribut nastavit na
public
, protože nebudeme chtít, aby nám někdo mohl již u
vytvořené kostky měnit počet stěn. Přidáme do třídy tedy metodu
vratPocetSten()
, která nám vrátí hodnotu atributu
pocetSten
. Docílili jsme tím v podstatě toho, že je atribut
read-only (atribut není viditelný a lze ho pouze číst metodou,
změnit ho nelze). Nová metoda bude vypadat asi takto:
/** * Vrátí počet stěn hrací kostky * @return Počet stěn hrací kostky */ public int vratPocetSten() { return pocetSten; }
Přesuňme se do souboru TahovyBoj.java
a vyzkoušejme si
vytvořit kostku a vypsat počet stěn:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka kostka = new Kostka(); // v tuto chvíli se zavolá konstruktor System.out.println(kostka.vratPocetSten()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public int vratPocetSten() { return pocetSten; } } {/JAVA_OOP}
Výstup:
Konzolová aplikace
6
Vidíme, že se konstruktor opravdu zavolal. My bychom ale chtěli, abychom
mohli u každé kostky při vytvoření specifikovat, kolik stěn budeme
potřebovat. Přejdeme do třídy Kostka.java
a dáme tedy
konstruktoru parametr:
public Kostka(int aPocetSten) { pocetSten = aPocetSten; random = new Random(); }
Všimněte si, že jsme před název parametru metody přidali znak
a
, protože jinak by měl stejný název jako atribut a Javu by to
zmátlo. Vraťme se do souboru TahovyBoj.java
a zadejme tento
parametr do konstruktoru:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka kostka = new Kostka(10); // v tuto chvíli se zavolá konstruktor s parametrem 10 System.out.println(kostka.vratPocetSten()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka(int aPocetSten) { pocetSten = aPocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } } {/JAVA_OOP}
Výstup:
Konzolová aplikace
10
Vše funguje, jak jsme očekávali. Java nám již v tuto chvíli
nevygeneruje prázdný (tzv. bezparametrický konstruktor), takže kostku bez
parametru vytvořit nelze. My to však můžeme umožnit, vytvořme si další
konstruktor a tentokrát bez parametru. V něm nastavíme počet stěn na 6,
protože takovou hodnotu asi uživatel naší třídy u kostky očekává jako
výchozí. Přejdeme tedy zpátky do souboru Kostka.java
a
vytvoříme konstruktor bez parametru:
public Kostka() { pocetSten = 6; random = new Random(); }
Třída Kostka
má tedy nyní dva konstruktory.
Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem v
souboru TahovyBoj.java
:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); System.out.println(sestistenna.vratPocetSten()); System.out.println(desetistenna.vratPocetSten()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int aPocetSten) { pocetSten = aPocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } } {/JAVA_OOP}
Výstup:
Konzolová aplikace
6
10
Javě nevadí, že máme dvě metody se stejným názvem, protože jejich
parametry jsou různé. Hovoříme o tom, že metoda Kostka()
(tedy
zde konstruktor) má přetížení (overload). Toho můžeme
využívat i u všech dalších metod, nejen u konstruktorů. IDE nám
přehledně nabízí všechny přetížení metody ve chvíli, kdy zadáme její
název. V nabídce vidíme naše 2 konstruktory:

Mnoho metod v Javě má hned několik přetížení, zkuste se podívat
např. na metodu indexOf()
na třídě String. Je dobré si u metod
projít jejich přetížení, abyste neprogramovali něco, co již někdo
udělal před vámi.
Ukážeme si ještě, jak jde obejít nepraktický název atributu u
parametrického konstruktoru (v našem případě aPocetSten
) a
potom konstruktory opustíme. Problém je samozřejmě v tom, že když
napíšeme:
public Kostka(int pocetSten) { pocetSten = pocetSten; random = new Random(); }
Java neví, kterou z proměnných myslíme, jestli parametr nebo atribut. V
tomto případě přiřazujeme do parametru znovu ten samý parametr. IDE nás
na tuto skutečnost dokonce upozorní. Uvnitř třídy se máme možnost
odkazovat na její instanci, je uložena v proměnné this
.
Využití si můžeme představit např. kdyby kostka měla metodu
dejHraci(Hrac hrac)
a tam by volala
hrac.seberKostku(this)
. Zde bychom hráči pomocí referenční
proměnné this
předali sebe sama, tedy tu konkrétní kostku, se
kterou pracujeme. My se tím zde nebudeme zatěžovat, ale využijeme odkazu na
instanci při nastavování atributu:
public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); }
Pomocí proměnné this
jsme specifikovali, že levá proměnná
pocetSten
náleží instanci, pravou Java chápe jako z parametru.
Máme tedy dva konstruktory, které nám umožňují tvořit různé hrací
kostky. Přejděme dál.
Náhodná čísla
Definujme na kostce metodu hod()
, která nám vrátí náhodné
číslo od 1
do počtu stěn. Je to velmi jednoduché, metoda bude
public
(půjde volat zvenčí třídy) a nebude mít žádný
parametr. Návratová hodnota bude typu int
. Náhodné číslo
získáme tak, že na generátoru random
zavoláme metodu
nextInt()
. Ta má dvě přetížení:
nextInt()
- Varianta bez parametru vrací náhodné číslo v celém rozsahu datového typuint
, pro úplnost tedy konkrétně od-2147483648
do2147483647
.nextInt(do)
- Vrací nezáporná čísla menší než mezdo
. Napříkladrandom.nextInt(100)
tedy vrátí číslo od0
do99
.
Pro naše účely se nejlépe hodí druhé přetížení, píšeme do souboru
Kostka.java
tedy:
/** * Vykoná hod kostkou * @return Číslo od 1 do počtu stěn */ public int hod() { return random.nextInt(pocetSten) + 1; }
Dejte si pozor, abyste netvořili generátor náhodných čísel v metodě,
která má náhodné číslo vracet, tedy že by se pro každé náhodné
číslo vytvořil nový generátor. Výsledná čísla pak nejsou téměř
náhodná nebo dokonce vůbec. Vždy si vytvořte jednu sdílenou instanci
generátoru (např. do privátního atributu pomocí konstruktoru) a na té
potom metodu nextInt()
volejte.
Překrývání metody
toString()
Kostka je téměř hotová, ukažme si ještě jednu užitečnou metodu,
kterou ji přidáme a kterou budeme hojně používat i ve většině našich
dalších objektů. Řeč je o metodě toString()
, o které jsme se
již zmínili a kterou obsahuje každý objekt, tedy i nyní
naše kostka. Metoda je určena k tomu, aby vrátila tzv. textovou
reprezentaci instance. Hodí se ve všech případech, kdy si instanci
potřebujeme vypsat nebo s ní pracovat jako s textem. Tuto metodu mají např.
i čísla. Již víme, že v Javě funguje implicitní konverze, jakmile tedy
budeme chtít do konzole vypsat objekt, Java na něm zavolá metodu
toString()
a vypíše její výstup. Pokud si děláme vlastní
třídu, měli bychom zvážit, zda se nám takováto metoda nehodí.
Nikdy bychom si neměli dělat vlastní metodu, např. něco
jako vypis()
(co jsme používali dosud), když máme v Javě
připravenou cestu, jak toto řešit. U kostky nemá metoda
toString()
vyšší smysl, ale u bojovníka bude jistě vracet jeho
jméno. My si ji ke kostce stejně přidáme, bude vypisovat, že se jedná o
kostku a vrátí i počet stěn. Nejprve si zkusme vypsat do konzole naši
instanci kostky:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); System.out.println(sestistenna); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } public int hod() { return random.nextInt(pocetSten) + 1; } } {/JAVA_OOP}
Do konzole se vypíše pouze cesta k naší třídě, tedy
cz.itnetwork.Kostka
a tzv. hash kód objektu. V mém případě byl
vypsán tento řetězec:
Konzolová aplikace
cz.itnetwork.Kostka@7c1c8c58
V následující ukázce použijeme zavináč. Na české klávesnici ho napíšeme pomocí Pravého Alt + V:

Metodu toString()
již jednoduše nedefinujeme, ale protože
již existuje, musíme ji přepsat, resp. překrýt. Tím se
opět nebudeme nyní podrobně zabývat, nicméně chci, abychom již teď
uměli metodu toString()
používat. Pro přehledné překrytí
označíme metodu anotací @Override
:
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } public int hod() { return random.nextInt(pocetSten) + 1; } /** * Vrací textovou reprezentaci kostky * @return Textová reprezentace kostky */ @Override public String toString() { return String.format("Kostka s %s stěnami", pocetSten); } } {/JAVA_OOP}
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); System.out.println(sestistenna); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
Nyní opět zkusíme do konzole vypsat přímo instanci kostky.
Výstup:
Konzolová aplikace
Kostka s 6 stěnami
Ještě si naše kostky vyzkoušíme. Zkusíme si v programu s našima dvěma kostkami v cyklech házet a podíváme se, jestli fungují tak, jak se očekává:
{JAVA_OOP} {JAVA_MAIN_BLOCK} // vytvoření kostek Kostka sestistenna = new Kostka(); Kostka desetistenna = new Kostka(10); // hod šestistěnnou kostkou System.out.println(sestistenna); for (int i = 0; i < 10; i++) { System.out.print(sestistenna.hod() + " "); } // hod desetistěnnou kostkou System.out.println("\n\n" + desetistenna); for (int i = 0; i < 10; i++) { System.out.print(desetistenna.hod() + " "); } {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } public int hod() { return random.nextInt(pocetSten) + 1; } @Override public String toString() { return String.format("Kostka s %s stěnami", pocetSten); } } {/JAVA_OOP}
Výstup může vypadat nějak takto:
Konzolová aplikace
Kostka s 6 stěnami
3 6 6 1 6 3 6 2 6 3
Kostka s 10 stěnami
5 9 9 2 10 4 9 3 10 5
Máme hotovou docela pěknou a nastavitelnou třídu, která reprezentuje hrací kostku. Bude se nám hodit v naší aréně, ale můžete ji použít i kdekoli jinde. Vidíme, jak OOP umožňuje znovupoužívat komponenty.
V následujícím cvičení, Řešené úlohy k 3. lekci OOP v Javě, si procvičíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 2430x (9.37 kB)
Aplikace je včetně zdrojových kódů v jazyce Java