Aktuálně: Postihly zákazy tvou profesi? Poptávka po ajťácích prudce roste, využij slevové akce 30% výuky zdarma!
Pouze tento týden sleva až 80 % na e-learning týkající se PHP
Discount week - April - 30

Lekce 3 - Hrací kostka v Javě - konstruktory a náhodná čísla

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řme si nový projekt a pojmenujme ho TahovyBoj. K projektu si přidejme novou class 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, použijeme import java.util.Random. Import napíšeme nahoru, jak jsme zvyklí z používání Scanneru. Naše třída bude mít nyní 2 atributy:

  • pocetSten typu int
  • random typu Random, kde bude náhodný generátor.

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 public. Naše třída nyní vypadá asi 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;
}

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. Kostku bychom nyní v TahovyBoj.java vytvořili takto:

Kostka kostka = new Kostka();

Právě Kostka() je konstruktor. Protože v naší třídě žádný 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é jméno jako je jméno třídy (začíná tedy narozdí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ě:

public Kostka() {
    pocetSten = 6;
    random = new Random();
}

Pokud kostku nyní vytvoříme, bude mít v atributu pocetSten 6 a v 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 TahovyBoj.java a vyzkoušejme si vytvořit kostku a vypsat počet stěn:

    Kostka kostka = new Kostka(); // v tuto chvíli se zavolá konstruktor
    System.out.println(kostka.vratPocetSten());
import java.util.Random;

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. Dáme tedy kostruktoru 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 k TahovyBoj.java a zadejme tento parametr do konstruktoru:

    Kostka kostka = new Kostka(10); // v tuto chvíli se zavolá konstruktor s par. 10
    System.out.println(kostka.vratPocetSten());
import java.util.Random;
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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í:

public Kostka() {
    pocetSten = 6;
    random = new Random();
}

Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem (v TahovyBoj.java):

    Kostka sestistenna = new Kostka();
    Kostka desetistenna = new Kostka(10);
    System.out.println(sestistenna.vratPocetSten());
    System.out.println(desetistenna.vratPocetSten());
import java.util.Random;

Výstup:

Konzolová aplikace
6
10

Javě nevadí, že máme 2 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ů. NetBeans 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:

Nápověda k přetíženým metodám v Javě

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. NetBeans 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.seberKos­tku(this). Zde bychom hráči pomocí 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í this jsme specifikovali, že levá proměnná pocetSten náleží instanci, pravou Java chápe jako z parametru. Máme tedy 2 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čí) a nebude mít žádný parametr. Návratová hodnota bude typu int. Náhodné číslo získáme tak, že na generátoru zavoláme metodu nextInt(). Ta má 2 přetížení:

  • nextInt(): Varianta bez parametru vrací náhodné číslo v celém rozsahu datového typu int;
  • nextInt(Do): Vrací nezáporná čísla menší než mez Do. random.nextInt(100) tedy vrátí číslo od 0 do 99.

Pro naše účely se nejlépe hodí druhé přetížení, píšeme 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(), když máme v Javě připravenou cestu, jak toto řešit. U kostky nemá 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:

    System.out.println(sestistenna);
import java.util.Random;

Do konzole se vypíše pouze cesta k naší třídě, tedy tahovyboj.Kostka a tzv. hash kód objektu. V mém případě byl vypsán tento řetězec:

Konzolová aplikace
[email protected]

Metodu již jednoduše nedefinujeme, ale protože již exituje, 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 toString() používat. Pro přehledné překrytí označíme metodu anotací @Override:

    /**
     * Vrací textovou reprezentaci kostky
     * @return Textová reprezentace kostky
     */
    @Override
    public String toString() {
    return String.format("Kostka s %s stěnami", pocetSten);
    }

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 kostkama v cyklech házet a podíváme se, jestli fungují tak, jak se očekává:

    // vytvoření
    Kostka sestistenna = new Kostka();
    Kostka desetistenna = new Kostka(10);

    // hod šestistěnnou
    System.out.println(sestistenna);
    for (int i = 0; i < 10; i++) {
        System.out.print(sestistenna.hod() + " ");
    }

    // hod desetistěnnou
    System.out.println("\n\n" + desetistenna);
    for (int i = 0; i < 10; i++) {
        System.out.print(desetistenna.hod() + " ");
    }

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ěla 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 1908x (18.8 kB)
Aplikace je včetně zdrojových kódů v jazyce java

 

Předchozí článek
Řešené úlohy k 2. lekci OOP v Javě
Všechny články v sekci
Objektově orientované programování v Javě
Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
42 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 13 let. Má rád Nirvanu, sushi a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity (16)

 

 

Komentáře
Zobrazit starší komentáře (72)

Avatar
Jan Béňa
Člen
Avatar
Jan Béňa:29.1.2020 18:21

Ahoj všem.
Asi bych potřeboval poradit. Snažil jsem se udělat program, který by vygeneroval mezi sebou dvě náhodné čísla a ty pak porovnal mezi sebou. Pokud by jedno bylo větší přičetl by +1 do proměnné třeba a v opačném případě do b. při rovnosti nic. Snažil jsem se to naprogramovat pomocí OOP a vše funguje jak jsem popsal. Ovšem když to dám do cyklu tak to vygeneruje jednou náhodně a pak to vezme první náhodné generování. Nějak nemůžu docílit toho, aby to vždycky vygenerovalo nové čísla do cyklu tak že mám vždy jen tři výsledky.(1:0,0:1 nebo 0:0). Myslím že pokud jsem došel sem, měl bych to zvládnout, akorát už se s tím lámu týden a pořád nic. Mohl by mi když tak nějaký zkušený borec dát nějaký tip aby mě nasměroval. Děkuji mockrát.

 
Odpovědět
29.1.2020 18:21
Avatar
Odpovídá na Jan Béňa
Matúš Olejník:29.1.2020 20:14

