4it101 devátá přednáška
DESCRIPTION
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í - PowerPoint PPT PresentationTRANSCRIPT
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átorec
h přístupu !!!!
Co nabízí potomek ze svého předka?
• Datové atributy• Metody• Konstruktory• Statické atributy• Statické metody
Záleží na modifikátorech, u překrytých metod
pozdní vazba
Záleží na modifikátorech, včasná vazba
DĚDIČNOST A KONSTRUKTORY
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
• 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 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;} public ZiroUcet (int noveCislo, 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
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()
a) nicb) řetězec Potomekc) řetězec Předekd) řetězec null
String popis = null String popis = null
clone()equals()finalize()
hashCode()toString()
...........
nastavPopis()
Object
Predek
PotomeknastavPopis()
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";}
}
String popis = null String popis = null
clone()equals()finalize()
hashCode()toString()
...........
nastavPopis()
Object
Predek
PotomeknastavPopis()
Predek() { super(); nastavPopis(); System.out.println(popis);}
Potomek () { super(); nastavPopis();}
Object(){}
void nastavPopis() {this.popis="Potomek";}
“Potomek”
(this.popis);
DĚDIČNOST A MODIFIKÁTORY PŘÍSTUPU
Modifikátory přístupu • private• „přátelský“ („package
private“)• 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
protected a clone()
Metoda clone() Predek p = new Predek();Predek p2 = p;System.out.println(p + " "+ p2);System.out.println(p == p2);
PředekPredek@9304b1 Predek@9304b1true
Metoda clone()Predek p = new Predek();Predek p2 = (Predek)p.clone();System.out.println(p + " "+ p2);System.out.println(p == p2);
Metoda clone()public class Predek implements Cloneable{
private String popis;
Predek() {nastavPopis();System.out.println(popis);
}
@Overridepublic Object clone() throwsCloneNotSupportedException {
return super.clone();}
Metoda clone()Predek p = new Predek();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@190d11false
POLYMORFISMUSA DĚDIČNOST
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 hafb) haf ňafc) ňaf haf
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));
}.......................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);
}......
impl
emen
tace
equ
als(
) s
inst
ance
Of
...........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);
}
DĚDIČNOSTA STATICKÉ PRVKY
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 ňafb) haf hafc) ňaf haf
ABSTRAKTNÍ TŘÍDY
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;}
}}
POUŽÍVÁNÍ DĚDIČNOSTI – PRAVIDLA A OMEZENÍ
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); }}
Dědičnost narušuje zapouzdření
• 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
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; }}
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);
a) nastaví se nový rozměr, obdélník bude dále obdélníkemb) nastaví se nový rozměr, z obdélníku se stane kosodélníkc) kód skončí výjimkou UnsupportedOperationExceptiond) kód skončí výjimkou NullPointerExceptione) kód skončí výjimkou StackOverflowErrorf) kód skončí nekonečným cyklem
Dědičnost narušuje zapouzdření
• správné řešení: public void nastavRozmer(double stranaA,
double stranaB, double uhel) {
if (uhel != 90) { throw new UnsupportedOperationException(
"U obdelníka musí být úhel 90°"); } super.nastavRozmer(stranaA, stranaB, 90); }
Použití skládání místo dědičnosti
public class Obdelnik { private Kosodelnik kosodelnik; public Obdelnik(double stranaA, double stranaB) { kosodelnik = new Kosodelnik(stranaA, stranaB, 90); } public double obvod() { return kosodelnik.obvod(); } public double obsah() { return kosodelnik.obsah(); } public void nastavRozmer(double stranaA, double stranaB) {
kosodelnik.nastavRozmer(stranaA, stranaB); } public double getStranaA() {
return kosodelnik.getStranaA(); } public double getStranaB() { return kosodelnik.getStranaB(); }}