Obiecte Java
- Detalii
- Categorie: Programare Java
- Accesări: 8,328
În primul rînd sa observam ca, atunci cînd scriem programe în Java nu facem altceva decît sa definim noi si noi clase de obiecte. Dintre acestea, unele vor reprezenta însasi aplicatia noastra în timp ce altele vor fi necesare pentru rezolvarea problemei la care lucram. Ulterior, atunci cînd dorim sa lansam aplicatia în executie nu va trebui decît sa instantiem un obiect reprezentînd aplicatia în sine si sa apelam o metoda de pornire definita de catre aplicatie, metoda care de obicei are un nume si un set de parametri bine fixate. Totusi, numele acestei metode depinde de contextul în care este lansata aplicatia noastra.
Aceasta abordare a constructiei unei aplicatii ne spune printre altele ca vom putea lansa oricîte instante ale aplicatiei noastre dorim, pentru ca fiecare dintre acestea va reprezenta un obiect în memorie avînd propriile lui valori pentru variabile. Executia instantelor diferite ale aplicatiei va urma desigur cai diferite în functie de interactiunea fiecareia cu un utilizator, eventual acelasi, si în functie de unii parametri pe care îi putem defini în momentul crearii fiecarei instante.
Declaratia unei noi clase de obiecte
Pasul 1: Stabilirea conceptului reprezentat de clasa de obiecte
Sa vedem ce trebuie sa definim atunci cînd dorim sa cream o noua clasa de obiecte. În primul rînd trebuie sa stabilim care este conceptul care este reprezentat de catre noua clasa de obiecte si sa definim informatiile memorate în obiect si modul de utilizare a acestuia. Acest pas este cel mai important din tot procesul de definire al unei noi clase de obiecte. Este necesar sa încercati sa respectati doua reguli oarecum antagonice. Una dintre ele spune ca nu trebuiesc create mai multe clase de obiecte decît este nevoie, pentru a nu face dificila întelegerea modului de lucru al aplicatiei la care lucrati. Cea de-a doua regula spune ca nu este bine sa mixati într-un singur obiect functionalitati care nu au nimic în comun, creînd astfel clase care corespund la doua concepte diferite.
Medierea celor doua reguli nu este întotdeauna foarte usoara. Oricum, va va fi mai usor daca pastrati în minte faptul ca fiecare clasa pe care o definiti trebuie sa corespunda unui concept real bine definit, necesar la rezolvarea problemei la care lucrati. si mai pastrati în minte si faptul ca este inutil sa lucrati cu concepte foarte generale atunci cînd aplicatia dumneavoastra nu are nevoie decît de o particularizare a acestora. Riscati sa pierdeti controlul dezvoltarii acestor clase de obiecte prea generale si sa îngreunati dezvoltarea aplicatiei.
Pasul 2: Stabilirea numelui clasei de obiecte
Dupa ce ati stabilit exact ce doriti de la noua clasa de obiecte, sunteti în masura sa gasiti un nume pentru noua clasa, nume care trebuie sa urmeze regulile de constructie ale identificatorilor limbajului Java definite în capitolul anterior.
Stabilirea unui nume potrivit pentru o noua clasa nu este întotdeauna un lucru foarte usor. Problema este ca acest nume nu trebuie sa fie exagerat de lung dar trebuie sa exprime suficient de bine destinatia clasei. Regulile de denumire ale claselor sunt rezultatul experientei fiecaruia sau al unor conventii de numire stabilite anterior. De obicei, numele de clase este bine sa înceapa cu o litera majuscula. Daca numele clasei contine în interior mai multe cuvinte, aceste cuvinte trebuie de asemenea începute cu litera majuscula. Restul caracterelor vor fi litere minuscule.
De exemplu, daca dorim sa definim o clasa de obiecte care implementeaza conceptul de motor Otto vom folosi un nume ca MotorOtto pentru noua clasa ce trebuie creata. La fel, vom defini clasa MotorDiesel sau MotorCuReactie. Daca însa avem nevoie sa definim o clasa separata pentru un motor Otto cu cilindri în V si carburator, denumirea clasei ca MotorOttoCuCilindriÎnVSiCarburator nu este poate cea mai buna solutie. Poate ca în acest caz este preferabila o prescurtare de forma MotorOttoVC. Desigur, acestea sunt doar cîteva remarci la adresa acestei probleme si este în continuare necesar ca în timp sa va creati propria conventie de denumire a claselor pe care le creati.
Pasul 3: Stabilirea superclasei
În cazul în care ati definit deja o parte din functionalitatea de care aveti nevoie într-o alta superclasa, puteti sa derivati noua clasa de obiecte din clasa deja existenta. Daca nu exista o astfel de clasa, noua clasa va fi automat derivata din clasa de obiecte predefinita numita Object. În Java, clasa Object este superclasa direct sau indirect pentru orice alta clasa de obiecte definita de utilizator.
Alegerea superclasei din care derivati noua clasa de obiecte este foarte importanta pentru ca va ajuta sa refolositi codul deja existent. Totusi, nu alegeti cu usurinta superclasa unui obiect pentru ca astfel puteti încarca obiectele cu o functionalitate inutila, existenta în superclasa. Daca nu exista o clasa care sa va ofere doar functionalitatea de care aveti nevoie, este preferabil sa derivati noua clasa direct din clasa Object si sa apelati indirect functionalitatea pe care o doriti.
Pasul 4: Stabilirea interfetelor pe care le respecta clasa
Stabilirea acestor interfete are dublu scop. În primul rînd ele instruiesc compilatorul sa verifice daca noua clasa respecta cu adevarat toate interfetele pe care le-a declarat, cu alte cuvinte defineste toate metodele declarate în aceste interfete. A doua finalitate este aceea de a permite compilatorului sa foloseasca instantele noii clase oriunde aplicatia declara ca este nevoie de un obiect care implementeaza interfetele declarate.
O clasa poate sa implementeze mai multe interfete sau niciuna.
Pasul 5: Stabilirea modificatorilor clasei
În unele cazuri trebuie sa oferim compilatorului informatii suplimentare relative la modul în care vom folosi clasa nou creata pentru ca acesta sa poata executa verificari suplimentare asupra descrierii clasei. În acest scop, putem defini o clasa ca fiind abstracta, finala sau publica folosindu-ne de o serie de cuvinte rezervate numite modificatori. Modificatorii pentru tipurile de clase de mai sus sunt respectiv: abstract, final si public.
În cazul în care declaram o clasa de obiecte ca fiind abstracta, compilatorul va interzice instantierea acestei clase. Daca o clasa este declarata finala, compilatorul va avea grija sa nu putem deriva noi subclase din aceasta clasa. În cazul în care declaram în acelasi timp o clasa de obiecte ca fiind abstracta si finala, eroarea va fi semnalata înca din timpul compilarii pentru ca cei doi modificatori se exclud.
Pentru ca o clasa sa poata fi folosita si în exteriorul contextului în care a fost declarata ea trebuie sa fie declarata publica. Orice clasa de obiecte care va fi instantiata ca o aplicatie trebuie declarata publica.
Pasul 6: Scrierea corpului declaratiei
În sfîrsit, dupa ce toti ceilalti pasi au fost efectuati, putem trece la scrierea corpului declaratiei de clasa. În principal, aici vom descrie variabilele clasei împreuna cu metodele care lucreaza cu acestea. Tot aici putem preciza si gradele de protejare pentru fiecare dintre elementele declaratiei. Uneori numim variabilele si metodele unei clase la un loc ca fiind cîmpurile clasei.
Forma generala a unei declaratii de clasa
Sintaxa exacta de declarare a unei clase arata în felul urmator:
{ abstract | final | public } class NumeClasa
[ extends NumeSuperclasa ]
[ implements NumeInterfata [ , NumeInterfata ] ]
{ [ CîmpClasa ] }
Variabilele unei clase
În interiorul claselor se pot declara variabile. Aceste variabile sunt specifice clasei respective. Fiecare dintre ele trebuie sa aiba un tip, un nume si poate avea initializatori. În afara de aceste elemente, pe care le-am prezentat deja în sectiunea în care am prezentat variabilele, variabilele definite în interiorul unei clase pot avea definiti o serie de modificatori care altereaza comportarea variabilei în interiorul clasei, si o specificatie de protectie care defineste cine are dreptul sa acceseze variabila respectiva.
Modificatori
Modificatorii sunt cuvinte rezervate Java care precizeaza sensul unei declaratii. Iata lista acestora:
- static
- final
- transient
- volatile
Dintre acestia, transient nu este utilizat în versiunea curenta a limbajului Java. Pe viitor va fi folosit pentru a specifica variabile care nu contin informatii care trebuie sa ramîna persistente la terminarea programului.
Modificatorul volatile specifica faptul ca variabila respectiva poate fi modificata asincron cu rularea aplicatiei. În aceste cazuri, compilatorul trebuie sa-si ia masuri suplimentare în cazul generarii si optimizarii codului care se adreseaza acestei variabile.
Modificatorul final este folosit pentru a specifica o variabila a carei valoare nu poate fi modificata. Variabila respectiva trebuie sa primeasca o valoare de initializare chiar în momentul declaratiei. Altfel, ea nu va mai putea fi initializata în viitor. Orice încercare ulterioara de a seta valori la aceasta variabila va fi semnalata ca eroare de compilare.
Modificatorul static este folosit pentru a specifica faptul ca variabila are o singura valoare comuna tuturor instantelor clasei în care este declarata. Modificarea valorii acestei variabile din interiorul unui obiect face ca modificarea sa fie vizibila din celelalte obiecte. Variabilele statice sunt initializate la încarcarea codului specific unei clase si exista chiar si daca nu exista nici o instanta a clasei respective. Din aceasta cauza, ele pot fi folosite de metodele statice.
Protectie
În Java exista patru grade de protectie pentru o variabila apartinînd unei clase:
- privata
- protejata
- publica
- prietenoasa
O variabila publica este accesibila oriunde este accesibil numele clasei. Cuvîntul rezervat este public.
O variabila protejata este accesibila în orice clasa din pachetul careia îi apartine clasa în care este declarata. În acelasi timp, variabila este accesibila în toate subclasele clasei date, chiar daca ele apartin altor pachete. Cuvîntul rezervat este protected.
O variabila privata este accesibila doar în interiorul clasei în care a fost declarata. Cuvîntul rezervat este private.
O variabila care nu are nici o declaratie relativa la gradul de protectie este automat o variabila prietenoasa. O variabila prietenoasa este accesibila în pachetul din care face parte clasa în interiorul careia a fost declarata, la fel ca si o variabila protejata. Dar, spre deosebire de variabilele protejate, o variabila prietenoasa nu este accesibila în subclasele clasei date daca aceste sunt declarate ca apartinînd unui alt pachet. Nu exista un cuvînt rezervat pentru specificarea explicita a variabilelor prietenoase.
O variabila nu poate avea declarate mai multe grade de protectie în acelasi timp. O astfel de declaratie este semnalata ca eroare de compilare.
Accesarea unei variabile
Accesarea unei variabile declarate în interiorul unei clasei se face folosindu-ne de o expresie de forma:
ReferintaInstanta .NumeVariabila
Referinta catre o instanta trebuie sa fie referinta catre clasa care contine variabila. Referinta poate fi valoarea unei expresii mai complicate, ca de exemplu un element dintr-un tablou de referinte.
În cazul în care avem o variabila statica, aceasta poate fi accesata si fara sa detinem o referinta catre o instanta a clasei. Sintaxa este, în acest caz:
NumeClasa .NumeVariabila
Vizibilitate
O variabila poate fi ascunsa de declaratia unei alte variabile cu acelasi nume. De exemplu, daca într-o clasa avem declarata o variabila cu numele unu si într-o subclasa a acesteia avem declarata o variabila cu acelasi nume, atunci variabila din superclasa este ascunsa de cea din clasa. Totusi, variabila din superclasa exista înca si poate fi accesata în mod explicit. Expresia de referire este, în acest caz:
NumeSuperClasa .NumeVariabila
sau
super .NumeVariabila
în cazul în care superclasa este imediata.
La fel, o variabila a unei clase poate fi ascunsa de o declaratie de variabila dintr-un bloc de instructiuni. Orice referinta la ea va trebui facuta în mod explicit. Expresia de referire este, în acest caz:
this .NumeVariabila
Variabile predefinite: this si super
În interiorul fiecarei metode non-statice dintr-o clasa exista predefinite doua variabile cu semnificatie speciala. Cele doua variabile sunt de tip referinta si au aceeasi valoare si anume o referinta catre obiectul curent. Diferenta dintre ele este tipul.
Prima dintre acestea este variabila this care are tipul referinta catre clasa în interiorul careia apare metoda. A doua este variabilasuper al carei tip este o referinta catre superclasa imediata a clasei în care apare metoda. În interiorul obiectelor din clasaObject nu se poate folosi referinta super pentru ca nu exista nici o superclasa a clasei de obiecte Object.
În cazul în care super este folosita la apelul unui constructor sau al unei metode, ea actioneaza ca un cast catre superclasa imediata.
Metodele unei clase
Fiecare clasa îsi poate defini propriile sale metode pe lînga metodele pe care le mosteneste de la superclasa sa. Aceste metode definesc operatiile care pot fi executate cu obiectul respectiv. În cazul în care una dintre metodele mostenite nu are o implementare corespunzatoare în superclasa, clasa îsi poate redefini metoda dupa cum doreste.
În plus, o clasa îsi poate defini metode de constructie a obiectelor si metode de eliberare a acestora. Metodele de constructie sunt apelate ori de cîte ori este alocat un nou obiect din clasa respectiva. Putem declara mai multe metode de constructie, ele diferind prin parametrii din care trebuie construit obiectul.
Metodele de eliberare a obiectului sunt cele care elibereaza resursele ocupate de obiect în momentul în care acesta este distrus de catre mecanismul automat de colectare de gunoaie. Fiecare clasa are o singura metoda de eliberare, numita si finalizator. Apelarea acestei metode se face de catre sistem si nu exista nici o cale de control al momentului în care se produce acest apel.
Declararea metodelor
Pentru a declara o metoda, este necesar sa declaram numele acesteia, tipul de valoare pe care o întoarce, parametrii metodei precum si un bloc în care sa descriem instructiunile care trebuiesc executate atunci cînd metoda este apelata. În plus, orice metoda are un numar de modificatori care descriu proprietatile metodei si modul de lucru al acesteia.
Declararea precum si implementarea metodelor se face întotdeauna în interiorul declaratiei de clasa. Nu exista nici o cale prin care sa putem scrie o parte dintre metodele unei clase într-un fisier separat care sa faca referinta apoi la declaratia clasei.
În forma generala, declaratia unei metode arata în felul urmator:
[Modificator] TipRezultat Declaratie [ClauzeThrows] CorpulMetodei
Modificatorii precum si clauzele throws pot sa lipseasca.
Numele si parametrii metodelor
Recunoasterea unei anumite metode se face dupa numele si tipul parametrilor sai. Pot exista metode cu acelasi nume dar avînd parametri diferiti. Acest fenomen poarta numele de supraîncarcarea numelui unei metode.
Numele metodei este un identificator Java. Avem toata libertatea în a alege numele pe care îl dorim pentru metodele noastre, dar în general este preferabil sa alegem nume care sugereaza utilizarea metodei.
Numele unei metode începe de obicei cu litera mica. Daca acesta este format din mai multe cuvinte, litera de început a fiecarui cuvînt va fi majuscula. În acest mod numele unei metode este foarte usor de citit si de depistat în sursa.
Parametrii metodei sunt în realitate niste variabile care sunt initializate în momentul apelului cu valori care controleaza modul ulterior de executie. Aceste variabile exista pe toata perioada executiei metodei. Se pot scrie metode care sa nu aiba nici un parametru.
Fiind o variabila, fiecare parametru are un tip si un nume. Numele trebuie sa fie un identificator Java. Desi avem libertatea sa alegem orice nume dorim, din nou este preferabil sa alegem nume care sa sugereze scopul la care va fi utilizat parametrul respectiv.
Tipul unui parametru este oricare dintre tipurile valide în Java. Acestea poate fi fie un tip primitiv, fie un tip referinta catre obiect, interfata sau tablou.
În momentul apelului unei metode, compilatorul încearca sa gaseasca o metoda în interiorul clasei care sa aiba acelasi nume cu cel apelat si acelasi numar de parametri ca si apelul. Mai mult, tipurile parametrilor de apel trebuie sa corespunda cu tipurile parametrilor declarati ai metodei gasite sau sa poata fi convertiti la acestia.
Daca o astfel de metoda este gasita în declaratia clasei sau în superclasele acesteia, parametrii de apel sunt convertiti catre tipurile declarate si se genereaza apelul catre metoda respectiva.
Este o eroare de compilare sa declaram doua metode cu acelasi nume, acelasi numar de parametri si acelasi tip pentru parametrii corespunzatori. Într-o asemenea situatie, compilatorul n-ar mai sti care metoda trebuie apelata la un moment dat.
De asemenea, este o eroare de compilare sa existe doua metode care se potrivesc la acelasi apel. Acest lucru se întîmpla cînd nici una dintre metodele existente nu se potriveste exact si cînd exista doua metode cu acelasi nume si acelasi numar de parametri si, în plus, parametrii de apel se pot converti catre parametrii declarati ai ambelor metode.
Rezolvarea unei astfel de probleme se face prin conversia explicita (cast) de catre programator a valorilor de apel spre tipurile exacte ale parametrilor metodei pe care dorim sa o apelam în realitate.
În fine, forma generala de declaratie a numelui si parametrilor unei metode este:
NumeMetoda ( [TipParametru NumeParametru] [,TipParametru NumeParametru] )
Modificatori de metode
Modificatorii sunt cuvinte cheie ale limbajului Java care specifica proprietati suplimentare pentru o metoda. Iata lista completa a acestora în cazul metodelor:
-
static - pentru metodele statice
-
abstract - pentru metodele abstracte
-
final - pentru metodele finale
-
native - pentru metodele native
-
synchronized - pentru metodele sincronizate
Metode statice
În mod normal, o metoda a unei clase se poate apela numai printr-o instanta a clasei respective sau printr-o instanta a unei subclase. Acest lucru se datoreaza faptului ca metoda face apel la o serie de variabile ale clasei care sunt memorate în interiorul instantei si care au valori diferite în instante diferite. Astfel de metode se numesc metode ale instantelor clasei.
Dupa cum stim deja, exista si un alt tip de variabile, si anume variabilele de clasa sau variabilele statice care sunt comune tuturor instantelor clasei respective si exista pe toata perioada de timp în care clasa este încarcata în memorie. Aceste variabile pot fi accesate fara sa avem nevoie de o instanta a clasei respective.
În mod similar exista si metode statice. Aceste metode nu au nevoie de o instanta a clasei sau a unei subclase pentru a putea fi apelate pentru ca ele nu au voie sa foloseasca variabile care sunt memorate în interiorul instantelor. În schimb, aceste metode pot sa foloseasca variabilele statice declarate în interiorul clasei.
Orice metoda statica este implicit si finala.
Metode abstracte
Metodele abstracte sunt metode care nu au corp de implementare. Ele sunt declarate numai pentru a forta subclasele care vor sa aiba instante sa implementeze metodele respective.
Metodele abstracte trebuie declarate numai în interiorul claselor care au fost declarate abstracte. Altfel compilatorul va semnala o eroare de compilare. Orice subclasa a claselor abstracte care nu este declarata abstracta trebuie sa ofere o implementare a acestor metode, altfel va fi generata o eroare de compilare.
Prin acest mecanism ne asiguram ca toate instantele care pot fi convertite catre clasa care contine definitia unei metode abstracte au implementata metoda respectiva dar, în acelasi timp, nu este nevoie sa implementam în nici un fel metoda chiar în clasa care o declara pentru ca nu stim pe moment cum va fi implementata.
O metoda statica nu poate fi declarata si abstracta pentru ca o metoda statica este implicit finala si nu poate fi rescrisa.
Metode finale
O metoda finala este o metoda care nu poate fi rescrisa în subclasele clasei în care a fost declarata. O metoda este rescrisa într-o subclasa daca aceasta implementeaza o metoda cu acelasi nume si acelasi numar si tip de parametri ca si metoda din superclasa.
Declararea metodelor finale este utila în primul rînd compilatorului care poate genera metodele respective direct în codul rezultat fiind sigur ca metoda nu va avea nici o alta implementare în subclase.
Metode native
Metodele native sunt metode care sunt implementate pe o cale specifica unei anumite platforme. De obicei aceste metode sunt implementate în C sau în limbaj de asamblare. Metoda propriu-zisa nu poate avea corp de implementare pentru ca implementarea nu este facuta în Java.
În rest, metodele native sunt exact ca orice alta metoda Java. Ele pot fi mostenite, pot fi statice sau nu, pot fi finale sau nu, pot sa rescrie o metoda din superclasa si pot fi la rîndul lor rescrise în subclase.
Metode sincronizate
O metoda sincronizata este o metoda care contine cod critic pentru un anumit obiect sau clasa si nu poate fi rulata în paralel cu nici o alta metoda critica sau cu o instructiune synchronized referitoare la acelasi obiect sau clasa.
Înainte de executia metodei, obiectul sau clasa respectiva sunt blocate. La terminarea metodei, acestea sunt deblocate.
Daca metoda este statica atunci este blocata o întreaga clasa, clasa din care face parte metoda. Altfel, este blocata doar instanta în contextul careia este apelata metoda.
Protejarea metodelor
Accesul la metodele unei clase este protejat în acelasi fel ca si accesul la variabilele clasei. În Java exista patru grade de protectie pentru o metoda apartinînd unei clase:
- privata
- protejata
- publica
- prietenoasa
O metoda declarata publica este accesibila oriunde este accesibil numele clasei. Cuvîntul rezervat este public.
O metoda declarata protejata este accesibila în orice clasa din pachetul careia îi apartine clasa în care este declarata. În acelasi timp, metoda este accesibila în toate subclasele clasei date, chiar daca ele apartin altor pachete. Cuvîntul rezervat este protected.
O metoda declarata privata este accesibila doar în interiorul clasei în care a fost declarata. Cuvîntul rezervat este private.
O metoda care nu are nici o declaratie relativa la gradul de protectie este automat o metoda prietenoasa. O metoda prietenoasa este accesibila în pachetul din care face parte clasa în interiorul careia a fost declarata la fel ca si o metoda protejata. Dar, spre deosebire de metodele protejate, o metoda prietenoasa nu este accesibila în subclasele clasei date daca aceste sunt declarate ca apartinînd unui alt pachet. Nu exista un cuvînt rezervat pentru specificarea explicita a metodelor prietenoase.
O metoda nu poate avea declarate mai multe grade de protectie în acelasi timp. O astfel de declaratie este semnalata ca eroare de compilare.
Apelul metodelor
Pentru a apela o metoda a unei clase este necesar sa dispunem de o cale de acces la metoda respectiva. În plus, trebuie sa dispunem de drepturile necesare apelului metodei.
Sintaxa efectiva de acces este urmatoarea:
CaleDeAcces .Metoda( Parametri )
În cazul în care metoda este statica, pentru a specifica o cale de acces este suficient sa furnizam numele clasei în care a fost declarata metoda. Accesul la numele clasei se poate obtine fie importînd clasa sau întreg pachetul din care face parte clasa fie specificînd în clar numele clasei si drumul de acces catre aceasta.
De exemplu, pentru a accesa metoda random definita static în clasa Math apartinînd pachetului java.lang putem scrie:
double aleator = Math.random();
sau, alternativ:
double aleator = java.lang.Math.random();
În cazul claselor definite în pachetul java.lang nu este necesar nici un import pentru ca acestea sunt implicit importate de catre compilator.
Cea de-a doua cale de acces este existenta unei instante a clasei respective. Prin aceasta instanta putem accesa metodele care nu sunt declarate statice, numite uneori si metode ale instantelor clasei. Aceste metode au nevoie de o instanta a clasei pentru a putea lucra, pentru ca folosesc variabile non-statice ale clasei sau apeleaza alte metode non-statice. Metodele primesc acest obiect ca pe un parametru ascuns.
De exemplu, avînd o instanta a clasei Object sau a unei subclase a acesteia, putem obtine o reprezentare sub forma de sir de caractere prin:
Object obiect = new Object();
String sir = obiect.toString();
În cazul în care apelam o metoda a clasei din care face parte si metoda apelanta putem sa renuntam la calea de acces în cazul metodelor statice, scriind doar numele metodei si parametrii. Pentru metodele specifice instantelor, putem renunta la calea de acces, dar în acest caz metoda acceseaza aceeasi instanta ca si metoda apelanta. În cazul în care metoda apelanta este statica, specificarea unei instante este obligatorie în cazul metodelor de instanta.
Parametrii de apel servesc împreuna cu numele la identificarea metodei pe care dorim sa o apelam. Înainte de a fi transmisi, acestia sunt convertiti catre tipurile declarate de parametri ai metodei, dupa cum este descris mai sus.
Specificarea parametrilor de apel se face separîndu-i prin virgula. Dupa ultimul parametru nu se mai pune virgula. Daca metoda nu are nici un parametru, parantezele rotunde sunt în continuare necesare. Exemple de apel de metode cu parametri:
String numar = String.valueOf( 12 );
// 12 -> String
double valoare = Math.abs( 12.54 );
// valoare absoluta
String prima = numar.substring( 0, 1 );
// prima litera
Valoarea de retur a unei metode
O metoda trebuie sa-si declare tipul valorii pe care o întoarce. În cazul în care metoda doreste sa specifice explicit ca nu întoarce nici o valoare, ea trebuie sa declare ca tip de retur tipul void ca în exemplul:
void a() { }
În caz general, o metoda întoarce o valoare primitiva sau un tip referinta. Putem declara acest tip ca în:
long abs( int valoare ) { }
Pentru a returna o valoare ca rezultat al executiei unei metode, trebuie sa folosim instructiunea return, asa cum s-a aratat în sectiunea dedicata instructiunilor. Instructiunea return trebuie sa contina o expresie a carei valoare sa poata fi convertita la tipul declarat al valorii de retur a metodei.
De exemplu:
long abs( int valoare ) {
return Math.abs( valoare );
}
Metoda statica abs din clasa Math care primeste un parametru întreg returneaza tot un întreg. În exemplul nostru, instructiunea return este corecta pentru ca exista o cale de conversie de la întreg la întreg lung, conversie care este apelata automat de compilator înainte de iesirea din metoda.
În schimb, în exemplul urmator:
int abs( long valoare ) {
return Math.abs( valoare );
}
compilatorul va genera o eroare de compilare pentru ca metoda statica abs din clasa Math care primeste ca parametru un întreg lung întoarce tot un întreg lung, iar un întreg lung nu poate fi convertit sigur la un întreg normal pentru ca exista riscul deteriorarii valorii, la fel ca la atribuire.
Rezolvarea trebuie sa contina un cast explicit:
int abs( long valoare ) {
return ( int )Math.abs( valoare );
}
În cazul în care o metoda este declarata void, putem sa ne întoarcem din ea folosind instructiunea return fara nici o expresie. De exemplu:
void metoda() {
if( )
return;
}
Specificarea unei expresii în acest caz duce la o eroare de compilare. La fel si în cazul în care folosim instructiunea return fara nici o expresie în interiorul unei metode care nu este declarata void.
Vizibilitate
O metoda este vizibila daca este declarata în clasa prin care este apelata sau într-una din superclasele acesteia. De exemplu, daca avem urmatoarea declaratie:
Apelul metodei a în interiorul metodei b din clasa B este permis pentru ca metoda a este declarata în interiorul clasei A care este superclasa pentru clasa B. Apelul metodei c în aceeasi metoda b este permis pentru ca metoda c este declarata în aceeasi clasa ca si metoda b.
Uneori, o subclasa rescrie o metoda dintr-o superclasa a sa. În acest caz, apelul metodei respective în interiorul subclasei duce automat la apelul metodei din subclasa. Daca totusi dorim sa apelam metoda asa cum a fost ea definita în superclasa, putem prefixa apelul cu numele superclasei. De exemplu:
class A{ void a(){} } class B extends A{ void b(){ a(); c(); } void c(){ .. } }
Initializatori statici
La încarcarea unei clase sunt automat initializate toate variabilele statice declarate în interiorul clasei. În plus, sunt apelati toti initializatorii statici ai clasei.
Un initializator static are urmatoarea sintaxa:
static BlocDeInstructiuni
Blocul de instructiuni este executat automat la încarcarea clasei. De exemplu, putem defini un initializator static în felul urmator:
class A { static double a; static int b; static { a = Math.random(); // numar dublu între 0.0 si 1.0 b = ( int )( a * 500 ); // numar întreg între 0 si 500 } }
Declaratiile de variabile statice si initializatorii statici sunt executate în ordinea în care apar în clasa. De exemplu, daca avem urmatoarea declaratie de clasa:
class A { static int i = 11; static { i += 100; i %= 55; } static int j = i + 1; }
valoarea finala a lui i va fi 1 ( ( 11 + 100 ) % 55 ) iar valoarea lui j va fi 2.
Constructori si finalizatori constructori
La crearea unei noi instante a unei clase sistemul aloca automat memoria necesara instantei si o initializeaza cu valorile initiale specificate sau implicite. Daca dorim sa facem initializari suplimentare în interiorul acestei memorii sau în alta parte putem descrie metode speciale numite constructori ai clasei.
Putem avea mai multi constructori pentru aceeasi clasa, acestia diferind doar prin parametrii pe care îi primesc. Numele tuturor constructorilor este acelasi si este identic cu numele clasei.
Declaratia unui constructor este asemanatoare cu declaratia unei metode oarecare, cu diferenta ca nu putem specifica o valoare de retur si nu putem specifica nici un fel de modificatori. Daca dorim sa returnam dintr-un constructor, trebuie sa folosim instructiunea return fara nici o expresie. Putem însa sa specificam gradul de protectie al unui constructor ca fiind public, privat, protejat sau prietenos.
Constructorii pot avea clauze throws.
Daca o clasa nu are constructori, compilatorul va crea automat un constructor implicit care nu ia nici un parametru si care initializeaza toate variabilele clasei si apeleaza constructorul superclasei fara argumente prin super( ). Daca superclasa nu are un constructor care ia zero argumente, se va genera o eroare de compilare.
Daca o clasa are cel putin un constructor, constructorul implicit nu mai este creat de catre compilator.
Cînd construim corpul unui constructor avem posibilitatea de a apela, pe prima linie a blocului de instructiuni care reprezinta corpul constructorului, un constructor explicit. Constructorul explicit poate avea doua forme:
this ( [Parametri] );
super ( [Parametri] );
Cu aceasta sintaxa apelam unul dintre constructorii superclasei sau unul dintre ceilalti constructori din aceeasi clasa. Aceste linii nu pot aparea decît pe prima pozitie în corpul constructorului. Daca nu apar acolo, compilatorul considera implicit ca prima instructiune din corpul constructorului este:
super ();
si în acest caz se va genera o eroare de compilare daca nu exista un constructor în superclasa care sa lucreze fara nici un parametru.
Dupa apelul explicit al unui constructor din superclasa cu sintaxa super( ) este executata în mod implicit initializarea tuturor variabilelor de instanta (non-statice) care au initializatori expliciti. Dupa apelul unui constructor din aceeasi clasa cu sintaxa this( ) nu exista nici o alta actiune implicita, deci nu vor fi initializate nici un fel de variabile. Aceasta datorita faptului ca initializarea s-a produs deja în constructorul apelat.
Exemplu:
class A extends B { String valoare; A( String val ) { // aici exista apel implicit // al lui super(), adica B() valoare = val; } A( int val ) { this( String.valueOf( val ) );// alt constructor } }
Finalizatori
În Java nu este nevoie sa apelam în mod explicit distrugerea unei instante atunci cînd nu mai este nevoie de ea. Sistemul ofera un mecanism de colectare a gunoaielor care recunoaste situatia în care o instanta de obiect sau un tablou nu mai sunt referite de nimeni si le distruge în mod automat.
Acest mecanism de colectare a gunoaielor ruleaza pe un fir de executie separat, de prioritate mica. Nu avem nici o posibilitate sa aflam exact care este momentul în care va fi distrusa o instanta. Totusi, putem specifica o functie care sa fie apelata automat în momentul în care colectorul de gunoaie încearca sa distruga obiectul.
Aceasta functie are nume, numar de parametri si tip de valoare de retur fixe:
void finalize ()
Dupa apelul metodei de finalizare (numita si finalizator), instanta nu este înca distrusa pîna la o noua verificare din partea colectorului de gunoaie. Aceasta comportare este necesara pentru ca instanta poate fi revitalizata prin crearea unei referinte catre ea în interiorul finalizatorului.
Totusi, finalizatorul nu este apelat decît o singura data. Daca obiectul revitalizat redevine candidat la colectorul de gunoaie, acesta este distrus fara a i se mai apela finalizatorul. Cu alte cuvinte, un obiect nu poate fi revitalizat decît o singura data.
Daca în timpul finalizarii apare o exceptie, ea este ignorata si finalizatorul nu va mai fi apelat din nou.
Crearea instantelor
O instanta este creata folosind o expresie de alocare care foloseste cuvîntul rezervat new. Iata care sunt pasii care sunt executati la apelul acestei expresii:
- Se creeaza o noua instanta de tipul specificat. Toate variabilele instantei sunt initializate pe valorile lor implicite.
- Se apeleaza constructorul corespunzator în functie de parametrii care sunt transmisi în expresia de alocare. Daca instanta este creata prin apelul metodei newInstance, se apeleaza constructorul care nu ia nici un argument.
- Dupa creare, expresia de alocare returneaza o referinta catre instanta nou creata.
Exemple de creare:
A o1 = new A(); B o2 = new B(); class C extends B { String valoare; C( String val ) { // aici exista apel implicit // al lui super(), adica B() valoare = val; } C( int val ) { this( String.valueOf( val ) ); } } C o3 = new C( "Vasile" ); C o4 = new C( 13 );
O alta cale de creare a unui obiect este apelul metodei newInstance declarate în clasa Class. Iata pasii de creare în acest caz:
- Se creeaza o noua instanta de acelasi tip cu tipul clasei pentru care a fost apelata metoda newInstance. Toate variabilele instantei sunt initializate pe valorile lor implicite.
- Este apelat constructorul obiectului care nu ia nici un argument.
- Dupa creare referinta catre obiectul nou creat este returnata ca valoare a metodei newInstance. Tipul acestei referinte va fi Object în timpul compilarii si tipul clasei reale în timpul executiei.
Derivarea claselor
O clasa poate fi derivata dintr-alta prin folosirea în declaratia clasei derivate a clauzei extends. Clasa din care se deriva noua clasa se numeste superclasa imediata a clasei derivate. Toate clasele care sunt superclase ale superclasei imediate ale unei clase sunt superclase si pentru clasa data. Clasa nou derivata se numeste subclasa a clasei din care este derivata.
Sintaxa generala este:
class SubClasa extends SuperClasa
O clasa poate fi derivata dintr-o singura alta clasa, cu alte cuvinte o clasa poate avea o singura superclasa imediata.
Clasa derivata mosteneste toate variabilele si metodele superclasei sale. Totusi, ea nu poate accesa decît acele variabile si metode care nu sunt declarate private.
Putem rescrie o metoda a superclasei declarînd o metoda în noua clasa avînd acelasi nume si aceiasi parametri. La fel, putem declara o variabila care are acelasi nume cu o variabila din superclasa. În acest caz, noul nume ascunde vechea variabila, substituindu-i-se. Putem în continuare sa ne referim la variabila ascunsa din superclasa specificînd numele superclasei sau folosindu-ne de variabila super.
Exemplu:
class A { int a = 1; void unu() { System.out.println( a ); } } class B extends A { double a = Math.PI; void unu() { System.out.println( a ); } void doi() { System.out.println( A.a ); } void trei() { unu(); super.unu(); } }
Daca apelam metoda unu din clasa A, aceasta va afisa la consola numarul 1.
Daca apelam metoda unu din clasa B, aceasta va afisa la consola numarul PI. Apelul îl putem face de exemplu cu instructiunea:
B obiect = new B();
obiect.unu();
Observati ca în metoda unu din clasa B, variabila referita este variabila a din clasa B. Variabila a din clasa A este ascunsa. Putem însa sa o referim prin sintaxa A.a ca în metoda doi din clasa B.
În interiorul clasei B, apelul metodei unu fara nici o alta specificatie duce automat la apelul metodei unu definite în interiorul clasei B. Metoda unu din clasa B rescrie metoda unu din clasa A. Vechea metoda este accesibila pentru a o referi în mod explicit ca în metoda trei din clasa B. Apelul acestei metode va afisa mai întîi numarul PI si apoi numarul 1.
Daca avem declarata o variabila de tip referinta catre o instanta a clasei A, aceasta variabila poate sa contina în timpul executiei si o referinta catre o instanta a clasei B. Invers, afirmatia nu este valabila.
În clipa în care apelam metoda unu pentru o variabila referinta catre clasa A, sistemul va apela metoda unu a clasei A sau B în functie de adevaratul tip al referintei din timpul executiei. Cu alte cuvinte, urmatoarea secventa de instructiuni:
A tablou[] = new A[2];
tablou[0] = new A();
tablou[1] = new B();
for( int i = 0; i < 2; i++ ) {
tablou[i].unu();
}
va afisa doua numere diferite, mai întîi 1 si apoi PI. Aceasta din cauza ca cel de-al doilea element din tablou este, în timpul executiei, de tip referinta la o instanta a clasei B chiar daca la compilare este de tipul referinta la o instanta a clasei A.
Acest mecanism se numeste legare tîrzie, si înseamna ca metoda care va fi efectiv apelata este stabilita doar în timpul executiei si nu la compilare.
Daca nu declaram nici o superclasa în definitia unei clase, atunci se considera automat ca noua clasa deriva direct din clasa Object, mostenind toate metodele si variabilele acesteia.
Interfete
O interfata este în esenta o declaratie de tip ce consta dintr-un set de metode si constante pentru care nu s-a specificat nici o implementare. Programele Java folosesc interfetele pentru a suplini lipsa mostenirii multiple, adica a claselor de obiecte care deriva din doua sau mai multe alte clase.
Sintaxa de declaratie a unei interfete este urmatoarea:
Modificatori interface NumeInterf [ extends [Interfata][, Interfata]]
Corp
Modificatorii unei interfete pot fi doar cuvintele rezervate public si abstract. O interfata care este publica poate fi accesata si de catre alte pachete decît cel care o defineste. În plus, fiecare interfata este în mod implicit abstracta. Modificatorul abstract este permis dar nu obligatoriu.
Numele interfetelor trebuie sa fie identificatori Java. Conventiile de numire a interfetelor le urmeaza în general pe cele de numire a claselor de obiecte.
Interfetele, la fel ca si clasele de obiecte, pot avea subinterfete. Subinterfetele mostenesc toate constantele si declaratiile de metode ale interfetei din care deriva si pot defini în plus noi elemente. Pentru a defini o subinterfata, folosim o clauza extends. Aceste clauze specifica superinterfata unei interfete. O interfata poate avea mai multe superinterfete care se declara separate prin virgula dupa cuvîntul rezervat extends. Circularitatea definitiei subinterfetelor nu este permisa.
În cazul interfetelor nu exista o radacina comuna a arborelui de derivare asa cum exista pentru arborele de clase, clasa Object.
În corpul unei declaratii de interfata pot sa apara declaratii de variabile si declaratii de metode. Variabilele sunt implicit statice si finale. Din cauza faptului ca variabilele sunt finale, este obligatoriu sa fie specificata o valoare initiala pentru aceste variabile. În plus, aceasta valoare initiala trebuie sa fie constanta (sa nu depinda de alte variabile).
Daca interfata este declarata publica, toate variabilele din corpul sau sunt implicit declarate publice.
În ceea ce priveste metodele declarate în interiorul corpului unei interfete, acestea sunt implicit declarate abstracte. În plus, daca interfata este declarata publica, metodele din interior sunt implicit declarate publice.
Iata un exemplu de declaratii de interfete:
public interface ObiectSpatial { final int CUB = 0; final int SFERA = 1; double greutate(); double volum(); double raza(); int tip(); } public interface ObiectSpatioTemporal extends ObiectSpatial { void centrulDeGreutate( long moment, double coordonate[] ); long momentInitial(); long momentFinal(); }
Cele doua interfete definesc comportamentul unui obiect spatial respectiv al unui obiect spatio-temporal. Un obiect spatial are o greutate, un volum si o raza a sferei minime în care se poate înscrie. În plus, putem defini tipul unui obiect folosindu-ne de o serie de valori constante predefinite precum ar fi SFERA sau CUB.
Un obiect spatio-temporal este un obiect spatial care are în plus o pozitie pe axa timpului. Pentru un astfel de obiect, în afara de proprietatile deja descrise pentru obiectele spatiale, trebuie sa avem în plus un moment initial, de aparitie, pe axa timpului si un moment final. Obiectul nostru nu exista în afara acestui interval de timp. În plus, pentru un astfel de obiect putem afla pozitia centrului sau de greutate în fiecare moment aflat în intervalul de existenta.
Pentru a putea lucra cu obiecte spatiale si spatio-temporale este nevoie sa definim diverse clase care sa implementeze aceste interfete. Acest lucru se face specificînd clauza implements în declaratia de clasa. O clasa poate implementa mai multe interfete. Daca o clasa declara ca implementeaza o anumita interfata, ea este obligatoriu sa implementeze toate metodele declarate în interfata respectiva.
De exemplu, putem spune ca o minge este un obiect spatial de tip sfera. În plus, mingea are o pozitie în functie de timp si un interval de existenta. Cu alte cuvinte, mingea este chiar un obiect spatio-temporal. Desigur, în afara de proprietatile spatio-temporale mingea mai are si alte proprietati precum culoarea, proprietarul sau pretul de cumparare.
Iata cum ar putea arata definitia clasei de obiecte de tip minge:
import java.awt.Color; class Minge extends Jucarie implements ObiectSpatioTemporal { int culoare = Color.red; double pret = 10000.0; double raza = 0.25; long nastere; long moarte; // metodele din ObiectSpatial double greutate() { return raza * 0.5; } double raza() { return raza; } double volum() { return ( 4.0 / 3.0 ) * Math.PI * raza * raza * raza; } int tip() { return SFERA; } // metodele din interfata ObiectSpatioTemporal boolean centrulDeGreutate( long moment, double coordonate[] ) { if( moment < nastere || moment > moarte ) { return false; } … coordonate[0] = x; coordonate[1] = y; coordonate[2] = z; return true; } long momentInitial() { return nastere; } long momentFinal() { return moarte; } int ceCuloare() { return culoare; } double cePret() { return pret; } }
Observati ca noua clasa Minge implementeaza toate metodele definite în interfata ObiectSpatioTemporal si, pentru ca aceasta extinde interfata ObiectSpatial, si metodele definite în cea din urma. În plus, clasa îsi defineste propriile metode si variabile.
Sa presupunem în continuare ca avem si o alta clasa, Rezervor, care este tot un obiect spatio-temporal, dar de forma cubica. Declaratia acestei clase ar arata ca:
class Rezervor extends Constructii implements ObiectSpatioTemporal { }
Desigur, toate metodele din interfetele de mai sus trebuiesc implementate, plus alte metode specifice.
Sa mai observam ca cele doua obiecte deriva din clase diferite: Mingea din Jucarii iar Rezervorul din Constructii. Daca am putea deriva o clasa din doua alte clase, am putea deriva Minge din Jucarie si ObiectSpatioTemporal iar Rezervor din Constructie si ObiectSpatioTemporal. Într-o astfel de situatie, nu ar mai fi necesar ca ObiectSpatioTemporal sa fie o interfata, ci ar fi suficient ca acesta sa fie o alta clasa.
Din pacate, în Java, o clasa nu poate deriva decît dintr-o singura alta clasa, asa ca este obligatoriu în astfel de situatii sa folosim interfetele. Daca ObiectSpatioTemporal ar fi putut fi o clasa, am fi avut avantajul ca puteam implementa acolo metodele cu functionare identica din cele doua clase discutate, acestea fiind automat mostenite fara a mai fi nevoie de definirea lor de doua ori în fiecare clasa în parte.
Putem crea în continuare metode care sa lucreze cu obiecte spatio-temporale, de exemplu o metoda care sa afle distanta unui corp spatio-temporal fata de un punct dat la momentul sau initial. O astfel de metoda se poate scrie o singura data, si poate lucra cu toate clasele care implementeaza interfata noastra. De exemplu:
double distanta( double punct[], ObiectSpatioTemporal obiect ) { double coordonate[] = new double[3]; obiect.centrulDeGreutate( obiect.momentInitial(),coordonate ); double x = coordonate[0] - punct[0]; double y = coordonate[1] - punct[1]; double z = coordonate[2] - punct[2]; return Math.sqrt( x * x + y * y + z * z ); }
Putem apela metoda atît cu un obiect din clasa Minge cît si cu un obiect din clasa Rezervor. Compilatorul nu se va plînge pentru ca el stie ca ambele clase implementeaza interfata ObiectSpatioTemporal, asa ca metodele apelate în interiorul calculului distantei (momentInitial si centruDeGreutate) sunt cu siguranta implementate în ambele clase. Deci, putem scrie:
Minge minge;
Rezervor rezervor;
double punct[] = { 10.0, 45.0, 23.0 };
distanta( punct, minge );
distanta( punct, rezervor );
Desigur, în mod normal ar fi trebuit sa proiectam si un constructor sau mai multi care sa initializeze obiectele noastre cu valori rezonabile. Acesti constructori ar fi stat cu siguranta în definitia claselor si nu în definitia interfetelor. Nu avem aici nici o cale de a forta definirea unui anumit constructor cu ajutorul interfetei.