Java
5
Nadtriedy a podtriedy. Práca konštruktorov pri vytváraní prvkov
podtried. Volanie členských premenných a metód nadtriedy. Kľúčové slovo
“super”. Abstraktné triedy a metódy. Vstup z klávesnice.
Dedičnosť je jeden zo spôsobov, ako možno
použiť už raz vytvorený kód – A ako možno rozvinúť funkčnosť už raz vytvorených objektov
rôznymi smermi, bez toho, že by tieto východiskové objekty museli byť zmenené.
Navyše, niekedy je veľmi výhodné použiť znovu filozofiu východiskového objektu
aj pri práci s jeho „potomkami“.
Triedy Chlieb, Mlieko, Kniha môžu byť napríklad potomkami triedy Tovar.
Pri pokladni s prvkami týchto tried by mohla pokladníčka narábať jednoducho ako s objektami typu Tovar –
zistí si ich cenu a spracuje tento údaj. Inokedy nás zaujímajú tieto
objekty aj pre ich špeciálnejšie vlastnosti. Buďme konkrétni:
Kódy P5.1 A-C
P5.1A:
Výpis:
Hlasi sa konstruktor
triedy Tovar.
Vytvoril tovar v cene 2
Process Exit...
P5.1B:
public class Kniha extends Tovar {
String
autor;
String
titul;
public
Kniha(String pisatel, String nazov, int cost){
//
volanie konstruktora triedy Tovar:
super(cost);
System.out.println("Hlasi
sa konstruktor triedy Kniha.");
autor = pisatel;
titul = nazov;
}
public
static void main(String[] args) {
Kniha memoare1 = new
Kniha("Dracula", "Moj vecny smad", 99);
System.out.println("Vytvoril som knihu s
titulom \"" +memoare1.titul +
"\", cena
je: " + memoare1.cena);
}//EOMain
}//EOClass
Výpis:
Hlasi sa
konstruktor triedy Tovar.
Hlasi sa konstruktor triedy Kniha.
Vytvoril som knihu s titulom "Moj vecny
smad", cena je: 99
Process Exit...
P5.1C
public class Pokladnicka{
public
void povedzCenu(Tovar zbozie){
System.out.println("Tento
tovar stoji " + zbozie.cena + " Sk");
}
public
static void main(String[] args){
Tovar
prvyTovar = new Tovar(48);
Kniha
druhyTovar = new Kniha("Jack London","Martin Eden",198);
Pokladnicka
evicka = new Pokladnicka();
evicka.povedzCenu(prvyTovar);
evicka.povedzCenu(druhyTovar);
}//EOMain
}//EOClass
Výstup:
Hlasi sa konstruktor triedy Tovar.
Hlasi sa konstruktor triedy Tovar.
Hlasi sa konstruktor triedy Kniha.
Tento tovar stoji 48 Sk
Tento tovar stoji 198 Sk
Process Exit...
Dedičnosť
umožňuje v Jave odvodzovať jednu triedu od druhej. Niekedy sa tento proces
porovnáva aj s vkladaním: do nadtriedy (superclass) Tovar je vložená
podtrieda (subclass) Kniha. Technicky sa toto odvodzovanie zapisuje slovom extends.
Napr.: public class Kniha extends Tovar { ...
T. j. podtrieda určítým
spôsobom rozširuje svoju nadtriedu.
Odvodené
triedy majú prístup ku všetkým (nie súkromným) premenným a metódam
nadtriedy. Môžu sa teda, okrem iného, správať ako prvok nadtriedy. Prvok triedy
Kniha dedí všetky údaje a funkcionalitu zahrnutú v prvkoch triedy
Tovar. Zároveň môže mať svoje nové, špecifické premenné a metódy. To, že trieda
Tovar je nadtriedou inej triedy neznamená, že by nemohla byť bežne použitá. Viď
kód v triede Pokladnicka.
Ak chceme vytvoriť objekt podtriedy, v jeho konštruktore vždy najprv voláme (alebo Java volá za nás) konštruktor nadtriedy. Nami zvolený konštruktor nadtriedy voláme pomocou slova super. Npríklad
ako v konštruktore triedy Kniha:
“super(cost)”;
Vo všeobecnosti super( zoznamParametrov); je príkaz, volajúci konštruktor nadtriedy, ktorý
sme jednoznačne určili práve udaním parametrov. Čo sa stane, ak žiaden
konštruktor nadtriedy nezavoláme?
Ak zakomentujeme v konštruktore triedy Kniha príkaz “super(cost)” a dame kód preložiť, prekladač vyhlási:
cannot resolve symbol
symbol : constructor Tovar ()
location: class Tovar
Prekladač sa teda správa, ako keby mal
implicitne, pred všetkými príkazmi v konštruktore podtriedy Kniha vykonať
najprv príkaz “super();”, ekvivalentný volaniu konštruktora Tovar() . Ten však v triede Tovar nie je
definovaný. Akonáhle tam taký konštruktor pridáme:
public Tovar(){}
a Tovar.java a Kniha.java preložíme, znova všetko funguje. Metóda „Kniha.main()“
sa spustí bez problémov. Len jej výstup je asi iný, než sme čakali:
Hlasi sa
konstruktor triedy Kniha.
Vytvoril som knihu
s titulom "Moj vecny smad", cena je: 0
Process Exit...
Prirodzene,
z formálneho hľadiska sa vytvoril aj podobjekt typu “Tovar” pre konštruovaný
objekt „Kniha“, ale konštruktor Tovar()
nemá v sebe inštrukciu, aby nastavil cenu. Táto premenná preto
ostáva na svojej defaultnej nulovej hodnote. Poučenie z celého procesu je
jasné:
Za správne vytvorenie
podobjektu a celého objektu, za použitie-volanie vhodných konštruktorov
zodpovedá programátor.
V triede Slovo sme vytvorili triedu,
ktorá by mohla byť celkom prirodzene nadtriedou tried typu:
PodstatnéMeno, Sloveso, Predložka atď. Každý
špecializovaný slovný typ má prirodzene iné vlastné „funkcie“ – podstatné meno sa dá skloňovať, sloveso
časovať atď. Začiatky takéhoto možného rozloženia triedy Slovo na podtriedy
a jeho využitia vidíme na nasledujúcom príklade:
public class PodstatneMeno extends Slovo{
public
String vzor = "neznamy";
public
PodstatneMeno(String text, String akyVzor){
super(text);
System.out.println("Konstruktor
PodstatneMeno vidi vzor \" " + vzor+ "\"");
this.vzor = akyVzor;
}
public
String toString(){
return getText();
}
public
String piatyPadJednotnehoCisla(){
if (vzor.equals("dub")){
String sklonovane =getText() + "e";
return sklonovane;
}
if (vzor.equals("chlap")){
String sklonovane =getText() + "ovi";
return sklonovane;
}
if
(vzor.equals("zena")){
String sklonovane =getText().substring(0,getText().length() -1)
+"e";
return sklonovane;
}
return "Sorry, neviem vytvorit piaty
pad, nepoznam taky vzor";
}
public static void main(String[] args){
PodstatneMeno hrdina = new
PodstatneMeno("zub","dub");
System.out.println("Podstatne meno je: " +
hrdina);
System.out.println("Jeho piaty pad je: " +
hrdina.piatyPadJednotnehoCisla());
hrdina = new
PodstatneMeno("Tarzan","chlap");
System.out.println("Podstatne meno je: " +
hrdina);
System.out.println("Jeho piaty pad je: " +
hrdina.piatyPadJednotnehoCisla());
PodstatneMeno hrdinka = new
PodstatneMeno("Snehulienka","zena");
System.out.println("Teraz kratka
rozpravka:");
System.out.println(hrdina + " snival o " +
hrdinka.piatyPadJednotnehoCisla() + ".");
}
}//EOClass
Všimnite si teraz výpis
z programu. Usúďte z výpisu čo najviac o poradí, v akom sa
konštruuje objekt triedy PodstatneMeno. Ako je to napríklad s tvorením
jeho premennej „vzor“? Výpis z programu je:
Konstruktor Slovo vytvoril
slovo s textom " zub" s dlzkou 3
Konstruktor PodstatneMeno
vidi vzor " neznamy"
Podstatne meno je: zub
Jeho piaty pad je: zube
Konstruktor Slovo vytvoril
slovo s textom " Tarzan" s dlzkou 6
Konstruktor PodstatneMeno
vidi vzor " neznamy"
Podstatne meno je: Tarzan
Jeho piaty pad je:
Tarzanovi
Konstruktor Slovo vytvoril
slovo s textom " Snehulienka" s dlzkou 11
Konstruktor PodstatneMeno
zapisal vzor " neznamy"
Teraz kratka rozpravka:
Tarzan snival o
Snehulienke.
Process Exit...
Ako z výpisu vidno,
pri tvorení objektu PodstatneMeno sa najprv vytvorí kompletný podobjekt typu
Slovo. Potom prebehne inicializácia premenných inštancie. Premennej vzor je
priradená hodnota „neznamy“. Konštruktor potom túto hodnotu upraví podľa
zadaného parametra.
Len jedna nadtrieda
Každá nová podtrieda môže
byť v Jave dedičom iba jednej nadtriedy.
Dedenie od viacerých tried paralelne, typu
public class Dieta extends
Otec extends Matka {
teda neexistuje.
Každá podtrieda ale môže
mať svojich vlastných potomkov. Vzniká tak viacstupňová hierarchia.
Kód P5.3
public class Detektivka
extends Kniha {
String menoDetektiva = "zatial sa nevie";
String menoVraha
="zatial sa nevie";
int pocetMrtvol;
boolean jeToKrvak;
public Detektivka(String autor, String nazov, int cena,
String detektiv, String vrah, int obete){
super(autor,nazov,cena);
System.out.println("Tu konstruktor triedy Detektivka.");
menoDetektiva =
detektiv;
menoVraha = vrah;
pocetMrtvol =
obete;
if( obete > 10)
jeToKrvak = true;
}
public void dajInfo(){
System.out.println("\n Detektivka \"" + titul +
"\", ktoru napisal renomovany autor " +
autor + "\n znova ukaze, ze
detektiv " + menoDetektiva +
"
odhali kazdeho vraha. \n Tentokrat rafinovaneho vraha " + pocetMrtvol +
" obeti.");
if(jeToKrvak){
System.out.println("Osobam so slabsimi nervami
odporucame citat v cakarni u lekara.");
}
}
public static void main(String[] args){
Detektivka haluze = new Detektivka("Markiz
Fero", "Smrt v krovi", 432, "Oresiansky","Jozef
Zahradnik", 13);
haluze.dajInfo();
}//EOMain
}//EOClass
Výstup :
Hlasi sa konstruktor
triedy Kniha.
Tu konstruktor triedy
Detektivka.
Detektivka "Smrt v krovi", ktoru napisal renomovany
autor Markiz Fero
znova ukaze, ze detektiv Oresiansky odhali kazdeho vraha.
Tentokrat rafinovaneho vraha 13 obeti.
Osobam so slabsimi nervami
odporucame citat v cakarni u lekara.
Process Exit...
Skladanie, alebo
dedičnosť?
Ak chceme vytvoriť nové, bohatšie, funkčnejšie objekty, ktoré
v sebe obsahujú funkcie iných, už existujúcich objektov, máme naporúdzi dve
riešenia. Skladanie, alebo dedičnosť (poprípade postup príbuzný dedičnosti –
použitie rozhrania).
Pod skladaním sa rozumie využitie faktu, že objekt môže mať za
členskú premennú ľubovoľný iný objekt.
Vezmime si ako príklad triedy String, Slovo a PodstatneMeno. Prečo
sme napríklad PodstatneMeno definovali ako potomka triedy Slovo, ale Slovo sme
nedefinovali ako potomka triedy String?
Odpoveď znie: preto, lebo každé slovo má textovú reprezentáciu
(„má“ String), kdežto každé podstatné meno je slovo. Pri premýšľaní
konkrétneho projektu a plánovaní tried, pri vyjasňovaní ich vzťahov nám rozlišovanie pomocou kľúčových slov má
a je často pomôže.
Sprístupnenie
premenných a metód nadtriedy pomocou kľúčového slova super
Niekedy meno premennej,
alebo metódy v podtriede „prekryje“ rovnomennú premennú alebo metódu
v nadtriede. Napríklad datumUkonceniaSkoly u vyštudovaného vysokoškoláka prekryje tú istú premennú, ktorú
používal už po maturite – vtedy ako
vyštudovaný stredoškolák. Ku starej, prekrytej premennej, či metóde sa môžeme
dopracovať pomocou kľúčového slova super.
To ukazuje nasledujúci, trocha umelý, ale rukolapný príklad:
Kódy P5.4A-B
Kód P5.4A
public class Stredoskolak{
int datumUkonceniaSkoly = 1973;
String info = "Flakal sa ako sa dalo, ale nakoniec
zmaturoval.";
public void informuj(){
System.out.println("Student ukoncil strednu skolu roku " +
datumUkonceniaSkoly + ".
\n" + info);
}
public static void main(String[] args){
Stredoskolak Bill = new Stredoskolak();
Bill.informuj();
}//EOMain
}//EOClass
Výpis:
Student ukoncil strednu skolu roku 1973.
Flakal sa ako sa dalo, ale
nakoniec zmaturoval.
Process Exit...
Kód P5.4B
public class Vysokoskolak
extends Stredoskolak{
int datumUkonceniaSkoly = 1980;
String info = "Trvalo mu to trocha dlhsie, ale
vysledky mal uspokojive.";
public void informuj(){
System.out.println("Studium: " + super.datumUkonceniaSkoly +
" - " + datumUkonceniaSkoly);
super.informuj();
System.out.println("Student ukoncil vysoku skolu roku " +
datumUkonceniaSkoly + ". \n" +
info);
}
public static void main(String[] args){
Vysokoskolak
Bill = new Vysokoskolak();
Bill.informuj();
// skuste
potom toto:/Vysokoskolak Bill = new
Stredoskolak();
// a toto:
Stredoskolak Bill = new Vysokoskolak();
// a tiez:
(Stredoskolak)Bill).informuj();
}//EOMain
}//EOClass
Výpis :
Studium: 1973 - 1980
Student ukoncil strednu skolu roku 1973.
Flakal sa ako sa dalo, ale
nakoniec zmaturoval.
Student ukoncil vysoku
skolu roku 1980.
Trvalo mu to trocha
dlhsie, ale vysledky mal uspokojive.
Process Exit...
Abstraktné metódy
Predstavme
si, že by sme potrebovali zostaviť program, ktorý by iba skloňoval podstatné
mená. V takom prípade by stačilo vyjsť z materskej triedy podstatného
mena a bolo by asi vhodné definovať-rozlišovať jej potomkov podľa jediného kritéria. Tým by bol vzor, podľa ktorého sa skloňujú.
Na druhej strane každé
podstatné meno má prvý pád jednotného čísla, druhý pád množného čísla atď
(vynechajme výnimky z pravidla). Tento fakt by sa mal už nejako objaviť
pri navrhovaní materskej triedy. Pri
tomto navrhovaní ale ešte nemá zmysel implementovať napr. metódu
siestyPadJednotnehoCisla(), lebo ešte
nevieme, ku akému vzoru bude potomok patriť. Ako programátori by sme však radi
zabezpečili, aby každý z potomkov takúto funkciu k dispozícii mal.
Riešením je použitie abstraktnej funkcie.
Pozrime sa na to na príklade:
Kódy P5.5A-B
Kód5.5-A
abstract class PodstMeno{
private String text;
PodstMeno(String textovaPodoba){
text =
textovaPodoba;
}
public String getText(){
return text;
}
abstract String prvyPadJednotnehoCisla();
abstract String druhyPadJednotnehoCisla();
// ......
abstract String siestyPadMnoznehoCisla();
}//EOClass
Kód P5.5B
public class VzorDub
extends PodstMeno{
public VzorDub(String text){
super(text);
}
public String prvyPadJednotnehoCisla() {
return text;
}
public String druhyPadJednotnehoCisla(){
return text + "u";
}
// ......
public String siestyPadMnoznehoCisla(){
return text +
"mi";
}
public static void main (String[] args){
VzorDub slovo =
new VzorDub("hrab");
System.out.println("\n Sklonujem slovo " + slovo);
System.out.println(slovo.prvyPadJednotnehoCisla());
System.out.println(slovo.druhyPadJednotnehoCisla());
System.out.println("..............");
System.out.println("..............");
System.out.println(slovo.siestyPadMnoznehoCisla());
}//EOMain
}//EOClass
Výpis:
Sklonujem slovo hrab
hrab
hrabu
..............
..............
hrabmi
Process Exit...
Všimnime si jednu
vec: Triedu PodstMeno sme nazvali
„abstract class“. Každú triedu, ktorá obsahuje aspoň jednu abstraktnú metódu,
treba takto označiť. Ak tak neurobíme, prekladač nás upozorní na chybu.
Abstraktné triedy slúžia
len ako akási „prvotná forma“, určená na vytváranie konkrétnych potomkov. Preto
nemôžeme vytvárať konkrétne objekty, ktoré by boli ich inštanciou. Riadok
PodstMeno instancia = new PodstMeno(“text“);
vyskytujúci sa v maine ľubovoľnej triedy prekladač okomentuje takto:
PodstMeno is abstract; cannot be instantiated
PodstMeno instancia = new PodstMeno("text");
Formátovaný Vstup
Aby sme mohli robiť
zaujímavejšie programy, odbočme na chvíľu od problematiky dedičnosti. Vyrobme
si metódu, ktorá nám umožní načítať údaje za behu programu: programu umožníme
načítať riadkový reťazec dlý až niekoľko desiatok znakov. Všetky detaily
spomenuté v triede Vstup si vysvetlíme až v nasledujúcich kapitolách.
Teraz len stručne opíšeme metódu
„citaj()“, ktorú sa chystáme definovať.
Metóda vráti ako hodnotu
textový reťazec typu String. Objekt „in“ je typu InputStream a je schopný
načítať pole bajtov, ktoré odosielame, keď zadávame znaky z klávesnice.
Každý znak sa načíta ako jeden bajt , vrátane znaku (znakov) ukončenia riadku.
Pole bajtov premeníme na objekt typu String. Potom príkazom „trim()“ odrežeme ukončovacie a prázdne znaky.
Podrobnejšie si prácu metódy „trim()“ pozrite v dokumentácii triedy
String.
Aj v programoch iných
tried z implicitného balíka budeme môcť teraz použiť príkaz „Vstup.citaj()“, ktorý nám umožní načítavať
údaje text za behu programu. Pozrime sa na nasledujúci kód:
Kód P5.6
import java.io.*;
public class Vstup{
public static
String citaj(){
String nacitane =
"zatialNenacitane";
try{
byte[] riadok = new byte[60];
System.in.read(riadok);
nacitane = new
String(riadok).trim();
return
nacitane;
}
catch(IOException e){
System.out.println("Chyba
v nacitani!");
return nacitane;
}
}
public static void main(String[] args){
System.out.println("Napis podstatne meno
vzoru dub a daj enter:");
String podstMeno = Vstup.citaj();
System.out.println("Siesty pad slova "
+
podstMeno + " je: " + podstMeno + "e");
}
}
Jeden z možných
výpisov:
Napis podstatne meno vzoru
dub a daj enter:
lob
Siesty pad slova lob je:
lobe
Process Exit.
Cvičenia 5
Cv5.1 malý hlavolam
Majú objekty podtriedy (odvodenej triedy, subclass) prístup
k privátnym premenným a metódam svojej nadtriedy (materskej triedy,
superclass)? V kóde P5.1A zmeňte premennú cena na súkromú
(private). Zmažte príslušné preložené súbory Tovar.class, Kniha.class,
Pokladnicka.class a dajte preložiť kódy P5.1A - P5.1C a sledujte, čo sa bude diať. Prípadné problémy odstráňte tak,
aby výstup z programov bol rovnaký.
Riešenia
Cv.5.1
Objekty podtriedy nemajú prístup k súkromným
prvkom ani metódam svojej nadtriedy. V triede Tovar by premenná
„cena“ mala byť rozhodne typu private.
Ak sa už cena v konštruktore stanoví, zrejme by ju nemal len tak hocikto
mať možnosť zmeniť. Problémy s prístupom sa dajú vyriešiť napríklad
definovaním metódy public int
getCena() v triede Tovar a príslušným volaním tejto metódy
v kódoch P5.1B-C.
Cv.5.2
V kóde P5.4B
odkomentujte vždy jeden zo zakomentovaných riadkov a sledujte reakciu
prekladača, poprípade výpis z programu.
Cv5.3
Použite – rozšírte – kódy P5.5 a použite metódu Vstup.citaj() z kódu P5.6
Na naprogramovanie triedy “Sklonuj”. Objekt tejto triedy bude schopný vypýtať si slovo, potom si vypýtať vzor. Ak vzor bude preňho známy (naprogramujte aspoň vzor dub), vyskloňuje podstatné meno podľa vzoru a vyskloňované tvary zadaného slova vytlačí.
Riešenia
Cv.5.1
Objekty podtriedy nemajú prístup
k súkromným prvkom ani metódam svojej nadtriedy. V triede Tovar by
premenná „cena“ mala byť rozhodne typu
private. Ak sa už cena v konštruktore stanoví, zrejme by ju nemal len tak
hocikto mať možnosť zmeniť. Problémy s prístupom sa dajú vyriešiť
napríklad definovaním metódy public int
getCena() v triede Tovar a príslušným volaním tejto metódy
v kódoch P5.1B-C.
© Ivan Kupka
Vaše komentáre:
kupka@fmph.uniba.sk