Programare Java Curs – 5 FOLOSIREA METODELOR PENTRU INDEPLINIREA SARCINILOR Metodele sunt partea cea mai importanta a oricarui limbaj de programare orientat obiect deoarece ele definesc fiecare actiune indeplinita de un obiect . Clasele si obiectele ofera un cadru de lucru . Variabilele de instanta si de clasa ofera o modalitate de a defini ceea ce reprezinta aceste clase si obiecte . Doar metodele pot defini comportamentul unui obiect – lucrurile pe care este capabil sa le realizeze sau modul cum interactioneaza cu alte clase sau obiecte . In cursurile anterioare am vazut cum sa definim o metoda si sa lucram cu ea practic . In continuare vom detalia unele caracteristici care fac metodele sa fie mai eficiente si mai usor de folosit : -
supraincarcarea metodelor ( overloading ) – crearea de metode cu diferite semnaturi si definitii insa cu acelasi nume
-
crearea metodelor constructor – metode care permit initializarea obiectelor , pentru a le defini starea initiala din momentul crearii lor
-
suprascrierea metodelor (overriding ) – crearea unei definitii diferite penttu o metoda care a mai fost definita in superclasa
-
metode de finalizare ( finalizer ) – metode care elibereaza resursele ocupate de un obiect dupa terminarea lucrului cu un el , inainte ca acesta sa fie inlaturat din sistem
CREAREA DE METODE CU ACELASI NUME SI ARGUMENTE DIFERITE Un exemplu de astfel de metoda este valueOf() ; metoda apartine clasei java.lang.String . In general in Java vom intalni des clase care contin mai multe metode cu acelasi nume . Metodele cu acelasi nume se diferentiaza intre ele prin doua caracteristici : -
numarul argumentelor pe care le preiau tipul datelor sau obiectelor fiecarui argument
Aceste doua caracteristici definesc semnatura metodei ; folosirea mai multor metode cu acelasi nume si semnaturi diferite se numeste supraincarcare . In exemplul clasei String , metodele valueOf() sunt supraincarcate deoarece preiau ca parametri tipuri de date diferite . Supraincarcarea metodelor elimina nevoia de a defini metode complet diferite care sa faca in principiu acelasi lucru . Supraincarcarea face de asemenea posibila comportarea diferita a metodelor in functie de argumentele primite . Metodele valueOf() pot fi folosite pentru a converti diverse tipuri de date sau obiecte in siruri . Atunci cand apelam o metoda a unui obiect Java verifica numele si argumentele acesteia pentru a vedea ce metoda va executa . Pentru a crea o metoda supraincarcata intr-o clasa vom defini metode diferite , cu acelasi nume insa cu liste de argumente diferite . Diferenta poate consta in numarul de argumente , in tipul de argumente sau ambele . Java permite supraincarcarea metodelor atat timp cat lista de argumente este unica pentru acelasi nume de metoda . Mentionez ca Java nu ia in considerare tipul valorii returnate pentru a face diferentierea metodelor supraincarcate . Daca incercam sa cream doua metode care difera doar prin tipul valorii de retur vom obtine o eroare inca de la compilare . In plus numele variabilelor pe care le alegem pentru fiecare argument nu au importanta – tot ceea ce conteaza este numarul si tipul acestora .
1
In continuare vom detalia un exemplu de metoda supraincarcata . Vom crea intai o clasa care defineste o forma rectangulara cu patru variabile de instanta , pentru a preciza colturile din stanga-sus si dreaptajos ale unui dreptunghi : x1 , y1 , x2 , y2 . class DreptunghiulMeu { int x1=0; int y1=0; int x2=0; int y2=0; } Atunci cand este creata o noua instanta a clasei noastre toate valorile vor fi initializate cu 0 . In continuare vom defini o metoda care preia patru argumente intregi si returneaza obiectul rectangular . Deoarece argumentele au acelasi nume cu variabilele de instanta , in cadrul metodei vom folosi cuvantul cheie this pentru a referi variabilele de instanta : DreptunghiulMeu construireDreptunghi ( int x1 , int y1 , int x2, int y2) { this.x1=x1; this.y1=y1; this.x2=x2; this.y2=y2; return this; } O alta varianta ar fi folosirea obiectelor Point in locul coordonatelor individuale . Pentru a implementa aceasta varianta putem supraincarca metoda noastra astfel incat lista de argumente sa contina doua obiecte Point : DreptunghiulMeu construireDreptunghi ( Point stangaSus , Point dreaptaJos ) { x1=stangaSus.x; y1=stangaSus.y; x2=dreaptaJos.x; y2=dreaptaJos.y; return this; } Pentru ca metoda anterioara sa functioneze clasa Point trebuie importata la inceputul codului sursa . O alta modalitate de a defini un dreptunghi este de a folosi coordonatele coltului din stanga-sus impreuna cu valorile inaltimii si latimii sale : DreptunghiulMeu construireDreptunghi(Point stangaSus , int l , int h ) { x1=stangaSus.x; y1=stangaSus.y; x2=(x1+l); y2=(y1+h); return this; } Pentru a finaliza exemplul mai cream o clasa , afisareDreptunghi() , care urmeaza sa afiseze coordonatele dreptunghiului , si o metoda main() care sa apeleze toate aceste metode :
2
import java.awt.Point; class DreptunghiulMeu { int x1=0; int y1=0; int x2=0; int y2=0; DreptunghiulMeu construireDreptunghi(int x1 , int y1 , int x2 , int y2) { this.x1=x1; this.y1=y1; this.x2=x2; this.y2=y2; return this; } DreptunghiulMeu construireDreptunghi(Point stangaSus , Point dreaptaJos ) { x1=stangaSus.x; y1=stangaSus.y; x2=dreaptaJos.x; y2=dreaptaJos.y; return this; } DreptunghiulMeu construireDreptunghi(Point stangaSus , int l , int h) { x1=stangaSus.x; y1=stangaSus.y; x2=(x1+l); y2=(y1+h); return this; } void afisareDreptunghi() { System.out.print(“Dreptunghiul meu : <”+x1+”, “+y1); System.out.println(“, “+x2+”, “+y2+”>”); } public static void main(String argumente[]) { DreptunghiulMeu dreptunghi=new DreptunghiulMeu(); System.out.println(“Apelam construireDreptunghi cu coordonatele 25,25,50,50 : “); dreptunghi.construireDreptunghi(25,25,50,50); dreptunghi.afisareDreptunghi(); System.out.println(“***”); System.out,println(“Apelam construireDreptunghi cu punctele (10,10) , (20,20): “); dreptunghi.construireDreptunghi(new Point(10,10, new Point(20,20)); dreptunghi.afisareDreptunghi(); System.out.println(“***”); System.out.print(“Apelam construireDreptunghi cu 1 punct (10,10),”); System.out.println(“ latime 50 si inaltime 50 : “); dreptunghi.construireDreptunghi( new Point(10,10) , 50 , 50 ); dreptunghi.afisareDreptunghi(); System.out.println(“***”); } }
3
Atunci cand avem mai multe metode care fac lucruri asemanatoare , intr-o metoda putem apela o alta . De exemplu , in cazul de mai sus , metoda construireDreptunghi care primeste ca argumente doua obiecte Point poate fi inlocuita cu o versiune mult mai scurta : DreptunghiulMeu construireDreptunghi(Point stangaSus , Point dreaptaJos) { return construireDreptunghi(stangaSus.x , stangaSus.y , dreaptaJos.x , dreaptaJos.y ); } METODE CONSTRUCTOR In afara de metode obisnuite in clase putem defini si metode constructor . O metoda constructor este o metoda apelata la crearea unui obiect – cu alte cuvinte , atunci cand obiectul este construit . Spre deosebire de alte metode , o metoda constructor nu poate fi apelata direct ; Java apeleaza metodele constructor in mod automat . Atunci cand este folosita instructiunea new pentru crearea unui nou obiect , Java executa trei activitati : -
aloca memorie pentru obiect initializeaza variabilele de instanta ale obiectului fie la valorile initiale fie la cele prestabilite ( o pentru numere , null pentru obiecte , false pentru valori booleene si “\0” pentru caractere ) apeleaza metodele constructor ale clasei
Chiar daca o clasa nu are definita nici o metoda constructor este totusi posibila crearea unui obiect . Exista insa cazuri in care dorim sa setam anumite variabile de instanta sau sa apelam alte metode de care obiectul are nevoie pentru a se initializa . Prin definirea unor metode constructor in clase , putem seta valorile initiale ale variabilelor de instanta , putem apela metode pe bza acestor variabile , putem apela metode ale altor obiecte sau putem seta proprietatile initiale ale unui obiect . Metodele constructor pot fi si ele supraincarcate , la fel ca metodele obisnuite , pentru a crea un obiect care are proprietati specifice in functie de argumentele transmise prin instructiunea new . METODE CONSTRUCTOR DE BAZA Constructorii seamana cu netodele obisnuite , cu doua diferente : -
metodele constructor au totdeauna acelasi nume cu cel al clasei metodele constructor nu returneaza nimic
In exemplul de mai jos vom vedea o clasa Persoana care foloseste o metoda constructor pentru a-si initializa variabilele de instanta pe baza argumentelor primite de new : class Persoana { String nume; int varsta; Persoana (String n , int a) { nume=n; varsta=a; } void printPersoana() { System.out.print(“Eu sunt “+nume); System.out.println(“ si am “+varsta+” de ani”); } public static void main (String argumente[]) { Persoana p; p=new Persoana(“Ion”,50);
4
p.printPersoana(); System.out.Println(“----“); p=new Persoana(“Laura”,30); p.printPersoana(); System.out.println(“----“); } } APELAREA UNEI ALTE METODE CONSTRUCTOR Am vazut anterior cum o metoda poate apela o alta metoda in casrul ei . Acelasi lucru poate fi facut si in cazul constructorilor . Daca avem o metoda constructor ce reprezinta un comportament oarecum asemanator cu cel al unui constructor existent putem apela primul constructor din interiorul celui de-al doilea . Java ofera o sintaxa speciala pentru a realize acest lucru . Folosim urmatoarea instructiune pentru a apela o metoda constructor definite in clasa curenta : this(arg1 , arg2 , arg3); Folosirea cuvantului cheie this intr-o metoda constructor este similara modului lui de folosire pentru accesul la variabilele de instanta ale obiectului . In instructiunea anterioara argumentele primate de this() sunt argumentele metodei constructor . De exemplu , sa luam o clasa care defineste un cerc folosind coordonatele (x,y) ale centrului si lungimea razei . Clasa CerculMeu poate avea doi constructori : unul in care este definita raza si unul in care raza primeste valoarea prestabilita 1 : class CerculMeu { int x,y,raza; CerculMeu (int coordX , int coordY , int lungRaza) { this.x=coordX; this.y=coordY; this.raza=lungRaza; } CerculMeu (int coordX , int coordY) { this(coordX, coordY, 1); } } A doua metoda constructor din clasa CerculMeu preia doar coordonatele x si y ale cercului . Deoarece nu este definite nici o raza se foloseste valoarea prestabilita 1 ; se apeleaza apoi prima metoda constructor care primeste ca argumente coordX , coordY si literalul 1 . SUPRAINCARCAREA METODELOR CONSTRUCTOR Ca si metodele obisnuite constructorii pot avea un numar diferit de argumente sau tipuri ale acestora . Aceasta ne permite sa cream un obiect cu proprietatile dorite sau ofera acestuia posibilitatea de a-si calcula proprietatile pornind de la date de intrare diferite . De exemplu , metodele construireDreptunghi , definite in exemplele anterioare , pot constitui niste metode constructor foarte logice deoarece sunt folosite pentru a initializa variabilele de instanta . Astfel , in loc de metoda originala construireDreptunghi ( care primea patru parametric ) am putea crea un constructor . SUPRASCRIEREA METODELOR Atunci cand apelam metoda unui obiect , Java cauta definitia metodei respective in clasa obiectului . Daca nu o gaseste cauta mai sus in ierarhia de clase pana cand gaseste o definitie . Procesul de
5
mostenire a metodelor ne permite sa definim si sa folosim repetat metode in subclase fara a fi nevoie sa replicam codul . Totusi pot exista cazuri cand dorim ca un obiect sa raspunda acelorasi metode , dar sa aiba un comportament diferit la apelarea acestora . In acest caz , metoda se poate suprascrie . Pentru a suprascrie o metoda , definim intr-o subclasa o metoda cu aceeasi semnatura ca a unei metode dintr-o superclasa . Astfel , atunci cand metoda este apelata , metoda din subclasa este gasita prima si executata in locul celei din superclasa . CREAREA DE METODE CARE SUPRASCRIU METODE EXISTENTE Pentru a suprascrie o metoda , in practica trebuie sa cream o metoda cu aceeasi semnatura ( nume , tip , valoare returnata , lista de argumente ) ca a metodei din superclasa . Mai jos cream un exemplu pentru a ilustra supraincarcarea unei metode : class AfisareClasa { int x=0; int y=0; void afisareDate() { System.out.println(“x este “+x+” si y este “+y); System.out.println(“Sunt o instanta a clasei “+this.getClass().getName()); } } Cream in continuare si o subclasa a clasei de mai sus , cu o singura diferenta , subclasa contine si variabila z : class AfisareSubClasa extends AfisareClasa { int z=3; public static void main(String argumente[]) { AfisareSubClasa obiect=new AfisareSubClasa(); obiect.afisareDate(); } } Deoarece subclasa nu defineste o metoda afisareDate() , Java o cauta in superclasa si o gaseste acolo pentru a o putea executa . Aceasta metoda insa nu afiseaza si variabila de instanta z . Sa cream o noua subclasa care sa suprascrie metoda afisareDate() : class AfisareSubClasa2 extends AfisareClasa { int z=3; void afisareDate() { System.out.println(“x este “+x+” si y este “+y+” iar z este “+z); System.out.println(“Sunt o instanta a clasei “+this.getClass().getName()); } public static void main(String argumente[]) { AfisareSubClasa2 obiect=new AfisareSubClasa2(); obiect.afisareDate(); } } Acum , dupa initializarea obiectului AfisareSubClasa2 si apelarea metodei afisareDate() va fi apelata versiunea existenta in subclasa si nu pe cea din superclasa AfisareDate .
6
APELAREA METODEI ORIGINALE De obicei exista doua motive pentru care se face suprascrierea unei metode implementate deja de o superclasa : -
pentru a inlocui complet definitia metodei originale pentru a extinde functionalitatea metodei originale
In multe cazuri practice comportamentul metodei originale trebuie doar completat si nu inlocuit definitiv , mai ales in cazurile cand se realizeaza acelasi tip de actiuni si in metoda originala si in cea care o suprascrie . Prin apelarea metodei originale in cadrul metodei de suprascriere putem adauga numai insusirea suplimentara . Pentru a apela metoda originala in cadrul metodei de suprascriere folosim cuvantul cheie super . In acest fel apelul metodei este transferat mai sus in cadrul ierarhiei de obiecte : void metodaMea ( String a , String b ) { //cod sursa super.metodaMea(a,b); //cod sursa } Cuvantul cheie super este asemanator cuvantului cheie this , deoarece este o denumire generica pentru superclasa clasei curente . Il putem folosi oriunde am putea folosi si this , insa super refera superclasa nu clasa curenta . Sa ne amintim de cele doua metode afisareDate() diferite , folosite anterior . In loc sa copiem majoritatea codului metodei superclasei in subclasa , putem modifica metoda superclasei astfel incat ulterior sa se poata adauga cu usurinta o caracteristica suplimentara : // din AfisareClasa void afisareDate() { System.out.println(“Sunt o instanta a clasei “+this.getClass().getName()); System.out.println(“X este “+x); System.out.println(“Y este “+y); } Apoi , cand suprascriem metoda afisareDate() in subclasa putem apela metoda originala si adauga doar codul suplimentar : // din AfisareSubClasa2 void afisareDate() { super.afisareDate(); System.out.println(“Z este “+z); } SUPRASCRIEREA CONSTRUCTORILOR Din punct de vedere tehnic constructorii nu pot fi suprascrisi . Pentru ca au totdeauna acelasi nume ca al clasei curente , metodele constructor nu se mostenesc ci se creaza altele noi . Acest sistem este multumitor in marea majoritate a cazurilor ; atunci cand este apelata metoda constructor a clasei se apeleaza si metoda constructor cu aceeasi semnatura pentru toate superclasele . Din aceasta cauza initializarea se face pentru toate partile clasei pe care o mostenim .
7
Totusi , atunci cand definim metode constructor pentru clasa noastra putem modifica felul in care este initializat obiectul nu doar prin initializarea noilor variabile adaugate clasei , ci si prin modificarea continutului variabilelor deja prezente . Pentru aceasta vom apela explicit metodele constructor ale superclasei si apoi vom modifica variabilele dorite . Pentru a apela o metoda obisnuita apartinand superclasei vom folosi sintaxa super.nume_metoda(lista_argumente) . Deoarece metodele constructor nu au nume sub care pot fi apelate vom folosi urmatoarea forma : super(arg1 , arg2 , ... ); Retinem ca Java are o regula stricta pentru folosirea metodei super() : aceasta trebuie sa sie prima instructiune folosita in cadrul constructorului . Daca nu apelam super() explicit in cadrul constructorului Java face acest lucru implicit , folosind super() fara argumente . Deoarece apelarea super() trebuie sa fie prima instructiune nu putem folosi un cod de genul : if (conditie==true) super(1 , 2 , 3); // apelarea unui constructor al superclasei else super( 1 , 2 ); // apelarea unui alt constructor La fel ca in folosirea this(...) intr-o metoda constructor , super(...) apeleaza metoda constructor pentru superclasa imediat urmatoare ( care la randul sau va apela constructorul superclasei sale si asa mai departe ) . Retinem ca in superclasa trebuie sa existe un constructor cu semnatura respectiva pentru ca apelul super() sa functioneze . Compilatorul Java verifica aceste lucruri atunci cand incercam sa compilam fisierul sursa . Nu trebuie sa apelam constructorul din superclasa care are aceeasi semnatura cu cea a constructorului clasei noastre ; trebuie doar sa apelam constructorul pentru valorile pe care dorim sa le initializam . De fapt , putem crea o clasa care are constructori cu semnaturi total diferite de oricare dintre constructorii superclasei . In exemplul urmator vom prezenta o clasa PunctCuNume care extinde clasa Point a pachetului java.awt . Clasa Point are un singur constructor care preia argumentele x si y si returneaza un obiect Point . PunctCuNume contine o variabila de instanta suplimentara si defineste un constructor care initializeaza x , y si numele . import java.awt.Poin; class PunctCuNume ( int x , int y , String nume ) { super(x,y); this.nume=nume; } public static void main(String argumente[]) { PunctCuNume pn=new PunctCuNume(5,5,”PunctulA”); System.out.println(“x este “+pn.x); System.out.println(“y este “+pn.y); System.out.println(“Numele este “+pn.nume); } } Metoda constructor definita aici pentru PunctCuNume apeleaza metoda constructor a clasei Point pentru a initializa variabilele de instanta x si y . Chiar daca am putea initializa si singuri , explicit , variabilele x si y exista posibilitatea sa existe si alte lucruri care s-ar intampla la initilizarea obiectelor Point ; din aceasta cauza este mai bine sa apelam metodele constructor aflate deasupra in ierarhie , pentru a fi siguri ca totul se configureaza corect .
8
METODE DE FINALIZARE Metodele de finalizare sunt opusul metodelor constructor . O metoda constructor este folosita pentru a initializa un obiect iar metodele de finalizare sunt apelate chiar inainte de distrugerea obiectului si recuperarea memoriei ocupate . Metoda de finalizare este finalize() . Clasa Object defineste o metoda de finalizare prestabilita , care nu face nimic ( este vida ) . Pentru a crea o metoda de finalizare pentru propriile clase putem suprascrie metoda finalize() folosind semnatura : protected void finalize() throws Throwable { super.finalize(); } In cadrul metodei finalize() putem specifica toate actiunile de “curatare” pe care dorim sa le realizam in legatura cu obiectul . De asemenea putem apela super.finalize() pentru a permite superclasei clasei noastre sa finalizeze obiectul daca este nevoie . Putem apela metoda finalize() oricand – este o metoda ca oricare alta . Totusi apelarea ei nu semnaleaza sistemului distrugerea obiectului si recuperarea memoriei ocupate . Numai stergerea tuturor referintelor la obiectul respectiv duce la marcarea lui pentru distrugere . Metodele de finalizare sunt folosite de obicei pentru optimizarea distrugerii unui obiect – de exemplu , pentru distrugerea referintelor catre alte obiecte . In marea majoritate a cazurilor practice nu este nevoie se folosim deloc metoda explicita finalize() .
9