Java - polymorfizmus

Z Kiwiki
Verzia z 15:43, 18. november 2010, ktorú vytvoril Juraj (diskusia | príspevky)
Skočit na navigaci Skočit na vyhledávání
Imbox draft.png
Toto je projekt, na ktorom sa ešte stále pracuje!!

Aj keď sú v tomto dokumente použiteľné informácie, ešte nie je dokončený. Svoje návrhy môžete vyjadriť v diskusii o tejto stránke.

Definícia polymorfizmu

Polymorfizmus (viacznačnosť) je schopnosť objektu nadobúdať viacero foriem. Najčastejšie použitie polymorfizmu je vtedy, keď v referencii na triedu rodiča používame odkaz na objekty triedy potomka.

Je dôležité vedieť, že jediný možný spôsob prístupu k objektu je cez referenčnú premennú. Referenčná premenná môže byť len z jedného typu. už definovanému objektu nemožno zmeniť typ.

Referenčná premenná môže byť znovu viackrát priradená iným objektom. Typ referenčnej premennej určuje aké metódy môžeme v rámci objektu volať. Referenčná premenná môže odkazovať ľubovoľný objekt, ktorý má rovnaký typ ako je typ referencie alebo podtypu referencie.[1]

Ilustračný príklad

   public interface Vegetarian{}
   public class Zviera{}
   public class Jelen extends Zviera implements Vegetarian{}

O triede Jelen môžeme tvrdiť:

  • Jeleň je zviera (Zviera)
  • Jeleň je vegetaríán (Vegetarian)
  • Jeleň je Jelen
  • Jeleň je objekt (Object)

Vidieť, že na triedu Jelen sa môžeme pozerať viacerými spôsobmi.

Uvažujme nasledujúce deklarácie:

   Jelen j = new Jelen();
   Zviera z = j;
   Vegetarian v = j;
   Object o = j;

Všetky referenčné premenné (j,z,v,o) odkazujú na jeden objekt - inštanciu triedy Jelen.

Virtuálne metódy

V tejto časti ukážeme ako využiť preťažovanie metód v súvislosti s polymorfizmom.

V predchádzajúcej časti sme hovorili o preťažovaní metód. Virtuálna metóda je vlastne preťažaná metóda z rodičovskej triedy. Rozdiel je však v možnosti použitia takejto metódy.

Opäť si zoberme triedy Zviera, Cicavec a Pes. Do týchto doplňme virtuálne metódy toString, ktoré vrátia textovú reprezentáciu daného objektu:

public class Zviera {
    public String nazov;
    public int vaha;

    //konštruktory sa oproti minulému príklady nezmenili

    public String toString()
    {
        return "Zviera: "+this.nazov+", Vaha: "+this.vaha;
    }
}

public class Cicavec extends Zviera{
    public int dlzka_gravidity;
    public String typ_srsti;

    //konštruktory sa oproti minulému príklady nezmenili

    public String toString()
    {
        String g= super.toString();
        return g+", Gravidita: "+this.dlzka_gravidity+", Srst: "+this.typ_srsti;
    }
}

public class Pes extends Cicavec {
    public String typPsa;

    //konštruktory sa oproti minulému príklady nezmenili

    public String toString()
    {
        String g= super.toString();
        return g+", Typ psa: "+this.typPsa;
    }   
}

Teraz ukážeme použitie viruálnej metódy toString. Princíp je ten, že nebudeme vytvárať referenciu na triedu Cicavec alebo Pes, ale vždy na triedu Zviera:

public class program {

    public static void main(String[] args) {
        Zviera a = new Zviera("Krokodíl", 200);
        Zviera b = new Cicavec("Jelen", 120, 14, "kratka");
        Zviera c = new Pes("Vlkolak", 60, 15, "kratka", "divý");

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }
}

Výstupom bude:

Zviera: krokodíl, Vaha: 200
Zviera: Jelen, Vaha: 120, Gravidita: 14, Srst: kratka
Zviera: Vlkolak, Vaha: 60, Gravidita: 15, Srst: kratka, Typ psa: divý

Ešte raz poznamenajme, že a, b, c sú referencie na triedu Zviera. Avšak pre tiero referencie používame odkaz na potomka (len pri b, c). O tom, ktorá verzia metódy sa spustí sa rozhodne až pri zavolaní metódy v bežiacom programe. Teda nie pri kompilácii ale pri behu programu.

Dynamické pretypovanie

V Jave okrem jednoduchého explicitného pretypovania (napr. int a=(int)3.2; ) existuje termín dynamické pretypovanie, ktoré ďalej rozdeľujeme na pretypovanie smerom hore (upcasting) a pretypovanie smerom dole (downcasting). Tieto pojmy majú zmysel len pri použití dedenia, konkrétne pri polymorfizme[2]. Java umožnuje objektu vytvoreného ako inštancie z podtriedy byť považovaný za objekt rodičovskej triedy. Táto vlastnosť sa nazýva upcasting. Upcasting sa vykonáva automaticky, zatiaľ čo downcasting musí byť vykonaný explicitne na to určeným príkazom.

