Kihagyás

Polimorfizmus

Az előadás videója elérhető a itt.

Polimorfizmus

Amikor objektum hierarchiákkal fogalkozunk, akkor gyakorta találkozunk azzal a jelenséggel, hogy ahelyett, hogy a speciális típusként hivatkoznánk egy-egy objektumra, inkább annak ős típusát használjuk, így adott ponton elképzelhető, hogy a különböző specializációkból más-mást használunk. Ez a megközelítés a többalakúság, polimorfizmus, amely biztosítja az objektumok felcserélhetőségét. Ráadásul könnyen bővíthetővé válik a program, adhatunk hozzá új speciális típusokat, amiket aztán behelyettesíthetünk tetszőlegesen az ős helyén.

Ezekben az esetekben az ős interfészét használhatjuk, meghívhatjuk annak valamennyi metódusát. Azonban ha az a metódus a gyermek osztályokban felül van definiálva, akkor a híváskor a gyermek osztályban felüldefiniált metódus fog meghívódni. Azaz a fordítási időben nem is lehet eldönteni, hogy melyik konkrét operáció fog meghívódni (az örökölt, vagy valamelyik felüldefiniált). Ez csak futási időben fog kiderülni. Ez a late binding, azaz kései kötés mechanizmusa.

Emlékezzünk vissza, hogy találkoztunk a korai kötés esetével is, amikor már fordítási időben kiderül a hívott eljárás abszolút címe. Ez jellemzi a statikus metódushívásokat.

Térjünk vissza a jól ismert Alakzatos példához, ahol az ős Alakzat osztály definiálja a rajzolj metódust, amelyet valamennyi gyermek osztály (Haromszog, Kor es Negyzet is) felüldefiniál:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Alakzat {
    public void rajzolj() { /*A*/ }
}

class Haromszog extends Alakzat {
    public void rajzolj() { /*H*/ }
}

class Negyzet extends Alakzat {
    public void rajzolj() { /*N*/ }
}

class Kor extends Alakzat {
    public void rajzolj() { /*K*/ }
}

Legyen egy fő programunk, amiben definiáljunk egy statikus csinald metódust (azaz olyan metódust, amit az osztály példányosítása nélkül meghívhatunk), és adjunk neki egy Alakzat objektumot paraméterként. Az Alakzat helyére a többalakúságot kihasználva kerülhet tetszőleges Alakzat objektum, legyen az Haromszog, Kor, vagy Negyzet. Mivel az Alakzat definiálja a rajzolj metódust, így az meg is hívható a paraméter objektumon keresztül a csinald metódusból.

A main metódusát úgy alakítsuk ennek a példának, hogy az a csinald metódust egy kör, egy négyzet és egy háromszög objektummal is meghívja. Az, hogy konkrétan a Haromszog, vagy a Negyszog, vagy a Kor osztályban definiált rajzolj metódus hívódik meg, az ezen objektumok dinamikus típusától függ, amit fordítási időben nem tudunk meghatározni a csináld metóduson belül.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class AlakzatPelda {
    static void csinald(Alakzat a) {
        // ...
        a.rajzolj();
    }

    public static void main(String[] args) {
        Kor k = new Kor();
        Haromszog h = new Haromszog();
        Negyzet n = new Negyzet();
        csinald(k);
        csinald(h);
        csinald(n);
    }
}

"Elfelejteni" a típust?

Az előző példában a csinald(k) hívás során tulajdonképpen „elveszik” a típus, mert a metódus Alakzat-ot vár, de Kor (illetve Haromszog, vagy Negyszog) van átadva.

Mi lenne, ha a csinald metódus egyenesen Kor-t várna? Nem lenne praktikus, mert kellene egy külön csinald metódus Negyzet, és egy Haromszog parameterrel is. Illetve mindannyiszor, amikor definiálunk egy új speciális alakzat osztályt, kellene egy új csinald metódus az új paraméterrel, vagyis a kód karbantarthatósága nagyon nehézzé válna.

Bővíthetőség

A polimorfizmus lehetővé teszi, hogy tetszőleges új típussal bővítsük a programot. Az előbbi csináld metódus tetszőleges Alakzat-ot vár, de amikor meghívjuk a csináld metódust, akkor ott valójában nem egy Alakzat objektumot, hanem annak egy speciális változatát adjuk át. Gyakorlatilag a paraméteren egy upcasting (beolvasztás) operáció történik. Speciális helyett innentől általánosként hivatkozunk rá. Amikor a csináld metódusban meghívódik a rajzolj metódus, akkor azonban a megfelelő típus rajzolj metódusa fog meghívódni, azon típusé, amellyel meghívtuk a csináld metódust. Ezt egy olyan mechanizmus teszi lehetővé, amely a futás közben rendeli hozzá a megfelelő metódust a függvényhívás helyéhez. Azaz fordítási időben nem eldönthető, hogy pontosan ezen csináld metódus hívásokkor mely metódus is fog meghívódni.

Objektum orientáltság

Az eddigiek során megismerkedtünk az objektumorientáltság 3 legfőbb tulajdonságával. Foglaljuk össze és memorizáljuk ezeket!

  1. Egységbezárás – absztrakt adattípusok létrehozásával az adatokat, és rajtuk végezhető műveleteket egységbe zárjuk.
  2. Öröklődés segítségével az interfészét egy-egy absztrakt adattípusnak újrafelhasználhatóvá tesszük.
  3. Polimorfizmus - többalakúság segítségével megvalósulhat a kései kötés.

Az első Java program - értelmezve

Térjünk vissza az első Java programunkhoz, és nézzük meg mit is csináltunk benne. Az eddigiek segítségével talán minden részlete világossá válik.

A feladat annyi volt, hogy írjuk ki a "Hello, a datum: " szöveget, majd az aktuális dátumot. Ehhez a következő kódot írtuk:

1
2
3
4
5
6
7
import java.util.*;
public class HelloDate {
    public static void main(String[] args) {
        System.out.println("Hello, a datum: ");
        System.out.println(new Date());
    }
}
  • Adott egy statikus metódus, a main, ezt annélkül tudjuk hívni, hogy példányosítjuk a HelloDate osztályt.
  • A main metódusban használjuk a System osztályt, asszociatív kapcsolat van tehát a HelloDate és System között.
  • A Sytem tartalmaz egy PrintStream típusú, out nevű objektumot. Ez egy aggregáció. Statikus az adattag, ezt mutatja, hogy az osztályon keresztül tudunk rá hivatkozni.
  • Amikor meghívjuk ennek a println metódusát, akkor független attól, hogy milyen paramétert kapott, az a kapott paramétert, mint Objectet kezeli. Ennek meghívja a toString metódusát, ami attól függően, hogy egy sima String, vagy egy Date objektumra hívódik meg, más más toString implementáció kötődik be kései kötéssel.

Utolsó frissítés: 2021-05-04 07:53:32