520 likes | 665 Views
4IT101 devátá přednáška. Dědičnost - pokračování. Specializace. Při práci s objekty dané třídy často odhalíme skupiny instancí se speciálními, avšak pro celou skupinu společnými vlastnostmi Příklady: Auta můžeme dělit na osobní, nákladní, autobusy a speciální
E N D
4IT101 devátá přednáška Dědičnost - pokračování
Specializace • Při práci s objekty dané třídy často odhalíme skupiny instancí se speciálními, avšak pro celou skupinu společnými vlastnostmi • Příklady: • Auta můžeme dělit na osobní, nákladní, autobusy a speciální • Vesmírná tělesa dělíme na hvězdy, planety a atd. • Osoby dělíme na muže a ženy • Mezi čtyřúhelníky můžeme vydělit obdélníky, mezi nimi pak čtverce • To, že je daný objekt členem speciální podskupinynijak neovlivňuje jeho členství v původní skupině
Zobecnění • Často provádíme obrácený myšlenkový postup:u řady různých druhů objektů nacházíme společné vlastnostia definujeme pak společné skupiny • Příklady: • Lidé spolu s řadou zvířecích druhů tvoří skupiny savců • Auta, kola, povozy, vlaky, letadla, lodě & spol. jsou dopravní prostředky • Přirozené číslo je speciálním případem celého čísla,které je speciálním případem racionálního čísla,které je speciálním případem reálného čísla,které je speciálním případem komplexního čísla • V objektově orientovaných programech je vše považováno za objekt
Co z předka lze používat (volat) v potomkovi? • Datové atributy • Metody • Konstruktory • Statické atributy • Statické metody Záleží na modifikátorech přístupu !!!!
Co nabízí potomek ze svého předka? Záleží na modifikátorech, u překrytých metod pozdní vazba • Datové atributy • Metody • Konstruktory • Statické atributy • Statické metody Záleží na modifikátorech, včasná vazba
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
this a super • this – odkaz na tuto instanci • this.data • this.metoda() • this() • super – odkaz na předka • super.data • super.metoda() • 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); }
Na co si dát u konstruktorů pozor • V konstruktoru bychom měli používatpouze soukromé a konečné metody • Tj. neměli by se volat metody, které lze překrýt, neboť nemusí být správně inicializovány datové atributy
Hádanka class Predek { private String popis; Predek() { nastavPopis(); System.out.println(popis); } void nastavPopis() { popis="Předek"; } } class Potomek extends Predek { private String popis; Potomek () { nastavPopis(); } void nastavPopis() { popis="Potomek"; } } • Co se vypíše při vytvoření • instance potomka • new Potomek() • nic • řetězec Potomek • řetězec Předek • řetězec null
String popis = null String popis = null clone() equals() finalize() hashCode() toString() ...... ..... Object Predek nastavPopis() nastavPopis() Potomek
opakování kódu classPredek { privateString popis; Predek() { nastavPopis(); System.out.println(popis); } voidnastavPopis() { popis="Předek"; } } class Potomek extendsPredek { privateString popis; Potomek () { nastavPopis(); } voidnastavPopis() { popis="Potomek"; } }
Object() {} String popis = null String popis = null “Potomek” Predek() { super(); nastavPopis(); System.out.println(popis); } clone() equals() finalize() hashCode() toString() ...... ..... Object (this.popis); Predek Potomek () { super(); nastavPopis(); } void nastavPopis() { this.popis="Potomek"; } nastavPopis() nastavPopis() Potomek
Modifikátory přístupu • private • „přátelský“ („packageprivate“) • protected • public • u datových atributů • u metod • u tříd – pozor na rozdíl u „normálních“ a vnitřních tříd
Metoda clone() Predek p = newPredek(); Predek p2 = p; System.out.println(p + " "+ p2); System.out.println(p == p2); Předek Predek@9304b1 Predek@9304b1 true
Metoda clone() Predek p = newPredek(); Predek p2 = (Predek)p.clone(); System.out.println(p + " "+ p2); System.out.println(p == p2);
Metoda clone() public class Predek implements Cloneable{ privateString popis; Predek() { nastavPopis(); System.out.println(popis); } @Override public Object clone() throwsCloneNotSupportedException { returnsuper.clone(); }
Metoda clone() Predek p = newPredek(); Predek p2 = null; try { p2 = (Predek)p.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.println(p + " "+ p2); System.out.println(p == p2); Předek Predek@9304b1 Predek@190d11 false
Polymorfismus • 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, • přetěžování metod (overloading), též ad-hoc polymorphism • překrývání metod (overriding), též subtype polymorphism
class Pes { public void stekej(){ System.out.print("haf"); } } class DomaciMazanek extends Pes { public void stekej(){ System.out.print("ňaf"); } } ..................... public static void main(String [] args){ Pes azor = new Pes(); Pes fifi = new DomaciMazanek(); azor.stekej(); fifi.stekej(); } a) haf haf b) haf ňaf c) ňaf haf Hádanka
Polymorfismus v souvislosti s dědičností • Pro metody instancí platí tzv. pozdní vazba, viz volání metody stekej() v příkladu hádanky
Metoda equals() a dědičnost • public boolean equals( Object o) • pravidla • je reflexivní, tj. x.equals(x) = true • je symetrická, tj. x.equals(y)= true pouze tehdy když y.equals(x)=true • je tranzitivní, tj. když x.equals(y)=true a y.equals(z) = true tak x.equals(z) musí vrátit také true • je konzistentní, tj. pro dvě instance vrací vždy stejnou hodnotu, • pro všechny hodnoty, které nejsou null, vrací x.equals(null) false opakování pravidel pro equals()
Metoda equals() a dědičnost • třída Zvire – datový atribut druh, • potomek DomaciZvire – další datový atribut jmeno, ........... Zvire z1= new Zvire(“pes”); DomaciZvire z2 = new DomaciZvire(“pes”, “Rek”); ................ Mají si být tyto dvě instance rovny? Jak po přetypování z2 na typ Zvire?
Metoda equals() a dědičnost • předek se má rovnat potomkovi • použijeme instanceof v metodě equals(), • předek se nemá rovnat potomkovi • porovnáváme přes shodnost třídy, označení třídy získáme metodou getClass() ze třídy Object
class DomaciZvire extends Zvire { private String jmeno; public boolean equals( Object o){ if (!(o instanceof DomaciZvire)) return false; DomaciZvire z = (DomaciZvire) o; String dr = z.getDruh(); String jm = z.jmeno; return (dr.equals(getDruh()) && jmeno.equals(jm)); } ....................... implementace equals() s instanceOf class Zvire { private String druh; public boolean equals( Object o){ if (!(o instanceof Zvire)) return false; Zvire z = (Zvire) o; String dr = z.druh; return druh.equals(dr); } ......
........... Zvire z1= new Zvire(“pes”); DomaciZvire z2 = new DomaciZvire(“pes”, “Rek”); z1.equals(z2); z2.equals(z1); ................ Výsledkem tohoto porovnání je true (pes je pes) Výsledkem tohoto porovnání je false (porovnávám domácí zvíře a zvíře) řešení – v potomcích by se nemělo překrývat equals()
equals() – předek se nerovná potomkovi class Zvire { private String druh; public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; String dr = z.druh; return druh.equals(dr); }
Statické prvky a dědičnost • statické prvky se volají jménem třídy => jména tříd se nedědí, nepřekrývají, • statické prvky používají včasnou vazbu a ne pozdní vazbu,
class Pes { public static void stekej(){ System.out.print("haf"); } } class DomaciMazanek extends Pes { public static void stekej(){ System.out.print("ňaf"); } } ..................... public static void main(String [] args){ Pes azor = new Pes(); Pes fifi = new DomaciMazanek(); azor.stekej(); fifi.stekej(); } a) haf ňaf b) haf haf c) ňaf haf Hádanka
Abstraktní třídy • abstraktní třídy – v situaci, kdy nemá smysl vytvářet instanci této třídy • klíčové slovo abstract, • abstraktní metody (nemusí být), • může mít konkrétní datové atributy, konkrétní metody, • třídy Motýl a Včela • abstraktní třída LetajícíHmyz
public abstract class LetajiciHmyz { private Pozice pozice; public abstract void jedenPohyb (); protected void preletni() { // vyber náhodně květinu v nejbližším okolí // přesuň se na vybranou květinu } protected boolean naKvetineSNektarem() { if (pozice.jeKvetina()) { Kvetina kvetina = pozice.getKvetina(); return kvetina.maNektar(); } else { return false; } } }
Důvody pro použití dědičnosti • Specializace • Překrývání metod a polymorfismus • Znovupoužití kódu
Pravidla pro použití dědičnosti • podtřída by měla být podtypem své nadtřídy. Pokud máte pocit, že by třída B měla být potomkem třídy A, položte si otázku: "Je každé B skutečně nějakým A?" Pokud si odpovíte záporně, tak třída B by neměla být potomkem třídy A, ale měla by obsahovat instanci třídy A jako datový atribut – třída A je poté detailem implementace B
Pravidla pro použití dědičnosti • pokud jsou třídy B a C potomkem třídy A, neměla by nastat situace, kdy existují objekty, které by měly být současně B a C
Pravidla pro použití dědičnosti • pokud se nějaká instance stane odrazem nějakého reálného objektu, neměla by v rámci aplikace vzniknout potřeba změnit instanci na jiný typ bez podstatné změny reálného objektu. Existence instance by měla sledovat existenci odpovídajícího objektu s přihlédnutím doby používání aplikace a s přihlédnutím k administrativnímu rozsahu použití aplikace
Porušení vztahu „je nějakým“ při návrhu dědičnosti
Dědičnost narušuje zapouzdření • máme třídu Kosodelnik, chceme vytvořit třídu Obdelnik
Dědičnost narušuje zapouzdření • varianta a: obdélník je potomkem kosodélníka • problém zavoláním zděděných metod nastavUhel (uhel) činastavRozmery(stranaA, stranaB, uhel)vznikne z obdélníka kosodélník, public class Obdelnik extends Kosodelnik { public Obdelnik(double stranaA, double stranaB) { super(stranaA, stranaB, 90); } }
public class Obdelnik extends Kosodelnik { // ... konstruktor vynechán @Override public void nastavUhel(double uhel) { if (uhel != 90) { throw new UnsupportedOperationException( "U obdelníka musí být úhel 90°"); } } @Override public void nastavRozmer(double stranaA, double stranaB, double uhel) { if (uhel != 90) { throw new UnsupportedOperationException( "U obdelníka musí být úhel 90°"); } nastavRozmer(stranaA, stranaB); } } • Co se stane při zavolání metody nastavRozmer u obdelníka (následující kód): • Obdelnik obd = new Obdelnik(3,5); • obd.nastavRozmer(5,10); • nastaví se nový rozměr, obdélník bude dále obdélníkem • nastaví se nový rozměr, z obdélníku se stane kosodélník • kód skončí výjimkou UnsupportedOperationException • kód skončí výjimkou NullPointerException • kód skončí výjimkou StackOverflowError • kód skončí nekonečným cyklem Dědičnost narušuje zapouzdření public class Kosodelnik { // .... část vynechána public void nastavRozmer(double stranaA, double stranaB, double uhel) { this.stranaA = stranaA; this.stranaB = stranaB; this.uhel = uhel; } public void nastavRozmer(double stranaA, double stranaB) { nastavRozmer(stranaA, stranaB, this.uhel); } public void nastavUhel(double uhel) { this.uhel=uhel; } } • první řešení - překrytí problematických metod nastavUhel a nastavRozmer • nejdříve si ukážeme tyto metody ve třídě Kosodelník, • potom jejich překrytí ve třídě Obdelnik