Upcasting a downcasting nie je pretypovanie ako ho chápemem pri primitívnych dátových typoch.

Pre vysvetlenie týchto pojmov si opäť zoberme hierarchiu tried Zviera-Cicavec-Pes, Mačka:

Zvierence2.svg

Na rozdiel od minulého príkladu, pribudla tieda Mačka. Trieda Mačka je potomkom triedy Cicavec. Na obrázku je naznačené dedenie z triedy Object, pretože ako už bolo povedné, každá trieda v Jave je potomkom triedy Object.

Pripomeňme niektoré skutočnosti: Trieda Zviera je potomkom triedy Object. Trieda Cicavec je priamym potomkom triedy Zviera. Trieda Cicavec a trieda Object sú vo vzájomnom vzťahu "starý rodič - vnuk", čiže rozdiel sú 2 generácie. Trieda Objekt je voči triede Mačka praprarodič.

Pre úplnosť uvedieme deklaráciu tried (bez ich vlastností a metód). Doplníme len triedu Macka

 public class Zviera { }

 public class Cicavec extends Zviera { }

 public class Pes extends Cicavec { }

 public  class Macka extends Cicavec { 

    public int pocet_zivotov;
    public String typMacky;

    public Macka(String nazov, int vaha, int dlzka_gravidity, String typ_srsti, int pocet_zivotov, String typ_macky) {
        this.pocet_zivotov = pocet_zivotov;
        this.typMacky = typ_macky;
    }

    public Macka() {
        super("Macka",5,9,"hladká");
        this.pocet_zivotov = 9;
        this.typMacky = "perzská";
    }
    public String toString()
    {
        String g= super.toString();
        return g+", Typ macky: "+this.typMacky+", zivoty: "+this.pocet_zivotov;
    }

    public void mnaucat()
    {
        System.out.println("Mnau!!!");
    }
}

Upcasting

Ako prvé prvé musíme podotknúť že pretypovanie nemení typ objektu! Iba ho označí iným spôsobom. Ako zoberme si nasledujúci príklad:

  • Na riadku 1 sme vytvorili objekt m typu Macka. Výpis na riadku 2 vypíše všetky vlastnosti mačky.
  • V ďalšom riadku (č. 4) vytvoríme objekt c, krotý je typu Cicavec. Objekt m priradíme objektu c (ide o upcasting).
    • Objekt c, hoci je definovaný ako Cicavec, ale zmenili sme jeho typ na Macka.
    • Objekt c je vlastne typ Macka, ale v tomto stave sú skryté všetky špecifické metódy triedy Mačka.
    • Inými slovami, objekt c je Macka, ale správa sa ako zviera (neosahuje metódy triedy zviera).

Objekt m obsahuje metódu mnaucat (mňaučať), ale objekt c je neobsahuje. To, že metóda toString je prístupná tak ako pri objekte m ako aj pri objekte c je spôsobené pomocou vituálnych metód.

1         Macka m = new Macka();
2         System.out.println(m);
3         m.mnaucat();
4         Cicavec c = m; // upcasting
5         System.out.println(c);
6        // c.mnaucat();  CHYBA!!!;

Výstup:

Zviera: Cicavec, Vaha: 1, Gravidita: 7, Srst: , Typ macky: perzská, zivoty: 9
Zviera: Cicavec, Vaha: 1, Gravidita: 7, Srst: , Typ macky: perzská, zivoty: 9

Z výstupu programu je vidieť, že objekty m a c sú totožné. Po pretypovaní sa objekt m nezmenil, stále zostal objektom "Macka". Toto je dovolené preto, pretože Mačka je Cicavec (v zmysle navrhnutých tried).

Na predchádzajúcom obrázku je v hierarchii tried trieda Macka a Pes na jednej úrovni. To znamená, že medzi sebou nemajú žiaden vzťah. Preto namôže byť objekt Macka pretypovaný na objekt Pes (a opačne)

O objekte Mačka (Macka m = new Macka();) môžeme trvdiť:

  • Kompilátor Java pracuje s objektom m ako s inštanciou triedy Object.
  • Objekt m je Mačka; má k dispozícii všetky metódy z triedy Cicavec aj Macka.
  • Objekt m sa dá pretypovať na objekt Cicavec
  • Objekt m sa nedá pretypovať na objekt Pes

Downcasting