Ahoj, prihoď sem aj tvoj kód, so vstupom a požadovaným výstupom. Najlepšie by však bolo keby vytvoríš nové vlákno vo fóre pre Javu kde môžme viacej písať. Keď už sa s tým se#ieš týždeň nech to dokončíš :D

Odpovědět
29.1.2020 20:14
/* I am not sure why this works but it fixes the problem */
Avatar
Jan Béňa
Člen
Avatar
Odpovídá na Matúš Olejník
Jan Béňa:29.1.2020 20:17

Ok zkusím vytvořit vlákno na fóru.

 
Odpovědět
29.1.2020 20:17
Avatar
Gemy
Člen
Avatar
Gemy:25.3.2020 15:58

Jak dokáže stroj generovat náhodná čísla? Odkud je matematicky bere?

Odpovědět
25.3.2020 15:58
Peníze neznamenají úspěch.
Avatar
Alesh
Překladatel
Avatar
Odpovídá na Gemy
Alesh:25.3.2020 18:45

Nejedná se o náhodná čísla, ale o pseudonáhodná, protože jak správně tušíš, stroj nemůže "vymyslet" náhodné číslo. Laicky vysvětleno to funguje tak, že se vezme systémový čas, ten se vynásobí velkým prvočíslem a dále se to upravuje tak, aby vypadlo číslo z požadovaného intervalu.
Odborné vysvětlení najdeš na netu, třeba na Wikipedii, viz Generátor náhodných čísel.

Editováno 25.3.2020 18:46
 
Odpovědět
25.3.2020 18:45
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Aleš Fiala
Člen
Avatar
Aleš Fiala:29.6.2020 17:13
Exception in thread "main" java.lang.StackOverflowError
        at tahovyboj.Kostka.<init>(Kostka.java:16)
        at tahovyboj.Kostka.<init>(Kostka.java:16)
        at tahovyboj.Kostka.<init>(Kostka.java:16)
        at tahovyboj.Kostka.<init>(Kostka.java:16)
        at tahovyboj.Kostka.<init>(Kostka.java:16)
        at tahovyboj.Kostka.<init>(Kostka.java:16)
        at tahovyboj.Kostka.<init>(Kostka.java:16)
        at tahovyboj.Kostka.<init>(Kostka.java:16)
C:\Users\Lenovo\AppData\Local\NetBeans\Cache\12.0\executor-snippets\run.xml:111: The following error occurred while executing this line:
C:\Users\Lenovo\AppData\Local\NetBeans\Cache\12.0\executor-snippets\run.xml:94: Java returned: 1
BUILD FAILED (total time: 3 seconds)
Editováno 1.7.2020 11:12
 
Odpovědět
29.6.2020 17:13
Avatar
Aleš Fiala
Člen
Avatar
Odpovídá na Aleš Fiala
Aleš Fiala:29.6.2020 17:26

Jak už tu někdo psal: Už si z toho sypu popel nad hlavu. Stačilo jen dát u druhé třídy Clean and Build. Jak se dá tohle dlouhý smazat? :)

 
Odpovědět
29.6.2020 17:26
Avatar
Ondra Hájek
Člen
Avatar
Ondra Hájek:22.12.2020 20:47

Ahoj, je tu někdo zkušený kdo by se podíval na moje řešení? Někde jsem okoukal řetězení konstruktorů. Znamená to, že pokud zavolám konstruktor bez argumentu, ten následně zavolá konstruktor druhý a dosadí defaultní hodnotu argumentu? Chápu to správně? Viz kód.
Díky, Ondra

import java.util.Random;

public class Kostka {

    private int pocetSten;
    private Random random;

    public Kostka(int pocetSten) {
        this.pocetSten = pocetSten;
        this.random = new Random();
    }

    public Kostka() {
        this(6);
    }

    public int getPocetSten() {
        return this.pocetSten;
    }

    @Override
    public String toString() {
        return "Kostka{" +
                "pocetSten=" + this.pocetSten +
                '}';
    }

    public int randomNumber() {
        return random.nextInt(this.pocetSten) + 1;
    }
}
 
Odpovědět
22.12.2020 20:47
Avatar
Atrament
Super redaktor
Avatar
Odpovídá na Ondra Hájek
Atrament:23.12.2020 10:23

Chápeš to správně, ale asi bych se tomu vyhnul, osobně nemám tohle řetězení konstruktorů rád. U jednoduchého příkladu, kdy nastavuješ jednu defaultní hodnotu je to ještě v pohodě, ale jakmile chceš nastavovat více defaultních hodnot, tak se do toho můžeš velice snadno zamotat. V tom případě je mnohem lepší použít Builder Pattern...

 
Odpovědět
23.12.2020 10:23
Avatar
Karel Stehlík:4. ledna 23:46

Návrhové vzory jsou pokročilejší technika. Pro ty, kteří se ale s OOP teprve seznamují, je využití klíčového slova "this" při přetěžování konstruktorů ideální obrana před duplikováním kódu.

Ondro, pokud se název atributu objektu nedá zaměnit s názvem lokální proměnné - typicky u konstruktoru jeho parametrem, není nutné klíčové slovo "this" před atributem uvádět.

 
Odpovědět
4. ledna 23:46
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 82. Zobrazit vše