Sunteți pe pagina 1din 62

Introducere in programarea orientata pe obiecte

Programe si modele POO vs programare procedurala Definitia programarii orientate pe obiecte Alte informatii Tema Programe si modele Aproape toate programele modeleaza o problema din lumea reala. Un model este o reprezentare simplificata sau o abstractiune a unui sistem. Modelul evidentiaza doar caracteristicile importante intr-un anumit context ale sistemului reprezentat. De exemplu, o masina de jucarie prezinta anumite detalii exterioare cum ar fi carcasa si rotile, dar nu include componente ca: motorul, rezervorul etc. Intr-un program, elementele cu ajutorul carora realizam modelarea sunt: structurile de date si secventele de instructiuni sau operatiile. In functie de limbajul de programare utilizat, constructiile care redau structurile de date si operatiile sunt mai mult sau mai putin expresive, adica mai mult sau mai putin apropiate de obiectele ce formeaza domeniul problemei de rezolvat. La nivelul cel mai de jos, din acest punct de vedere, se situeaza limbajele-masina si apoi limbajele de asamblare care dispun de structuri de date si operatii primitive cu care nu se pot modela probleme complexe. Limbajele de nivel inalt asa-numite structurate (de genul Basic, Pascal si C) aduc o crestere substantiala a nivelului de abstractizare mai ales in exprimarea operatiilor. Acestea sunt redate cu ajutorul structurilor de control (secventa, selectie si iteratie), iar "caramizile" din care este construit un program sunt subprogramele (functiile si procedurile). Structurile de date oferite de aceste limbaje raman insa destul de sarace pentru a acoperi complexitatea problemelor de rezolvat. Limbajele structurate au inspirat metodele procedurale folosite la dezvoltarea produselor software; ele se caracterizeaza prin: Un program conceput conform unei metode procedurale pune accentul pe algoritmii care transforma datele initiale ale unei probleme in rezultate. Algoritmii sunt structurati in subprograme carora li se furnizeaza date sub forma de parametri sau care acceseaza datele respective prin intermediul unui fond global comun (fig.1.1).

Fig. 1.1 Limbajele de programare orientate pe obiecte (LPOO) aduc o noua viziune asupra dezvoltarii programelor, urmarind sa acopere "slabiciunile" limbajelor structurate privind structurile de date. Mai precis, programatorului i se da posibilitatea de a-si construi propriile tipuri de date, dotate cu operatiile necesare, care sa "mimeze" cat mai fidel entitatile din domeniul problemei de rezolvat. Un program dezvoltat cu ajutorul tehnologiei obiectuale are drept unitate de constructie nu subprogramul, ci obiectul (fig.1.2). Un obiect inglobeaza date si operatii si reprezinta o abstractiune a unei entitati din lumea reala.

Obiectele componente interactioneaza, iar rezultatul acestei interactiuni o reprezinta transformarea datelor de intrare in date de iesire, adica rezolvarea problemei.

Fig. 1.2 Folosind tehnologia obiectuala, abstractizarea (intelegand prin asta tranzitia de la entitatile domeniului problemei de rezolvat la programul care o modeleaza) este mai directa, mai naturala. Motivul sta in faptul ca noi, oamenii percepem lumea inconjuratoare in termeni de obiecte fizice si concepte mentale care se misca, se transforma, interactioneaza unele cu altele, determinand in felul acesta modificari ale mediului inconjurator. Un obiect din lumea reala are cateva caracteristici importante, si anume: este perceput prin intermediul comportamentului si insusirilor sale exterioare; de exemplu, vedem ca o pisica merge, fara sa stim (si fara sa ne intereseze chiar) care sunt mecanismele interioare care determina mersul. Cu alte cuvinte, obiectul are o fata exterioara (interfata publica) pe care o arata altor obiecte, precum si o reprezentare (stare) interna care imprima comportarea exterioara; are granite distinct conturate, care il delimiteaza de celelalte obiecte si este autodeterminat: pastrand exemplul cu pisica, pentru a vedea ca ea merge, nu avem nevoie sa vedem un alt obiect suplimentar; spunem ca obiectul are identitate distincta; In cazul programarii procedurale abstractizarea presupune realizarea unui set de algoritmi (subprograme) care sa modeleze transformarile din lumea reala, folosind pentru reprezentarea unor entitati complexe structuri de date primitive. In cazul programarii obiectuale, abstractizarea presupune definirea unor structuri de date complexe dotate cu un comportament exterior si cu o stare interna, care se suprapun peste obiectele din lumea reala. Intr-un program dezvoltat in maniera obiectuala NU mai avem date globale (sau in orice caz, foarte putine). Datele sunt repartizate si inglobate in obiecte. Putem spune ca metoda obiectuala de dezvoltare conduce la o mai buna respectare a unor principii ale ingineriei software cum ar fi: localizarea si modularizarea: codul sursa corespunzator unui obiect poate fi scris si actualizat independent de alte obiecte; ascunderea informatiei: un obiect are, dupa cum vom vedea si in lucrarile urmatoare, o interfata publica pe care celelalte obiecte o pot utiliza in vederea comunicarii. Dar, pe langa interfata publica, un obiect poate include date si operatii private, "ascunse" fata de alte obiecte, si care pot fi modificate oricand, fara a afecta restul obiectelor. reutilizarea codului: multe clase de obiecte pot fi definite pe baza altor clase deja existente, preluandu-se automat (prin mostenire) continutul acestora din urma; Trecand la un limbaj de programare orientat pe obiecte nu inseamna ca "aruncam" principiile programarii structurate. Operatiile inglobate in obiecte nu sunt altceva decat un fel de subprograme (chiar daca au alt nume) care vor fi dezvoltate utilizand structurile de control cunoscute de la limbajele structurate. Intr-o exprimare plastica: Diferenta esentiala intre limbajele procedurale si cele obiectuale este aceea ca limbajele procedurale au un caracter imperativ, fiind axate pe "verbe" (subprograme), in timp ce limbajele obiectuale au un caracter mai declarativ si sunt concentrate in jurul "substantivelor" (datele).

Tinand cont de cele afirmate pana aici, putem formula principiul dupa care realizam transpunerea enuntului unei probleme intr-un limbaj de programare orientat pe obiecte: identificam obiectele ce populeaza spatiul programului, prin evidentierea substantivelor din enuntul problemei; pentru fiecare obiect identificam datele si operatiile, prin evidentierea adjectivelor si verbelor ce caracterizeaza substantivul respectiv. POO vs programare procedurala In cele ce urmeaza vom prezenta un exemplu de problema modelata in maniera procedurala si in maniera obiectuala, pentru a vedea mai bine diferentele intre cele doua viziuni. Problema se refera la crearea si utilizarea unei stive de numere intregi. Dupa cum probabil stiti, o stiva este o colectie cu structura liniara, in/din care se pot introduce/extrage elemente pe la un singur capat, considerat a fi varful stivei (celalalt capat este baza). Operatiile asupra stivei au denumiri consacrate in literatura, si anume: introducerea elementelor este numita push, iar extragerea - pop; in plus, exista o operatie de consultare a valorii din varful stivei, numita top. Pentru a nu va solicita atentia cu detalii sintactico-semantice specifice unui anumit limbaj de programare, vom considera ca dispunem de un fel de pseudo-cod pentru ambele maniere de modelare. Convenim ca pentru reprezentarea stivei folosim un tablou cu elemente intregi, caruia i se asociaza un indicator de stiva reprezentand indicele ultimului element ocupat in tablou. De asemenea, vom folosi o variabila necesara memorarii dimensiunii totale a tabloului.