Na rozdiel od pretypovania smerom hore, pretypovanie smerom dole musí byť vždy určené explicitne. Uveďme príklad:

    Macka m1 = new Macka ();		 
    Zviera z = m1;		 //uptomatické pretypovanie HORE na Zviera
    Macka  m2 = (Macka ) z;    //manuálne pretypovanie DOLU naspäť na Mačku

Prečo je upcasting robený automaticky, ale downcasting musí byť explicitne definovaný? Ak sa ešte raz pozrieme na princíp upcastingu, tak pri samotnom pretypovaní smerom hore nemôže nikdy nastať neúspech. Teda, upcasting sa vždy podarí. Ale ak máme skupinu rôznych zvierat a každé chceme pretypovať na Mačku, je viac ako pravdepodobné že niektoré budú napríklad inštancie triedy Pes a vtedy pretypovanie stroskotá na nekompatibilite typov a vygeneruje sa výnimka ClassCastException.

Ukážeme si prípad "bezpečného" pretypovania smerom dolu. Využijeme operátor instanceof, ktorá zistí, či je objekt daného typu.

public class program {
    public static void main(String[] args) {

        Macka m1 = new Macka();
        Pes p1 = new Pes();        
        Zviera z1 = m1;		 //upcasting na Zviera
        Zviera z2 = p1;		 //upcasting na Zviera
        Macka m2 = null;
        Pes p2 = null;
        if (z1 instanceof Macka) {
            m2 = (Macka) z1;
        }
        if (z2 instanceof Pes) {
            p2 = (Pes) z2;
        }

        System.out.println(m2);
        System.out.println(p2);
    }
}

Výstup z programu:

Zviera: Macka, Vaha: 5, Gravidita: 9, Srst: hladká, Typ macky: perzská, zivoty: 9
Zviera: Pes, Vaha: 5, Gravidita: 5, Srst: , Typ psa: salašnícky

Poznamenajme, že pretypovanie sa nie vždy dá robiť oboma smermi. Vytvorme objekt c typu Cicavec (riadok 3). Tento objekt sa nedá pretypovať ani na objekt Macka, ani na objekt Pes.

public class program {
    public static void main(String[] args) {
        Cicavec c= new Cicavec();
        Macka m=(Macka)c;
        System.out.println(m);
        Pes p=(Pes)c;
        System.out.println(p);
    }
}

Zaujímavosťou ale je, že predchádzajúci program je bez syntaktických chýb. Čiže dá sa skompilovať. Program padne až po spustení, pri spracovávaní riadku č. 4. Tu je veľmi dobre vidieť vlastnosť polymorfizmu, keď sa vyberá ktorá z virtuálnych metód sa spustí. V tomto prípade sa o výbere správenj metódy nerozhoduje pri kompilácii ale až pri samotnom spustení programu.

Po spustení programu dostaneme nasledujúce chybové hlásenie:

Exception in thread "main" java.lang.ClassCastException: zvierata.Cicavec cannot be cast to zvierata.Macka
       at prvy.program.main(program.java:23)
Java Result: 1

Všeobecné pravidlo pri pretypovaní je zadefinovanie aký objekt je akého typu. Mohli by sme sa spýtať nasledovné:

  • Je Mačka Cicavec? - Áno, teda môžeme pretypovať.
  • Je Cicavec Mačka? - Nie, teda nemôžeme pretypovať.
  • Je Mačka Pes? - Nie, teda nemôžeme pretypovať.

Upcasting a downcasting pri volaní metód

Praktické využitie pretypovania sa dá ukázať pri vytváraní všeobecných metód, ktorých argumentom môže byť generická trieda, ktorá zastupuje všetky jej triedy potomkov. Uvažujme príklad:

 // metóda "Pochváľ zviera"
 public static void pochval(Zviera z){
        System.out.println(z + " si dobré!!!");
    }

Parametrom tejto metódy môže byť obajekt typu Zviera, ale aj Cicavec, Vták, Plaz, Mačka alebo Pes.

    Macka c = new Macka();		 
    Pes d = new Pes();		 
    pohlad(c); // automatický upcasting na triedu Zviera
    pohlad(d); // automatický upcasting na triedu Zviera

Uvažujeme opačný príklad, kde budeme definovať metódu, ktorej parametrom je objekt typu Macka:

 public static void pridaj_zivot(Macka m){
        m.pocet_zivotov++;
        System.out.println(m + " ma "+ m.pocet_zivotov + " zivotov");
    }

V tomto prípade musí byť parametrom metódy pridaj_zivot len objekt typu Macka:

    public static void main(String[] args) {
        Macka c = new Macka();
        Pes d = new Pes();
        Zviera e = new Macka();
        
        pridaj_zivot(c); // všetko v poriadku
        pridaj_zivot(d); // CHYBA!!!, d je Pes
        pridaj_zivot((Macka)e); // explicitné pretypovanie na objekt macka
    }

Zdroje a odkazy