Polimorfismul

Polimorfismul se manifestă atunci când unei variabile-referință pentru superclasă i se atribuie ca valoare o referință către o instanță a unei subclase a acesteia, în care una sau mai multe metode ale superclasei au fost redefinite. În limbajul Java se consideră că metodele sunt legate dinamic (engleză: dynamic method binding). Aceasta înseamnă că stabilirea metodei care va fi invocată se face în momentul execuției, fiind invocată metoda din clasa căreia îi aparține efectiv obiectul  indicat de referința respectivă.

Fie A o clasă, iar B o subclasă a clasei A și fie declarațiile
    A a1, a2, a3;
    B b1;
    Object ob1, ob2, ob3;
Unei variabile referință dintr-o clasă, i se pot atribui ca valori referințe la instanțe ale subclaselor sale. În consecință, pot exista instrucțiuni de atribuire de forma
    a1=new A(); a2=new B(); b1=new B(); a3=b1;
    ob1=new B(); ob2=a1; ob3=b1;
Ultimele două atribuiri sunt posibile deoarece, în limbajul Java, toate clasele sunt subclase ale clasei Object.

Se pune întrebarea: dacă o metodă din clasa A, fie aceasta metoda1(), a fost redefinită în clasa B, care din ele va fi executata în expresia a2.metoda1()? Observăm că, în această expresie, variabila a2 aparține clasei A, în timp ce obiectul indicat de această variabilă aparține efectiv clasei B. Conform principiului polimorfismului, va fi executata metoda1() din clasa B.
 
Atenție: polimorfismul se aplică numai metodelor de instanță. În cazul câmpurilor statice și metodelor statice, care sunt redeclarate în subclasa, nu are loc o redefinire, ci o ascundere. În consecință, legarea acestora este statică (la compilare), iar la execuție se vor folosi câmpurile și metodele statice din clasa căreia ii aparține variabila-referința, nu din clasa căreia îi aparține instanța indicată de aceasta.
În atentia programatorilor de C++
În limbajul C++ polimorfismul se manifestă numai pentru metodele virtuale. Metodele care nu au fost declarate cu modificatorul virtual sunt legate static (la compilare), deci la execuție se invocă în astfel de cazuri metoda clasei căreia îi aparține variabila-referința (sau pointerul) și nu cea căreia îi aparține obiectul indicat. Folosind conceptele din C++, se poate considera că în limbajul Java toate metodele de instanță sunt implicit virtuale (sunt legate dinamic), în timp ce metodele statice nu sunt virtuale (sunt legate static).

Conversia unei referințe la clasă într-o referință la subclasă

Am arătat mai sus că atribuiri de forma ob1=new B() sau ob2=a1 (unde ob1 și ob2 sunt variabile-referință la Object, iar a1 este variabila-referință la A) sunt permise, deoarece unei variabile referință dintr-o clasa i se pot da ca valori referințe la instanțe ale unor subclase. În schimb, dacă încercăm să utilizăm expresia ob1.metoda1() sau ob2.metoda1() obținem erori de compilare, deoarece metoda1() nu există în clasa Object. Pentru a evita astfel de erori, este necesar să convertim explicit (prin cast) referința la Object în referință la clasa A sau B, în care există metoda1(). În acest scop, vom folosi expresiile ((B)ob1).metoda1() sau, respectiv,  ((A)ob2).metoda1().

Având în vedere că B este subclasă a lui A, iar variabila ob1 indică efectiv un obiect din clasa B, atât în expresia ((B)ob1).metoda1(), cât și în expresia ((A)ob1).metoda1() se va invoca  metoda1() din clasa B, adică din clasa căreia îi aparține efectiv obiectul indicat de variabila-referință ob1.
 
Exemplu
În fișierul Polimorf1.java se da următorul exemplu de aplicație în care se testează polimorfismul, folosind clasa S1 și subclasele ei CD1 și CD2 definite anterior:
 
