490 likes | 644 Views
4IT101 8.přednáška. Polymorfismus Dědičnost. Abstraktní datový typ. tři vlastnosti ADT: lze definovat nové typy a ukrývat reprezentaci těchto typů, lze definovat množinu operací/funkcí s těmito typy ADT je definován v jedné syntaktické jednotce, vytvářet proměnné tohoto typu lze i jinde,
E N D
4IT101 8.přednáška Polymorfismus Dědičnost
Abstraktní datový typ • tři vlastnosti ADT: • lze definovat nové typy a ukrývat reprezentaci těchto typů, • lze definovat množinu operací/funkcí s těmito typy • ADT je definován v jedné syntaktické jednotce, vytvářet proměnné tohoto typu lze i jinde, • výhody: • klient není závislý na konkrétní implementaci ADT, • klient se nemůže „vrtat“ v ADT – přistupuje pouze prostřednictvím veřejných operací/metod; zvýšená spolehlivost,
Objekty • požadavky na objekty: • abstraktní datový typ (zapouzdření, ukrývání implementace), • třída je typ • instance má k sobě přiřazeny datové atributy a metody instance, • polymorfismus a pozdní vazba, • dědičnost, • překrytí metod • jednonásobná x vícenásobná dědičnost
Pojem polymorfismus • polymorfismus – mnohotvarost • biologie: existence několika forem jedinců jednoho druhu nebo populace • programování: při stejném volání metody se provádí různý kód. Který kód se provede závisí: • na parametrech metod, • na instanci (objektu), kterému je zpráva předávána,
Typy polymorfismu přetěžování metod (overloading), též ad-hoc polymorphism překrývání metod (overriding), též subtype polymorphism parametrický polymorfismus – např. šablony v C++,
Přetěžování metod • více metod stejného jména, liší se • počtem parametrů, • typem parametrů, • pořadím parametrů int nextInt() int nextInt(int n) static String valueOf(double d) static String valueOf(int i) static String valueOf(boolean b) static String valueOf(char c)
Překrývání metod • Každá třída, která implementuje rozhraní, překrývá všechny metody z rozhraní. • Překrývání metod souvisí také s dědičností (viz dále)
Příklad: motýli a včely na louce • dvě třídy Motyl a Vcela • třetí třída Louka – úkolem je napsat metody pro přidávání motýlů a včel na louku. Máte dva seznamy: varianta 1 – dvě metody různých názvů: public void pridejMotyla (Motyl motyl) { motyli.add(motyl); } public void pridejVcelu (Vcela vcela) { vcely.add(vcela); } private List<Vcela> vcely; private List<Motyl> motyli;
Příklad: motýli a včely na louce 2. varianta – jedna metoda s rozskokem dle typu parametru public void pridej (Object o) { if (o instanceof Vcela) { Vcela vcela = (Vcela)o; vcely.add(vcela); } else if (o instanceof Motyl) { Motyl motyl = (Motyl)o; motyli.add(motyl); } else { throw new InvallidArgumentException( "lze vkládat pouze motýly a včely"); } }
Příklad: motýli a včely na louce 3. varianta – přetížení metody publicvoid pridej (Vcela vcela) { vcely.add(vcela); } publicvoid pridej (Motyl motyl) { motyli.add(motyl); } použití: louka.pridej(new Vcela( ...... )); louka.pridej(new Vcela( ...... )); louka.pridej(new Motyl( ...... )); louka.pridej(new Motyl( ...... ));
Příklad: motýli a včely na louce • dvě třídy Motyl a Vcela • třetí třída Louka – úkolem je napsat metody pro přidávání motýlů a včel na louku. • varianta 4 : • ve třídě Louka pouze jeden seznam, • použití polymorfismu s využitím rozhraní (interface) – jedna metoda pro přidávání do seznamu,
Deklarace interface [public] interfaceidentifikátor [extendsrozhraní1 [, rozhraní2 ...]] { [hlavička_metody1 ;] [hlavička_metody2;] … [konstanta1;] [konstanta2;] … [vnořená-třída]….. } public interface ObyvatelLouky { public void jednaAkce(); }
Implementace rozhraní public class Motyl implements ObyvatelLouky { public void jednaAkce () { if (naKvetineSNektarem()) { // sbirej nektar } else { preletni(); } } }
Rozhraní a subtype polymorfismus (překrývání) deklarace: private List<ObyvatelLouky> obyvateleLouky; inicializace: obyvateleLouky = new ArrayList<ObyvatelLouky>(); metoda pro vkládání motýlů a včel: public void pridej (ObyvatelLouky obyvatel) { obyvateleLouky.add(obyvatel); }
Rozhraní umožňuje volbu implementace staticky (při překladu) i dynamicky private List <String> slova; v konstruktoru: slova = new ArrayList<String>(); private List <String> slova; v konstruktoru: if (podmínka) { slova = new ArrayList<String>(); } else { slova = new LinkedList<String>(); }
Rozhraní umožňuje předávat metody jako parametry • příklad rozhraní Comparator a metoda Collections.sort()
Dědičnost mezi rozhraními • extends v hlavičce rozhraní
Dependency-Inversion Principle • Moduly vyšší úrovně nesmí záviset na modulech nižší úrovně. Oba typy by měli záviset na abstrakci. • Abstrakce by neměla záviset na detailech. Detaily obvykle závisí na abstrakci.
Terminologie • Rodičovská třída • Bázová třída • Nadtřída • Dceřiná třída • Odvozená třída • Podtřída
Hierarchie dědičnosti • třídy v Javě mají stromovou strukturu,v jejímž kořeni je třída Object • každá třída s výjimkou třídy Objectmá právě jednoho předka • třída Object je společným (pra)rodičem všech tříd • Na rozdíl od některých jiných jazyků (např. C++) jazyk Java nepodporuje násobnou dědičnost tříd
Deklarace dědičnosti • klíčové slovo extends • v hlavičce třídy public classLiskaextendsZvire{ …. } public class CDextendsAbstractPolozka { … } public class KnihaextendsAbstractPolozka implements Comparable { …. } • implementace rozhraní se uvádí až za deklarací dědičnosti
Definice dědičnosti • dědictví od třídy Object se uvádět nemusí,nemá-li někdo v hlavičce uvedeného předka,je přímým potomkem třídy Object • třídy z nepojmenovaného (kořenového) balíčku nemají úplný název, a proto nemohou mít potomky v jiných balíčcích
Co se vlastně dědí? • Co znamená výraz, že se něco dědí? V potomkovi mohu používat prvky předka Když vytvořím instanci potomka, tak mohu používat zděděné metody a atributy předka Potomek obsahuje všechny prvky (metody, atributy) předka
Vnučka Dcera Matka Object • Aby bylo možno zabezpečit funkci všech (i soukromých) vazeb, obsahuje každý objekt třídy potomka jako svoji součást podobjekt svého předka, • Objekt potomka se nemůže začít budovat dřív,než bude zcela vybudován podobjekt předka
Postup vytváření instance • Načtou se soubory class do paměti • nejdříve se musí načíst soubory .class předka • při nahrání souborů .class se inicializují statické prvky, • Inicializují se datové atributy • Zavolá se a provede konstruktor předka • Provede se tělo konstruktoru
Co z předka lze používat (volat) v potomkovi? Záleží na modifiká-torech přístupu !!!! • Datové atributy • Metody • Konstruktory • Statické atributy • Statické metody
Co nabízí potomek ze svého předka? Záleží na modifikátorech, nesmí být překryté, pozdní vazba • Datové atributy • Metody • Konstruktory • Statické atributy • Statické metody Záleží na modifikátorech, včasná vazba
Final u třídy • V hlavičce třídy může být modifikátor final – nemůže mít potomky public finalclassStringextendsObjectimplementsSerializable, Comparable<String>, CharSequence
Překrytá metoda • v potomkovi je metoda se stejnou hlavičkou (jméno a parametry), jako v předkovi • metoda v potomkovi překryla metodu předka
Použití překryté verze metody • Překrytí není předefinování ani přepsání překryté metody • Při překrytí metody jinou metodou zůstává překrytá metoda nedotčená a můžete ji v potomkovi kdykoliv použít kvalifikace klíčovým slovem super public voidmetoda() { super.metoda(); …. }
Další vlastnosti překrývání metod • Překrývající metoda musí mít stejnou hlavičku(název, počet parametrů a jejich typy, typ návratové hodnoty)jako metoda překrývaná • nemá-li metoda stejnou hlavičku,nejedná se o překrytí, ale o přetížení
Příklad s účty - dědičnost • Chceme vytvořit účet s možností výběru do mínusu. • budemít stejné proměnné jako třída Ucet a navíc proměnnou limit. • musí se změnit metoda pro výběr z účtu.
Příklad s účty – dědičnost a zapouzdření public classUcet{ privateintcislo; privateString vlastnik; privatedouble stav; public void vloz (double castka) { stav += castka; } public booleanvyber (double castka) {....... } ............................................................................... }
Příklad s účty - dědičnost public classZiroUcetextendsUcet{ privatedouble limit; public booleanvyber (double castka) { .......... } .............. }
Příklad s účty - překrytí metody boolean vyber (double castka) { if (stav < castka) { return false; } else { stav = stav – castka; return true; } } Ucet.java boolean vyber (double castka) { if ((stav+ limit) < castka) { return false; } else { stav = stav – castka; return true; } } Nepřeloží se, stav je private!!! ZiroUcet.java
Příklad s účty - překrytí metody a zapouzdření Ucet.java public void setStav (double novyStav){ stav = novyStav; } • Možnosti řešení • máme možnost změnit třídu Ucet a doplníme do ní metodysetStav() • nemáme možnost měnit třídu Ucet a musíme nějak použít, to co je. Použijeme metodu vloz() se záporným parametrem. Pokud ale v implementaci tuto možnost třída Ucet neumožní, nejsme schopni dědičnost využít. public boolean vyber (double castka) { if ((getStav() + limit) < castka) { return false; } else { setStav( getStav() – castka); return true; } } ZiroUcet.java
Přetypování • Přetypování nahoru k předkovi • probíhá automaticky • instanci jakékoli třídy lze přetypovat na typ Object • Přetypování dolů • musí být v kódu uvedeno • jedná se o návrat k typu, ze kterého proběhlo přetypování směrem nahoru
Přetypovávání, operátor instanceOf public void vypis() { for (Ucetucet : seznam) { if (ucetinstanceofZiroUcet) { ZiroUcetziro = (ZiroUcet) ucet; System.out.println(ziro.getVlastnik() + "\tstav: " + ziro.getStav()+"\tlimit: "+ ziro.getLimit()); } else { System.out.println(ucet.getVlastnik() + "\t" + ucet.getStav()); } } } Vhodnější řešit polymorfismem !!!
Dědičnost a konstruktory • Konstruktor se nedědí • Při spuštění konstruktoru se jako první automaticky volá konstruktor předka, pokud neurčíme který, volá se konstruktor bez parametru. • Pro určení volaného konstruktoru předka slouží klíčové slovo super
Volání konstruktoru předka, super public Ucet (int noveCislo, String jmeno, double castka){ cislo = noveCislo; vlastnik = jmeno; stav = castka; } public Ucet (int noveCislo, String jmeno){ cislo = noveCislo; vlastnik = jmeno; stav = 0; } • Máme několik problémů • třída Ucet nemá konstruktor bez parametru • i když vytváříme GiroUcet chceme určit číslo učtu, vlastníka a případně i stav účtu, navíc určujeme limit public ZiroUcet(intnoveCislo, String jmeno, double castka, double limit){ super(noveCislo, jmeno,castka); this.limit = limit; }
public ZiroUcet (int cisloUctu, String vlastnik, double pocatecniVklad, double limit){ super(cisloUctu, vlastnik, pocatecniVklad); this.limit = limit; } public ZiroUcet (int cisloUctu, String vlastnik, double pocatecniVklad){ this(cisloUctu, vlastnik, pocatecniVklad, 0); } public ZiroUcet (int cisloUctu, String vlastnik){ this(cisloUctu, vlastnik, 0, 0); //volání prvního konstruktoru }
Na co si dát u konstruktorů pozor • V těle konstruktoru nesmíme volat virtuální metody, a to ani zprostředkovaně (tj. volat metodu, která volá virtuální metodu) • Pokud potomek danou metodu překryje, může v překryvné verzi používat atributy, které při práci rodičovského konstruktoru ještě neexistují (přesněji nejsou ještě inicializovány)
Na co si dát u konstruktorů pozor • V konstruktoru bychom proto měli používatpouze soukromé a konečné metody • Potřebujeme-li použít virtuální (= překrytelnou) metodu,definujeme její soukromou verzi, kterou bude volat jak konstruktor, tak daná virtuální metoda