Lekce 8 - Aréna s mágem (dědičnost a polymorfismus)
V minulé lekci, Dědičnost a polymorfismus, jsme si vysvětlili dědičnost a polymorfismus.
Dnes máme slíbeno, že si dědičnost a polymorfismus vyzkoušíme v praxi. Bude to opět na naší aréně, kde z bojovníka budeme dědit mága. Tento tutoriál již patří k těm náročnějším a bude tomu tak i u dalších. Proto si průběžně procvičujte práci s objekty, zkoušejte si naše cvičení a také vymýšlejte nějaké své aplikace, abyste si zažili základní věci. To, že je tu přítomen celý seriál neznamená, že ho celý najednou přečtete a pochopíte Snažte se programovat průběžně.
Než začneme něco psát, shodněme se na tom, co by měl mág umět. Mág
bude fungovat stejně, jako bojovník. Kromě života bude mít však i
manu. Zpočátku bude mana plná. V případě plné many
může mág vykonat magický útok, který bude mít
pravděpodobně vyšší damage, než útok
normální (ale samozřejmě záleží na tom, jak si ho nastavíme). Tento
útok manu vybije na 0
. Každé kolo se bude mana zvyšovat o
10
a mág bude podnikat jen běžný útok. Jakmile se mana zcela
doplní, opět bude moci magický útok použít. Mana bude zobrazena grafickým
ukazatelem, stejně jako život.
Do původního projektu TahovyBoj
vytvoříme tedy třídu
Mag.java
, zdědíme ji z třídy Bojovnik
a dodáme ji
atributy, které chceme oproti bojovníkovi navíc. Bude tedy vypadat takto
(opět si ji okomentujte):
class Mag extends Bojovnik { private int mana; private int maximalniMana; private int magickyUtok; }
V mágovi nemáme zatím přístup ke všem proměnným, protože jsou v
bojovníkovi nastavené jako privátní. Musíme třídu Bojovnik
lehce upravit. Změníme modifikátory private
u atributů na
protected
. Budeme potřebovat jen atributy kostka
a
jmeno
, ale klidně nastavíme jako protected
všechny
atributy charakteru, protože se v budoucnu mohou hodit, kdybychom se rozhodli
oddědit další typy bojovníků. Naopak atribut zprava
není
vhodné nastavovat jako protected
, protože nesouvisí s
bojovníkem, ale s nějakou vnitřní logikou třídy. Třída tedy bude vypadat
nějak takto:
protected String jmeno; protected int zivot; protected int maximalniZivot; protected int utok; protected int obrana; protected Kostka kostka; private String zprava; // ...
Přejděme ke konstruktoru.
Konstruktor potomka
Java nedědí konstruktory! Je to pravděpodobně z toho důvodu, že předpokládá, že potomek bude mít navíc nějaké atributy a původní konstruktor by u něj byl na škodu. To je i náš případ, protože konstruktor mága bude brát oproti tomu z bojovníka navíc 2 parametry (mana a magický útok).
Definujeme si tedy konstruktor v potomkovi Mag
, který bere
parametry potřebné pro vytvoření bojovníka a několik parametrů navíc pro
mága.
O potomků je nutné vždy volat konstruktor předka. Je to z toho důvodu, že bez volání konstruktoru nemusí být instance správně inicializovaná. Konstruktor předka nevoláme pouze v případě, že žádný nemá. Náš konstruktor musí mít samozřejmě všechny parametry potřebné pro předka plus ty nové, co má navíc potomek. Některé potom předáme předkovi a některé si zpracujeme sami. Konstruktor předka se vykoná před naším konstruktorem.
V Javě existuje klíčové slovo super
, které je podobné
námi již známému this
. Na rozdíl od klíčového slova
this
, které odkazuje na konkrétní instanci třídy,
super
odkazuje na předka. My tedy můžeme
zavolat konstruktor předka s danými parametry a poté vykonat navíc
inicializaci pro mága.
Konstruktor mága bude tedy vypadat takto:
public Mag(String jmeno, int zivot, int utok, int obrana, Kostka kostka, int mana, int magickyUtok) { super(jmeno, zivot, utok, obrana, kostka); this.mana = mana; this.maximalniMana = mana; this.magickyUtok = magickyUtok; }
Stejně můžeme volat i jiný konstruktor v té samé třídě
(ne předka), jen místo klíčového slova super
použijeme
this
.
Přesuňme se nyní do souboru TahovyBoj.java
a druhého
bojovníka (u nás to je Shadow) změňme na mága, např. takto:
Bojovnik gandalf = new Mag("Gandalf", 60, 15, 12, kostka, 30, 45);
Změnu samozřejmě musíme udělat i v řádku, kde bojovníka do arény
vkládáme. Všimněte si, že mága ukládáme do proměnné typu
Bojovnik
. Nic nám v tom nebrání, protože bojovník je jeho
předek. Stejně tak si můžeme typ proměnné změnit na Mag
.
Když aplikaci nyní spustíme, bude fungovat úplně stejně, jako předtím.
Mág vše dědí z bojovníka a zatím tedy funguje jako bojovník.
Polymorfismus a přepisování metod
Bylo by výhodné, kdyby objekt Arena
mohl s mágem pracovat
stejným způsobem jako s bojovníkem. My již víme, že takovémuto mechanismu
říkáme polymorfismus. Aréna zavolá na objektu metodu
utoc()
se soupeřem v parametru. Nestará se o to, jestli bude
útok vykonávat bojovník nebo mág, bude s nimi pracovat stejně. U mága si
tedy přepíšeme metodu utoc()
z předka.
Přepíšeme zděděnou metodu tak, aby útok pracoval s manou, hlavička metody
však zůstane stejná.
Když jsme u metod, budeme v Bojovnik.java
ještě jistě
používat metodu nastavZpravu()
, ta je však privátní. Označme
ji jako protected
:
protected void nastavZpravu(String zprava) {
Při návrhu bojovníka jsme samozřejmě měli myslet na to,
že se z něj bude dědit a již označit vhodné atributy a metody jako
protected
. V tutoriálu k bojovníkovi jsem vás tím však
nechtěl zbytečně zatěžovat, proto musíme modifikátory změnit až teď,
kdy jim rozumíme
Pojďme přepsat metodu bojovníka utoc()
v mágovi. Metodu
normálně definujeme v souboru Mag.java
tak, jak jsme zvyklí,
pouze ji označíme klíčovým slovem @Override
pro
přepsání:
@Override public void utoc(Bojovnik souper) {
Podobně jsme přepisovali metodu toString()
u našich objektů,
každý objekt v Javě je totiž odděděný od java.lang.Object
,
který obsahuje několik defaultních metod a
jedna z nich je i metoda toString()
. Při její implementaci bychom
tedy měli označit, že se jedná o přepsanou metodu.
Chování metody utoc()
nebude nijak složité. Podle hodnoty
many buď provedeme běžný útok nebo útok magický. Hodnotu many potom buď
zvýšíme o 10
nebo naopak snížíme na 0
v
případě magického útoku:
@Override public void utoc(Bojovnik souper) { int uder = 0; // Mana není naplněna if (mana < maximalniMana) { mana += 10; if (mana > maximalniMana) { mana = maximalniMana; } uder = utok + kostka.hod(); nastavZpravu(String.format("%s útočí s úderem za %s hp", jmeno, uder)); } else { // Magický útok uder = magickyUtok + kostka.hod(); nastavZpravu(String.format("%s použil magii za %s hp", jmeno, uder)); mana = 0; } souper.branSe(uder); }
Kód je asi srozumitelný. Všimněte si omezení many proměnnou
maximalniMana
. Může se nám totiž stát, že tuto hodnotu
přesáhneme, když ji zvyšujeme o 10
. Když se nad kódem
zamyslíme, tak útok výše v podstatě vykonává původní metoda
utoc()
. Jistě by bylo přínosné zavolat podobu metody na
předkovi místo toho, abychom chování opisovali. K tomu opět použijeme
klíčové slovo super
:
{JAVA_OOP} class Mag extends Bojovnik { private int mana; private int maximalniMana; private int magickyUtok; public Mag(String jmeno, int zivot, int utok, int obrana, Kostka kostka, int mana, int magickyUtok) { super(jmeno, zivot, utok, obrana, kostka); this.mana = mana; this.maximalniMana = mana; this.magickyUtok = magickyUtok; } @Override public void utoc(Bojovnik souper) { // Mana není naplněna if (mana < maximalniMana) { mana += 10; if (mana > maximalniMana) { mana = maximalniMana; } super.utoc(souper); } else { // Magický útok int uder = magickyUtok + kostka.hod(); nastavZpravu(String.format("%s použil magii za %s hp", jmeno, uder)); souper.branSe(uder); mana = 0; } } } {/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}
{JAVA_OOP} class Bojovnik { protected String jmeno; protected int zivot; protected int maximalniZivot; protected int utok; protected int obrana; protected Kostka kostka; private String zprava; public Bojovnik(String jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maximalniZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public boolean jeZivy() { return (zivot > 0); } public String grafickyZivot() { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) zivot / maximalniZivot) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; } public void utoc(Bojovnik souper) { int uder = utok + kostka.hod(); nastavZpravu(String.format("%s útočí s úderem za %s hp", jmeno, uder)); souper.branSe(uder); } public void branSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.format("%s utrpěl poškození %s hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zemřel"; } } else { zprava = String.format("%s odrazil útok", jmeno); } nastavZpravu(zprava); } protected void nastavZpravu(String zprava) { this.zprava = zprava; } public String vratPosledniZpravu() { return zprava; } @Override public String toString() { return jmeno; } } {/JAVA_OOP}
{JAVA_OOP} class Arena { private Bojovnik bojovnikA; private Bojovnik bojovnikB; private Kostka kostka; public Arena(Bojovnik bojovnikA, Bojovnik bojovnikB, Kostka kostka) { this.bojovnikA = bojovnikA; this.bojovnikB = bojovnikB; this.kostka = kostka; } private void vykresli() { System.out.println("-------------- Aréna -------------- \n"); System.out.println("Zdraví bojovníků: \n"); System.out.printf("%s %s%n", bojovnikA, bojovnikA.grafickyZivot()); System.out.printf("%s %s%n", bojovnikB, bojovnikB.grafickyZivot()); } private void vypisZpravu(String zprava) { System.out.println(zprava); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Chyba, nepovedlo se uspat vlákno!"); } } public void zapas() { // původní pořadí Bojovnik bojovnikA = this.bojovnikA; Bojovnik bojovnikB = this.bojovnikB; System.out.println("Vítejte v aréně!"); System.out.printf("Dnes se utkají %s s %s! %n%n", bojovnikA, bojovnikB); // prohození bojovníků boolean zacinaBojovnikB = (kostka.hod() <= kostka.vratPocetSten() / 2); if (zacinaBojovnikB) { bojovnikA = this.bojovnikB; bojovnikB = this.bojovnikA; } System.out.printf("Začínat bude bojovník %s! %nZápas může začít...%n", bojovnikA); // cyklus s bojem while (bojovnikA.jeZivy() && bojovnikB.jeZivy()) { bojovnikA.utoc(bojovnikB); vykresli(); vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o obraně if (bojovnikB.jeZivy()) { bojovnikB.utoc(bojovnikA); vykresli(); vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o obraně } System.out.println(); } } } {/JAVA_OOP}
{JAVA_OOP} {JAVA_MAIN_BLOCK} // vytvoření objektů Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik gandalf = new Mag("Gandalf", 60, 15, 12, kostka, 30, 45); Arena arena = new Arena(zalgoren, gandalf, kostka); // zápas arena.zapas(); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
Opět vidíme, jak můžeme znovupoužívat kód. S dědičností je spojeno opravdu mnoho technik, jak si ušetřit práci. V našem případě to ušetří několik řádků, ale u většího projektu by to mohlo mít obrovský význam.
Aplikace nyní funguje tak, jak má:
Konzolová aplikace
-------------- Aréna --------------
Zdraví bojovníků:
Zalgoren [############# ]
Gandalf [################# ]
Gandalf použil magii za 52 hp
Zalgoren utrpěl poškození 36 hp
Arena nás však neinformuje o maně mága, pojďme to napravit. Přidáme
mágovi veřejnou metodu grafickaMana()
, která bude obdobně jako
u života vracet textový řetězec s grafickým ukazatelem many.
Abychom nemuseli logiku se složením ukazatele psát dvakrát, upravíme
metodu grafickyZivot()
v souboru Bojovnik.java
.
Připomeňme si, jak vypadá:
public String grafickyZivot() { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) zivot / maximalniZivot) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; }
Vidíme, že není kromě proměnných zivot
a
maximalniZivot
na životě nijak závislá. Metodu přejmenujeme na
grafickyUkazatel
a dáme ji 2 parametry: aktuální hodnotu a
maximální hodnotu. Proměnné zivot
a maximalniZivot
v těle metody poté nahradíme za aktualni
a
maximalni
. Modifikátor bude protected
, abychom metodu
mohli v potomkovi použít:
protected String grafickyUkazatel(int aktualni, int maximalni) { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) aktualni / maximalni) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; }
Metodu grafickyZivot()
v souboru Bojovnik.java
naimplementujeme znovu, bude nám v ní stačit jediný řádek a to zavolání
metody grafickyUkazatel()
s příslušnými parametry:
public String grafickyZivot() { return grafickyUkazatel(zivot, maximalniZivot); }
Určitě jsem mohl v tutoriálu s bojovníkem udělat metodu
grafickyUkazatel()
rovnou. Chtěl jsem však, abychom si ukázali,
jak se řeší případy, kdy potřebujeme vykonat podobnou funkčnost
vícekrát. S takovouto parametrizací se v praxi budete setkávat často,
protože nikdy přesně nevíme, co budeme v budoucnu od našeho programu
požadovat.
Nyní můžeme vykreslovat ukazatel tak, jak se nám to hodí. Přesuňme se
do Mag.java
a naimplementujme metodu
grafickaMana()
:
public String grafickaMana() { return grafickyUkazatel(mana, maximalniMana); }
Jednoduché, že? Nyní je mág hotový, zbývá jen naučit arénu
zobrazovat manu v případě, že je bojovník mág. Přesuňme se tedy do
souboru Arena.java
.
Rozpoznání typu objektu
Jelikož se nám nyní vykreslení bojovníka zkomplikovalo, uděláme si na
něj samostatnou metodu vypisBojovnika()
, jejím parametrem bude
daná instance bojovníka:
private void vypisBojovnika(Bojovnik bojovnik) { System.out.println(bojovnik); System.out.print("Život: "); System.out.println(bojovnik.grafickyZivot()); }
Nyní pojďme reagovat na to, jestli je bojovník mág. Minule jsme si
řekli, že k tomu slouží operátor instanceof
:
private void vypisBojovnika(Bojovnik bojovnik) { System.out.println(bojovnik); System.out.print("Život: "); System.out.println(bojovnik.grafickyZivot()); if (bojovnik instanceof Mag) { System.out.print("Mana: "); System.out.println(((Mag) bojovnik).grafickaMana()); } }
Bojovníka jsme museli na mága přetypovat, abychom se k metodě
grafickaMana()
dostali. Samotná třída Bojovnik
ji
totiž nemá. To bychom měli, metodu vypisBojovnika()
budeme volat
v metodě vykresli()
, která bude vypadat takto:
{JAVA_OOP} class Arena { private Bojovnik bojovnikA; private Bojovnik bojovnikB; private Kostka kostka; public Arena(Bojovnik bojovnikA, Bojovnik bojovnikB, Kostka kostka) { this.bojovnikA = bojovnikA; this.bojovnikB = bojovnikB; this.kostka = kostka; } private void vykresli() { System.out.println("-------------- Aréna -------------- \n"); System.out.println("Bojovníci: \n"); vypisBojovnika(bojovnikA); System.out.println(); vypisBojovnika(bojovnikB); System.out.println(); } private void vypisZpravu(String zprava) { System.out.println(zprava); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Chyba, nepovedlo se uspat vlákno!"); } } private void vypisBojovnika(Bojovnik bojovnik) { System.out.println(bojovnik); System.out.print("Život: "); System.out.println(bojovnik.grafickyZivot()); if (bojovnik instanceof Mag) { System.out.print("Mana: "); System.out.println(((Mag) bojovnik).grafickaMana()); } } public void zapas() { // původní pořadí Bojovnik bojovnikA = this.bojovnikA; Bojovnik bojovnikB = this.bojovnikB; System.out.println("Vítejte v aréně!"); System.out.printf("Dnes se utkají %s s %s! %n%n", bojovnikA, bojovnikB); // prohození bojovníků boolean zacinaBojovnikB = (kostka.hod() <= kostka.vratPocetSten() / 2); if (zacinaBojovnikB) { bojovnikA = this.bojovnikB; bojovnikB = this.bojovnikA; } System.out.printf("Začínat bude bojovník %s! %nZápas může začít...%n", bojovnikA); // cyklus s bojem while (bojovnikA.jeZivy() && bojovnikB.jeZivy()) { bojovnikA.utoc(bojovnikB); vykresli(); vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o obraně if (bojovnikB.jeZivy()) { bojovnikB.utoc(bojovnikA); vykresli(); vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o obraně } System.out.println(); } } } {/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}
{JAVA_OOP} class Bojovnik { protected String jmeno; protected int zivot; protected int maximalniZivot; protected int utok; protected int obrana; protected Kostka kostka; private String zprava; public Bojovnik(String jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maximalniZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public boolean jeZivy() { return (zivot > 0); } protected String grafickyUkazatel(int aktualni, int maximalni) { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) aktualni / maximalni) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; } public String grafickyZivot() { return grafickyUkazatel(zivot, maximalniZivot); } public void utoc(Bojovnik souper) { int uder = utok + kostka.hod(); nastavZpravu(String.format("%s útočí s úderem za %s hp", jmeno, uder)); souper.branSe(uder); } public void branSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.format("%s utrpěl poškození %s hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zemřel"; } } else { zprava = String.format("%s odrazil útok", jmeno); } nastavZpravu(zprava); } protected void nastavZpravu(String zprava) { this.zprava = zprava; } public String vratPosledniZpravu() { return zprava; } @Override public String toString() { return jmeno; } } {/JAVA_OOP}
{JAVA_OOP} class Mag extends Bojovnik { private int mana; private int maximalniMana; private int magickyUtok; public Mag(String jmeno, int zivot, int utok, int obrana, Kostka kostka, int mana, int magickyUtok) { super(jmeno, zivot, utok, obrana, kostka); this.mana = mana; this.maximalniMana = mana; this.magickyUtok = magickyUtok; } @Override public void utoc(Bojovnik souper) { // Mana není naplněna if (mana < maximalniMana) { mana += 10; if (mana > maximalniMana) { mana = maximalniMana; } super.utoc(souper); } else { // Magický útok int uder = magickyUtok + kostka.hod(); nastavZpravu(String.format("%s použil magii za %s hp", jmeno, uder)); souper.branSe(uder); mana = 0; } } public String grafickaMana() { return grafickyUkazatel(mana, maximalniMana); } } {/JAVA_OOP}
{JAVA_OOP} {JAVA_MAIN_BLOCK} // vytvoření objektů Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik gandalf = new Mag("Gandalf", 60, 15, 12, kostka, 30, 45); Arena arena = new Arena(zalgoren, gandalf, kostka); // zápas arena.zapas(); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
Hotovo
Konzolová aplikace
-------------- Aréna --------------
Bojovníci:
Zalgoren
Život: [########## ]
Gandalf
Život: [##### ]
Mana: [############# ]
Zalgoren útočí s úderem za 28 hp
Aplikaci ještě můžeme dodat hezčí vzhled, vložil jsem ASCIIart nadpis
Aréna, který jsem vytvořil ASCII generátorem. Metodu k
vykreslení ukazatele jsem upravil tak, aby vykreslovala plný obdélník místo
#
(ten napíšete pomocí kláves Alt +
219). Výsledek může vypadat takto:
Konzolová aplikace
__ ____ ____ _ _ __
/__\ ( _ \( ___)( \( ) /__\
/(__)\ ) / )__) ) ( /(__)\
(__)(__)(_)\_)(____)(_)\_)(__)(__)
Bojovníci:
Zalgoren
Život: ████
Gandalf
Život: ███████
Mana: █
Gandalf použil magii za 48 hp
Zalgoren utrpěl poškození 33 hp
Kód máte v příloze. Pokud jste něčemu nerozuměli, zkuste si článek přečíst vícekrát nebo pomaleji, jsou to důležité praktiky.
V následujícím cvičení, Řešené úlohy k 5.-8. 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 1683x (17.17 kB)
Aplikace je včetně zdrojových kódů v jazyce Java