class Polimorf1 {
  public static void main(String args[]) {
    Object ob1, ob2, ob3, ob4;
    S1 s1a, s1b, s1c;
    CD1 cd1a;
    CD2 cd2a;
    String s1="sirul1";
    s1a=new S1(1, 2.5, 3);
    cd1a=new CD1(-1, -2.5, -3);
    cd2a=new CD2(10, 20.5, 30, 100, 200.5, 300);
    s1b=cd1a; s1c=cd2a;
    ob1=s1; ob2=s1a; ob3=cd1a; ob4=cd2a;
    System.out.println("s1a="+s1a+" s1b="+s1b);
    System.out.println("s1c="+s1c);
    System.out.println("s1a.f2()="+s1a.f2()+" s1a.f3()="+s1a.f3());
    System.out.println("s1b.f2()="+s1b.f2()+" s1b.f3()="+s1b.f3());
    System.out.println("s1c.f2()="+s1c.f2()+" s1c.f3()="+s1c.f3());
    System.out.println("ob1="+ob1+" ob2="+ob2+" ob3="+ob3);
    System.out.println("ob4="+ob4);

    System.out.println("cd1a.f2()="+cd1a.f2()+" cd1a.f3()="+cd1a.f3());
    System.out.println("cd2a.f2()="+cd2a.f2()+" cd2a.f3()="+cd2a.f3());
    System.out.println("((S1)ob2).f2()="+((S1)ob2).f2()+
      " ((S1)ob2).f3()="+((S1)ob2).f3());
    System.out.println("((S1)ob3).f2()="+((S1)ob3).f2()+
      " ((S1)ob3).f3()="+((S1)ob3).f3());
    System.out.println("((CD1)ob3).f2()="+((CD1)ob3).f2()+
      " ((CD1)ob3).f3()="+((CD1)ob3).f3());
    System.out.println("((S1)ob4).f2()="+((S1)ob4).f2()+
      " ((S1)ob4).f3()="+((S1)ob4).f3());
    System.out.println("((CD2)ob4).f2()="+((CD2)ob4).f2()+
      " ((CD2)ob4).f3()="+((CD2)ob4).f3());
  }
}

La executarea acestei aplicații se obțin pe ecran următoarele rezultate:
 
s1a=(S1: 1 2.5 3) s1b=(S1: -1 -2.5 -3)
s1c=(CD2: (S1 10 20.5 30) 100.0 200.5 300.0)
s1a.f2()=7 s1a.f3()=18.0
s1b.f2()=-2 s1b.f3()=-3.0
s1c.f2()=70 s1c.f3()=711.0
ob1=sirul1 ob2=(S1: 1 2.5 3) ob3=(S1: -1 -2.5 -3)
ob4=(CD2: (S1 10 20.5 30) 100.0 200.5 300.0)
cd1a.f2()=-2 cd1a.f3()=-3.0
cd2a.f2()=70 cd2a.f3()=711.0
((S1)ob2).f2()=7 ((S1)ob2).f3()=18.0
((S1)ob3).f2()=-2 ((S1)ob3)f3()=-3.0
((CD1)ob3.f2()=-2 ((CD1)ob3).f3()=-3.0
((S1)ob4).f2()=70 ((S1)ob4).f3()=711.0
((CD2)ob4).f2()=70 ((CD2)ob4).f3()=711.0

Remarcăm următoarele:
  - atribuiri de forma s1b=cd1a sau ob1=s1 sunt corecte, întrucât unei variabile-referință dintr-o clasă i se pot da ca valori referințe la instanțe ale subclaselor acesteia;
  - expresiile s1a.f2() și ((S1)ob2).f2() au aceeași valoare (7), intrucat in ambele cazuri este invocata metoda f2() din clasa S1;
  - în expresia ((S1)ob2).f2() s-a folosit castul pentru a converti referința la Object intr-o referință la S1, întrucat în clasa Object nu există o metoda f2();
  - expresiile s1b.f2(), cd1a.f2(), ((S1)ob3).f2() și ((CD1)ob3).f2()au aceeasi valoare (-2), deoarece în toate cele trei cazuri metoda f2() se aplică asupra aceluiași obiect;
  - în expresia s1b.f2() nu a fost necesar să se folosească o conversie explicită (cast) de forma ((CD1)s1b).f2(), deoarece o metoda cu signatura f2() există și în clasa S1, căreia îi aparține variabila s1b. Totuși, în momentul execuției, se aplică efectiv metoda f2() din clasa CD2, căreia îi aparține obiectul indicat de s1b;
  - în mod similar, expresiile s1c.f2(), cd2a.f2(), ((S1)ob4).f2() si ((CD2)ob4).f2() au aceeași valoare (70);
  - în instrucțiunea System.out.println("s1a="+s1a+" s1b="+s1b); se folosește implicit metoda toString()pentru a se converti obiectele indicate de variabilele-referință s1a și s1b în șiruri. Se folosește în ambele cazuri metoda toString() din clasa S1, deoarece în clasa CD1 nu a fost redefinită o astfel de metodă;
  - în instrucțiunea System.out.println("s1c="+s1c); se folosește implicit metoda toString() din clasa CD2, căreia îi aparține efectiv obiectul indicat de variabila s1c;
 



© Copyright 2000 - Severin BUMBARU, Universitatea "Dunărea de Jos" din Galați