Generické implementace
Dědičnost
 Vytisknout studijní materiál

Dědičnost

Obecný způsob vytvoření více specializované třídy je vytvoření podtřídy nějaké třídy, kterou nazveme nadtřídou. Terminologie není ustálená a jako synonyma se používají také základní a odvozená třída nebo rodičovská třída a třída potomek. Tomuto procesu, jak jsme již předeslali v předcházejícím článku se říká dědičnost. Podtřída, odvozená třída, potomek dědí vlastnosti nadtřídy, základní třídy, rodiče.

V Javě deklarujeme podtřídu B třídy A pomocí klíčového slova extends.

class B extends A {

...

}

Třída B je tedy rozšířením třídy A o další vlastnosti, je tedy specifickým případem třídy A, protože současně má všechny vlastnosti třídy A.

Jako podtřídy nějaké třídy může být deklarováno více tříd, například třída C je také podtřídou třídy A

class C extends A {

...

}

Vztah rodič potomek, odvozování dalších tříd, může dále pokračovat například vytvořením podtřídy D třídy C.

class D extends C {

...

}

Vzniká tak hierarchie tříd. Třída D je ovšem také podtřídou třídy A, protože má všechny její vlastnosti, které získala tím, že je odvozena od třídy C a tato je odvozena od třídy A.

Podtřída, odvozená třída

- získává všechny metody a proměnné nadtřídy

- může přidat nové metody a proměnné třídy

- může předefinovat metody a proměnné rodiče

Považujme za generickou třídu (základní, rodičovskou) třídu Auto z první kapitoly (obr.), kde můžeme namítat, že spotřeba se u osobních aut mění s obsazením a u nákladních aut s nákladem, a že tedy obecně není dostatečným modelem reality. Na druhé straně má vlastnosti, které auta mají obecně. Můžeme tedy od ní odvodit třídy OsobniAuto a NakladniAuto.

V odvozené třídě OsobniAuto (obr.) jsme členské proměnné třídy Auto rozšířili o maximální počet osob pro dané auto, skutečné obsazení a aktuální spotřebu, která zvyšuje základní spotřebu v závislosti od počtu osob v autě. Protože rodičovská třída má konstruktor s parametry, konstruktor potomka volá s odpovídajícími argumenty konstruktor rodiče pomocí super( ), tedy konstruktor nadtřídy (supertřídy).

Pomocí super můžeme zpřístupnit v potomkovi proměnné i metody rodiče, podobně jako pomocí this zpřístupňujeme proměnné a metody vlastního objektu.

Dále jsme přidali metody realizující nástup a výstup osob z auta. Protože nyní dojezd auta, jakož i jeho spotřeba po ujetí zadaného počtu kilometrů závisí na aktuální a ne na základní spotřebě, nutno předefinovat metody dojezd( ) a ujelo( ). Jelikož jsme pro ně použili stejného jména jako v rodičovské třídě a také stejné formální parametry, došlo k překrytí těchto metod. Nyní můžeme s objekty třídy OsobniAuto pracovat obvyklým způsobem (obr.).

Podobně můžeme od třídy Auto odvodit třídu NakladniAuto (obr.).

Objekt třídy OsobniAuto nebo NakladniAuto je i objektem třídy Auto, tedy referenční proměnná nadtřídy může obsahovat i odkazy na objekty podtřídy. Tedy je-li

OsobniAuto a = new OsobniAuto(55.0F, 5.0F, 5);

potom můžeme také psát

Auto x = a;

Nyní ovšem x mohlo také odkazovat na objekt třídy NakladniAuto. Jestli x odkazuje na objekt třídy OsobniAuto můžeme zjistit pomocí operátoru instanceof a je-li tomu tak, můžeme s x pracovat pomocí obvyklé techniky přetypování (OsobniAuto)x jako s objektem třídy OsobniAuto. Použití operátoru instanceof vrací hodnotu true nebo false podle toho, je-li objekt dané třídy nebo ne.

Mějme pole auta pro osobní i nákladní auta. Potom informace o nich, podle toho, jde-li o osobní nebo nákladní auto, můžeme vytisknout jak je uvedeno na obr..

Obecně má v Javě každá třída jedinou rodičovskou třídu tvoříce tak hierarchii tříd. Jediná třída, která nemá rodičovskou třídu a současně je tak nadtřídou všech tříd je třída Object. To nám umožní vytvořit například generický zásobník obsahující jakékoliv prvky (obr.). Nyní můžeme vkládat do zásobníku objekty libovolných tříd a při jejich výběru je přetypovat, jak ukazují následující příkazy vložení a výběru prvků.

Zasobnik zasobnik = new Zasobnik();

A a = new A();

B b = new B();

zasobnik.push(a);

zasobnik.push(b);

...

b = (B)zasobnik.pop();

a = (A)zasobnik.pop();

Musíme si ovšem být vědomi toho, že porušíme-li správnost přetypování, což v případě složitějších případů může záviset na konkrétním vykonání programu, toto bude zjištěno až v průběhu vykonávání programu. Příkladem je následující posloupnost příkazů vložení a výběru prvků.

zasobnik.push(a);

zasobnik.push(b);

...

a = (A)zasobnik.pop();

b = (B)zasobnik.pop();

V článku 3.2 jsme uvedli, že jazyk Java poskytuje pro objekty obecnou třídu Stack. Umožňuje pracovat s prvky, které jsou objekty třídy Object. Program pro vkládání objektů třídy Sorozenec analogický k programu na obr. v článku 3.2 je na obr..

Chceme-li generický zásobník z obr. použít pro základní datové typy, například int, musíme použít obalující třídy, které jsou k dispozici pro každý základní typ, což je pro int třída Integer. Potom pro vložení int x do zásobníku musíme psát

zasobnik.push(new Integer(x));

a pro výběr

((Integer) zasobnik.pop()).intValue();

Uvedený způsob použití generického zásobníku odporuje definici ADT pomocí definovaného rozhraní. Řešením je použít generickou třídu pro implementaci speciální třídy, kterou nazýváme adaptér a implementuje požadované rozhraní ADT, například zásobníku hodnot typu int. Pro rozhraní definované pro IntZasobnik tak máme adaptér IntZasobnik pro třídu Zasobnik na obr., který pro implementaci rozhraní IntZasobnik využívá generický zásobník a jeho metody.

Podobně jako iterátor v článku 3.3 je i adaptér návrhový vzor implementující rozhraní známé klientovi a umožňuje přístup k třídě, která není známá klientovi.