300 likes | 483 Views
Öröklődés 2. Ismétlés – 1-3. példa. //Inicializálatlan változó használata -> FORDÍTÁSI HIBA! Alkalmazott a; a.fizetéstEmel(20); //null referencián keresztül műveletvégzés -> NullPointerException Alkalmazott a = null; a.fizetéstEmel(20000);
E N D
Ismétlés – 1-3. példa • //Inicializálatlan változó használata -> FORDÍTÁSI HIBA!Alkalmazott a;a.fizetéstEmel(20); • //null referencián keresztül műveletvégzés -> NullPointerExceptionAlkalmazott a = null;a.fizetéstEmel(20000); • // objektum példányosításanew Alkalmazott();// objektum példányosítása + változóhoz rendeléseAlkalmazott a = new Alkalmazott();// vagy Alkalmazott a; … a = new Alkalmazott();
Ismétlés – 4. példa // referencia állítása objektumra Alkalmazott a = new Alkalmazott(); Alkalmazott b = a; // Nem készül másolat!!! b.fizetéstEmel(10000); System.out.println(a.fizetés); // 10000
Ismétlés – 5. példa class Alkalmazott { ... // információ elrejtése, invariáns megőrzése privateintfizetés; // a fizetést kívülről ne lehessen közvetlenül módosítani, mert // akkor az évesfizetés elromlik! privateintévesFizetés; // az évesFizetést kívülről sehogyan se lehessen módosítani // a fizetés megváltozásával szükségszerűen az ő értéke is változzon publicint getFizetés() { returnfizetés; } publicint getÉvesFizetés() { returnévesFizetés; } publicvoid fizetéstBeállít(int új) { fizetés = új; évesFizetés = 12*fizetés; } }
Ismétlés – 6. példa class Alkalmazott { ... // osztályszintű változó + metódus staticintnyugdíjKorhatár = 60; publicstaticvoid nyugdíjKorhatártEmel( int mennyivel ) { nyugdíjKorhatár+=mennyivel; } } class Program { ... // hivatkozás osztályszintű változóra Alkalmazott a = new Alkalmazott(); System.out.println(a.nyugdíjKorhatár); // warning -> osztályon keresztül illik elérni System.out.println(Alkalmazott.nyugdíjKorhatár); }
Ismétlés – 7. példa – túlterhelés • metódus szignatúrája = név + paraméterek típusának sorozata • túlterheléshez szükségesek a különböző szignatúrák classAlkalmazott { ... // túlterhelés publicvoid fizetéstEmel() { fizetés += 10000; } void fizetéstEmel( int mennyivel ) { fizetés += mennyivel; } }
Ismétlés – 8. példa – konstruktorok class Alkalmazott { ... // konstruktorok privateintévesFizetés; public Alkalmazott(String név, int fizetés) { this.név = név; this.fizetés = fizetés; évesFizetés = 12*fizetés; } // Mivel írtunk konstruktort, nem jön létre public Alkalmazott() {} // Másik konstrukor meghívása : this(...) // Csak a konstruktor első sorában lehet! public Alkalmazott(String név) { this(név,100000); } }
Öröklődés során mit tehetünk a leszármazott osztályokban? • Az öröklött adattagokat közvetlenül használhatjuk. • Deklarálhatunk azonos nevű adattagot, mint ami már az ősosztályban is szerepelt. Ezt nevezzük elfedésnek.Adattagok elfedése rossz programozási módszerre vall, használata nem ajánlott. • Deklarálhatunk olyan adattagokat, amelyek az ősosztályban nem szerepelnek. • Az öröklött metódusokat közvetlenül használhatjuk. • Írhatunk új példánymetódusokat ugyanolyan névvel és szignatúrával, mint az ősosztályban. Ezt nevezzük felüldefiniálásnak. (ld. részletesen később) • Írhatunk új osztálymetódusokat ugyanolyan névvel és szignatúrával, mint az ősosztályban. Ezt nevezzük elfedésnek. • Deklarálhatunk új metódusokat, amelyek nem szerepelnek az ősosztályban. • Írhatunk konstruktort, ami meghívja az ősosztály valamelyik konstruktorát (implicit vagy explicit módon).
Konstruktorhívások a Javában Hozzunk létre egy új csomagot, majd tegyük bele a már megírt Alkalmazott.java osztályt. Hozzuk létre a Főnök osztályt az Alkalmazott osztály leszármazottjaként! Az új adattagok legyenek: public class Főnök extends Alkalmazott { private Alkalmazott[] beosztottak; privatestaticfinalintmaxBeosztott = 10; privateintbeosztottakSzáma; } Fordítási hibát kapunk! Miért???
Konstruktorhívások – a fordítási hiba oka • A Főnök osztályban nem írtunk konstruktort, ezért implicit módon létrejön egy üres törzsű. • Ha egy konstruktor nem hív meg másik konstruktort (ez csak az első sorában tehető meg), akkor implicit módon létrejön egy super() hívás az első sorban. • Ez azt jelenti, hogy az Alkalmazott osztály üres konstruktora akar végrehajtódni. • Mivel (az Alkalmazott osztályban írtunk konstruktort, implicit módon nem jön létre üres törzsű) + (nem írtunk üres törzsű konstruktort) = az Alkalmazott osztálynak nincs üres törzsű konstruktora. • Ezek után a fordítási hiba nyilvánvaló.
Konstruktorhívások – Hogyan javítsuk a hibát? • Lehetőleg ne az Alkalmazott osztályban írjunk üres konstruktort! (ezt sok esetben nem is tehetjük meg, hiszen már más által megírt osztályból származtatunk.) • FELADAT: • Írjunk a Főnök osztályban egy egy- és egy kétparaméteres konstruktort! • Az egyik hívja meg az Alkalmazott osztály valamelyik konstruktorát, a másik pedig az egyiket! • Gondoljuk át, hogy melyik legyen az egyik, és melyik a másik? • Írjunk egy újBeosztott metódust is, amely a paraméterként kapott alkalmazottat felveszi a beosztottak közé, ha van még hely, és igazzal tér vissza. Ha már nincs hely, akkor térjen vissza hamissal.
Konstruktorhívások public Főnök(String név, int fizetés) { super(név,fizetés); beosztottak = new Alkalmazott[maxBeosztott]; actBeosztott = 0; } public Főnök(String név) { this(név,200000); } boolean újBeosztott(Alkalmazott a) { if (beosztottakSzáma<maxBeosztott) { beosztottak[beosztottakSzáma] = a; beosztottakSzáma++; returntrue; } else { returnfalse; } }
Konstruktorhívások – a lefutási sorrend A főrogramban a Főnök f = new Főnök("Feri"); sor hatására milyen sorrendben hívódnak meg és futnak le a különböző konstruktorok? • Meghívódik a Főnök osztály egyparaméteres konstruktora. • Mivel az első sorában hívunk meg másik konstruktort, az fog végrehajtódni. Tehát elkezdődik a Főnök osztály kétparaméteres konstruktorának a végrehajtása. • A JRE újra megvizsgálja, hogy ez a konstruktor hív-e meg másik konstruktort. A válasz igen, mégpedig az Alkalmazott osztály kétparaméteres konstruktorát. Így ennek a végrehajtása kezdődik meg. • Meghívódik az Object osztály paraméter nélküli konstruktora!!! Miért??? • Befejeződik az Alkalmazott osztály kétparaméteres konstruktorának végrehajtása. • Befejeződik a Főnök osztály kétparaméteres konstruktorának végrehajtása. • Befejeződik a Főnök osztály egyparaméteres konstruktorának végrehajtása .
Altípus fogalma (informálisan) • Egy A típus altípusa egy B típusnak, ha minden olyan helyzetben, ahol B típusú objektum használható, A típusú objektum is használható. • Ez a tulajdonság teljesül a Java öröklődése esetén. Miért? • Megjegyzés: A múlt órán csak az öröklődés másik fő előnyét, a kód-újrafelhasználást használtuk ki.
Polimorfizmus (altípusos polimorfizmus) • Altípusos polimorfizmusnak azt a jelenséget nevezzük, hogy egy rögzített típusú változó (pl. Alkalmazott) hivatkozhat több különböző típusú objektumra (pl. Alkalmazott, Főnök). • Emellett a műveletek is „több típussal rendelkezhetnek”: az Alkalmazott osztály műveletei meghívhatók Főnök típusú objektumokra is.
Polimorfizmus Az Alkalmazott osztály műveletei meghívhatók Főnök típusúobjektumokra is: Főnök f = new Főnök("Feri"); int i = f.getFizetés(); Egy Alkalmazott típusú referenciának értékül adható egy Főnök típusú objektum: Alkalmazott a = new Főnök("Feri"); Egy Alkalmazott formális paraméterrel rendelkező metódus meghívható Főnök típusú aktuális paraméterrel: Főnök f = new Főnök("Feri"); Alkalmazott a = new Alkalmazott("Pisti"); boolean b = a.többetKeresMint(f);
Statikus és dinamikus típus • Ha egy Alkalmazott típusúként deklarált referenciaváltozó egy Főnök típusú objektumra vonatkozik, akkor mi is az igazi típusa a változónak? • Statikus típus: a deklarációnál megadott típus • Dinamikus típus: a változó által mutatott objektum tényleges típusa • A dinamikus típus vagy maga a statikus típus, vagy egy leszármazottja. (különben fordítási hibát kapunk) • A statikus típus állandó, a dinamikus típus változhat a program futása során.
Statikus és dinamikus típus -- példák Alkalmazott a = new Alkalmazott(„Pisti"); //statikus=dinamikus=Alkalmazott Főnök b = new Főnök("Feri"); //statikus típus=dinamikus típus=Főnök Alkalmazott c = new Főnök("Zoli"); //statikus=Alkalmazott, dinamikus=Főnök a = c; //st. típus=Alkalmazott, din. típus=Főnök a = new Alkalmazott("Tomi"); //statikus típus=dinamikus típus=Alkalmazott a = b; //st. típus=Alkalmazott, din. típus=Főnök Object o = new Alkalmazott("Józsi"); o.fizetéstEmel(20000); // fordítási hiba: Object-nek nincs ilyen metódusa Alkalmazott a = o; // fordítási hiba: o nem feltétlenül Alkalmazott
Statikus és dinamikus típus szerepe • A statikus típus dönti el azt, hogy milyen műveletet végezhetők a referenciaváltozón keresztül. • A dinamikus típus dönti el azt, hogy a végrehajtandó metódusnak melyik törzse fusson le. • Ld. dinamikus kötés
Felüldefiniálás • A gyermek osztályban sokszor azt szeretnénk, ha másképp tudnánk bizonyos eseményekre reagálni, mint ahogy a szülő reagál. • Például a múlt órai Autó, Taxi osztályokban a költséget számító metódus és a toString() esetén. • Akkor beszélünk felüldefiniálásról, ha: • Az ősosztálybeli és a leszármazott osztálybeli metódus szignatúrája megegyezik. • A visszatérési típus megegyezik. (Java 5.0-tól kovariánsan változhat) • A hozzáférési kategória nem szűkülhet a leszármazott osztályban. • A kiváltható kivételek halmaza nem bővülhet.
Dinamikus kötés • A múlt órán tulajdonképpen az öröklődésnek „csak” a kód-újrafelhasználó előnyét használtuk ki, az altípusképző előnyét nem. (Hiszen ha írtunk volna copy-paste technikával egy sima Taxi osztályt öröklődés nélkül, akkor a főprogam ugyanúgy működne.) • Az öröklődés altípusképző előnyét a dinamikus kötés segítségével lehet kihasználni. • A dinamikus kötés bemutatásához szükség van egy, a leszármazott osztályban felüldefiniált metódusra.
Dinamikus kötés FELADAT • Definiáljuk felül a pótlék() metódust a Főnök osztályban! • Adja vissza az Alkalmazott-nak kiszámított pótlékot, és még beosztottanként 20000 forintot! • Definiáljuk felül a toString() metódust is!
Dinamikus kötés Mit csinál a főprogram? Alkalmazott a = new Alkalmazott("Józsi"); Főnök f = new Főnök("Feri"); f.újBeosztott(a); int i = a.pótlék(); int j = f.pótlék(); a=f; int k = a.pótlék();// DINAMIKUS KÖTÉS! int l = f.teljesFizetés(); // DINAMIKUS KÖTÉS A METÓDUS TÖRZSÉN BELÜL IS!
Dinamikus kötés – megjegyzések • Nagyon fontos, hogy a dinamikus kötéshez elengedhetetlen a felüldefiniált metódus! • Mindig győződjünk meg róla, hogy tényleg felüldefiniáltuk-e a kívánt metódust, mert ha véletlenül túlterheltük, nem kapunk fordítási hibát, de futási időben nem az fog történni, amit várunk!
Dinamikus kötés – helytelen példa int pótlék(int i) {// a Főnök osztályban írjuk meg! returnsuper.pótlék()+i; } Tegyük fel, hogy még nem definiáltuk felül az Alkalmazott osztály pótlék metódusát. Úgy gondolkodhatunk (helytelenül!!!), hogy a Főnök osztályban úgy számoljunk pótlékot, hogy az Alkalmazott szerinti pótlékhoz adjuk hozzá a paraméterként kapott számot. Ez nem felüldefiniálás, hanem túlterhelés! Alkalmazott a = new Alkalmazott("Józsi"); Főnök f = new Főnök("Feri"); int i = a.pótlék(); int j = f.pótlék(); int l = a.pótlék(10000); // fordítási hiba int n = f.pótlék(10000); // OK a=f; int k = a.pótlék();// alkalmazott szerinti pótlék int m = a.pótlék(10000); // fordítási hiba
Statikus metódusok és az elfedés • Statikus metódusokat nem lehet felüldefiniálni! • Ebből következik, hogy dinamikus kötés sincs statikus metódusoknál! • Abban az esetben, amikor egy leszármazott osztályban egy azonos szignatúrájú és visszatérési értékű metódust írunk, mint ami van az ősosztályban, akkor nem felüldefiniálásról, hanem elfedésről beszélünk. • Ez azt jelenti, hogy statikus típus alapján dől el, hogy melyik törzs hajtódik végre.
Objektumok konverziói • A leszármazott osztályba tartozó objektum automatikusan konvertálódik ősosztálybeli objektummá. Ezt nevezik alulról felfelé történő konverziónak. Alkalmazott a = new Főnök(); • Ősosztálybeli objektumot csak explicit módon tudunk leszármazott osztálybeli objektummá konvertálni. Alkalmazott a = new Főnök();Főnök f = (Főnök)a; • Ezzel az a probléma, hogy fordítási időben nem derül ki, hogy tényleg Főnök-e a konvertálni kívánt Alkalmazott. Ha ez nem teljesül, akkor futási időben ClassCastException váltódik ki. Ha ezt nem akarjuk, akkor ellenőrzést is végezhetünk az instanceof operátor segítségével. if (a instanceof Főnök) { Főnök f2 = (Főnök) a; ... }
Feladat • Egy vállalatnál alkalmazottak dolgoznak. Közülük néhányan főnökök is. A főnököknek legyenek alkalmazottaik! Legyen olyan sima alkalmazott és főnök is, akinek van nyelvvizsgája! • A vállalat alkalmazottainak adatait tömbben tároljuk! Mi legyen a tömb objektumra mutató referenciaváltozó statikus típusa? (Object, Alkalmazott vagy Főnök?) • Számítsuk ki az összes alkalmazott (ebbe a főnökök is beletartoznak) pótlékokkal együtt számított átlagos teljes fizetését! Emellett számítsuk ki külön a főnökök pótlékokkal együtt számított átlagos teljes fizetését!
Házi feladat • Készítsd el a Pont osztályt! Tulajdonságok: x és y koordináta, művelet: eltolás dx és dy értékekkel. • Az adattagokat explicit módon inicializáld 0-ra! • Készítsd el a Kör osztályt! Tulajdonságok: középpont (Pont) és sugár (double), műveletek: eltol és nagyít. (a középpontot kell eltolni, nagyításnál a sugarat szorozni) • A Kör osztályban vezess be egy új attribútumot, a területet tartsuk benne nyilván! Írd meg a sugaratBeállít műveletet! Használd a Math.PI értéket! Az attribútumok legyenek privátok, a műveletek publikusak! Készíts publikus lekérdező műveleteket a sugárhoz és a területhez! • A Pont és a Kör osztályokhoz készítsd el a távolság() metódust, mely megadja az objektum távolságát egy, a paraméterként átadott Pont objektumtól! A Kör osztályban definiálj példánymetódust, mely a paraméterként átadott pont objektumot a kör középpontjába állítja: public void középpontba(Pont p)! • A Kör osztályhoz írj illeszkedik() műveletet, mely eldönti, hogy a paraméterként megadott Pont objektum illeszkedik-e a körvonalra -- egy adott tűréshatáron belül. A tűréshatár értéke a Kör osztály jellemzője. • A Kör osztály sugaratBeállít metódusának formális paramétere legyen ugyanúgy elnevezve, mint a sugár attribútum!
Házi feladat (folytatás) • Készíts középpontos tükrözést végző műveleteket a Pont és a Kör osztályokban. A műveleteket meg lehessen hívni Pont objektummal is és két koordinátával (cx,cy) is! Valósítsd meg a középpontos tükrözés műveleteket úgy, hogy egymást hívják! • Készítsd el a SzínesPont osztályt a Pont osztály leszármazottjaként! Új tulajdonság: szín. Új műveletek: szín beállítása és lekérdezése. A szín attribútum legyen privát! • A Pont és a Kör osztályokhoz készíts konstruktorokat! A Pont osztályhoz csak egyet, aminek a koordinátákat lehet átadni. A Kör osztályhoz hármat: az elsőnek a sugarat és egy Pont objektumot (ez lesz a középpont), a másodiknak a sugarat és a középpont koordinátáit lehet átadni, a harmadik legyen paraméter nélküli konstruktor. Melyik Kör konstruktor hívhat meg másikat? Miért nem fordul a SzínesPont osztály? Javítsd! • Készítsd el a SzínesKör osztályt a Kör osztály leszármazottjaként! Új műveletei: a szín beállítása és lekérdezése. Ne vezess be új adattagot: a színes körök színét a középpontjuk színe határozza meg, amely nem közönséges Pont, hanem SzínesPont! • Készíts public String toString() műveletet a Pont és SzínesPont osztályokhoz, mely adatokat szolgáltat az ilyen objektumokról egy String formájában! • A Pont osztályban definiáljunk println metódust, mely kiírja a pontot a szabványos kimenetre! (Ehhez az előző feladatban megírt toString metódust használjuk!) Nézzük meg, hogyan működik az örökölt println metódus a SzínesPont objektumokon! • Hozz létre egy Program osztályt! Ebben írj statikus main metódust! Hozz létre egy körökből álló 5 elemű tömb objektumot! A legnagyobb területű kör jellemzőit írasd ki a képernyőre! Lehetőleg ne használj explicit konverziót!