6. gyakorlat
Ismétlés¶
- Osztályok (adattagok, metódusok, konstruktorok, toString)
- Láthatóságok (private, protected, semmi, public)
- Öröklődés (super, extends, felüldefiniálás)
Az órai anyaghoz szükséges lesz a korábbi Macskás példa is, amit szükség esetén itt tölthetsz le.
Polimorfizmus - Többalakúság¶
A többalakúság az objektum-orientált programozás egyik alapja. De mit is jelent pontosan a többalakúság? Hogy képzelhetjük el? Először is fontos tisztázni, hogy milyen polimorfizmusról van szó, ugyanis a polimorfizmusnak két típusa van: statikus és dinamikus polimorfizmus.
Statikus polimorfizmus¶
A statikus, vagy fordításidejű polimorfizmus a polimorfizmusnak az a formája, ami fordításidőben eldönthető. Ez a gyakorlatban azt jelenti, hogy több, azonos nevű, de más paraméterlistával rendelkező metódusból fordításidőben el tudjuk dönteni, hogy egy hívási helyen melyik változata fog meghívódni.
Ez esetleg overload néven lehet ismerős. Egy nagyon egyszerű példa:
public class Kalkulator {
public int szorzas(int egesz1, int egesz2) {
return egesz1 * egesz2;
}
public int szorzas(int egesz1, int egesz2, int egesz3) {
return egesz1 * egesz2 * egesz3;
}
public String szorzas(String mit, int hanyszor) {
return mit.repeat(hanyszor);
}
}
main
metóduson belül:
public class KalkMain {
public static void main(String[] args) {
Kalkulator kalk = new Kalkulator();
System.out.println(kalk.szorzas(2, 5));
System.out.println(kalk.szorzas(2, 5, 3));
System.out.println(kalk.szorzas("Körte", 4));
}
}
Természetesen ez csak egy egyszerű példa, a valóságban bármilyen osztály esetén előjöhet az overloadra az igény, ahogy azt láthattuk már például a konstruktorok esetén is, ahol több, azonos nevű konstruktorunk volt, a fordító mégis tudta, hogy a paraméterek alapján melyik konstruktort szerettük volna használni.
Az objektum-orientált programozásnál azonban talán az érdekesebb dolog a dinamikus polimorfizmus lesz.
Dinamikus polimorfizmus¶
Ebben az esetben a gyerekosztály egy példánya kezelhető a szülő egy példányaként is, egy Tigris
objektumot tárolhatunk Macskafele
példányként is (azaz egy Macskafele
típusú referenciában), sőt akár egy ős típusú tömbben eltárolhatjuk az ős- és gyerektípusokat vegyesen, például macskaféléket, házi macskákat, tigriseket és kardfogú tigriseket is.
Azonban, ha ős típusként tárolunk egy gyerek típusú objektumot, akkor a gyerek típusú objektum saját osztályában definiált (új) metódusait nem látjuk. Például:
public class MacskaMain {
public static void main(String[] args) {
Macskafele valami0 = new Macskafele("Csöpi", 10);
Macskafele valami1 = new HaziMacska("Cirmi", 4.9, true);
Macskafele valami2 = new Tigris("Lüszi", 24);
// valami2.erosebb(...) nem látható így
Macskafele valami3 = new KardfoguTigris("Nyüzsi", 30);
}
}
A fenti kódrészletben a kikommentelt kódrészlet nem működik, mert az átlagos Macskafele nem tudja eldönteni, hogy erősebb-e mint egy Tigris. Mivel egy Macskafele
referenciában tároljuk a Tigris
objektumot, így csak a Macskafeleben definiált metódusokat használhatjuk! Ennek kiküszöbölésére később látni fogunk egy módszert.
Ennek oka, hogy mivel Macskafele
referenciában tároljuk az objektumot, nem tudhatjuk (most még), hogy milyen objektumra mutat az adott referencia.
Macskafele[] macskak = new Macskafele[4];
macskak[0] = new Macskafele("Csöpi", 10);
macskak[1] = new HaziMacska("Cirmi", 4.9, true);
macskak[2] = new Tigris("Lüszi", 24);
macskak[3] = new KardfoguTigris("Nyüzsi", 30);
for (int i = 0; i < macskak.length; i++) {
macskak[i].nyavog();
}
Fordításkor még nem tudjuk, hogy a Macskafele
tömbben milyen típusú objektumok lesznek: Macskafele
objektumok, Tigris
, vagy pedig KardfoguTigris
objektumok, esetleg vegyesen, hiszen megtehetjük, hogy egy tömbbe gyerek típusokat teszünk.
Viszont, ha meghívjuk mindegyik elem nyavog()
metódusát, azt látjuk, hogy a sima macskafélék esetében a Macskafele
osztályban definiált metódus fut le, míg a házi macska esetében a HaziMacska
osztályban definiált nyavog()
metódus hívódik meg, stb.
Ennek oka pedig a kései kötés (late binding). A kései és korai kötésről bővebben itt és itt olvashatsz.
A forráskód kimenete:
Kimenet
Cirmi nyávog: MeoOoOow
Lüszi üvölt: RaaAaAaAaAaAaAaAaAaAaAaAaAwR
Nyüzsi üvölt: RaaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAaaAAwR
Ahogy említettük, a gyerek típus kezelhető ősként, viszont ez fordítva nem működik! Tigris
tömbbe nem tehetünk ős típusú, azaz sima Macskafele
típusú objektumokat.
Csomagok¶
Osztályainkat csomagokba rendezhetjük, ahogy erről az UML esetében is említést tettünk. Java osztályok esetében ez egy fizikai és egy logikai csoportosítást is jelent, általában különböző logikai egységenként hozunk létre csomagokat, illetve ennek használatával megismerkedhetünk a kulcsszó nélküli, package private láthatósággal, és kiküszöbölhetőek vele a névütközések is. Csomagokban lehetnek egyéb csomagok is, teljes csomag-hierarchiát hozhatunk vele létre (például egy csomag, ami a felhasználói felület megjelenítésével foglalkozik, ennek is lehetnek logikailag különálló részei, melyek ezen a csomagon belül helyezkedhetnek el).
Csomagokba szervezés¶
Ha az osztályunkat egy csomagba szeretnénk berakni, akkor egyrészt be kell tennünk fizikailag abba a mappába vagy mappaszerkezetbe, ami a csomagunkat/csomaghierarchiánkat szimbolizálja, majd a forrásfájl első nem komment sorába ezt jelölni is kell a package
kulcsszó használatával.
Ennek jelentése: az osztályunk a macskak
nevű csomagban van, ez a csomag a szte
nevű csomagban van, ez pedig a hu
nevű csomagban. Fizikailag az osztály a projekt gyökérkönyvtár/hu/szte/macskak
mappában van. A projekt gyökérkönyvára a projektben már sokszor látott src mappa.
Általában a fordított domain jelölést szokták alkalmazni csomaghierarchiák szervezésére. Bővebb információ a csomagok elnevezéséről itt olvasható.
Csomagot készíthetünk IDEA segítéségével (jobb klikk az src
mappán majd New > Package), ilyenkor az osztály elején ott kell lennie a package csomagneve;
sornak. Figyelni kell, hogy a könyvtárszerkezet ezzel megegyező legyen.
Ha nem megfelelő csomagban van a forrásfájlunk, akkor a legtöbb IDE egyébként segítséget is nyújt a rendezésben (fájl mozgatásával, vagy a csomagmegjelölés módosításával).
Minden osztálynak van egy teljes neve, amely a teljes csomag hierarchia + osztály nevéből áll. Tehát a fenti Macskafele osztálynak a teljes elérési neve (fully qualified name), ha a hu.szte.macskak
csomagban van: hu.szte.macskak.Macskafele
. Erre néhány további példa: java.lang.String
, java.util.Scanner
.
Ugyanakkor ilyen hosszú nevet mi sosem írtunk, ha egy Stringet (vagy Scannert) szerettünk volna létrehozni. És ezt a továbbiakban is elkerülhetjük, ha a szükséges csomagok tartalmát importáljuk a programunkba.
A csomagokba szervezést természetesen az UML-ben is jelölhetjük, a kapcsolódó osztályokat (vagy egyéb csomagokat) egy UML-beli csomagba tehetjük, ahogy látható az alábbi ábrán is.
Csomagok importálása¶
Másik csomagban lévő osztályokra hivatkozás előtt be kell őket importálni, vagy pedig teljes nevet kell használni (a programban végig), hogy a fordító tudja, mire gondolunk.
Amikor sok oszályt importálunk egy csomagból, akkor az egyesével való importálás helyett beimportálhatunk minden csomagban lévő osztályt, a csillag karakter segítségével:
Az importjainkat rendezhetjük az IDEA segítéségével, amire a Ctrl+Alt+O (vagy az Optimize Imports menüpont) segítségével
Ez vonatkozik saját osztályainkra, de az egyéb, nem általunk megírt osztályokra is (például a JDK kész osztályaira).
Kivétel ezalól a java.lang
összes osztálya, melyekbe például az összes csomagoló osztály (Integer
, Float
, stb.) vagy a String
osztály tartozik. Ezt a csomagot eddig sem importáltuk soha, és tudtunk String
példányokat létrehozni.
Használhatunk statikus importot is, ám ez sok esetben csak ront a kód olvashatóságán, érthetőségén, karbantarthatóságán. A statikus importról bővebben itt olvashatsz.
Java fájljainkban a csomag jelölése (ha létezik ilyen) mindig megelőzi az importálásokat, sőt igaziból minden mást is, csak komment lehet a package csomagneve;
sor előtt.
Láthatóságok¶
UML jelölés | Módosító | Osztály | Csomag | Leszármazottak | Mindenki |
---|---|---|---|---|---|
+ | public | Látható | Látható | Látható | Látható |
# | protected | Látható | Látható | Látható | Nem látható |
~ | nincs kulcsszó | Látható | Látható | Nem látható | Nem látható |
- | private | Látható | Nem látható | Nem látható | Nem látható |
Garbage collection - Szemétgyűjtés¶
Objektumok élettartama Java-ban: (élettartam = objektum létrejöttétől a memória felszabadításáig)
Amikor létrehozunk egy objektumot a new kulcsszóval, az objektum a heapen jön létre (ellentétben a primitív típusokkal).
A memória felszabadítása automatikus Javaban, a garbage collector (Szemétgyűjtő) végzi el a feladatokat. A Java szemétgyűjtőről bővebben ezen, ezen és ezen az oldalon olvashatunk.
Ha egy objektumot nem használunk tovább, beállíthatjuk null
-ra, ez a kulcsszó jelöli, hogy az adott referencia nem hivatkozik egyetlen objektumra sem (pl. tigris = null;
)
Garbage collector hívása manuálisan (nem biztos hogy lefut):
Lehetőségünk van az osztályainknak egy finalize()
-nak nevezett metódust készíteni. Ez a metódus akkor fog lefutni, amikor a szemétgyűjtő felszabadítja a feleslegesnek ítélt memóriát, és egy adott objektum, amit már nem használunk, törlődni fog. Azt természetesen nem tudni, hogy mikor fog lefutni, vagy hogy le fog-e egyáltalán. A metódus célja az objektum által használt valamilyen erőforrás felszabadítása (erre később látunk példát).
Feladatok¶
Kocsmaszimulátor elkészítése
Videók¶
- Öröklődés (EAIJ): https://youtu.be/hPeKsdtzvSo
- Gyerek- és szülőosztály kapcsolata, super (EAIJ): https://youtu.be/R6usRUaF1_o
- Metódus felüldefiniálása (EAIJ): https://youtu.be/dMXOYFR9Clw
- Tömb saját objektumból, referenciák (EAIJ): https://youtu.be/_HFkySuBPOY
- Futás idejű típusmeghatározás, típuskényszerítés, instanceof (EAIJ): https://youtu.be/udHoeeHn7Fg
- Csomagok (EAIJ): https://youtu.be/iaYBYNZTlOs
- Feladat 1. - Autók: https://youtu.be/Gf5n7i5Be78
- Feladat 2a. - Csomagszállítás: https://youtu.be/LOr9jtkFJlg
- Feladat 2b. - Csomagszállítás folytatása: https://youtu.be/c6WRAZ-6JZM
- Feladat 2c. - Csomagszállítás folytatása (az elkészült program kipróbálása): https://youtu.be/QoQ9EEGV2DM