În programare, polimorfismul se referă la capacitatea unui obiect sau a unei metode de a se comporta în moduri diferite, în funcție de context.
O analogie din viața reală ar fi mașina: aceeași mașină poate fi folosită pentru transportul de persoane, pentru transportul de mărfuri sau pentru curse. Deși vorbim despre o singură entitate, care este mașina, aceasta poate îndeplini mai multe funcții.
Un exemplu mai tehnic îl reprezintă forme geometrice. O clasă Forma poate avea o metodă arie(), însă clasele derivate, precum Triunghi sau Cerc, vor calcula aria în mod diferit, în funcție de proprietățile lor specifice acestora.
În funcție de momentul în care se decide care metodă va fi executată, polimorfismul se clasifică în:
1. Polimorfism Static (Early Binding)
- Se realizează la compilare.
- Compilatorul știe exact ce metodă să apeleze verificând semnătura, adică numele și parametrii acesteia.
- Se implementează prin Supraîncărcarea metodelor (Overloading).
- Atunci când supraîncărcăm metode într-o clasă, compilatorul alege ce metodă să fie apelată în funcție de numărul, tipul și ordinea parametrilor transmiși.
2. Polimorfism Dinamic (Late Binding)
- Se realizează la execuție (runtime).- Decizia privind metoda apelată se ia în funcție de tipul obiectului creat efectiv în memorie, nu de tipul referinței.- Se implementează prin Supradefinirea metodelor (Overriding) și este strâns legat de conceptul de Moștenire.
Supraîncărcarea metodelor se referă la definirea a două sau mai multe metode în aceeași clasă cu același nume, dar cu liste diferite de parametri.
Deci, metodele supraîncărcate:
- Trebuie să conțină nume identic
- Trebuie să aibă listă de argumente diferită (număr de argumente și tip diferit)
- Pot avea tip de return diferit
- Pot avea diferiți modificatori de acces
- Pot arunca diferite tipuri de excepții
În următoarele exemple vom studia cum și în ce contexte putem utiliza supraîncărcarea metodelor.
Exemplul 1. Supraîncărcarea constructorului.
Deci, metodele supraîncărcate:
- Trebuie să conțină nume identic
- Trebuie să aibă listă de argumente diferită (număr de argumente și tip diferit)
- Pot avea tip de return diferit
- Pot avea diferiți modificatori de acces
- Pot arunca diferite tipuri de excepții
În următoarele exemple vom studia cum și în ce contexte putem utiliza supraîncărcarea metodelor.
Exemplul 1. Supraîncărcarea constructorului.
class Elev {private String nume, prenume;private int virsta;public Elev() {}public Elev(String nume, String prenume) {
this.nume = nume;this.prenume = prenume;
}public Elev(String nume, String prenume, int virsta) {
this.nume = nume;this.prenume = prenume;this.virsta = virsta;
}
Exemplul 2. Supraîncărcarea unei metode din clasa părinte în clasa copil.
class Angajat{protected String nume, prenume;protected int nrOre, plataOra;int salariu() {
return nrOre*plataOra;
}}class Manager extends Angajat {int salariu(int adaos) {
return nrOre*plataOra+adaos;
}}
Exemplul 3. Supraîncărcarea unei metode pentru număr diferit de parametri, în interiorul aceleiași clase:
class Operatii{
int suma(int a, int b){
return a+b;
}
int suma(int a, int b, int c){
return a+b+c;
}
int suma(int a, int b, int c, int d){
return a+b+c+d;
}}
Exemplul 4. Supraîncărcarea unei metode pentru parametri cu tip diferit, în interiorul aceleiași clase:
class Operatii{int suma(int a, int b){
return a+b;
}double suma(double a, double b){
return a+b;
}double suma(int a, double b, int c){
return a+b+c;
}}
Reţineţi !
1. Dacă vom supraîncărca metode cu aceeași listă de parametri, dar tip de return diferit vom obține eroare de compilare, aceasta nu oferă suficientă informație compilatorului pentru a face diferență.
2. Metodele statice, private și finale pot fi supraîncărcate.
Supradefinirea reprezintă o formă de polimorfism prin care o subclasă oferă o implementare proprie pentru o metodă moștenită de la superclasă. Metoda supradefinită (suprascrisă) trebuie să aibă aceeași semnătură (nume, tip de returnare și lista de parametri) ca și metoda moștenită. Orice modificare a semnăturii în subclasă va duce la o eroare de compilare.
Este tip de polimorfism dinamic deoarece la faza de execuție este selectată metoda corespunzătoare, în funcție de tipul real al obiectului.
Exemplele ce urmează sunt exemple de supraefinire a metodelor.
Exemplul 5. Supradefinirea metodei run().
class Vehicul {void run(){
System.out.println("Vehiculul merge !");
}}class Bicicleta extends Vehicul {void run(){
System.out.println("Bicicleta merge !");
}}class TestAutomobile {public static void main(String[] args) {
Vehicul v1 = new Vehicul();Bicicleta b1 = new Bicicleta();Vehicul c1 = new Bicicleta(); //detaliiv1.run();b1.run();c1.run();
}}
Rețineți !
1. Numai metodele moștenite pot fi supradefinite.
2. Metodele supradefinite trebuie să conţină același nume, aceeaşi listă de parametri şi acelaşi tip de return altfel nu merge vorba de supradefinire.
3. Metodele de tip private, final, static şi constructorii nu pot fi supradefiniţi.
4. O metoda supradefinită nu trebuie să conțină un modificator de acces mai restrictiv decât metoda originală din clasa părinte:
1. Numai metodele moștenite pot fi supradefinite.
2. Metodele supradefinite trebuie să conţină același nume, aceeaşi listă de parametri şi acelaşi tip de return altfel nu merge vorba de supradefinire.
3. Metodele de tip private, final, static şi constructorii nu pot fi supradefiniţi.
4. O metoda supradefinită nu trebuie să conțină un modificator de acces mai restrictiv decât metoda originală din clasa părinte:
- Dacă metoda originală are modificatorul implicit, metoda supradefinită poate avea unul din modificatorii: implicit, protected sau public.- Dacă metoda originală are modificatorul protected, metoda supradefinită poate avea unul din modificatorii: protected sau public.- Dacă metoda originală are modificatorul public, metoda supradefinită poate avea doar modificatorul public.
5. Adăugarea anotării @Override deasupra metodei suprascrise este o practică bună, deoarece ajută la identificarea erorilor de supradefinire în timpul compilării. Dacă metoda nu respectă criteriile de supradefinire, compilatorul va genera o eroare.
După cum deja cunoașteți, toate clasele sunt impicit subclase ale clasei Object. Clasa Object are metoda toString() care returnează un obiect de tip String ce conțin numele clasei și codul hash al acesteia. Aveți posibilitatea de a supradefini această metodă pentru a afișa mai multe informații despre clasa dată.
Exemplul 6. Supradefinirea metodei toString()
După cum deja cunoașteți, toate clasele sunt impicit subclase ale clasei Object. Clasa Object are metoda toString() care returnează un obiect de tip String ce conțin numele clasei și codul hash al acesteia. Aveți posibilitatea de a supradefini această metodă pentru a afișa mai multe informații despre clasa dată.
Exemplul 6. Supradefinirea metodei toString()
class Elev {private String nume, prenume;private int virsta;public Elev(String nume, String prenume, int virsta) {
this.nume = nume;this.prenume = prenume;this.virsta = virsta;
}@Overridepublic String toString() {
return "Elev nume=" + nume + ", prenume=" + prenume + ", virsta=" + virsta;
}}
public class sConstructor {public static void main(String[] args) {
Elev e1 = new Elev("Creanga", "Ion",50);System.out.println(e1);
}}
Ce se va întâmpla dacă în clasa Elev nu va fi supradefinită metoda toString()? Vom obține rezultatul, numele clasei urmat de codul hash al acesteia:
class Elev {private String nume, prenume;private int virsta;public Elev(String nume, String prenume, int virsta) {
this.nume = nume; this.prenume = prenume; this.virsta = virsta;
}public String getNume() { return nume; }public String getPrenume() { return prenume; }public int getVirsta() { return virsta; }}public class sConstructor {public static void main(String[] args) {
Elev e1 = new Elev("Creanga", "Ion",50);System.out.println("Elev nume="+e1.getNume() + ", prenume="+ e1.getPrenume()+",virsta="+e1.getVirsta());
}}
Avantajele polimorfismului în contextul lecției curente:
- Permite scrierea de cod generic care poate fi utilizat cu diferite tipuri de date, astfel înbunătățind reutilizarea codului.- Facilitează modificarea și extinderea codului existent făcându-l în acest sens flexibil.- Permite scrierea de cod mai concis și mai ușor de citit.- Permite optimizarea codului pentru anumite tipuri de date ceea ce duce la o performanță mai rapidă.
Dezavantajele polimorfismului:
- Pot apărea erori dacă tipul real al obiectului nu este compatibil cu metoda apelată.- Coul este mai dificil de înțeles și de depanat.- Erorile de compilare pot fi mai dificil de identificat și de remediat în codul polimorfic.
Urmează un spot video educațional la tema de astăzi:
Să aveți o zi deosebită!
❤️