*alocaspatiu in unTab pentru dimTab eleme e t n * f sa p vi b *i a o eb aeT fa d r v * m o T fi p s sT pa un }s e l e sT p a uT rt n e a u }s e l *f * *fie sp1,sp2, sp3variabilecu de indi t ri stiva rol ca o de d i }d i d i -i cl a l il l cl l a i c a - c d rt n i se u o psa cm t dlb ah( i, u1 1, l p cl l a l e e pm a cm t dst ah( ih( u,l la 2b , 2 pl2 csb d 2, a,l i u e se g rt x e a p cl el = a 1 o l p c e = a 2 l t cl xl = a 1 l t c x = a 2 l

* dE; nm ie rm l s p =T ; se m 1b +a p ; tb e = a l }s e l =T sa 1 p ; t rb a }s e l sl c. 1 a s. l c. l 2 a sl cl 3 a - c d rt n i se u o s cl a 1 s cl l a 1 sl l cl l a 2 s c a 2 se g rt x e a s cl el = a 1 s c e = a 2 s cl xl = a 1 s c x = a 2 e e

l l l l

Comparand cele doua secvente de mai sus, un prim aspect care trebuie remarcat este faptul ca in varianta procedurala am definit un set de operatii reprezentate ca subprograme, care primesc ca parametri stivele asupra carora lucreaza, in timp ce in varianta obiectuala, operatiile sunt inglobate in interiorul unui obiect reprezentand stiva. Pot sa am oricate obiecte de acest fel, fiecare din ele va avea

setul sau propriu de operatii care vor lucra asupra datelor interne ale obiectului. Din acest motiv operatiile nu necesita atatia parametri, ca in varianta procedurala. Cu alte cuvinte: in varianta procedurala operatiile (subprogramele) sunt detinatoarele datelor asupra carora lucreaza, in timp ce in varianta obiectuala datele (obiectele) sunt cele care poseda operatiile. O alta observatie importanta este faptul ca simbolul Stiva definit ca o clasa este de fapt un tip definit de utilizator. Ca orice tip, el poate fi folosit pentru a declara variabile asupra carora se vor aplica operatii recunoscute de tipul respectiv. Pentru a intelege mai bine, sa ne gandim ce este un tip dintr-un limbaj de programare: Un tip este o multime de valori dotata cu un anumit set de operatii. In majoritatea limbajelor procedurale suntem obisnuiti sa lucram cu asa-numitele tipuri predefinite sau primitive. Spre exemplu, tipul int din limbajul C desemneaza multimea numerelor intregi reprezentabile pe 2 octeti. Setul de operatii cu care este dotat acest tip cuprinde, printre altele cele patru operatii aritmetice. De multe ori tipurile predefinite sunt prea sarace pentru a permite modelarea unor entitati complexe din lumea reala. De aceea, este necesar ca programatorul sa aiba libertatea de a-si crea propriile tipuri (impreuna cu operatiile aferente) pe care apoi sa le foloseasca la fel cum foloseste tipurile predefinite. Limbajele de programare obiectuale au fost concepute tocmai in acest sens. Tot pe marginea celor doua exemple prezentate, ar trebui spus ca in varianta obiectuala programatorul este ajutat sa evite o serie intreaga de erori, datorita incapsularii datelor si operatiilor caracteristice unei abstractiuni (in cazul nostru stiva) in interiorul unui obiect. Spre exemplu, in varianta procedurala, programatorul poate la un moment dat sa modifice accidental, printr-o atribuire simpla, valoarea lui sp1, dand astfel peste cap stiva respectiva. In cealalta varianta, asa cum vom vedea pe parcurs, datele din interiorul unui obiect pot fi declarate ca ascunse fata de exteriorul obiectului, astfel incat modificarea indicatorului de stiva sa nu se poata face decat via operatiile push si pop, adica intr-un mod controlat. Definitia programarii orientate pe obiecte Programarea orientata pe obiecte este o metoda de implementare in care programele sunt organizate ca ansamble de obiecte ce interactioneaza unele cu altele, fiecare obiect reprezentand instanta unei clase; fiecare clasa apartine unei ierarhii in cadrul careia clasele sunt legate prin relatii de mostenire. Aceasta definitie cuprinde trei parti importante, si anume: obiectele si nu algoritmii sunt blocurile logice fundamentale; fiecare obiect este o instanta a unei clase. Clasa este o descriere a unei multimi de obiecte caracterizate prin structura si comportament similare, iar prin instanta intelegem o valoare particulara din multimea respectiva clasele sunt legate intre ele prin relatii de mostenire. Un limbaj de programare care ofera suport pentru utilizarea claselor si a obiectelor, dar care nu are implementat mecanismul relatiilor de mostenire intre clase este un limbaj de programare bazat pe obiecte. Programarea bazata pe clase si pe obiecte, care nu face uz de relatia de mostenire se mai numeste programare cu tipuri de date abstracte.

Primele programe Java


Un program simplu Cum ajung de la sursa la executie Ce este Java API Alte informatii Tema Un program simplu // fisier PrimulProgram.java

import java.io.*; public class PrimulProgram{ public static void main(String[ ] arg) { System.out.print("Primul program Java"); System.out.println(". . . dar nu si ultimul!"); } } Programul prezentat nu face altceva decat sa afiseze o secventa de caractere pe ecran, dar poate servi la evidentierea unor elemente de baza care apar in orice program Java. In primul rand trebuie spus ca programele Java sunt constituite ca seturi de clase. In particular, programul prezentat mai sus este constituit dintr-o singura clasa. NU EXISTA ceea ce in limbajele procedurale (C, Pascal, Basic) numim date globale. De asemenea, NU EXISTA functii de sine statatoare: avem DOAR functii (metode) incluse in clase. Pentru descrierea unei clase se foloseste o constructie sintactica de forma: class nume_clasa { //continut clasa } Aproape orice program, indiferent ca este scris intr-un limbaj procedural sau obiectual, are o radacina sau un punct de plecare. Astfel, in programele Pascal avem ceea ce se numeste program principal, iar in C avem functia main. In programele Java vom avea o clasa radacina care se caracterizeaza prin faptul ca include o functie al carei antet este: public static void main(String[ ] arg) Elementele desemnate prin cuvintele public si static vor fi lamurite putin mai tarziu. Deocamdata le folosim asa cum le vedem. Numele clasei radacina este un identificator dat de programator. Parametrul functiei main este de tip tablou de elemente String (tablou de siruri de caractere). Prin intermediul acestui parametru putem referi si utiliza in program eventualele argumente specificate la lansarea in executie a programului. In ceea ce priveste clauza import plasata chiar la inceput, ea se refera la faptul ca programul utilizeaza operatii ale unor clase aflate in pachetul numit java.io. Despre pachete si importarea claselor vom discuta mai detaliat in paragraful dedicat documentatiei API. Actiunile executate de programul de mai sus presupun 2 operatii de afisare: print si println. Observam modul putin mai ciudat de apel al acestor operatii. Prefixul System.out care insoteste numele celor 2 operatii reprezinta numele unui obiect: este vorba despre un obiect predefinit care se utilizeaza atunci cand destinatia unei operatii de scriere este ecranul monitorului. Asa dupa cum vom vedea si in modulele urmatoare, deoarece metodele sunt incluse in obiecte, apelul presupune precizarea numelui obiectului "posesor". Exista si situatii in care apelul unei metode presupune precizarea numelui clasei in loc de numele unui obiect: este vorba despre metodele statice, despre care vom invata in saptamana urmatoare. Cu alte cuvinte, in Java, apelul unei operatii se realizeaza folosind una din notatiile: nume_obiect.nume_metoda(parametri_actuali) sau: nume_clasa.nume_metoda(parametri_actuali) Metodele print/println pot primi ca parametru un sir de caractere dat fie sub forma de constanta, asa ca in exemplul prezentat, fie sub forma de expresii al caror rezultat este de tip String. Metodele print/println mai pot primi ca parametru si valori ale tipurilor primitive sau obiecte, dar acestea sunt convertite tot la tipul String inainte de afisare. Despre tipul String vom vorbi in detaliu putin mai tarziu. Deocamdata stim despre el ca modeleaza lucrul cu siruri de caractere. Diferenta dintre print si println este aceea ca println realizeaza, dupa afisarea textului dat ca parametru, un salt la linie noua. Se precizeaza ca println poate fi apelata si fara parametri, caz in care executa doar

un salt la linie noua: System.out.println( ); Cum ajung de la sursa la executie Pana acum am fost obisnuiti ca programele pe care le scriam sa parcurga pana la executie clasicul traseu: editare -> compilare -> linkeditare -> executie. Prin compilare se obtinea cod masina chiar pentru masina pe care se executa compilarea. In cazul programelor Java traseul amintit este in esenta acelasi, dar produsele obtinute sunt diferite, iar faza de linkeditare practic dispare din punct de vedere al utilizatorului. Vom descrie in continuare care sunt pasii pe care trebuie sa-i parcurgem pentru a ajunge sa executam un program scris in Java. Pentru aceasta ne vom referi la exemplul dat in paragraful anterior si consideram ca utilizam ca mediu de dezvoltare produsul SDK. 1. Programul se EDITEAZA cu un editor de text ASCII simplu. Exemple de asemenea editoare sunt: o pentru sistemul de operare Unix: emacs, pico, joe, editorul programului MidnightCommander; o pentru Windows: Notepad, editorul programului NortonCommander sau chiar editoarele unor medii integrate de dezvoltare cum sunt cele din gama Borland; Atentie: editoarele de genul MSWord sau WordPad NU sunt editoare de text ASCII simplu, deci NU le vom folosi pentru editare de programe sursa !!! Sursa se salveaza intr-un fisier care va avea OBLIGATORIU extensia .java. si numele clasei public. In exemplu: PrimulProgram.java. 2. Dupa editare programul se COMPILEAZA cu comanda: javac nume_fisier_sursa.java de exemplu: javac PrimulProgram.java Atentie: prezenta extensiei .java in comanda de compilare este OBLIGATORIE. In urma compilarii vor rezulta un numar de fisiere egal cu numarul claselor continute de sursa. Fiecare dintre fisierele rezultate are extensia .class si numele identic cu numele clasei careia ii corespunde. Prin urmare, pentru exemplul nostru va rezulta un fisier cu numele PrimulProgram.class. Un fisier .class contine cod masina virtual numit si Java Byte Code. Codul virtual este codul masina al unui calculator imaginar. Pentru a putea fi executat pe o masina reala, este necesar un interpreter (sau executiv) care sa execute fiecare instructiune a codului virtual in termenii operatiilor masina ai calculatorului real. Avantajul utilizarii codului virtual este urmatorul: dupa compilare, programul in cod virtual obtinut poate fi transportat pe orice masina pentru care exista interpreterul corespunzator, iar scrierea unui interpreter este mult mai usoara decat a unui compilator intreg. In concluzie, dupa compilare, urmatorul pas in prelucrarea unui program Java este executia prin intermediul interpreterului. 3. LANSAREA IN EXECUTIE a interpreterului se face cu comanda: java nume_clasa_radacina Argumentul dat interpreterului Java la lansarea in executie este de fapt numele unui fisier .class, si anume acela corespunzator clasei radacina (adica cea care contine metoda main). Pentru exemplul nostru comanda de executie va fi deci: java PrimulProgram Recomandare: pentru a putea lucra mai comod la dezvoltarea unui program Java, se recomanda deschiderea pe ecran a 2 ferestre de lucru: una pentru editarea sursei si celalta pentru compilarea si executia programului.

Am afirmat in paragraful anterior ca parametrul metodei main din clasa radacina serveste la accesarea eventualelor argumente date in linia de comanda la lansarea in executie a programului. Pentru a ilustra acest aspect, vom considera un program care afiseaza o formula de salut, urmata de numele si prenumele utilizatorului, date ca argumente: // fisier Salutare.java import java.io.*; public class Salutare{ public static void main(String[ ] arg) { System.out.println("Te salut, "+arg[0]+" "+arg[1]); } } Dupa ce compilam acest program, il putem lansa in executie de exemplu cu comanda: java Salutare mai Popescule Observatii: Programului i se dau 2 argumente (mai si Popescule). Acestea sunt interpretate ca primele 2 elemente ale tabloului arg ce figureaza ca parametru formal al metodei main (se poate spune ca arg este un omolog al lui _argv din programele C). In Java elementele unui tablou se indexeaza incepand cu 0.

Daca programului nu i se furnizeaza cele 2 argumente, rezultatul este oprirea executiei cu un mesaj prin care se reclama depasirea indicelui de tablou (IndexOutOfBoundsException). Este vorba de tabloul argcare are dimensiunea 0 in cazul lipsei argumentelor. De aceea, daca dorim sa fim mai rigurosi, e bine sa prevedem secvente de verificare in program, prin care sa testam daca utilizatorul a furnizat numarul necesar de parametri:

// fisier Salutare1.java import java.io.*; class Salutare1{ public static void main(String[ ] arg) { if(arg.length<2) System.out.println("Nu ati dat parametrii necesari!!"); else System.out.println("Te salut, "+arg[0]+" "+arg[1]); } } Expresia arg.length utilizata in secventa de mai sus ne da numarul de elemente ale tabloului arg. Posibilitatea de a folosi aceasta expresie (despre care vom afla mai multe in capitolul despre tablouri) explica de ce metoda main nu are nevoie de un al 2-lea parametru, omolog al lui _argc din programele C. O alta observatie care trebuie facuta in contextul acestui exemplu priveste parametrul functiei println. De data acesta am folosit o expresie construita prin aplicarea operatorului + asupra unor operanzi de tipString, al carei rezultat este un sir de caractere obtinut prin concatenarea operanzilor. Ceea ce trebuie sa retinem de aici este faptul ca, spre deosebire de functiile printf din C, care acceptau un numar variabil de parametri, functiile print/println din Java accepta UN SINGUR parametru. Ca urmare, atunci cand dorim sa scriem printr-o singura instructiune mai multe valori trebuie sa le concatenam, spre a forma un singur sir.

In scrierea programelor Java se tine seama de recomandarile de la Code Conventions for the JavaTM Programming Language http://java.sun.com/docs/codeconv/index.html, cele mai importante fiind urmatoarele: numele de clase incep cu majuscule - OClasa, ale variabilelor si metodelor cu litere mici - oVariabila, oMetoda, constantele se scriu cu majuscule - static final int MIN = 4; majuscula este prima litera a tuturor cuvintelor ce formeaza un identificator numele fisierului .java este cel al clasei publice pe care o contine - doar o singura clasa poate fi publica intr-un fisier sursa. Ce este Java API Intr-un program Java vom lucra in principiu cu 2 tipuri de clase: clase definite de programator (adica de noi) si clase predefinite, furnizate impreuna cu mediul de dezvoltare Java. Un exemplu de asemenea clasa este String. Clasele predefinite formeaza o biblioteca numita API (Application Programming Interface) - adica interfata pentru programarea aplicatiilor. Pentru a putea scrie un program Java este foarte important sa avem acces la o documentatie a API, ca sa stim la ce si cum sa folosim clasele respective. In mod normal, mediul de dezvoltare trebuie sa includa documentatia API. De obicei ea apare sub forma de colectie de pagini web. Documentatii API se gasesc in numar mare si pe Internet. Pe situl Sun: API Documentation. Clasele predefinite sunt grupate in asa-numite pachete. Un pachet contine clasele care rezolva o anumita problema. De exemplu, pachetul java.io contine clase care se ocupa de operatiile de intrare/iesire. Numele pachetelor sunt formate din 2 sau mai multe cuvinte separate intre ele prin caracterul "." (punct). Pentru a putea folosi o clasa predefinita ar trebui sa-i specificam numele complet, adica: nume_pachet.nume_clasa Intrucat acest lucru ar fi incomod, avem la dispozitie clauza import prin care putem specifica faptul ca dorim sa utilizam clase dintr-un anumit pachet. Clauza import poate avea una din urmatoarele forme: import nume_pachet.nume_clasa; daca dorim sa utilizam doar o anumita clasa din pachet, sau: import nume_pachet.*; daca dorim sa utilizam toate clasele din pachetul respectiv. Aceasta din urma forma este cel mai des utilizata, fiind mai comoda (daca am utiliza prima forma, pentru fiecare clasa predefinita necesara in program ar trebui sa avem cate o clauza import separata). In biblioteca Java exista un pachet special, numit java.lang care grupeaza clase fundamentale, fara de care practic nu am putea scrie vreo aplicatie. Pentru a folosi clasele din acest pachet NU avem nevoie de clauzaimport, ea fiind subinteleasa implicit. Clasa String este un exemplu de clasa aflata in pachetul java.lang. Trebuie sa spunem ca o documentatie API ne da informatii despre: pachetele din biblioteca, clasele din fiecare pachet si componenta fiecarei clase.

Tipuri de date. Clase si obiecte


Tipuri primitive Tipuri definite de utilizator: clase Crearea obiectelor Reguli de vizibilitate Initializarea campurilor unui obiect. Constructori Membrii statici ai claselor Alte informatii Tema Tipuri primitive In limbajul Java avem la dispozitie urmatoarele tipuri primitive de date:

boolean: este format din 2 valori: true si false byte: este multimea numerelor intregi cu semn reprezentabile pe 8 biti; short: este multimea numerelor intregi cu semn reprezentabile pe 16 biti; int: este multimea numerelor intregi cu semn reprezentabile pe 32 biti long: este multimea numerelor intregi cu semn reprezentabile pe 64 biti float: este multimea numerelor reale simpla precizie reprezentabile pe 32 biti double: este multimea numerelor reale dubla precizie reprezentabile pe 64 biti char: este multimea caracterelor codificate in sistem Unicode, pe 16 biti; practic sistemul Unicode vine sa extinda sistemul ASCII (in care caracterele sunt reprezentate pe 1 octet), pentru a include un numar mai mare de caractere speciale, cum ar fi literele arabe sau chinezesti, litere din limbile slave, litere cu diverse accente asociate etc. In ceea ce priveste tipurile numerice (intregi si reale), cu ajutorul exemplului de mai jos puteti afla care sunt limitele maxime pentru fiecare. Pentru a putea intelege pe deplin exemplul, va trebui sa aveti putintica rabdare, pana ajungem la capitolul cu clasele infasuratoare. Deocamdata tot ce aveti de facut este sa compilati si sa executati programul: // M .java a // Aplicatie ce afiseaza valorile limita / bt s B et y.r Ba y e g l so S s gr a l = rt e S t h int lno largestInteger = Integer.MAX_VALUE; L gs o.e La t g r l /m nu r / r e e fo F s gr largestDouble = Double.MAX_VALUE; F at e = t l double a l /p al tu pt ir /e i coa hl an ra h C bu n ao te Ble r; oe / System.out. println("Valoar byte maxima este "+ largestBy ); ea te System.out.println("Valoare short maximaeste" + largestShor ; a t) System.out.println("Valoarea a integer maximaest "largestLong);g ); e + largestInte er System.out.println("Valoare long maxima este" + System.out.println("Valoare float maximaeste"++largestFloat)e ; a " ; System.out.println("Valoare double maximaeste a largestDoubl ) }s e{ l e } System.out.println("Valoara variabilei aBoolen e a este " + aBoolean; ) Daca vreti sa aflati si limitele minime pentru tipurile numerice trebuie sa inlocuiti in exemplul de mai sus simbolul MAX_VALUE cu MIN_VALUE si apoi sa rulati programul. Asa dupa cum stim, tipurile sunt folosite pentru a putea declara variabile. Indiferent ca e vorba de tipuri primitive sau nu, intr-un program Java putem avea declaratii de variabile in urmatoarele locuri: in interiorul claselor, caz in care variabilele sunt campuri sau date membru ale claselor respective; in listele de parametri formali din antetul functiilor; un exemplu in acest sens este args, parametrul functiei main (parametru care nu e de tip primitiv); in interiorul functiilor, caz in care variabilele sunt variabile locale ale functiilor respective; in exemplul de mai sus toate variabilele sunt variabile locale ale functiei main. Despre functiile dintr-un program Java vom vorbi mai pe larg in modulul urmator, iar despre variabile care nu sunt de tipuri primitive vom vorbi in capitolul despre Crearea obiectelor. In afara de variabile, intr-un program Java putem intalni si constante literale. In cele ce urmeaza, prin cateva exemple vom arata cum se scriu constantele pentru fiecare tip primitiv: numere intregi: 124, 45635 etc.; compilatorul de Java asimileaza orice asemenea constanta ca fiind de tipul int; numere intregi de tip long: 8864L, 125363646L;

numere reale: 12.4, 26.77e-2; compilatorul de Java asimileaza orice asemenea constanta ca fiind de tipul double. Programatorul poate scrie si explicit o constanta reala de tip double: 456.34D; numere reale de tip float: 87.353F; constante de tip char: 'n', 'G', '%', '\'' (caracterul apostrof), '\n' (caracterul salt la linie noua); constantele de tip boolean: true si false.

Tipuri definite de utilizator: clase Asa cum am aratat in primul modul, unul din principalele avantaje aduse de limbajele de programare OO il constituie posibilitatea utilizatorului de a-si defini propriile tipuri. Aceste tipuri sunt de fapt clasele. O clasa reprezinta descrierea unei multimi de obiecte care au aceeasi structura si acelasi comportament. Ca urmare, intr-o clasa vom gasi definitiile datelor si ale operatiilor (functiilor) ce caracterizeaza obiectele clasei respective. Datele definite intr-o clasa se mai numesc date-membru, variabile-membru, atribute sau campuri, iar operatiile se mai numesc metode sau functii-membru. Constructia sintactica folosita pentru a defini o clasa in Java este: class nume_clasa { // date si metode } Pentru a prezenta modul in care definim membrii dintr-o clasa, vom utiliza urmatorul exemplu concret: class Punct{ private int x; private int y; public void init(int xx, int yy) { x = xx; y = yy; } public void move(int dx, int dy) { x += dx; y += dy; } public int getX( ) { return x; } public int getY( ) { return y; } } Clasa Punct modeleaza un punct de pe ecranul unui monitor si are ca date-membru 2 variabile de tip int: x si y reprezentand coordonatele punctului, iar ca metode: functia init care initializeaza coordonatele punctului; functia move care deplaseaza un punct pe o anumita distanta; functiile getX si getY care returneaza coordonatele curente ale punctului. Se observa ca definitiile datelor si ale functiilor din interiorul clasei sunt prefixate de anumite cuvinte cheie, ca public si private, care se numesc modificatori de acces. Vom vedea putin mai jos care este rolul acestora. In limbajele de POO clasele pot fi considerate ca mecanisme prin care programatorul isi construieste propriile sale tipuri de date, pe care apoi le va folosi aproape la fel cum foloseste tipurile predefinite. Cu alte cuvinte: Clasa este un tip de date si ca atare, unul dintre rolurile ei fundamentale este acela de a servi la declararea variabilelor. Valorile unui tip clasa se numesc obiecte sau instante ale clasei respective. Pentru a avea o imagine mai clara a diferentei dintre o clasa si un obiect vom considera urmatorul exemplu luat din matematica: daca dorim sa descriem multimea formata din numerele naturale mai mari decat 5 vom scrie: M5 = { x din N | x > 5 }

10

Valoarea 10 este un element particular al multimii M5. In cazul unui program OO, clasa este similara lui M5, adica este o descriere a unei multimi, in timp ce obiectul este un element particular al multimii respective. Generalizand, vom spune ca intr-un program: Tipurile sunt multimi, iar constantele si valorile variabilelor sunt elemente ale respectivelor multimi. Daca luam exemplul clasei Punct, putem spune ca ea descrie multimea tuturor obiectelor caracterizate prin 2 coordonate intregi si prin posibilitatea de a executa operatiile definite in clasa Punct. Un element particular al acestei multimi se obtine dand valori concrete coordonatelor x si y. Observatie: si tipurile primitive ale unui limbaj de programare reprezinta tot multimi, dar care nu au o descriere explicita, ci una subinteleasa, derivata din modul de reprezentare interna a valorilor tipurilor respective. De exemplu, in Java tipul short este multimea numerelor intregi reprezentabile pe 2 octeti.

Crearea obiectelor In cele ce urmeaza vom considera un mic program Java care foloseste clasa Punct prezentata mai sus, pentru a vedea cum se creaza obiecte ale acestei clase: class Punct{ private int x; private int y; public void init(int xx, int yy) { x = xx; y = yy; } public void move(int dx, int dy) { x += dx; y += dy; } public int getX( ) { return x; } public int getY( ) { return y; } } class ClientPunct { public static void main(String[ ] arg) { Punct p1 = new Punct( ); //creez o instanta a clasei Punct Punct p2 = new Punct( ); // si inca una p1.init (10,20); p2.init (30,40); //apelez metodele init ale instantelor p1.move(5,5); p2.move(6,-2); //apelez metodele move ale instantelor System.out.println("(x1,y1) = ("+p1.getX( )+","+p1.getY( ) +")"); //afisez coordonatele curente ale primului punct System.out.println("(x2,y2) = ("+p2.getX( )+","+p2.getY( ) +")"); //afisez coordonatele curente ale celui de-al 2-lea punct } } Variabilele p1 si p2 sunt referinte la obiecte de tip Punct. In Java orice variabila al carei tip este o clasa constituie o referinta, adica o variabila ale carei valori sunt adrese de memorie. La adresele respective se vor afla obiecte ale clasei care reprezinta tipul referintei. Spunem careferinta indica spre un obiect.

11

Obiectul indicat de o referinta nu apare automat, prin simpla declarare a referintei, ci el trebuie creat. De fapt lucrurile se intampla asa: intai se creaza obiectul si apoi adresa de memorie unde el a fost pus este atribuita referintei. Nu este suficient sa declaram: Punct p1; deoarece acest lucru este echivalent cu a declara o variabila pe care nu o initializam cu nici o valoare. In programul de mai sus se poate observa modul in care are loc crearea unui obiect, si anume, prin aplicarea operatorului new, care se mai numeste operator de alocare: p1 = new Punct( ); Efectul acestui operator este urmatorul: in memorie se aloca un spatiu necesar pentru stocarea unui obiect al clasei Punct, iar adresa zonei alocate se returneaza ca valoare. Vom vedea ceva mai incolo (la capitolul despre constructori) ce semnificatie are constructia Punct( ) care apare in contextul operatorului new. Dupa initializare, referintele vor putea fi folosite pentru a apela metodele obiectelor create. Notatia prin care se realizeaza un apel de metoda este: nume_referinta . nume_metoda ( lista_parametri_actuali ) In figura de mai jos este reprezentata in mod intuitiv imaginea in memorie a celor 2 obiecte create in program:

Se observa ca in interiorul obiectelor am figurat atat datele cat si functiile. Putem sa ne imaginam ca acele date si functii exista in atatea exemplare, cate obiecte sunt create in program la un moment dat. Poate va intrebati de ce in figura apar cuvintele "stiva" si "heap". Aici trebuie spus ca variabilele locale ale metodelor sunt alocate in mod static, iar zona de memorie utilizata in acest scop este exploatata in regim de stiva. In ceea ce priveste obiectele, ele sunt create in mod dinamic, iar zona de memorie utilizata pentru alocari dinamice se mai numeste heap (gramada). In cazul unui apel de forma p1.init(. . .) se mai spune ca se apeleaza metoda init pentru obiectul referit de p1, sau ca obiectul referit de p1 receptioneaza cererea (mesajul) de a executa operatia init. De aceea, cand vom prezenta anumite metode, vom utiliza frecvent denumirea de obiect receptor ca sa desemnam obiectul care detine metodele respective. Nu intamplator am numit clasa principala din programul nostru ClientPunct: aceasta clasa (prin intermediul metodei main) solicita unor obiecte ale clasei Punct sa execute anumite functii sau, altfel spus, solicita prestarea unor servicii. Spunem in acest caz ca obiectele clasei Punct sunt obiecte server, iar ClientPunct este clientul lor. Practic, interactiunile dintre obiecte se bazeaza pe conceptul de client-server: obiectul care apeleaza o metoda este clientul, iar obiectul care contine metoda respectiva este serverul. Reguli de vizibilitate Am vazut ca in definitiile membrilor unei clase se utilizeaza niste cuvinte speciale, numite modificatori de acces. Acestia stabilesc drepturile de acces ale clientilor la membrii unei clase. Cand discutam despre drepturile de acces la membrii unei clase trebuie sa abordam acest subiect din 2 perspective:

12

1. Interiorul clasei sau, mai concret, metodele clasei.In cadrul metodelor unei clase exista acces nerestrictiv la toti membrii, date sau functii. De exemplu, in metodele clasei Punct se face referire la campurile x si y. In mod asemanator s-ar fi putut referi (apela) si metodele. De exemplu, am fi putut defini functia move pe baza functiei init astfel: class Punct{ //. . . public void move(int dx, int dy) { init(x+dx, y+dy); } //. . . } Se observa ca in interiorul clasei nu se foloseste notatia cu punct pentru a referi membrii, acestia fiind pur si simplu accesati prin numele lor. Cand o metoda face referire la alti membri ai clasei, de fapt sunt accesati membrii corespunzatori ai obiectului receptor, indiferent care ar fi el. De exemplu, cand se apeleaza metoda init a obiectului referit de p1, are loc initializarea membrilor x si y ai acelui obiect. In legatura cu accesul din interiorul unei clase, trebuie spus ca absenta restrictiilor se aplica si daca este vorba despre membrii altui obiect din ACEEASI clasa, dar diferit de cel receptor. De exemplu, daca in clasaPunct am avea cate o metoda de calcul al distantei pe vericala/orizontala dintre 2 puncte, unul fiind obiectul receptor, iar celalalt un obiect dat ca parametru, atunci am putea scrie:

/. . / pt in d }. / . / Se observa ca din interiorul metodelor distV / distH putem accesa liber membrii privati ai obiectului p dat ca parametru. La fel ar sta lucrurile si daca p ar fi o variabila locala a unei metode din clasa Punct. Observatie: in fraza de mai sus am utilizat expresia "obiectul p"; de fapt, corect este "obiectul indicat de p". Din motive de simplificare a exprimarii insa, de acum inainte, acolo unde nu exista riscul unei confuzii, vom "imprumuta" obiectului numele referintei. 2. Exteriorul sau clientii clasei. Clientii unei clase pot accesa doar acei membri care au ca modificator de acces cuvantul public. Membrii declarati cu modificatorul private NU sunt vizibili in afara, sunt ascunsi. Daca am incerca sa folosim in metoda main din exemplul nostru o referinta de genul: p1.x compilatorul ar raporta o eroare. O observatie importanta pe care o putem desprinde din exemplul clasei Punct este aceea ca structura unei clase, sau modul ei de reprezentare, care este dat de variabilele membru, de regula se ascunde fata de clienti. Daca este necesar ca acestia sa poata consulta valorile datelor membru, se va opta pentru definirea unor metode de genulgetValoare, iar nu pentru declararea ca publice a datelor respective. Intr-o clasa Java putem declara membri care sa nu fie precedati de nici un modificator de acces. In acest caz, membrii respectivi se spune ca sunt accesibili la nivel de pachet ("package"), adica sunt vizibili de catre toti clientii aflati in acelasi pachet cu clasa in cauza. De asemenea, mai exista un modificator de acces, protected, utilizat in contextul ierarhiilor de clase. Despre el vom vorbi cand vom invata despre mostenire. Initializarea campurilor unui obiect. Constructori Am vazut in exemplul cu ClientPunct ca dupa crearea obiectelor p1 si p2, pentru ele s-a apelat metoda init care avea drept scop initializarea campurilor x si y respectiv ale celor 2 obiecte. Practic, metoda init poate constitui un exemplu de mod de initializare a campurilor unui obiect, dar nu este cel mai bun. De exemplu, un repros care i se poate aduce este acela ca programatorul ar putea omite apelul

13

metodei, caz in care campurile vor avea alte valori (vom vedea care) decat cele dorite. La definirea unei clase in Java, programatorul poate prevedea una sau mai multe metode speciale, numite constructori, al caror scop este tocmai initializarea datelor unui obiect imediat dupa ce el a fost creat. Vom ilustra, tot cu ajutorul clasei Punct, modul de specificare a constructorilor: class Punct{ //. . . public Punct(int xx, int yy) { x = xx; y = yy; } public Punct(Punct p) { x = p.x; y = p.y; } //. . . } Cele 2 metode declarate in secventa de mai sus se numesc constructori. Iata care sunt caracteristicile unui constructor: este o metoda care are acelasi nume cu clasa in care este definita; NU are tip returnat (nici macar void); se apeleaza in mod automat la crearea unui obiect. Astfel, in loc sa folosim metoda init, in functia main din clasa ClientPunct am fi putut scrie: class ClientPunct{ public static void main(String[ ] arg) { Punct p1 = new Punct(10,20 ); //creez o instanta a clasei Punct Punct p2 = new Punct(30,40 ); // si inca una //. . . } } Deci, dand parametrii doriti, la crearea celor 2 instante automat vor intra in executie constructorii corespunzatori, astfel incat este eliminat riscul omiterii initializarii. Am vazut ca intr-o clasa putem avea mai multi constructori. De fapt, acest lucru este un efect al facilitatii de supraincarcare a functiilor existenta in limbajul Java. Prin supraincarcare se intelege posibilitatea de a defini in acelasi domeniu de vizibilitate mai multe functii cu acelasi nume, dar cu parametri diferiti ca tip si/sau numar. NU se permite ca functiile supraincarcate sa difere DOAR prin tipul returnat. In exemplul nostru am definit 2 constructori: unul cu 2 parametri de tip int, iar celalalt cu un parametru de tip Punct. Am putea atunci sa mai cream o instanta a clasei Punct, sub forma: Punct p3 = new Punct(p1); caz in care obiectul referit de p3 ar avea aceleasi valori ale coordonatelor ca si obiectul referit de p1. Intrebare: avand in vedere ca p3 si p1 sunt referinte, ce s-ar fi intamplat daca am fi scris: Punct p3 = p1; in loc de fraza de mai sus? Atunci cand un constructor nu are parametri el se numeste constructor no-arg. Daca programatorul nu prevede intr-o clasa NICI UN constructor, atunci compilatorul va insera automat in clasa respectiva un constructor implicit de tip no-arg si al carui corp de instructiuni este vid. Asa au stat lucrurile in cazul primei definitii a clasei Punct. Astfel se explica de ce la crearea unui obiect Punct am folosit notatia: p1 = new Punct ( ); Practic, aici am apelat constructorul implicit generat automat de compilator.

14

Daca programatorul include intr-o clasa CEL PUTIN un constructor, indiferent de care, compilatorul NU va mai genera constructorul implicit pentru acea clasa. Astfel, in definitia clasei Punct care include cei 2 constructori descrisi mai inainte nu avem constructor implicit, dar nici constructor no-arg. Ca urmare, daca incercam sa scriem: p1 = new Punct ( ); compilatorul va semnala eroare, in sensul ca nu exista vreo definitie de constructor care sa nu aiba parametri. In legatura cu constructorii trebuie spus ca, exceptand anumite cazuri deosebite, ei vor trebui sa aiba modificatorul de acces public. Altfel, nu vom putea crea obiecte ale claselor respective. Alte metode de initializare a campurilor unei clase Pe langa constructori, limbajul Java mai ofera si alte posibilitati de a initializa campurile unei clase: Initializatori la declarare, care pot fi impliciti sau expliciti: class Punct{ private int x; //initializator implicit private int y = 1; //initializator explicit //presupunem ca nu avem constructori } //. . . Punct p1 = new Punct(); //in acest caz p1.x = 0 si p1.y=1 Initializatorii impliciti depind de tipul campului respectiv, si anume: sunt 0 pentru tipurile numerice, false pentru tipul boolean si null pentru referinte la obiecte. Atentie! Initializarile implicite nu se aplica si in cazul variabilelor locale ale metodelor. Acestea trebuie initializate explicit de catre programator. Blocuri de initializare: sunt secvente de cod cuprinse intre acolade, plasate imediat dupa declaratia campului pe care il initializeaza, si care pot fi asimilate cu niste constructori no-arg. Ele se folosesc atunci cand valoarea de initializare a unui camp nu este o constanta simpla, ci trebuie obtinuta prin calcule. Ca exemplu vom presupune ca variabila x din clasa Punct trebuie initializata cu valoarea sumei primelor 6 numere Fibonacci: class Punct{ private int x=2; //initializator explicit { //bloc de initializare int a=1,b=1; for(int i=3;i<=6;i++){ b+=a; a=b-a; x+=b; } } //. . . } Pentru o clasa in care apar mai multe mecanisme de initializare, ordinea lor de aplicare este urmatoarea: 1. Initializatorii din declarare. 2. Blocurile de initializare, in ordinea textuala in care ele apar in interiorul clasei. 3. Constructorii. Deosebirea esentiala intre constructori si celelalte tipuri de initializari este aceea ca, in cazul constructorilor putem initializa fiecare instanta cu valori diferite pe care le dam ca parametri. Celelalte mecanisme de initializare presupun ca toate instantele clasei vor fi initializate cu aceleasi valori in campurile respective. Membrii statici ai claselor

15

Pana acum, in clasa Punct am definit doar membri non-statici. Acestia se mai numesc si membri ai instantelor, deoarece, asa cum am vazut si in figura prezentata mai la inceputul acestui material, fiecare instanta isi are propriile exemplare ale membrilor respectivi. Limbajul Java permite definirea unei categorii speciale de membri, numiti statici sau membri de clasa. Acesti membri vor exista in exemplare unice pentru fiecare clasa, fiind accesati in comun de toate instantele clasei respective. Mai mult, membrii statici pot fi referiti chiar si fara a instantia clasa, ei nedepinzand de obiecte. Pentru a defini un membru static se utilizeaza cuvantul cheie static, plasat dupa modificatorul de acces: modificator_acces static tip_membru nume_membru ; Referirea unui membru static NU se va face prin intermediul numelui obiectului, ci prin intermediul numelui clasei: nume_clasa . nume_membru_static Pentru exemplificare vom modifica clasa Punct astfel incat sa putem calcula numarul de apeluri ale metodei move pentru TOATE obiectele create intr-un program. Pentru aceasta vom defini o variabila statica contor si o metoda statica getContor: class Punct{ private int x; private int y; private static int contor=0; //initializator implicit public Punct(int xx,int yy ) { x=xx; y=yy; } public void move(int dx, int dy) { x+=xx; y+=yy; contor++;} public static int getContor() {return contor;} //. . . } //metoda main din clasa radacina Punct p1 = new Punct(10,20); Punct p2 = new Punct(15,13); p1.move(8,-2); p2.move(6,7); //. . . System.out.println("S-au executat "+Punct.getContor()+" mutari."); In figura de mai jos este reprezentata in mod intuitiv imaginea in memorie a celor 2 obiecte create in programul de mai sus:

In legatura cu membrii statici ai unei clase trebuie facuta urmatoarea observatie:

16

Intr-o metoda statica NU este permisa referirea simpla a unui membru non-static. Acest lucru se explica prin aceea ca un membru non-static exista doar in interiorul unui obiect, pe cand membrii statici exista independent de obiecte. Daca in exemplul nostru am fi declarat variabila contor ca non-statica, atunci ea ar fi existat in atatea exemplare, cate obiecte s-ar fi creat si ar fi contorizat pentru fiecare obiect in parte numarul de apeluri ale metodeimove. In legatura cu drepturile de acces la membrii statici ai unei clase, regulile sunt aceleasi ca si pentru membrii non-statici. Dintre clasele predefinite ale mediului Java, un exemplu tipic de clasa in care majoritatea membrilor sunt statici este clasa Math din pachetul java.lang. Aceasta contine metodele necesare aplicatiilor stiintificoingineresti: functii transcendente, functii de rotunjire numerica, constantele pi si e (care sunt campuri statice ale clasei). Cu toate aceste metode putem lucra fara a crea instante ale clasei Math. De asemenea, obiectul out folosit in operatiile de afisare pe ecran este un membru static al clasei System. In fine, metoda main care apare in clasa radacina am vazut inca de la inceput ca este o metoda statica. Ea este apelata inainte ca in program sa se creeze vreun obiect.

Tot despre clase si obiecte


Simboluri speciale: null si this Colectorul de reziduuri (Garbage Collector) Metode. Transmiterea parametrilor Setul de instructiuni Lucrul cu tablouri in Java Lucrul cu siruri de caractere: clasa String Aplicatii Alte informatii Tema Simboluri speciale: null si this Asa cum am vazut in capitolele precedente, numele unei clase poate fi utilizat la fel ca numele unui tip predefinit, pentru a declara variabile. In Java variabilele al caror tip este o clasa sunt reprezentate ca referinte (adrese) spre obiecte ale clasei. Ca sa putem initializa o asemenea referinta trebuie sa generam un obiect nou sau sa folosim o alta referinta la un obiect deja existent. De exemplu, folosind clasa Punct definita in modulul 2.1, putem scrie: Punct p1 = new Punct( ); // se creaza un obiect nou Punct p2 = p1; // p2 va referi acelasi obiect ca si p1 In legatura cu referintele exista o valoare speciala, null, care poate fi atribuita unei variabile-referinta. Cand o referinta are valoarea null, aceasta inseamna ca ea nu refera nici un obiect. Cu alte cuvinte, null este echivalent cu "nici-o-adresa". In programe se poate testa daca o variabila-referinta are valoarea null: Punct p2 = null; // alte prelucrari asupra lui p2 if ( p2 == null ) FaCeva( ); else FaAltceva( ); // de exemplu p2.move(...) Observatie: valoarea null nu este atribuita automat tuturor variabilelor referinta la declararea lor. Regula este urmatoarea: daca referinta este o data-membru a unei clase si ea nu este initializata in nici un fel (vezicapitolul despre initializari), la crearea unui obiect al clasei respective referinta va primi implicit valoarea null. Daca insa referinta este o variabila locala a unei metode

17

initializarea implicita nu mai functioneaza. De aceea se recomanda ca programatorul sa realizeze INTOTDEAUNA o initializare explicita a variabilelor. class OClasa { //. . . } class AltaClasa { private OClasa r; public void metoda( ) { OClasa rloc; /* rloc nu se initializeaza implicit; corect ar fi: OClasa rloc=null; */ AltaClasa v = new AltaClasa( ); /* campul v.r se initializeaza implicit cu null */ //. . . } //. . . } Simbolul this Am vazut pana acum ca obiectele unei clase pot fi accesate de clientii lor prin intermediul referintelor. Se pune problema cum putem obtine o referinta la un obiect din INTERIORUL lui insusi, mai precis din interiorul functiilor membru ale obiectului. Pentru aceasta exista simbolul this. Simbolul this este o referinta care poate fi utilizata doar in cadrul functiilor membru non-statice ale unei clase. this este de fapt, din punctul de vedere al unei functii membru, referinta spre obiectul receptor, "posesor" al acelei functii. Se poate spune ca this reprezinta intr-un fel "constiinta de sine" a unui obiect, in sensul ca obiectul isi cunoaste adresa la care este localizat in memorie. Practic orice referire a unui membru non-static v in interiorul unei metode apartinand aceleiasi clase ca si v poate fi considerata ca echivalenta cu this.v. De exemplu, metoda move din clasa Punct definita in lucrarea precedenta poate fi scrisa sub forma: class Punct { //. . . public void move(int dx, int dy) { this.x += dx; this.y += dy; } } In realitate toate referirile la membrii non-statici ai unui obiect, din interiorul unei metode non-statice sunt considerate de catre compilatorul de Java ca si cum ar fi prefixate de this. Cand FOLOSIM explicit referinta this? Ea este necesara in primul rand acolo unde ar putea exista conflicte de nume cu datele membru ale unui obiect. De exemplu: class Punct { //. . . public Punct(int x, int y) { /* parametrii constructorului au nume respectiv identice cu cele ale datelor membru this.x = x; this.y = y; } } O alta categorie de situatii in care este necesara referinta this apare atunci cand: A. O metoda trebuie sa returneze ca rezultat o referinta la obiectul ei receptor: class Rational { // clasa care modeleaza lucrul cu numere rationale

18

} // exemplu de utilizare a clasei class ClientRational { public static void main(String[ ] arg){ Rational a = new Rational(); Rational b = new Rational(); a.adauga(5).adauga(6); /* putem apela metoda adauga in cascada, deoarece ea returneaza o referinta la un obiect Rational } }

private int numitor=1; private int numarator=0; public Rational aduna(Rational q) { numarator = numarator*q.numitor+numitor*q.numarator; numitor *= q.numitor; return this; } public Rational aduna(int n) { /* se supraincarca functia adauga */ numarator += n*numitor; return this; }

In exemplul de mai sus apelul in lant al metodei adauga se desfasoara astfel: mai intai se executa primul apel, avand ca obiect receptor obiectul indicat de referinta a; rezultatul obtinut este returnarea unei referinte spre un obiect al clasei Rational; acest obiect va fi in continuare obiectul receptor pentru al doilea apel al metodei adauga. B. Referinta la obiectul receptor trebuie transmisa ca parametru la apelul unei alte metode: class Context { private int x; private Algoritm a; /* obiectul Algoritm va executa anumite calcule pt. un obiect al clasei Context */ public Context(Algoritm a, int x) { this.a = a; this.x = x; } public int Calcul() { x = a.Calcul(this); return x; } public int getX() { return x; } } class Algoritm { public int Calcul(Context c) { return c.getX()*c.getX(); } } /* exemplu de utilizare a claselor */ class Client { public static void main(String[ ] arg){

19

} }

Algoritm a = new Algoritm(); Context c1 = new Context(a, 5); Context c2 = new Context(a, 3); int x = c1.Calcul(); int y = c2.Calcul(); //. . .

In exemplul de mai sus se observa ca acelasi obiect Algoritm deserveste 2 obiecte Context. De aceea, fiecare dintre obiectele c1 si c2 se va pasa pe sine ca parametru la apelul metodei Calcul din Algoritm. In fine, simbolul this reprezinta mijlocul prin care poate fi apelat un constructor al unei clase din interiorul altui constructor al aceleiasi clase:

class Punct { //. . . public Punct(int x, int y) { this.x = x; this.y = y; } public Punct(Punct p) { this(p.x, p.y); /* se apeleaza constructorul cu 2 parametri int */ } } Atentie: Apelul unui constructor din interiorul altui constructor al clasei in cauza NU inseamna crearea unui nou obiect, ci pur si simplu, pentru obiectul receptor curent se va executa codul constructorului apelat, la fel ca in cazul apelarii unei functii-membru obisnuite; efectul va fi atribuirea de valori in campurile obiectului, conform codului constructorului apelat. Cu alte cuvinte, putem considera ca, privind din interiorul unei clase, numele constructorilor ei sunt this(lista_parametri).

Un constructor nu poate fi apelat cu ajutorul simbolului this DECAT din interiorul altui constructor si nu al altor metode. In felul acesta se asigura indeplinirea conditiei ca pentru un obiect initializarea datelor prin constructor sa se execute O SINGURA data. Cum NU TREBUIE SA FOLOSIM referinta this? Referinta this nu poate fi modificata (nu poate aparea ca membru stang intr-o atribuire: class OClasa { //. . . public void oMetoda(OClasa ob) { this = ob; //NUUUUUUUUU!!! } //. . . } Ceea ce vrea exemplul de mai sus sa scoata in evidenta este faptul ca un obiect nu-si poate muta singur locul in memorie. Intuitiv, o atribuire ca cea din exemplu seamana cu isprava baronului Munchausen care se lauda ca odata s-a salvat dintr-o mlastina tragandu-se singur in sus de propriile urechi (chestie care contravine legii conservarii impulsului :-)).

20

Referinta this nu poate sa apara in afara corpului de instructiuni al metodelor non-statice ale unei clase sau in afara blocurilor de initializare asociate cu variabilele membru ale unei clase. Referinta this NU poate fi utilizata in interiorul unei functii-membru statice. DE CE oare ? Putem comenta in Conferinta Software Consulting.

Colectorul de reziduuri (Garbage Collector) In legatura cu obiectele claselor stim acum ca ele se creaza dinamic, folosind operatorul new. Nu am spus insa nimic referitor la eliberarea memoriei ocupate de obiecte. Si in limbajele Pascal si C exista posibilitatea alocarii dinamice a memoriei, dar programatorul are obligativitatea de a gestiona singur aceasta memorie, in sensul ca, la un moment dat mai trebuie sa si elibereze ceea ce a alocat. In Java programatorul este scutit de sarcina de a face curat in memoria dinamica. De acest lucru se ocupa o componenta a masinii virtuale (interpreterului) numita Garbage Collector (GC). Principiul de lucru al GC-ului este urmatorul: Daca spre un anumit obiect nu mai exista nici o referinta externa, in nici o functie activa, acel obiect ramane "orfan" si devine candidat la eliminare din memoria heap. Nu este obligatoriu ca un obiect sa fie sters imediat dupa ce dispare si ultima referinta spre el. Este posibil ca obiectul sa ramana alocat pana cand, in decursul executiei programului, se ajunge in situatia ca spatiul liber din heap sa nu mai fie suficient. De asemenea, este posibil ca un program sa nu aiba nevoie de memorie multa si ca urmare, sa nu trebuiasca sa se colecteze reziduurile. In cazul programelor didactice, in care numarul total al obiectelor create nu depaseste cateva zeci, practic GC-ul nici nu intervine. In exemplul de mai jos sunt ilustrate situatiile in care un anumit obiect ar putea ajunge sa fie candidat la stergere: public void oMetoda( ) { Punct p = new Punct(); p = null; /* am pierdut referinta la obiectul creat mai sus; acest obiect devine astfel candidat la stergere */ Punct q = new Punct(); //. . . } public void altaMetoda() { //. . . oMetoda(); /* la revenirea din oMetoda dispare variabila locala q; astfel, obiectul indicat de ea devine candidat la stergere */ //. . . } Pentru utilizator actiunea GC-ului este transparenta. Singurul efect care ar putea fi sesizat in unele programe ar fi o anumita incetinire a lor. Pentru a vizualiza insa activitatea GC-ului, vom incerca sa simulam o situatie de umplere a memoriei heap cu obiecte orfane, astfel incat sa-l fortam sa intre in actiune. Se pune problema cum vom vedea noi ca intr-adevar GC-ul elimina obiecte ? Aici ne bazam pe faptul ca GC nu actioneaza "fara preaviz", ci, inainte de a elimina un obiect din memorie, el incearca sa apeleze o anumita metoda speciala detinuta de obiectul respectiv. Aceasta metoda are antetul urmator: protected void finalize() throws Throwable;

21

si ea va fi executata chiar inainte ca obiectul sa dispara (incercati sa ignorati cuvantul protected , precum si clauza throws Throwable, pentru ca despre ele vom invata la capitolul cu mostenirea, respectiv cu tratarea exceptiilor). Am putea de exemplu sa definim metoda finalize astfel incat aceasta sa afiseze pe ecran un anumit mesaj. Atunci cand vom vedea pe ecran mesajul respectiv vom sti ca in momentul acela un obiect a fost eliminat de catre GC din memorie. Daca prevedem si in constructor afisarea unui mesaj, putem urmari practic traseul vietii unui obiect. In secventa de mai jos este propus un program in care sa surprindem interventiile GC-ului: import java.io.*; class OClasa { private long a; /* in variabila a vom avea un numar care sa identifice obiectul */ public OClasa(long x){ a=x; System.out.println("S-a creat obiectul "+a); } protected void finalize() throws Throwable { System.out.println("A murit obiectul "+a); } } class ClientOClasa{ public static void oMetoda(){ OClasa c = new OClasa(-1); System.out.println("am fost in metoda"); } public static void main(String[] arg){ long nr_ob = 50000; // nr. de obiecte care se vor crea for(long i=1;i<=nr_ob;i++){ OClasa ref_ob = new OClasa(i);/* creez un obiect indicat de referinta ref_ob */ ref_ob=null;// acum il las orfan (sterg referinta la el) } /* in bucla for de mai sus am "inundat" memoria heap cu obiecte orfane; cand memoria se umple, urmatoarea operatie de creare de obiect nu se va mai putea face decat dupa ce intervine GC sa faca curatenie; atunci vom constata aparitia pe ecran a mesajului tiparit de metoda finalize */ oMetoda(); System.out.println("am revenit din metoda"); /* deci am pierdut referinta la obiectul local de acolo */ System.out.println("acum ies din main"); } } Pentru a putea analiza in liniste mesajele afisate prin executia programului de mai sus, se recomanda ca in comanda de lansare in executie sa se faca o redirectare a iesirii spre un fisier care apoi va putea fi vizualizat cu un editor oarecare. Pentru aceasta, comanda de executie se va da astfel: java ClientOClasa > fis.txt Metode

22

Asa cum am aratat cand am vorbit despre componenta unei clase, functiile care fac parte dintr-o clasa se mai numesc metode sau operatii. Definitia unei metode respecta in general urmatoarea sintaxa: tip_returnat nume_metoda (lista_parametri_formali){ //corp de instructiuni } unde tip_returnat poate fi un tip primitiv, o referinta de obiect sau void. In acest din urma caz metoda nu returneaza de fapt rezultate. In Java metodele nu pot sa apara decat in interiorul claselor. In ceea ce priveste transmiterea parametrilor, aceasta se face NUMAI prin VALOARE. La transmiterea prin valoare metoda primeste o copie a parametrilor de apel. Ca urmare, orice modificare efectuata asupra parametrilor in interiorul metodei se produce asupra copiei respective, care dispare cand metoda isi incheie executia. Astfel, la revenirea in apelant modificarile nu se mai "vad". Exemplu: class OClasa{ public static void metoda1(int x){ x=10; //se modifica parametrul x System.out.println("x="+x); //valoarea tiparita pentru x este 10 } public static void metoda2(){ int a=5; metoda1(a); System.out.println("a="+a); //valoarea tiparita pentru a este 5 } } In exemplul dat parametrul metodei a fost de tip primitiv. Aspecte mai interesante apar cand parametrul este o referinta la un obiect. Practic si in acest caz transmisia se face prin valoare, dar cea care se transmite asa este referinta, adica adresa obiectului. Prin urmare, daca in interiorul metodei modificam valoarea referintei, modificarea nu va fi resimtita in apelant. In schimb, daca modificam interiorul obiectului indicat de referinta, modificarea respectiva ramane: class Masina{ private String culoare; public Masina(String c){ culoare=c; } public void schimbaCuloare(String c){ culoare=c; } public String ceCuloare(){ return culoare; } public static void metoda1(Masina m){ m.schimbaCuloare("verde"); /* am modificat interiorul obiectului indicat de m */ } public static void metoda2(Masina m){ m=new Masina("galben"); /* am modificat valoarea lui m System.out.println(m.ceCuloare()); /* aici se va tipari

23

"galben" */ } public static void metoda3(){ Masina a=new Masina("rosu"); System.out.println(a.ceCuloare()); /* aici se va tipari "rosu" */ metoda1(a); System.out.println(a.ceCuloare()); /* aici se va tipari "verde" deoarece metoda1 a modificat continutul obiectului indicat de a*/ metoda2(a); System.out.println(a.ceCuloare()); /* aici se va tipari tot "verde" */ } } Ca sa intelegem mai bine ce se intampla si de ce, intr-un caz ca cel din exemplul de mai sus, sa ne uitam putin pe figura urmatoare:

In figura este surprins momentul in care s-a intrat in metoda metoda1, inainte de a se efectua prima instructiune din ea. Se observa ca parametrul m al metodei este o copie a parametrului de apel, a, ceea ce inseamna ca ambele referinte indica spre acelasi obiect. Asa se explica de ce modificarea adusa de metoda metoda1 asupra interiorului obiectului se vede si in metoda3, la revenirea din apel. Sa vedem acum figura urmatoare:

24

Aici este surprins momentul de dupa executia primei instructiuni din metoda metoda2, adica modificarea referintei m. Acum m indica alt obiect decat a. La revenirea din metoda2 se pierd variabilele locale ale acesteia, deci si m. Ca urmare, in metoda metoda3 lucrurile se regasesc asa cum au fost lasate. E adevarat ca in heap a ramas obiectul creat de metoda metoda2, dar spre el nu mai exista nici o referinta vizibila si ca atare, acest obiect va constitui un candidat la stergere de catre colectorul de reziduuri. Setul de instructiuni In cele expuse pana acum am considerat o abordare cumva intuitiva a instructiunilor din metode, in ideea ca participantii la acest curs au deja notiuni elementare de programare (si chiar mai mult :-)) si prin urmare nu intampina probleme in a intelege o instructiune de atribuire, o instructiune de test sau o instructiune ciclica. Am accentuat mai mult aspectele legate strict de lucrul cu obiecte, care se presupune a fi ceva nou pentru cursanti. E bine insa sa dedicam un paragraf si prezentarii sistematice a setului de instructiuni Java. El se bazeaza pe setul de instructiuni din limbajele C/C++, fiind format din urmatoarele categorii principale: Atribuire Sintaxa: variabila = expresie; Semantica: se evalueaza expresia din membrul drept, iar rezultatul se depune la variabila specificata in membrul stang. In general este necesar ca cei 2 membri sa aiba acelasi tip. Expresiile din membrul drept pot fi: aritmetice, in care operatorii pot fi: + (adunare), - (scadere), * (inmultire), / (impartire) si % (modulo); logice, in care operatorii pot fi: || (sau logic), && (si logic) sau ! (negare - operator unar); operanzii expresiilor logice nu pot fi decat expresii care dau ca rezultat una din constantele booleene true sau false; de comparare, in care operatorii pot fi: == (test egalitate), != (test inegalitate), < (test mai mic), <= (test mai mic sau egal), > (test mai mare), >= (test mai mare sau egal); aceste expresii dau ca rezultat una din constantele booleene true sau false; de tip referinta; o asemenea expresie poate fi formata din: o o variabila referinta; o un apel de metoda care returneaza ca rezultat o referinta; o o creare de obiect prin aplicarea operatorului new; de selectie; o asemenea expresie are forma: conditie ? expresie1 : expresie2 unde conditie este o expresie logica sau de comparare, iar expresie1 si expresie2 sunt expresii oarecare. Rezultatul expresiei de selectie este expresie1 daca evaluarea conditiei da rezultatul true, respectiv expresie2 in caz contrar. Practic, o atribuire in care membrul drept este expresia de selectie poate fi inlocuita cu o instructiune de testare de forma: if(conditie) variabila=expresie1; else variabila=expresie2; Operanzii din expresii pot fi variabile, constante ale tipurilor primitive sau apeluri de metode, cu conditia ca acestea sa nu aiba void ca tip returnat. Instructiuni de test Sintaxa: if(conditie) instructiune1; sau if(conditie) instructiune1; else instructiune2;

25

In loc de instructiune1, respectiv instructiune2, putem avea secvente formate din mai multe instructiuni, terminate prin caracterul ;, dar atunci secventele respective trebuie incluse intre acolade. Semantica instructiunilor de test: se evalueaza conditia si daca rezultatul obtinut este true, se executa ramura instructiune1; in caz contrar se executa instructiunea ce urmeaza dupa instructiunea de test (cazul fara ramura esle) sau ramura instructiune2 (daca avem ramura else). Instructiuni de ciclare In Java avem 3 tipuri de instructiuni de ciclare: Ciclu cu test la inceput Sintaxa: while(conditie) instructiune1; sau while(conditie){ secventa_instructiuni; } Semantica: se evalueaza conditia si daca rezultatul este true, se executa instructiunile din corpul ciclului, dupa care se reia evaluarea conditiei; cand rezultatul evaluarii conditiei este false se trece la instructiunea care urmeaza instructiunii while. De aici deducem ca exista situatii in care o bucla while poate sa nu se execute niciodata, anume atunci cand conditia da false inca de la prima evaluare. Ciclu cu test la sfarsit Sintaxa: do instructiune1; while(conditie); sau do{ secventa_instructiuni; }while(conditie); Semantica: se executa instructiunile din corpul ciclului, apoi se evalueaza conditia si daca rezultatul este true se reia corpul ciclului; cand rezultatul evaluarii conditiei este false se trece la instructiunea care urmeaza instructiunii do. De aici deducem o bucla do se executa cel putin o data. Ciclu cu contor Asa cum vom vedea in continuare, ciclul cu contor din Java este de fapt o generalizare a ceea ce se intelege de obicei prin ciclu cu contor, in sensul ca executia lui nu este neaparat controlata de valoarea unui contor. Denumirea provine insa de la faptul ca acest tip de ciclu este cel mai adesea folosit in contextul cu contor. Sintaxa: for(instr1;conditie;instr2) instructiune3; sau for(instr1;conditie;instr2){ secventa_instructiuni; } Semantica: se executa instructiunea instr1, apoi se evalueaza conditia; daca rezultatul este true, se executa corpul ciclului, apoi se executa instr2 si se reia evaluarea conditiei; cand rezultatul conditiei este false se trece la instructiunea de dupa ciclu. Se observa ca si aici, ca si in cazul

26

buclei while pot exista situatii cand bucla nu se executa nici o data. In particular, oricare dintre elementele instr1, conditie si instr2 pot sa lipseasca. Daca lipseste conditie, se considera ca e ca si cum am avea acolo tot timpul rezultat true. Exemple: /* caz clasic de folosire a buclei for */ for(int i=0;i<arg.length;i++) System.out.println(arg[i]); /* se afiseaza elementul de index i al tabloului arg */ /* varianta echivalenta cu cea de mai sus */ int i=0; for(;;){ if(i>=arg.length) break; /* iesire fortata din bucla */ System.out.println(arg[i]); i++; } /* caz in care bucla for nu are contor */ double a=0.005; double b; for(b=a; b<1; a=a*2){ b=Math.sqrt(a); System.out.println("a="+a+" b="+b); } Instructiuni de control al ciclurilor Sunt instructiuni care pot sa apara in corpul unui ciclu. Instructiunea de iesire fortata din ciclu Sintaxa: break; Semantica: executia acestei instructiuni presupune parasirea ciclului si trecerea la instructiunea urmatoare celei de buclare. Asa cum vom vedea imediat, instructiunea break poate sa apara si intr-o structuraswitch. Un exemplu tipic de folosire a lui break putem vedea in exemplul de mai sus, in cadrul instructiunii for. Instructiunea de revenire la inceputul ciclului Sintaxa: continue; Semantica: executia acestei instructiuni presupune neglijarea eventualelor instructiuni de dupa continue si trecerea la evaluarea conditiei, apoi, in functie de rezultatul evaluarii reluarea obisnuita a ciclului sau parasirea lui. Exemplu: /* presupunem ca avem o bucla in care trebuie sa citim 10 numere cuprinse in intervalul [1,100] si sa calculam media lor aritmetica; in cazul in care utilizatorul introduce o valoare eronata, este invitat sa mai tasteze o data; pentru citire vom pune in comentariu secventa, pentru ca nu stim inca sa citim de la tastatura; presupunem ca cele 10 valori trebuie depuse intr-un tablou tab declarat in prealabil */ double med=0;

27

for(int i=0;i<10;i++){ //citeste tab[i] if(tab[i]<1 || tab[i]>100){ System.out.print("numar eronat, mai tastati o data:"); continue; } med=med+tab[i]; } med=med/10; Sa vedem cum interpretam secventa de mai sus: in cazul in care utilizatorul introduce un numar eronat, i se afiseaza un mesaj corespunzator, dupa care instructiunea continue determina ca executia sa se reia de la testul conditiei, adica i<10, neglijandu-se portiunea din bucla care cuprinde actualizarea variabilei med, precum si incrementarea contorului i (va aduceti aminte ca la bucla de tip for, ultima instructiune din corpul buclei este de fapt instructiunea care apare ca al treilea element intre parantezele ce urmeaza dupa cuvantul for). Deci contorul ramane la aceeasi valoare, ceea ce inseamna ca se va citi acelasi element al tabloului tab pana cand se va tasta o valoare corecta. Abia atunci ramura instructiunii if va fi "sarita", executia continuand cu actualizarea lui med si a lui i. Instructiunea de revenire din metode Sintaxa: return expresie; sau return; Prima forma este admisa doar in metodele al caror tip returnat NU este void, iar tipul expresiei trebuie sa fie compatibil cu tipul returnat. A doua forma este admisa doar in metodele care au ca tip returnat void, adica acele metode care nu returneaza de fapt rezultate. Semantica: executia unei instructiuni return are ca efect parasirea metodei curente si revenirea in apelant. Pentru metodele care returneaza rezultat, are loc copierea in stiva, pe spatiul apelantului a rezultatului returnat, care poate fi astfel utilizat in calcule sau pentru atribuire. Operatii speciale Operatii speciale de atribuire Sunt o categorie aparte de atribuiri, care utilizeaza urmatorii operatori: +=,-=,*=,/= si %=, in loc de obisnuitul =. Exemplu: a+=expr; O asemenea instructiune este echivalenta cu: a=a+expr; Similar se interpreteaza si ceilalti operatori speciali de atribuire. Operatii de incrementare/decrementare Presupun utilizarea operatorilor ++, respectiv --. Cu operatorul ++ ne-am mai intalnit in exemplele de pana acum si l-am preluat "din mers", in mod intuitiv. Acum i-a venit randul la o explicatie mai detaliata. In primul rand trebuie spus ca acesti operatori sunt operatori unari (au un singur operand) si se pot aplica in 2 moduri: prefix si postfix. Cu ajutorul unor exemple vom vedea care este diferenta dintre cele 2 moduri. Inainte de aceasta, insa, vom preciza ca efectul acestor operatori consta in cresterea/scaderea valorii operandului lor cu valoarea 1.

28

Sa consideram ca in cele ce urmeaza vom aplica operatorul de incrementare asupra unei variabile i care initial are valoarea 5. In varianta prefix sintaxa operatiei este: ++i iar in varianta postfix: i++ . Operatia de incrementare poate constitui o instructiune in sine, daca este urmata de caracterul ;. In acest caz diferenta dintre forma postfix si cea prefix nu este sesizabila. Daca insa incrementarea apare ca operand intr-o expresie, atunci cele 2 forme au efecte diferite. Fie instructiunea: a=++i; In acest caz efectul este: se mareste valoarea lui i cu 1, deci se ajunge la 6 si apoi aceasta valoare se atribuie lui a. Daca insa avem: a=i++; lucrurile se petrec asa: mai intai se ia valoarea veche a lui i, adica 5 si se atribuie lui a si dupa aceea se mareste valoarea lui i. Deci in ambele situatii i va avea in final valoarea 6, dar a va avea valori diferite. Lucrul cu tablouri in Java Ca si alte limbaje de programare de nivel inalt, limbajul Java ofera ca tip predefinit tipul tablou, cu ajutorul caruia se pot crea colectii de variabile de acelasi tip. Componentele tabloului pot fi accesate cu ajutorul unui index. Acesta trebuie sa apartina unui tip intreg, iar numerotarea elementelor intr-un tablou incepe intotdeauna de la 0. Pentru a declara un tablou se utilizeaza notatia: tip_element[ ] nume_tablou; O simpla declaratie ca aceasta nu presupune alocare de spatiu. In Java elementele unui tablou se aloca DINAMIC, specificandu-se dimensiunea tabloului: tip_element[ ] nume_tablou = new tip_element[numar_elemente]; In Java numele unui tablou este considerat ca o REFERINTA DE OBIECT, iar tabloul propriu-zis este un obiect. Dupa ce s-a rezervat spatiu pentru elemente NU mai putem modifica dimensiunea tabloului, decat daca cream un alt tablou, asa ca in exemplul de mai jos: int [ ] tab = new int[10]; // am creat un tablou de 10 intregi /* acum ii completez cumva elementele si constat ca imi trebuie un tablou mai mare iata cum procedez: mai intai creez un alt tabou, cu dimensiunile dorite (de ex. 20) */ int [ ] tab1 = new int[20]; /* apoi mut elementele vechiului tablou in cel nou: */ for(int i=0; i<10; i++) tab1[ i ] = tab[ i ]; /* dupa care actualizez referinta tab pt. a indica spre noul tablou */ tab = tab1; /* acum vechiul tablou se pierde si devine un candidat la stergere */ La crearea unui tablou se memoreaza dimensiunile sale, programatorul putand in orice moment sa le cunoasca, prin intermediul atributului length: static void oFunctie(int[ ] tabp) { //. . . for(int i=0; i < tabp.length; i++) { /* tabp.length ne da

29

} public static void main(String[ ] arg) { int [ ] tab = new int[10]; //. . . oFunctie(tab); //. . . } Din acest exemplu se observa ca dimensiunea unui tablou este cunoscuta oriunde in program, chiar si cand tabloul este transmis ca parametru. Putem sa ne imaginam ca un obiect de tip tablou arata asa ca in figura de mai jos:

dimensiunea tabloului */ // prelucreaza tabp[ i ] }

Faptul ca pentru fiecare tablou se memoreaza dimensiunea permite ca la executie sa se detecteze orice tentativa de depasire a limitelor tablourilor. Asemenea tentative se sanctioneaza prin emiterea cate unei exceptiiIndexOutOfBoundsException. In Java tipul tablou este considerat ca o clasa, iar in aceasta calitate, asa ca oricare alta clasa Java, el mosteneste implicit clasa predefinita Object (mai multe despre acest subiect in modulul cu mostenirea). Ceea ce deosebeste un tablou fata de alte obiecte este, in esenta, posibilitatea de a aplica asupra lui operatorul de indexare pentru a-i accesa elementele. Nici o alta clasa din Java nu are definita operatia de indexare. Daca elementele unui tablou sunt la randul lor de tip clasa, aceasta inseamna ca de fapt elementele respective vor fi referinte la obiecte ale acelei clase. La crearea tabloului se rezerva spatiu DOAR pentru acele referinte si ele sunt initializate cu valoarea null. Daca dorim ca acele referinte sa indice spre obiecte propriu-zise, trebuie fie sa cream obiectele in mod dinamic, asa cum am vazut in modulele precedente, fie sa atribuim elementelor tabloului valoarea altor referinte nenule: Punct [ ] tab = new Punct[10]; // am creat un tablou de 10 REFERINTE la Punct //. . . for(int i =0; i< tab.length; i++) tab[i] = new Punct( ); // acum creez si OBIECTELE de tip Punct Tablouri bidimensionale Java accepta si declaratii de tablouri bidimensionale: tip_element[ ] [ ] nume_matrice; Un tablou bidimensional este considerat de fapt ca un tablou de tablouri. Deci, elementele unui tablou bidimensional sunt referinte la tablouri unidimensionale. Rezervarea de spatiu pentru elementele unui tablou bidimensional se poate face in urmatoarele variante: toate liniile tabloului contin ACELASI numar de elemente: nume_matrice = new tip_element[nr_linii][nr_coloane]; fiecare linie a tabloului contine un numar DIFERIT de elemente:

30

nume_matrice = new tip_element[nr_linii][ ]; for(int i = 0; i< nume_matrice.length; i++) nume_matrice[i] = new tip_element[i+2]; in aceasta secventa s-a creat o matrice in care prima linie are 2 elemente, a 2-a linie 3 elemente, s.a.m.d; in continuare, pentru a prelucra elementele matricii vom proceda ca in secventa: for(int i=0; i < nume_matrice.length; i++) for(int j=0; j < nume_matrice[i].length; j++) // prelucreaza nume_matrice[ i ][ j ] In secventele prezentate nume_matrice.length ne da numarul de linii, iar nume_matrice[i].length ne da numarul de elemente de pe linia i. Lucrul cu siruri de caractere: clasa String Clasa String este una dintre clasele predefinite fundamentale, care modeleaza lucrul cu siruri de caractere. Modul cel mai simplu de manipulare a obiectelor de tip String il constituie constantele String, care sunt siruri de caractere incadradate de ghilimele ("). Orice referire a unei asemenea constante in cadrul unui program Java are ca efect crearea unui obiect String care contine secventa de caractere respectiva. Exemplu: String s1 = "un sir"; Efectul frazei de mai sus este urmatorul :

Putem crea un obiect String si prin aplicarea unui constructor: String s2 = new String(s1); In acest exemplu obiectul indicat de s2 va avea acelasi continut ca si cel indicat de s1. Intrebare: ce ar fi trebuit sa facem pentru ca s1 si s2 sa indice unul si acelasi obiect? Alti constructori ai clasei String mai sunt: constructorul no-arg care creaza un sir vid: String svid = new String( );

constructorul care ia ca parametru un tablou cu elemente de tip char: char[ ] tc = {'a','b','c'}; String stc = new String(tc); Sirurile de caractere se pot obtine si prin diverse prelucrari ale unor siruri existente. In acest scop clasa String ofera un set de metode dintre care enumeram: String toUpperCase(); String toLowerCase(); String substring(int); String substring(int, int);

31

Metodele clasei String includ si operatii care permit consultarea stringurilor, respectiv obtinerea de informatii cu privire la continutul lor: int length(); char charAt(int); int indexOf(char); int indexOf(String); int lastIndexOf(char); int lastIndexOf(String); Semnificatia acestor metode, precum si alte informatii legate de clasa String se gasesc in documentatia API si cursantii sunt rugati sa cerceteze aceasta documentatie (a se vedea adresa in rubrica Linkuri). In afara de cele enumerate mai sus, clasa String mai este dotata si cu operatii de uz general, existente si in alte clase de altfel, printre care amintim: operatiile de comparare si cele de conversie. Despre acestea vom vorbi in modulul urmator. O proprietate foarte importanta a metodelor clasei String este aceea ca ele nu modifica NICIODATA obiectul receptor, ci, atunci cand este necesar, creaza obiecte noi in care este inclus sirul de caractere modificat. Fie spre exemplu urmatoarea secventa: String s1 = "un sir"; String s2 = s1.toUpperCase(); Desi am fi tentati sa credem ca efectul acestei secvente consta in transformarea sirului continut in obiectul indicat de s1, astfel incat toate literele sa devina majuscule, in realitate ceea ce se intampla este ilustrat in figura urmatoare:

Deci obiectul indicat de s1 si-a pastrat continutul nemodificat. Metoda toUpperCase a creat un obiect NOU in care a plasat sirul cu litere mari si a returnat ca rezultat o referinta la acest nou obiect. In concluzie, putem spune ca un obiect String odata creat nu mai poate fi modificat in nici un fel. Concatenarea stringurilor In Java aceasta operatie se realizeaza cu ajutorul operatorului + . Efectul este crearea unui obiect nou, al carui continut va fi sirul rezultat prin concatenare. Exemplu: String s1 = "codul "; String s2 = "penal"; String s3 = s1 + s2; Efectul secventei de mai sus este ilustrat in urmatoarea figura:

32

Sa rescriem acum exemplul de mai sus sub forma: String s1 = "codul "; String s2 = s1 + "penal"; Efectul acestei secvente este ilustrat mai jos:

Deosebirea fata de cazul anterior o constituie faptul ca obiectul care contine sirul "penal" nu este indicat de nici o referinta. Acest obiect a fost creat ca urmare a regulii ca orice constanta sir de caractere dintr-un program Java presupune crearea unui obiect nou, dar adresa lui nu a fost memorata explicit in nici o variabila a utilizatorului. Ca atare, dupa evaluarea expresiei de concatenare, obiectul ramane izolat in heap si nu mai poate fi accesat. Asemenea obiecte "orfane" vor fi eliminate din memorie la prima "razie" a colectorului de reziduuri. In legatura cu operatia de concatenare a stringurilor, trebuie facute urmatoarele observatii: Clasa String este singura asupra careia se poate aplica operatorul +. In afara de ea, doar tipurile primitive numerice si tipul char mai au definit acest operator (cu alta semnificatie insa). Intr-o expresie de concatenare a stringurilor pot sa apara, pe langa obiecte string, si valori ale tipurilor primitive, acestea fiind automat convertite la string: int a = 6; String s1 = "tab[" + a + "]="; /* s1 va contine sirul "tab[6]=" */ String s2 = s1+8.5; /* s2 va contine sirul "tab[6]=8.5" */ Artificiu: o metoda foarte simpla de a converti o valoare primitiva in string este aceea de a o concatena cu sirul vid; de exemplu: String s1 = "" + a; Intr-o expresie de concatenare putem sa avem ca operanzi si obiecte ale altor clase decat String, acestea fiind convertite la String, dar intr-un mod mai special decat

33

valorile primitive. In capitolul despre conversii de date vom vedea cum. Cu alte cuvinte, operatorul + folosit pentru obiecte are un singur sens: concatenare de siruri. Stringurile si tablourile de caractere Desi aparent un string si un tablou de caractere (tipul char[ ] ) modeleaza acelasi lucru, adica o secventa de caractere, cele doua tipuri se comporta diferit. In primul rand, asa cum am vazut, unui obiect String nu i se poate modifica continutul. Unui tablou de caractere i se poate modifica oricare dintre elemente: String vs; char[ ] tc; //. . . tc[pos] = 'x'; //corect //pentru vs nu avem o posibilitate similara Pentru a obtine caracterul aflat la o anumita pozitie pos intr-un String, vs de exemplu, nu putem sa folosim notatia vs[pos] ca n cazul tablourilor, ci trebuie sa utilizam metoda charAt(pos): String vs; char[ ] tc; //. . . char c1 = tc[pos]; //corect char c2 = vs.charAt(pos); //corect char c3 = tc.charAt(pos); //eroare char c4 = vs[pos]; //eroare Atributul length aplicabil pentru tablouri nu are tocmai aceeasi semnificatie cu metoda length( ) de la stringuri, asa dupa cum va rezulta din urmatoarea secventa: String vs = "ababu"; char[ ] tc = new char[10]; /* am creat un tablou cu 10 elemente de tip char */ for(int i=0; i<vs.length(); i++) tc[i] = vs.charAt(i); /* am pus in tc aceleasi caractere ca si in vs */ System.out.println("Lungimea lui vs = "+vs.length()); System.out.println("Lungimea lui tc = "+tc.length);} Rezultatul afisat de acest program va fi: Lungimea lui vs = 5 Lungimea lui tc = 10 Cu alte cuvinte, nu putem sa aflam cate caractere semnificative se afla intr-un tablou de caractere, decat daca parcurgem tabloul element cu element si le testam. Intre tipurile String si char[ ] exista totusi o legatura, in sensul ca limbajul Java ofera metode de conversie prin care putem sa tranformam un String n tablou de caractere si viceversa: String vs="blabla"; char[ ] tc = vs.toCharArray(); /* crearea unui tablou dintr-un String */ char[ ] atc = new char[5]; for(int i=0; i < atc.length; i++)> atc[i] = '0'+i; /* completez elementele tabloului cu caracterele '0','1','2',... */ String avs = new String(atc, 0, atc.length); /* crearea unui String pe baza unui tablou */

34

Metoda toCharArray creaza un tablou ale carui elemente sunt caracterele stringului sursa. Operatia inversa se realizeaza cu ajutorul unui constructor al clasei String. Acesta n principiu poate initializa un string preluand un subsir din tabloul de caractere. Parametrii necesari sunt: referinta tabloului, indicele primului caracter din subsirul dorit si lungimea acestui subsir. In exemplul nostru subsirul cuprinde tot tabloul. Aplicatii

/** * Aplicatia afiseaza tripletele Pitagora in care numerele sunt <= o valoare maxima * Valorile in triplete vor fi in ordine crescatoare * Se face observatia ca nu exista triplete cu doua valori egale */ // Modificati programul astfel incat valoarea maxima sa se citeasca si // la sfarsit sa se afiseze numarul de solutii import javax.swing.JOptionPane; public class NumerePitagora { public static void main (String [] args) { tiparesc final int numarGrup = 10; // grup de triplete ce se final int maxVal = 100; // valoare maxima String mesaj = ""; // mesaj de iesire int contor = 0; // numara tripletele gasite, cand sunt numarGrup, se tiparesc // determinare triplete for (int i = 1; i <= maxVal; i++) for (int j = i + 1; j <= maxVal; j++) // for (int k = j + 1; k <= maxVal; k++) // verificare relatie if ( mesaj + ", " + k + " )\n"; // incrementare contor i*i + j*j == k*k ) {

// se adauga solutia la mesaj += "( " + i + ", " + j

35

contor ++; if (contor == numarGrup) { // tiparire si punere contor pe 0 JOptionPane.showMessageDialog(null,mesaj, Pitagora",JOptionPane.INFORMATION_MESSAGE); contor = 0; "Numere mesaj = ""; } // if contor } // if relatie // iesire for-uri incuibate if (contor != 0 ) // mai sunt solutii netiparite JOptionPane.showMessageDialog(null,mesaj, "Numere Pitagora",JOptionPane.INFORMATION_MESSAGE); System.exit(0); } }

Operatii asupra obiectelor. Operatii de intrareiesire


Compararea obiectelor Clasele infasuratoare Conversii de date intre tipul String si tipurile primitive Metoda toString Operatii de intrare-iesire Citirea fisierelor de text disponibile pe Internet Alte informatii Tema

Compararea obiectelor Compararea la egalitate Una dintre operatiile cele mai frecvente de comparare a obiectelor este testarea egalitatii. In Java aceasta operatie poate avea doua sensuri: testarea identitatii: in acest caz ceea ce se compara de fapt sunt 2 referinte de obiect, iar testul presupune a verifica daca cele 2 referinte indica unul si acelasi obiect. Testarea identitatii se realizeaza cu ajutorul operatorului relational de egalitate ==;

36

testarea echivalentei: in acest caz operanzii sunt 2 obiecte propriu-zise si testul presupune a verifica daca cele 2 obiecte au acelasi continut. De regula, pentru testarea echivalentei se utilizeaza o metoda denumita equals, operanzii fiind un obiect receptor si unul dat ca parametru metodei. Aceasta metoda este definita in clasa Object si drept urmare ea este mostenita de toate clasele ( de ce se intampla lucrul acesta vom discuta mai in detaliu in capitolul cu mostenirea ).

Problema este ca definitia metodei equals, asa cum apare ea in clasa Object, pentru majoritatea claselor utilizatorului nu este satisfacatoare, deoarece metoda returneaza valoarea true doar daca cei 2 operanzi sunt unul si acelasi obiect (deci doar in caz de identitate). Ca urmare, programatorul va trebui sa-si defineasca propriile variante de equals, acolo unde este cazul. Daca luam spre exemplu clasa Punct, prezentata in modulul 3, o posibila implementare a metodei equalspentru ea ar fi urmatoarea: class Punct{ //. . . public boolean equals(Punct p) { return ((x==p.x)&&(y==p.y)); } } public class ClientPunct { public void oMetoda( ) { Punct p1 = new Punct(1,2); Punct p2 = new Punct(1,2); Punct p3 = p1; boolean t1 = (p1==p2); /* test de identitate; rezultat: false */ boolean t2 = p1.equals(p2); /* test de echivalenta; rezultat: true */ boolean t3 = (p1==p3); /* test de identitate; rezultat: true */ //. . . } } Se observa din implementarea metodei equals ca 2 obiecte Punct sunt considerate "egale" in sensul echivalentei, daca valorile coordonatelor lor sunt respectiv egale. Rezultatele obtinute la testele de egalitate din clasa ClientPunct pot fi mai usor intelese daca se analizeaza figura de mai jos, in care este redata configuratia memoriei corespunzatoare secventei de program in discutie.

37

Programatorul nu este obligat sa dea numele equals metodelor care testeaza echivalenta pentru clasele definite de el, dar acest nume constituie un fel de standard in programele Java. De exemplu, in clasele predefinite care implementeaza colectii de obiecte ( despre care vom invata in modulul urmator ) operatiile de cautare folosesc implicit pentru comparare metoda equals. Cele mai multe dintre clasele predefinite ale mediului Java contin variante adecvate ale metodei equals, care realizeaza testarea continutului obiectelor respective. Clasa String nu face exceptie de la regulile expuse la inceputul acestui paragraf. Ca atare, vom utiliza operatorul == pentru a testa identitatea obiectelor String si metoda equals pentru a testa egalitatea sirurilor de caractere continute de obiectele String. Exemplu: class OClasa { public void oMetoda() { String s1 = "ababu"; String s2 = "ababu"; String s3 = s1; String s4 = "ababa"; boolean t1 = (s1==s2); /* test de identitate; rezultat: true; desi aici ar parea ca sunt 2 obiecte distincte, in realitate, cand se foloseste o constanta String care mai apare o data in program, masina virtuala nu mai creaza inca un obiect pentru a nu consuma inutil memoria */ boolean t2 = s1.equals(s2); /* test de echivalenta; rezultat: true */ boolean t3 = (s1==s3); /* test de identitate; rezultat: true */ boolean t4 = s1.equals(s4); /* test de echivalenta; rezultat: false */ //. . . } } In particular, clasa String mai contine o metoda de testare a echivalentei, care insa nu considera literele mari ca fiind diferite de omoloagele lor mici. Aceasta metoda se numeste equalsIgnoreCase. De exemplu, considerand referinta s1 din secventa de mai sus, apelul s1.equalsIgnoreCase("aBAbu") ar da rezultatul true. In cazul variabilelor si constantelor tipurilor primitive avem un singur fel de testare a egalitatii, si anume testarea egalitatii valorilor lor, care se realizeaza folosind operatorul ==. Compararea la mai mic/mare Acest tip de comparare se poate aplica intre obiecte asupra carora este definita o relatie de ordine totala. Aceasta inseamna ca, avand 2 asemenea obiecte, putem decide ca unul dintre ele este "mai mic" decat celalalt. Bineinteles ca relatia de ordine se refera la continutul obiectelor. In cazul obiectelor de tip String de exemplu, relatia de ordine care exista intre ele este ordinea lexicografica ce caracterizeaza sirurile de caractere. Metoda folosita pentru a testa relatia mai mic/mare este compareTo. Aceasta metoda returneaza: o valoare negativa daca obiectul receptor este mai mic decat cel dat ca parametru, valoarea zero daca cele 2 obiecte sunt echivalente si respectiv o valoare pozitiva daca obiectul receptor este mai mare decat cel dat ca parametru. Exemplu: class OClasa {

38

public void oMetoda() { String s1 = "ababu"; String s2 = "ababu"; String s3 = s1; String s4 = "ababa"; int t1 = s1.compareTo(s2); /* t1 ia valoarea 0 */ int t2 = s1.compareTo(s4); /* t2 ia o valoare pozitiva */ int t3 = s4.compareTo(s3); / t3 ia o valoare negativa */ //. . . } } In cazul clasei Punct se observa ca nu avem o relatie de ordine prestabilita, adica nu putem spune ca un punct e mai mare decat altul. Prin urmare, nu are sens sa comparam 2 obiecte de tip Punct la mai mare/mic. De altfel metoda compareTo, spre deosebire de equals nu mai este automat mostenita de la Object. Este adevarat ca programatorul poate sa impuna o anumita relatie de ordine, definita de el, asupra obiectelor unei clase si sa definesca in acea clasa metoda compareTo. In capitolul despre colectii de obiecte vom vedea cum putem sa facem asa ceva. In cazul claselor predefinite, acolo unde exista o relatie de ordine prestabilita, exista si metoda compareTo. In clasa String exista in particular si o alta varianta a acestei metode, care permite compararea fara a tine cont daca literele sunt mari sau mici: compareToIgnoreCase. Pentru tipurile primitive comparatia la mai mic/mare se realizeaza ca si in alte limbaje de programare, cu ajutorul operatorilor relationali: <, <=, >= si >. Acesti operatori NU pot fi folositi daca operanzii sunt referinte de obiecte. Clasele infasuratoare Tuturor tipurilor primitive ale limbajului Java le corespunde cate o clasa infasuratoare. Aceste clase au fost definite din 2 motive: sa inglobeze metode de conversie intre valori ale tipurilor primitive si obiecte ale altor clase, precum si constante speciale legate de tipurile primare; sa permita crearea de obiecte care sa contina valori ale tipurilor primare si care apoi sa poata fi utilizate ca elemente ale unor structuri de tip colectie unde nu pot sa apara decat obiecte, nu date simple ( despre aceasta vom invata in modulul urmator ). Clasele infasuratoare - Type-Wrapper Classes - sunt definite in pachetul java.lang si ele sunt: Integer pentru int Short pentru short Byte pentru byte Long pentru long Float pentru float Double pentru double Boolean pentru boolean Character pentru char Void pentru void Un obiect al unei clase infasuratoare contine ca data esentiala o valoare a tipului primitiv corespondent. Cu alte cuvinte, un asemenea obiect "infasoara" valoarea tipului primitiv ( de aici vine si denumirea de clase infasuratoare ). Clientul nu are acces direct la acea valoare, ci va trebui sa utilizeze o metoda care returneaza valoarea, asa cum vom vedea imediat mai jos. Exceptand clasa Void, restul claselor infasuratoare contin urmatoarele metode: cate un constructor care accepta ca parametru o valoare a tipului primitiv corespondent. Exemplu: Integer a = new Integer(5); Double b = new Double(12.13);

39

cate un constructor care accepta ca parametru o referinta String ( exceptie face clasa Character care nu are acest constructor ). Se presupune ca stringul respectiv contine o secventa compatibila cu tipul primitiv corespondent. Exemplu: Integer a = new Integer("5"); String s="143.55"; Double d = new Double(s); Boolean b = new Boolean("true"); In cazul clasei Boolean trebuie precizat ca daca stringul dat ca parametru constructorului contine altceva decat cuvantul "true" ( indiferent daca e cu litere mari sau mici ), sirul respectiv este interpretat ca "false".

o metoda statica valueOf care accepta ca parametru un String si returneaza un obiect al clasei infasuratoare, convertind continutul stringului; apelul acestei metode este echivalent cu a executa un new cu constructorul descris mai inainte. Exemplu: Integer a = Integer.valueOf("5"); Double b = Double.valueOf("-8.12"); o metoda toString care realizeaza practic conversia de la clasa infasuratoare la String ( despre aceasta metoda vom vorbi mai jos ). o metoda typeValue, unde type este numele tipului primitiv corespondent; aceasta metoda returneaza continutul obiectului ca valoare a tipului primitiv. Exemplu: Integer a = new Integer("5"); int x = a.intValue( ); Double b = new Double("3.14"); double y = b.doubleValue( ); o metoda equals care realizeaza testul echivalentei obiectelor. o metoda compareTo care accepta ca parametru un obiect al aceleeasi clase infasuratoare ca si obiectul receptor. Metoda returneaza o valoare de tip int pozitiva, nula sau negativa dupa cum continutul obiectului receptor este mai mare, egal sau mai mic decat cel al parametrului. Exemplu: Integer a = new Integer(7);

Integer b = new Integer(10); int x = b.compareTo(a); //val. x va fi pozitiva metode statice care realizeaza conversia unui String la o valoare primitiva; aceste metode le vom studia chiar in paragraful urmator. Pe langa aceste metode, unele dintre clasele infasuratoare mai contin si alte metode si variabile membru specifice. Astfel, clasele care reprezinta tipuri numerice contin constantele statice MIN_VALUE si MAX_VALUE( vezi exemplul de la capitolul cu tipurile primitive, unde va spuneam sa aveti putintica rabdare pana ajungeti la clasele infasuratoare :-)). In plus, clasele Float si Double, care modeleaza lucrul cu numere reale mai contin: constantele statice POSITIVE_INFINITY si NEGATIVE_INFINITY, reprezentand valorile +infinit si -infinit; metoda isInfinite care returneaza true daca valoarea obiectului receptor este fie +infinit, fie -infinit. Atentie: limbajul Java NU permite utilizarea operatorilor aritmetici pentru obiecte ale claselor infasuratoare! Doar operatorul + poate fi folosit, dar cu sensul de concatenare de siruri ( vezi si metoda toString). Pentru calcule se folosesc doar valorile tipurilor primitive. Conversii de date intre tipul String si tipurile primitive Intr-un program Java conversiile intre tipul String si tipurile primitive sunt necesare, in primul rand pentru ca datele de intrare ale programului sunt receptate in majoritatea cazurilor sub forma de siruri de caractere: fie ca sunt pasate ca argumente la lansarea in executie a programului, fie ca sunt citite de pe

40

un suport extern. In Java nu avem ceea ce in C se numea functie de citire formatata ( scanf ). Ca urmare, odata receptionate de catre program, datele de intrare vor trebui convertite in functie de necesitati. In ceea ce priveste conversiile, in Java exista o regula care spune ca: intotdeauna tipul spre care se face conversia trebuie sa aiba metoda necesara conversiei. In acest sens, clasa String este dotata cu metode numite valueOf care asigura conversiile de la tipurile primitive spre String. Exemplu: class oClasa { public void oMetoda( ) { int a = 4; double b = 13.2; boolean c = true; String s1 = String.valueOf(a); String s2 = String.valueOf(b); String s3 = String.valueOf(c); //. . . } } Din secventa de mai sus deducem ca valueOf este o metoda statica, supraincarcata astfel incat sa admita ca parametri valori ale tuturor tipurilor primitive. Efectul metodei este crearea unui obiect String al carui continut este obtinut prin conversia la sir de caractere a valorii parametrului. Ca rezultat, metoda returneaza o referinta la obiectul String creat. Pentru a realiza conversiile in sens invers, adica de la String la tipurile primitiva, conform regulii enuntate la inceputul acestui paragraf, ar trebui ca fiecare tip primitiv sa aiba cate o metoda adecvata. Cum nu putem dota un tip primitiv cu alte operatii decat cele predefinite, s-a recurs la folosirea claselor infasuratoare ca detinatoare ale metodelor de conversie. Probabil ca va si imaginati cam care ar fi procedura: mai intai se creaza un obiect al clasei infasuratoare folosind constructorul care accepta ca parametru un string, apoi se apeleaza metoda typeValue a obiectului creat pentru a obtine valoarea primitiva dorita. Spre exemplu, daca dorim sa transformam un sir de caractere care contine un numar intreg intr-o valoare de tip int putem proceda astfel: public class Client{ public static void main(String[] arg){ //presupunem ca in elementul arg[0] avem un numar intreg Integer i = new Integer(arg[0]); /* am creat un obiect Integer dand constructorului parametrul arg[0], care este un string */ int x = i.intValue(); /* extrag valoarea de tip int din obiectul i */ /* in continuare prelucrez variabila x dupa necesitati */ } } Daca se compileaza acest program si se lanseaza in executie cu comanda java Client 2734 stringul 2734 dat ca argument va si transformat din sir de caractere in numar intreg in reprezentare int. In afara de modalitatea de conversie prezentata mai sus, exista si una mai simpla care se bazeaza pe metode statice ale claselor infasuratoare, ceea ce inseamna ca putem face conversia fara a crea obiecte ale acestor clase. Astfel, exemplul de mai sus poate fi rescris dupa cum urmeaza:

41

public class Client{ public static void main(String[] arg){ //presupunem ca in elementul arg[0] avem un numar intreg int x = Integer.parseInt(arg[0]); /* convertesc stringul arg[0] la int folosind metoda parseInt a clasei Integer */ /* in continuare prelucrez variabila x dupa necesitati */ } } In cazul claselor infasuratoare care reprezinta tipuri numerice exista metodele parseType, unde Type este numele tipului primitiv corespondent scris cu prima litera majuscula; metoda accepta ca parametru stringul care trebuie convertit si returneaza valoarea corespunzatoare, in reprezentarea tipului primitiv respectiv. Pentru tipul boolean exista metoda getBoolean care functioneaza similar metodei parseType. public class Client{ public static void main(String[] arg){ String s1="67"; String s2="13.2"; String s3="true"; short a = Short.parseShort(s1); double b = Double.parseDouble(s2); boolean c = Boolean.getBoolean(s3); //. . . } } De retinut: Pentru versiunea 1 a limbajului Java, metodele parseType au fost prevazute doar la tipurile numerice intregi (byte, short, int si long); in cazul tipurilor reale conversia trebuia facuta asa cum s-a aratat la prima modalitate, adica prin crearea mai intai a unui obiect al clasei infasuratoare si apoi prin extragerea valorii primitive continute. Abia la versiunea Java 2 s-au generalizat metodele parseType la toate clasele numerice. In cazul tipurilor numerice, daca stringul de convertit nu contine o secventa conforma cu tipul primitiv spre care se face conversia, la executia metodei parseType se va genera o exceptie de tipNumberFormatException. Acelasi lucru se intampla daca se incearca sa se creeze un obiect al clasei infasuratoare, folosind constructorul care accepta ca parametru un string. Exemplu: public class Client{ public static void main(String[] arg){ String s1="18.3"; String s2="13.2a"; short a = Short.parseShort(s1); // se va genera eroare Double ob = new Double(s2); // se va genera eroare //. . . } } In figura de mai jos este redat sintetic mecanismul de conversie a datelor de la/spre tipurile primitive in Java:

42

Metoda toString Cand am vorbit despre concatenarea stringurilor am aratat ca daca intr-o expresie de concatenare apar si valori ale tipurilor primitive, acestea sunt convertite automat la string. Se pune intrebarea ce se intampla daca intre operanzii unei concatenari apar si obiecte ale altor clase, diferite de String, de exemplu obiecte de tip Punct? Ei bine, in acest caz, pentru obiectele respective se va apela automat o metoda numita toString, care returneaza un obiect String si acesta va participa de fapt la concatenare. Bine, bine, veti zice, dar ce ne facem daca obiectele noastre nu contin metoda toString? Raspunsul este: obiectele noastre contin INTOTDEAUNA o metoda toString, pentru ca o mostenesc de la clasa Object, care este "mama" tuturor claselor dintr-un program Java. Metoda toString asa cum este ea definita in clasa Object returneaza un sir de caractere care contine numele clasei si adresa obiectului receptor. De exemplu: public class Client{ public static void main(String[] arg){ Client ob = new Client(); String s = "obiectul ob este: "+ob; /* aici am concatenat un string cu un obiect al clasei Client; expresia de concatenare este echivalenta cu: "obiectul ob este: "+ob.toString()*/ System.out.println(s); } } In urma executiei acestui program eu am obtinut textul: obiectul ob este: Client@11385fc Daca veti rula si voi programul este foarte probabil ca valoarea adresei (cea care apare dupa caracterul "@") sa fie alta. Aceasta depinde de locul din memoria heap care se aloca obiectului respectiv. Sa vedem ce se intampla daca in loc de un obiect Client as folosi la concatenare un obiect Integer de exemplu: public class Client{ public static void main(String[] arg){ Integer ob = new Integer(427); String s = "obiectul ob este: "+ob; /* aici am concatenat un string cu un obiect al clasei Integer */ System.out.println(s); }

43

} Ruland acest exemplu, voi obtine pe ecran mesajul: obiectul ob este: 427 Cu alte cuvinte, metoda toString s-a comportat altfel pentru obiectele Integer. Explicatia sta in faptul ca proiectantii clasei Integer nu s-au multumit cu ceea ce le-a oferit clasa Object, ci au prevazut o varianta proprie de metoda toString, care sa construiasca un sir de caractere pe baza continutului obiectului Integer, si nu pe baza adresei lui. In fond, pe un client al clasei Integer il intereseaza mai degraba ce contin obiectele acestei clase. Mergand mai departe, trebuie spus ca si programatorul obisnuit isi poate prevedea in clasele sale variante proprii ale metodei toString, astfel incat aceasta sa construiasca siruri de caractere pe baza datelor interne ale obiectelor. Vom lua ca exemplu clasa Punct si vom presupune ca dorim sa afisam coordonatele punctelor sub forma: (coord_x, coord_y) Pentru aceasta vom proceda dupa cum urmeaza: public class Punct{ private int x; private int y; public Punct(int xx, int yy){ x = xx; y = yy; } public String toString(){ return "(" + x + ", " + y + ")"; caractere pe baza valorilor x si } // alte metode } class Client{ public static void main(String[] arg){ Punct p1 = new Punct(4, 7); Punct p2 = new Punct(16, 3); System.out.println("punctul p1 = System.out.println("punctul p2 = } }

/* am format un sir de y ale obiectului receptor */

"+p1); "+p2);

Incercati sa rulati acest exemplu, sa vedeti ce obtineti. Concluzii: Metoda toString poate fi vazuta ca o modalitate de conversie a obiectelor la tipul String (desi in realitate nu se face altceva decat sa se creeze un obiect String folosind valori interne ale altui obiect). Nu este obligatoriu ca programatorul sa denumeasca aceasta metoda toString. Doar ca acest nume constituie un fel de "standard" in Java, in sensul ca intr-o concatenare de stringuri el va fi apelat automat, fara sa-l specificam noi explicit. Daca programatorul foloseste alt nume, atunci va trebui sa apeleze explicit metoda respectiva. Situatiile in care ne folosim cel mai frecvent de metoda toString sunt acelea in care dorim sa tiparim continutul unor obiecte, asa cum am vazut in exemplele de mai sus. Tot aici trebuie spus ca in clasele de biblioteca pentru care are sens sa afisam obiectele, este redefinita metoda toString. Consultand documentatia API pentru diverse clase, se poate vedea acest lucru.

44

Operatii de intrare-iesire Pachetul java.io este un set de clase cu ajutorul carora se realizeaza operatiile de intrare/iesire intr-un program Java. In Java operatiile de intrare/iesire se bazeaza pe conceptul de flux de date (stream). Un flux de date este o secventa de date care se deplaseaza dinspre o sursa externa spre memorie - caz in care avem de a face cu un flux de intrare (input stream) sau din memorie spre o destinatie externa - flux de iesire (output stream). Pentru operatiile de intrare/iesire cele mai frecvente sursa externa este tastatura, iar destinatia este ecranul monitorului. Acestea se mai numesc si suporturi standard de intrare, respectiv iesire. Corespunzator suporturilor standard, in Java exista 2 obiecte predefinite: System.in pentru tastatura si System.out pentru monitor. Observatie:in si out sunt variabile membru statice ale clasei System definita in pachetul java.lang. Tipurile acestor variabile sunt clase din pachetul java.io, si anume: System.in este o referinta la clasaInputStream, iar System.out este o referinta la clasa PrintStream. Daca dorim sa citim sau sa scriem date din/in fisiere de pe disc, va trebui ca noi sa creem si sa utilizam obiecte ale unor clase din pachetul java.io. In cele ce urmeaza vom studia posibilitatea de a efectua operatii de intrare/iesire la nivel de linii de text. Operatii de citire a liniilor de text Clasa care modeleaza citirea unei linii dintr-un flux de intrare este BufferedReader, prin operatia readLine. Aceasta operatie nu are parametri, iar executia ei are ca efect citirea din fluxul de intrare a unei secvente de caractere pana la intalnirea terminatorului de linie. Operatia returneaza o referinta la un obiect String care contine caracterele citite, dar fara a include si terminatorul de linie (caracterul '\n'). Cu alte cuvinte, stringul returnat contine doar caracterele utile (semnificative) ale liniei. Daca s-a ajuns la sfarsitul fluxului de intrare, operatia returneaza valoarea null. Daca citirea nu se poate desfasura, operatia emite o exceptie de tip IOException (despre exceptii vom mai discuta ). De aceea, antetul unei functii care apeleaza metoda readLine, dar nu trateaza eventualele erori de citire, trebuie sa contina clauza throws IOException. Observatie: una dintre cele mai frecvente erori intr-un program Java este omiterea clauzelor throws din antetul functiilor utilizatorului care apeleaza functii predefinite dotate cu aceasta clauza. Acest lucru este semnalat la compilare. Despre acestea insa vom discuta in modulul urmator. Pentru a crea un obiect al clasei BufferedReader este necesar sa furnizam constructorului acesteia o referinta la un obiect al clasei InputStreamReader. Constructorul acestei clase, la randul lui necesita: o referinta la un obiect FileInputStream, daca dorim ca citirea sa se faca dintr-un fisier de pe disc, sau referinta System.in, daca dorim ca citirea sa se faca de la tastatura. Deci, daca urmeaza sa citim dintr-un fisier al carui nume este dat de o variabila String nume_fis, va trebui sa creem un obiect BufferedReader ca in secventa de mai jos: BufferedReader flux_in = new BufferedReader(new InputStreamReader(new FileInputStream(nume_fis))); Daca citirea se va face de la tastatura, obiectul BufferedReader se creaza astfel: BufferedReader flux_in = new BufferedReader(new InputStreamReader(System.in)); In continuare citirea se va realiza cu apelul: linie = flux_in.readLine(); unde linie este o referinta String. In urma apelului ea va indica spre un obiect care contine caracterele citite. In functie de natura datelor reprezentate de aceste caractere, uneori pot fi necesare conversii de la String la alte tipuri, in vederea utilizarii datelor respective in diverse calcule. Exemplu: vom prezenta un program care citeste numere intregi dintr-un fisier de text, calculeaza si afiseaza pe ecran suma acestora. Se presupune ca fiecare numar din fisier se afla pe cate o linie distincta, iar numele fisierului este dat ca argument la lansarea in executie a programului. Frazele din program care au legatura cu clase ale pachetului java.io si sunt implicate in operatiile de intrare/iesire

45

propriu-zise sunt scrise cu portocaliu.

import java.io.*; public class Suma { public static void main (String[ ] args) throws IOException { BufferedReader flux_in = new BufferedReader( new InputStreamReader (new FileInputStream(args[0]))); int suma = 0; String linie = null; while ((linie = flux_in.readLine()) != null) { /* cat timp nu am ajuns la sfarsitul fisierului */ suma += Integer.parseInt(linie); /* s-a convertit stringul la int */ } System.out.println("Suma = "+suma); flux_in.close(); /* se inchide fisierul (fluxul) */ } } Tema: sa se modifice programul de mai sus astfel incat citirea sa se faca de la tastatura. Operatii de scriere a liniilor de text Afisarea unei linii pe ecran este deja o operatie binecunoscuta, ea se realizeaza apeland metodele print/println definite in clasa PrintStream, pentru obiectul System.out. Pentru a scrie o linie intr-un fisier de pe disc vom folosi aceleasi metode, dar va trebui ca in prealabil sa se creeze un obiect separat al clasei PrintStream. Pentru aceasta trebuie sa furnizam constructorului claseiPrintStream, ca parametru, o referinta la un obiect FileOutputStream, asa ca in secventa de mai jos: PrintStream flux_out = new PrintStream (new FileOutputStream(nume_fis)); unde nume_fis este o referinta la un obiect String ce contine numele fisierului. Exemplu: vom prezenta un program care citeste dintr-un fisier de text o secventa de numere reale dispuse cate unul pe linie, determina numarul lor, suma, media aritmetica, valoarea minima/maxima si tipareste aceste informatii intr-un alt fisier. Numele ambelor fisiere implicate se dau ca argumente la lansarea in executie a programului. PrintStream flux_out= new PrintStream (new FileOutputStream(args[1])); im du sa 0 =b u . o e l it co n t n St = c o va s u i f }e es l { in l m( <( v) iva f a ; ix l ml >l v) ava f ; }/ e / f x u l f fl f l fues.i f x_ln l (o c o _ x u

46

Citirea fisierelor de text disponibile pe Internet In acest paragraf ne propunem sa prezentam cateva notiuni primare legate de accesul in citire la fisiere aflate pe Internet si care sunt disponibile public. O retea de calculatoare este un grup de calculatoare care pot schimba informatii intre ele in mod nemijlocit. Calculatoarele din retea sunt interconectate prin cabluri. Internetul este o retea de retele de calculatoare care permite unui calculator dintr-o retea sa comunice cu alt calculator aflat in oricare din celelalte retele. Retelele ce compun Internetul sunt legate intre ele prin linii telefonice, fibre optice sau prin satelit. Comunicarea intre calculatoarele legate la Internet se bazeaza pe faptul ca fiecare asemenea calculator are o adresa de Internet unica. In principiu o asemenea adresa este reprezentata pe 4 octeti si poate fi scrisa sub forma: val_octet1.val_octet2.val_octet3.val_octet4 Cum aceasta notatie nu este prea comoda pentru utilizator, adreselor de Internet li s-au asociat nume. Suportul software care face posibila comunicarea in retea dispune de un serviciu special care identifica adresa de Internet corespunzatoare unui nume dat. Informatiile accesibile pe Internet sunt organizate in unitati numite resurse de retea. De regula o resursa este un fisier care poate contine: imagini, secvente audio, text etc, stocat pe un calculator conectat la Internet. O asemenea resursa este identificata in mod unic cu ajutorul asa-numitului URL (Universal Resources Locator) adica locator universal de resurse. Un URL are forma: protocol://adresaInternet/caleFisier Protocolul identifica tipul suportului software necesar accesarii datelor din resursa. Pentru accesarea paginilor web (fisiere html) protocolul este http. Alte protocoale cunoscute: ftp, file. De exemplu, pagina de resurse eLearning de la Timsoft are URL-ul: http://www.timsoft.ro/resurse.shtml Revenind acum la limbajul Java, acesta ne permite sa scriem programe prin care sa citim date din resurse aflate pe Internet, daca aceste resurse sunt disponibile public. In esenta, pentru a citi date dintr-un fisier aflat pe retea se parcurg urmatorii pasi: se stabileste o conexiune cu resursa dorita, precizand URL-ul acesteia, se creaza obiectul BufferedReader necesar, dar nu pe baza numelui fisierului, ci folosind conexiunea creata in pasul anterior; se citesc datele. Pentru primul pas se utilizeaza clase predefinite din pachetul java.net. Considerand ca fisierul pe care dorim sa-l citim este pagina departamentului de calculatoare, al carei URL este http://www.cs.utt.ro dam mai jos un exemplu de program care citeste pagina respectiva, afisand pe ecran fiecare linie, precedata de numarul ei de ordine: ip m io m UL" tc e j b z a e r l u ca R r *es a O r / (h ut c Uurl R L n n et oC oi c /* I n pu t Sb rea t m N g co It /* e n a * po g m e i n Sun ) ie lx wn. fi =_ ! l h i /s ni fi c S+ y k + }i fn c h i e s / / L R j e

*/ m

u l Rulati aplicatia de mai sus in ambele variante, cu getContent, respectiv getInputStream.

47

Exista si posibilitatea de a utiliza metoda openStream() a clasei URL, care opereaza la un nivel mai scazut decat cele doua metode de mai sus. Cu aceasta, primele linii ale aplicatiei de mai sus ar fi - rulati din nou aplicatia: URL urlObject = new URL("http://www.cs.utt.ro"); BufferedReader flux_in = new BufferedReader (new InputStreamReader (urlObject.openStream()));

Tipuri de date in Java


// MaxVariablesDemo.java // Aplicatie ce afiseaza valorile limita public class Main { public static void main(String args[]) { // intregi byte largestByte = Byte.MAX_VALUE; short largestShort = Short.MAX_VALUE; int largestInteger = Integer.MAX_VALUE; long largestLong = Long.MAX_VALUE; // numere reale float largestFloat = Float.MAX_VALUE; double largestDouble = Double.MAX_VALUE; // alte tipuri primitive char aChar = 'S'; boolean aBoolean = true; // afisare System.out.println("Valoarea byte maxima este " + largestByte); System.out.println("Valoarea byte minima este " + Byte.MIN_VALUE); System.out.println("Numarul de biti pe care se memoreaza byte " + Byte.SIZE); System.out.println("\nValoarea short maxima este " + largestShort); System.out.println("Valoarea integer maxima este " + largestInteger); System.out.println("Valoarea long maxima este " + largestLong); System.out.println("Valoarea float maxima este " + largestFloat); System.out.println("Valoarea double maxima este " + largestDouble); if (Character.isUpperCase(aChar)) { System.out.println("Litera " + aChar + " este majuscula."); } else { System.out.println("Litera " + aChar + " nu este majuscula."); } System.out.println("Valoarea variabilei aBoolean este " + aBoolean); } } Valoarea byte maxima este 127 Valoarea byte minima este -128 Numarul de biti pe care se memoreaza byte 8

48

Valoarea Valoarea Valoarea Valoarea Valoarea Litera S Valoarea

short maxima este 32767 integer maxima este 2147483647 long maxima este 9223372036854775807 float maxima este 3.4028235E38 double maxima este 1.7976931348623157E308 este majuscula. variabilei aBoolean este true

Clasa punct
class Punct{ private int x; private int y; public void init(int xx, int yy) { x = xx; y = yy; } public void move(int dx, int dy) { x += dx; y += dy; } public void setX(int xx) { x = xx; } public void setY(int yy) { y = yy; } public int getX( ) { return x; } public int getY( ) { return y; } public double distanta(){ return Math.sqrt(x * x + y * y); } } public class Main { public static void main(String[ ] arg) { Punct p1 = new Punct( ); //creez o instanta a clasei Punct Punct p2 = new Punct( ); // si inca una Punct p3 = new Punct(); // inca o instanta p1.init (10,20); p2.init (30,40); //apelez metodele init ale instantelor p1.move(5,5); p2.move(6,-2); //apelez metodele move ale instantelor p3.setX(-10); p3.setY(10); System.out.println("(x1,y1) = //afisez coordonatele curente System.out.println("(x2,y2) = //afisez coordonatele curente System.out.println("(x3,y3) = //afisez coordonatele curente ("+p1.getX( )+","+p1.getY( )+")"); ale primului punct ("+p2.getX( )+","+p2.getY( )+")"); ale celui de-al 2-lea punct ("+p3.getX( )+","+p3.getY( )+")"); ale celui de-al 3-lea punct

System.out.println("Distanta p1 :" + p1.distanta()); System.out.println("Distanta p2 :" + p2.distanta()); System.out.println("Distanta p3 :" + p3.distanta());

49

} } (x1,y1) = (15,25) (x2,y2) = (36,38) (x3,y3) = (-10,10) Distanta p1 :29.154759474226502 Distanta p2 :52.3450093132096 Distanta p3 :14.142135623730951

Clasa Punct tridimensional


// varianta Punct in spatiu // se exemplifica lucrul cu constructori, date statice, tablouri class Punct { // implicit class Punct extends Object // variabilele clasei private double x, y, z; public static int numarObiecte = 0; // numara obiectele create // constructori public Punct ( ){ x = y = z = 0; numarObiecte++; } public Punct ( double x, double y, double z ){ this.x = x; this.y = y; this.z = z; numarObiecte++; } public Punct ( Punct p ){ x = p.x; y = p.y; z = p.z; numarObiecte++; } // metode public void setX ( double x ){ this.x = x; } public void setY ( double y ){ this.y = y; } public void setZ ( double z){ this.z = z;

50

} public double getX ( ){ return x; } public double getY ( ){ return y; } public double getZ ( ){ return z; } public double distanta ( ){ return Math.sqrt ( x * x + y * y + z * z ); } public String afisare ( ){ return "( " + x + ", " + y + ", " + z + " )"; // (x, y, z) } public String toString ( ){ // redefinirea metodei din Object return "( " + x + ", " + y + ", " + z + " )"; } public boolean equals ( Punct p ){ return x == p.x && y == p.y && z == p.z; } public int compareTo ( Punct p ){ if ( distanta() < p.distanta() ) return -1; if ( distanta() > p.distanta() ) return 1; return 0; } } // class Punct public class Main { public static void main ( String argv [] ){ puncte"); System.out.println ( "Exista " + Punct.numarObiecte + " Punct p1 = new Punct (); // apelare constructor implicit Punct p2 = new Punct ( 1, 4.5, 6 ); p2.setX( p2.getX() + 1.5); p2.setY( p2.getY() - 4.2); p2.setZ( p2.getZ() * 2); Punct p3 = new Punct ( p2 );

51

System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println System.out.println // acces var statica prin obiect

( ( ( ( ( ( ( ( ( ( ( ( (

"p1:" + p1.afisare() ); "p2:" + p2.afisare() ); "Coordonata y pentru p2:" + p2.getY() ); "p2:" + p2 ); // implicit p2.toString() "Distanta p1:" + p1.distanta() ); "Distanta p2:" + p2.distanta() ); "p3:" + p3.toString() ); p1 == p2 ); p1.equals ( p2 )); p2.equals ( p3 )); p1.compareTo ( p2 )); p2.compareTo ( p3 )); "Exista " + p1.numarObiecte + " puncte");

// creare tablou de 10 puncte Punct puncte[] = new Punct[10]; // instantiere elemente tablou for(int i = 0; i < puncte.length; i++) puncte[i] = new Punct(i, 2 * i, 3 * i); // tiparire coordonate puncte System.out.println("Sunt acum " + Punct.numarObiecte + " puncte, noile " + puncte.length + " puncte au coordonatele:"); for(int i = 0; i < puncte.length; i++) System.out.println(puncte[i] + " "); // apel implicit toString - System.out.println(puncte[i].toString() + " "); } // main } // class Main Exista 0 puncte p1:( 0.0, 0.0, 0.0 ) p2:( 2.5, 0.2999999999999998, 12.0 ) Coordonata y pentru p2:0.2999999999999998 p2:( 2.5, 0.2999999999999998, 12.0 ) Distanta p1:0.0 Distanta p2:12.261321299109651 p3:( 2.5, 0.2999999999999998, 12.0 ) false false true -1 0 Exista 3 puncte Sunt acum 13 puncte, noile 10 puncte au coordonatele: ( 0.0, 0.0, 0.0 ) ( 1.0, 2.0, 3.0 ) ( 2.0, 4.0, 6.0 ) ( 3.0, 6.0, 9.0 ) ( 4.0, 8.0, 12.0 ) ( 5.0, 10.0, 15.0 )

52

( ( ( (

6.0, 7.0, 8.0, 9.0,

12.0, 14.0, 16.0, 18.0,

18.0 21.0 24.0 27.0

) ) ) )

Teme
/*

AfisareDatePersonaleOvidiuMarinescu.java*/

class afisareDatePersonaleOvidiuMarinescu { public static void main(String[ ] arg) { System.out.print("Prenume: "); System.out.println("Ovidiu"); System.out.print("Nume: "); System.out.println("Marinescu"); System.out.print("Adresa: "); String strLocalitatea="Timisoara"; String strStrada="I.I. de la Brad, "; String strBloc="bloc B118"; String strAdresa=strLocalitatea+" "+strStrada+" "+strBloc; System.out.println(strAdresa); } } Prenume: Ovidiu Nume: Marinescu Adresa: Timisoara I.I. de la Brad,

bloc B118

Max, min, nr. de biti

// MaxVariablesDemo.java // Aplicatie ce afiseaza valorile limita public class Main { public static void main(String args[]) { // intregi byte largestByte = Byte.MAX_VALUE; short largestShort = Short.MAX_VALUE; int largestInteger = Integer.MAX_VALUE; long largestLong = Long.MAX_VALUE; // numere reale float largestFloat = Float.MAX_VALUE; double largestDouble = Double.MAX_VALUE; // alte tipuri primitive char aChar = 'S'; boolean aBoolean = true; // afisare System.out.println("Valoarea byte maxima este " + largestByte); System.out.println("Valoarea byte minima este " + Byte.MIN_VALUE);

53

System.out.println("Nr de biti pe care se memoreaza byte este " + Byte.SIZE); System.out.println("\nValoarea short maxima este " + largestShort); System.out.println("Valoarea short minima este " + Short.MIN_VALUE); System.out.println("Nr de biti pe care se memoreaza short este " + Short.SIZE); System.out.println("\nValoarea long maxima este " + largestLong); System.out.println("Valoarea long minima este " + Long.MIN_VALUE); System.out.println("Nr de biti pe care se memoreaza long este " + Long.SIZE); System.out.println("\nValoarea float maxima este " + largestFloat); System.out.println("Valoarea float minima este " + Float.MIN_VALUE); System.out.println("Nr de biti pe care se memoreaza float este " + Float.SIZE); System.out.println("\nValoarea double maxima este " + largestDouble); System.out.println("Valoarea double minima este " + Double.MIN_VALUE); System.out.println("Nr de biti pe care se memoreaza double este " + Double.SIZE); if (Character.isUpperCase(aChar)) { System.out.println("\nLitera " + aChar + " este majuscula."); } else { System.out.println("Litera " + aChar + " nu este majuscula."); } System.out.println("\nValoarea variabilei aBoolean este " + aBoolean); } } Valoarea byte maxima este 127 Valoarea byte minima este -128 Nr de biti pe care se memoreaza byte este 8 Valoarea short maxima este 32767 Valoarea short minima este -32768 Nr de biti pe care se memoreaza short este 16 Valoarea long maxima este 9223372036854775807 Valoarea long minima este -9223372036854775808 Nr de biti pe care se memoreaza long este 64 Valoarea float maxima este 3.4028235E38 Valoarea float minima este 1.4E-45 Nr de biti pe care se memoreaza float este 32 Valoarea double maxima este 1.7976931348623157E308 Valoarea double minima este 4.9E-324 Nr de biti pe care se memoreaza double este 64 Litera S este majuscula. Valoarea variabilei aBoolean este true

54

Distanta dintre puncte


class Punct{ private int x; private int y; public void init(int xx, int yy) { x = xx; y = yy; } public void move(int dx, int dy) { x += dx; y += dy; } public int getX( ) { return x; } public int getY( ) { return y; } public void setX(int xx) { x = xx; } public void setY(int yy) { y = yy; } public double distanta ( ){ return Math.sqrt(x * x + y * y); } } public class Main { public static void Punct p1 = new Punct p2 = new Punct p3 = new main(String[ ] arg) { Punct( ); //creez o instanta a clasei Punct Punct( ); // si inca una Punct( );//inca o instanta

p1.init (10,20); p2.init (30,40); //apelez metodele init ale instantelor p1.move(5,5); p2.move(6,-2); //apelez metodele move ale instantelor p3.setX(-10);p3.setY(10); System.out.println("(x1,y1) = ("+p1.getX( )+","+p1.getY( )+")"); //afisez coordonatele curente ale primului punct System.out.println("Distanta p1 :" +p1.distanta()); System.out.println("\n(x2,y2) = ("+p2.getX( )+","+p2.getY( )+")"); //afisez coordonatele curente ale celui de-al 2-lea punct System.out.println("Distanta p2 :" +p2.distanta()); System.out.println("\n(x3,y3) = ("+p3.getX( )+","+p3.getY( )+")"); //afisez coordonatele curente ale celui de-al 3-lea punct System.out.println("Distanta p3 :" +p3.distanta()); } } (x1,y1) = (15,25) Distanta p1 :29.154759474226502 (x2,y2) = (36,38) Distanta p2 :52.3450093132096

55

(x3,y3) = (-10,10) Distanta p3 :14.142135623730951

Comparatie
class Data{ private int zi; private int luna; private int an; public Data(int z, int l, int a) { zi = z; luna = l; an = a;} public int getZi() { return zi; } public int getLuna() { return luna; } public int getAn() { return an; } public void setZi(int z) { zi = z; } public void setLuna(int l) { luna = l; } public void setAn(int a) { an = a; } public String toString(){ String d = String.format("%d.%d.%d",zi,luna,an); return d; } public boolean equals(String a,String b){ boolean egal=false; if(a.equals(b)){ egal=true; } return egal; } public void compareTo(String a,String b){ if(a.compareTo(b)<0){ System.out.println("Data " +a +" < "+"Data "+b); } if(a.compareTo(b)>0){ System.out.println("Data " +a +" > "+"Data "+b); } if(a.compareTo(b)==0){ System.out.println("Data " +a +" = "+"Data "+b); } }

class ClientData { public static void main(String[ ] arg) { Data d1 = new Data(10,01,2013); //prima instanta a clasei Data Data d2 = new Data(11,01,2013); //a doua instanta a clasei Data System.out.println("Data 1:" + d1.toString());

56

System.out.println("Data 2:" + d2.toString()); String a = d1.toString(); String b = d2.toString(); System.out.println("Datele "+a +" si "+b +" sunt identice ?:" + d1.equals(a,b)); d1.compareTo(a,b); a = "12/01/2013"; b = "11/01/2013"; System.out.println("Datele "+a +" si "+b +" sunt identice ?:" + d1.equals(a,b)); d1.compareTo(a,b); a = "11/01/2013"; b = "11/01/2013"; System.out.println("Datele "+a +" si "+b +" sunt identice ?:" + d1.equals(a,b)); d1.compareTo(a,b); } } Data 1:10.1.2013 Data 2:11.1.2013 Datele 10.1.2013 si 11.1.2013 sunt identice ?:false Data 10.1.2013 < Data 11.1.2013 Datele 12/01/2013 si 11/01/2013 sunt identice ?:false Data 12/01/2013 > Data 11/01/2013 Datele 11/01/2013 si 11/01/2013 sunt identice ?:true Data 11/01/2013 = Data 11/01/2013

Tema 3
class Data{ private int zi, luna, an; public static int numarObiecte = 0; public Data( ) { zi = 1; luna = 1; an = 2000; numarObiecte++; } public Data(int z, int l, int a) { this.zi = z; this.luna = l; this.an = a; numarObiecte++; }

57

public Data(Data d) { zi = d.zi; luna = d.luna; an = d.an; numarObiecte++; } // 3 metode fara parametri public int getZi() { return zi; } public int getLuna() { return luna; } public int getAn() { return an; } // 3 metode care primesc cate un parametru intreg public void setZi(int z) { zi = z; } public void setLuna(int l) { luna = l; } public void setAn(int a) { an = a; } // metodele compareTo, toString si equals public int compareTo ( Data d ) { if ( this.toString().compareTo(d.toString())<0) return -1; if ( this.toString().compareTo(d.toString())>0 ) return 1; return 0; } public String toString ( ) { return "( " + zi + "." + luna + "." + an + " )"; } public boolean equals ( Data d ) { return zi == d.zi && luna == d.luna && an == d.an; } } class ClientData { public static void main(String[ ] arg) { System.out.println ( "Exista " + Data.numarObiecte + " date, respectiv: Data Data Data Data Data Data d1 = new Data(); // apelare constructor implicit d2 = new Data(23,1,2013); //creez o a doua instanta a clasei Data d3 = new Data(23,1,2013); //creez o a treia instanta a clasei Data d4 = new Data(24,1,2013); //creez o a patra instanta a clasei Data d5 = new Data(d1); d6=d5; //d5 si d6 sunt referinte catre acelasi obiect

");

58

System.out.println("Data System.out.println("Data System.out.println("Data System.out.println("Data d5.setZi(25); d5.setLuna(1); d5.setAn(2013); System.out.println("Data "."+d5.getAn()+ " )" ); System.out.println("Data "."+d6.getAn()+ " )" );

1:" 2:" 3:" 4:"

+ + + +

d1); d2.toString()); d3); d4.toString());

5:(" + d5.getZi() + "." + d5.getLuna()+ 6:(" + d6.getZi() + "." + d6.getLuna()+ + d1.equals(d2)); + d2.equals(d3)); data2 si data3: " + data5 si data6: " +

System.out.println( "Este egala data 1 cu data2? " System.out.println( "Este egala data 2 cu data3? " System.out.println("Referinta catre acelasi obiect (d2==d3)); System.out.println("Referinta catre acelasi obiect (d6==d5));

System.out.println("Comparam data2 si data1: " + d2.compareTo(d1)); System.out.println("Comparam data2 si data3: " + d2.compareTo(d3)); System.out.println("Comparam data2 si data4: " + d3.compareTo(d4)); // creare tablou de 10 date Data date[] = new Data[10]; // instantiere elemente tablou for(int i = 0; i < date.length; i++) date[i] = new Data(i+1, 1 , 2013); // tiparire date System.out.println("Sunt in total " + Data.numarObiecte + " date, cele " date.length + " noi date sunt:");

for(int i = 0; i < date.length; i++) System.out.println(date[i] + " "); // apel implicit toString System.out.println(date[i].toString() + " "); } } Exista 0 date, respectiv: Data 1:( 1.1.2000 ) Data 2:( 23.1.2013 ) Data 3:( 23.1.2013 ) Data 4:( 24.1.2013 ) Data 5:(25.1.2013 ) Data 6:(25.1.2013 ) Este egala data 1 cu data2? false Este egala data 2 cu data3? true Referinta catre acelasi obiect data2 si data3: false Referinta catre acelasi obiect data5 si data6: true Comparam data2 si data1: 1 Comparam data2 si data3: 0

59

Comparam data2 si data4: -1 Sunt in total 15 date, cele 10 noi date sunt: ( 1.1.2013 ) ( 2.1.2013 ) ( 3.1.2013 ) ( 4.1.2013 ) ( 5.1.2013 ) ( 6.1.2013 ) ( 7.1.2013 ) ( 8.1.2013 ) ( 9.1.2013 ) ( 10.1.2013 )

Patrat si cerc
import java.awt.*; import java.applet.*; public class CircleSquare extends Applet { public void paint (Graphics g) { // Draw a red, filled circle: g.setColor (Color.red); g.fillOval (50,50,70,70); // Draw an unfilled blue square next to it: g.setColor (Color.blue); g.drawRect (140,50,80,70); } }

60

61

62

S-ar putea să vă placă și