Florian Moraru – Programare Orientata pe Obiecte CUPRINS 1. Java ca limbaj de programare cu obiecte Diferente între limbajele Java si C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4 Structura programelor Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Tipuri clasã si tipuri referintã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Spatii ale numelor în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Definirea si utilizarea de vectori în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Exceptii program în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Platforma Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2. Introducere în programarea orientatã pe obiecte Clase si obiecte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Clasele sunt module de program reutilizabile . . . . . . . . . . . . . . . . . . . . . . . . . 19 Clasele creeazã noi tipuri de date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Clasele permit programarea genericã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Clasele creeazã un model pentru universul aplicatiei . . . . . . . . . . . . . . . . . . . 26 Vectori intrinseci si obiecte Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3. Utilizarea de clase si obiecte în Java Clase fãrã obiecte. Metode statice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Clase instantiabile. Metode aplicabile obiectelor . . . . . . . . . . . . . . . . . . . . . . . 30 Variabile referintã la un tip clasã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Argumente de functii de tip referintã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Clase cu obiecte nemodificabile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Metode statice si metode ale obiectelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Utilizarea unor clase de intrare-iesire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4. Definirea de noi clase în Java Definirea unei clase în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Functii constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Cuvântul cheie "this" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Atribute ale membrilor claselor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Incapsularea datelor în clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Structura unei clase Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Metode care pot genera exceptii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Clase si obiecte Java în faza de executie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5. Derivare. Mostenire. Polimorfism Clase derivate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 Derivare ca metodã de reutilizare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 Derivare pentru creare de tipuri compatibile . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Clasa Object ca bazã a ierarhiei de clase Java . . . . . . . . . . . . . . . . . . . . . . . . . 60 Polimorfism si legare dinamicã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Structuri de date generice în POO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 6. Clase abstracte si interfete Clase abstracte în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Interfete Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 Diferente între interfete si clase abstracte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

1

Florian Moraru – Programare Orientata pe Obiecte Compararea de obiecte în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Interfete pentru filtrare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Functii “callback”. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Clase abstracte si interfete pentru operatii de I/E . . . . . . . . . . . . . . . . . . . . . . .76 7. Colectii de obiecte Object Infrastructura claselor colectie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Multimi de obiecte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Liste secventiale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Clase dictionar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Colectii ordonate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Clase iterator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Definirea de noi clase colectie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Colectii virtuale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Colectii arborescente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 8. Colectii generice Clase generice în Java 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Forme posibile pentru parametri tip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Exemple comparative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Colectii Google . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 9. Reutilizarea codului în POO Reutilizarea codului prin delegare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Reutilizarea codului prin derivare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Comparatie între compozitie si derivare . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Combinarea compozitiei cu derivarea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Clase de intrare-iesire cu derivare si delegare . . . . . . . . . . . . . . . . . . . . . . . 112 10. Clase incluse Clase incluse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Clase interioare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Simplificarea comunicãrii între clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 Clase interioare cu date comune . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Clase interioare anonime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Probleme asociate claselor incluse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .123 11. Clase pentru o interfatã graficã Programarea unei interfete grafice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Clase JFC pentru interfata graficã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .126 Solutii de programare a interfetelor grafice . . . . . . . . . . . . . . . . . . . . . . . . . 127 Dispunerea componentelor într-un panou . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Componente vizuale cu text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Panouri multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Apleti Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 12. Programare bazatã pe evenimente Evenimente Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .137 Tratarea evenimentelor Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .137 Evenimente de mouse si de tastaturã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .138 Evenimente asociate componentelor JFC . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

2

Florian Moraru – Programare Orientata pe Obiecte Evenimente produse de componente cu text . . . . . . . . . . . . . . . . . . . . . . . . . 142 Mecanismul de generare a evenimentelor . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 Structura programelor dirijate de evenimente . . . . . . . . . . . . . . . . . . . . . . . . 145 Utilizarea de clase incluse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 Clase generator si receptor de evenimente . . . . . . . . . . . . . . . . . . . . . . . . . . 149 Reducerea cuplajului dintre clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 Comunicarea prin evenimente si clase “model” . . . . . . . . . . . . . . . . . . . . . . 152 13. Componente Swing cu model Arhitectura MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 Utilizarea unui model de listã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 Utilizarea unui model de tabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 Utilizarea unui model de arbore. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 14. Java si XML Fisiere XML în aplicatii Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 Utilizarea unui parser SAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .168 Utilizarea unui parser DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Utilizarea unui parser StAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Vizualizarea arborelui XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 O aplicatie cu fisiere XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 15. Proiectare orientatã pe obiecte Analiza si proiectarea orientate pe obiecte . . . . . . . . . . . . . . . . . . . . . . . . 183 Proiectarea unei aplicatii de traversare directoare . . . . . . . . . . . . . . . . . . . 184 Solutii posibile pentru clasa Finder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 Extinderea clasei Finder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Alte exemple de utilizare a clasei Finder . . . . . . . . . . . . . . . . . . . . . . . . . . 191 16. Scheme de proiectare Scheme de proiectare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Metode "fabricã" de obiecte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Fabrici abstracte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 Clase cu rol de comandã . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 Clase cu un singur obiect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Schema claselor observat-observator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Clase model în schema MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 17. Programarea cu fire de executie Fire de executie concurente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Fire de executie în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Fire de executie cu date comune . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 Sincronizarea firelor de executie concurente . . . . . . . . . . . . . . . . . . . . . . . 215 Probleme în programarea cu fire concurente . . . . . . . . . . . . . . . . . . . . . . . 218 Procese de tip producãtor-consumator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Fire de executie în aplicatii Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224

3

Florian Moraru – Programare Orientata pe Obiecte 1. Java ca limbaj de programare cu obiecte Diferente între limbajele Java si C Limbajul Java foloseste aceleasi instructiuni cu limbajul C, mai putin instructiunea goto. Tipurile de date primitive sunt aproape aceleasi, plus tipul boolean, care a schimbat si sintaxa instructiunilor care testeazã conditii. Diferentele importante apar la tipurile de date derivate (vectori, structuri, pointeri) si la structura programelor. In limbajul C existã un singur fel de comentarii, care încep prin perechea de caractere "/*" si se terminã prin perechea de caractere "*/". In C++ au apãrut, în plus, comentarii care încep prin perechea de caractere "//" si se terminã la sfârsitul liniei în care apare acel comentariu. Java preia aceste douã feluri de comentarii, la care se adaugã comentarii destinate generãrii automate a documentatiilor programelor (cu ajutorul programului “javadoc”); aceste comentarii încep printr-un grup de 3 caractere "/**" si se terminã la fel cu comentariile C, prin "*/" Exemplu:
/** Clasa Heap * @ Data : Apr. 2000 */

Tipurile de date primitive Java preia de la C si C++ aproape toate tipurile aritmetice (short, int, long, float, double) si tipul void, dar impune o aceeasi lungime si reprezentare a tipurilor numerice pentru toate implementãrile limbajului. Un întreg de tip int ocupã 32 de biti, un short ocupã 16 biti, iar un long ocupã 64 de biti. Un float ocupã 32 de biti iar un double ocupa 64 de biti. Tipul aritmetic byte ocupã 8 biti (valori între –128 si 127). Tipul char ocupã 16 biti pentru cã standardul de reprezentare a caracterelor este UTF-16 sau Unicode (în locul codului ASCII) si permite utilizarea oricãrui alfabet. Toate tipurile aritmetice din Java reprezintã numere cu semn si nu mai existã cuvântul unsigned pentru declararea de variabile aritmetice fãrã semn. Tipul boolean din Java ocupã un singur bit; constantele de tip boolean sunt true si false. Existenta acestui tip modificã sintaxa instructiunilor if, while, do si a expresiei conditionale, precum si rezultatul expresiilor de relatie (care este acum de tip boolean si nu de tip int). Asadar, instructiunile urmãtoare sunt gresite sintactic în Java, desi sunt corecte în C si C++.
while ( a%b) {a=b; b=a%b;} // corect este : while ( a%b !=0) {...}; return x ? 1:0 ; // corect este: return x !=0 ? 1:0 ; cu x de tip “int” if ( ! n) { ... } // corect este: if (n==0) { ... } do { nf=nf *n--;} while (n) ; // corect este: do { nf=nf*n--;} while ( n>0);

Variabilele declarate în functii nu primesc valori implicite iar compilatorul semnaleazã utilizarea de variabile neinitializate explicit de programator. In Java, se fac automat la atribuire numai conversiile de “promovare” de la un tip numeric inferior” la un tip aritmetic “superior”, care nu implicã o trunchiere. Exemple:
int n=3; float f; double d; d=f=n; // corect f=3.0, d=3.0 n=f; // gresit sintactic f=d; // gresit sintactic

Ierarhizarea tipurilor aritmetice, de la “inferior” la “superior” este: byte, short, int, long, float, double

4

length.i++) if (a[i]==b) return i. Deci nu existã posibilitatea de a declara explicit variabile pointer si nici operatorii unari ‘&’ (pentru obtinerea adresei unei variabile) si ‘*’ (indirectare printr-un pointer). Supradefinirea functiilor Supradefinirea sau supraîncãrcarea functiilor (“Function Overloading”) a fost introdusã în C++ pentru a permite definirea mai multor functii cu acelasi nume si cu acelasi tip dar cu argumente 5 . // conversie necesara de la double la int // functie de rotunjire din clasa Math public static int round (float a) { return (int)floor(a + 0. Exemplu: static float rest (int a) { return a. este absenta tipurilor pointer din Java. de promovare se face pentru rezultatul unei functii. char ch. ch =(char)b. pentru determinarea memoriei ocupate de un tip sau de o variabilã. Exemple : f= (float)d. 2 O altã conversie automatã. pentru cã nu este necesar acest operator (nici chiar pentru alocare dinamicã de memorie). // eroare de compilare ! } In Java nu existã operatorul sizeof din C.5f). // conversie int->float } Conversia de la un tip numeric “superior” la un tip aritmetic “inferior” trebuie cerutã explicit prin folosirea operatorului “cast” de fortare a tipului si nu se face automat ca în C. deoarece compilatorul face automat o atribuire a valorii argumentului efectiv la argumentul formal corespunzãtor. dacã tipul expresiei din instructiunea return diferã de tipul declarat al functiei.i<a. pe de o parte. Cea mai importantã diferentã dintre Java. Exemplu: byte b=65. b =(byte)ch.sqrt(2). int b) { // pozitia lui b in vectorul a for (int i=0. si limbajele C. C++ pe de altã parte. // "floor" are rezultat double } Compilatorul Java verificã dacã este specificat un rezultat la orice iesire posibilã dintr-o functie cu tip diferit de void (de exemplu.sqrt(4). // ch este 'A' // b este 10 Aceleasi reguli de conversie între tipuri numerice se aplicã si între argumentele efective si argumentele formale.Florian Moraru – Programare Orientata pe Obiecte Tipul char nu este un tip aritmetic dar se pot face conversii prin operatorul (tip) între tipul char si orice tip aritmetic întreg. ch='\n'. Exemplu: public static int indexOf (int a[]. // cu trunchiere int r = (int) Math. // return –1. // cu pierdere de precizie n=(int)f. // promovare de la int la double ptr. instructiuni if fãrã else ). Operatorul new din C++ pentru alocare dinamicã are în Java un rezultat o referintã si nu un pointer. Exemplu: double r = Math.

io // scrie un întreg // scrie un numãr real // scrie un boolean // scrie un sir de caractere Functia “String. } public void print (String s) { if (s== null) s= “null”. write (s). // ignora spatii start= crt. dar acesta nu este un caz de supradefinire.start). // sirul analizat private int crt.indexOf (' '..valueOf” este si ea supradefinitã pentru diferite argumente. // unde se termina acest cuvant if ( crt < 0) // ultimul cuvant nu trebuie urmat de spatiu crt= str. In Java. pentru fiecare tip de date primitiv si pentru tipurile clasã String si Object : public class PrintStream . 6 . Declaratii de variabile O declaratie de variabilã poate sã aparã fie într-o functie. } // din pachetul java. Locul declaratiei este important. Pot exista functii cu acelasi nume (eventual si cu acelasi argumente si tip) în clase diferite. // unde incepe acest cuvant crt= str.substring (start. care este formatã din numele. dar într-o clasã. // pozitia curenta in sir // constructor public StrTok (String s) { str=s. o variabilã dintr-o functie este localã acelei functii. ca si în C++. } public void print (boolean b) { write (b ? “true” : “false”).{ public void print (int i) { write (String.crt). iar o variabilã declaratã la nivel de clasã este utilizabilã de orice functie din clasã (si chiar de functii din alte clase).length(). tipul si argumentele functiei. // inceput de cuvant (variabila locala) while ( crt < str. } public void print (float f) { write (String. crt=0. return str. } // extrage urmatorul cuvant dintr-un sir public String nextToken () { int start. Un exemplu uzual de functii supradefinite este cel al functiilor de afisare la consolã în mod text “print” si “println”. care au mai multe definitii.length(). } // verifica daca mai sunt cuvinte public boolean hasMoreTokens () { return crt < str.valueOf(i))..Florian Moraru – Programare Orientata pe Obiecte diferite într-o aceeasi clasã. fiindcã ele se aflã în spatii de nume diferite. class StrTok { // variabile ale clasei private String str. nu existã variabile externe claselor. fie în afara functiilor.length() && str. o functie este deosebitã de alte functii din aceeasi clasã (de cãtre compilator) prin "semnãtura" sa (prin "amprenta" functiei).charAt(crt)==' ') ++crt.valueOf(f)).

care contine douã metode. // in clasa Math Alte diferente între Java si C In Java nu existã declaratia typedef deoarece definirea unei clase introduce automat un nume pentru un nou tip de date. etc. între alte instructiuni sau declaratii. Exemplu: public static boolean este ( int x[ ]. k++) if ( x[k]==y) break. } } In Java nu conteazã ordinea în care sunt scrise functiile (metodele) unei clase.length. ca metodã staticã. int y) { int n=x. deci nu existã directivele preprocesor atât de mult folosite în C si în C++ : #define. Compilatorul Java nu are preprocesor. #include. Orice functie apartine unei clase si nu se pot defini functii în afara claselor. iar la alocarea de memorie (cu new) nu trebuie specificatã dimensiunea alocatã. // lungime vector x for (int k=0. Exemplu: public static final double PI = 3. Exemplul urmãtor este un fisier sursã cu o singurã clasã. Structura programelor Java O aplicatie Java contine cel putin o clasã. iar valabilitatea acestei declaratii este limitatã la instructiunile repetate prin instructiunea for . doar cã “main” trebuie sã fie inclusã. ambele publice si statice: 7 .println (" Main started "). deci o functie poate fi apelatã înainte de a fi definitã si nici nu este necesarã declararea functiilor utilizate (nu se folosesc prototipuri de functii). Instructiunea for constituie un caz special: variabila contor se declarã de obicei în instructiunea for. pentru cã lungimea variabilelor este cunoscutã. care afiseazã un text constant: public class Main { public static void main (String arg[) { System. // eroare: k nedeclarat ! } In Java nu existã cuvântul cheie const iar constantele sunt declarate ca variabile cu atributele static si final. Domeniul de valabilitate al unei variabile începe în momentul declarãrii si se terminã la sfârsitul blocului ce contine declaratia. Ca si în C. return k==n ? false : true.out. executia unui program începe cu functia “main”. In Java nu existã operatorul sizeof . care contine cel putin o metodã cu numele "main" de tip void si cu atributele static si public.14159265358979323846. In C++ si în Java o declaratie poate apare oriunde într-un bloc. într-o clasã si trebuie sã aibã un argument vector de siruri. In exemplele urmãtoare se vor folosi numai clase care reunesc câteva metode statice. Metoda "main" trebuie sã aibã ca unic argument un vector de obiecte String.Florian Moraru – Programare Orientata pe Obiecte } } In C toate declaratiile dintr-un bloc trebuie sã preceadã prima instructiune executabilã din acel bloc. Exemplul urmãtor este un program minimal. functii care pot fi executate fãrã a crea obiecte de tipul clasei respective. k<n.

// orice punct din plan // coordonate punct 8 . Un fisier sursã Java poate contine mai multe clase. a. variabilã publicã din clasa System. y. } } public class Util { public static void writeln (String txt) { System. In Java nu mai existã cuvântul cheie struct . // coordonate punct } // creare si utilizare obiect din clasa "Point" Point a = new Point().y =-3. Exemplu: public class Point { // orice punct din plan public double x.x=2. echivalente structurilor din limbajele C si C++. Compilatorul Java creeazã pentru fiecare clasã din fisierul sursã câte un fisier cu extensia “class” si cu numele clasei. Se pot defini clase ce contin numai date publice. Tipuri clasã si tipuri referintã O clasã este o structurã care poate contine atât date cât si functii ca membri ai structurii. O clasã publicã este accesibilã si unor clase din alte pachete de clase.. Faza de executie a unui program Java constã din încãrcarea si interpretarea tuturor claselor necesare executiei metodei “main” din clasa specificatã în comanda “java”.println (txt). dacã este apelatã dintr-o metodã a unei alte clase. Dacã este necesar. dar numai una din ele poate avea atributul public. se compileazã si alte fisiere sursã cu clase folosite de fisierul transmis spre compilare.Florian Moraru – Programare Orientata pe Obiecte class Main { public static void main (String arg[ ]) { writeln ("Hello world !").y. Exemplu: public class Main { public static void main (String arg[ ]) { Util. generat automat a.writeln ("Hello world !"). iar numele ei trebuie precedat de numele obiectului (si un punct). } } // cu "main" incepe executia // afiseaza un text pe ecran O metodã ne-staticã trebuie apelatã pentru un anumit obiect.out. // un punct in cadranul 4 In practicã se preferã ca variabilele clasei "Point" sã fie de tip private (inaccesibile unor metode din alte clase) si ca initializarea lor sã se facã în constructorul clasei: public class Point { private double x. } } // cu "main" incepe executia // afiseaza un text pe ecran Numele unei metode statice trebuie precedat de numele clasei din care face parte (separate printr-un punct). Metoda "println" este apelatã pentru obiectul adresat de variabila "out".out. Numele fisierului sursã (de tip “java”) trebuie sã coincidã cu numele clasei publice pe care o contine. iar definirea unei clase foloseste cuvântul cheie class. // constructor implicit.println (txt). } public static void writeln (String txt) { System.

O metodã staticã corespunde unei functii din limbajul C. System.parseInt (str)."trei"}. String msg. .out.out. vectori sau functii de tip String. int n = Integer. care pot genera obiecte. utilizabile în alte clase. ce poate fi folosit în declararea de variabile. // valoarea absoluta a lui x // radical din x // conversie sir "str" la tipul "int" Definirea unei clase instantiabile T creeazã automat un nou tip de date T. Exemplu: int len = mesaj. iar o variabilã de un tip clasã corespunde unei variabile pointer la o structurã din C. double y = Math. Acest exemplu aratã cã membrii unei clase se folosesc la fel cu membrii unei structuri. } } // functie constructor Clasele (neabstracte) Java sunt de douã categorii: .abs(x)."doi". out este numele unei variabile publice (din clasa System) de un tip clasã (PrintStream). indiferent cã ei sunt variabile (câmpuri) sau functii (metode). Clasa String contine mai multe metode (functii) publice. String tcuv[] ={"unu". iar println este numele unei metode din clasa PrintStream. Clasa Java cu numele String defineste un tip de date String. msg = " Corect". Exemple: String msg = "Eroare".Florian Moraru – Programare Orientata pe Obiecte public Point (double xi. 9 .println (mesaj). fãrã argumente. iar variabila de tip clasã trebuie initializatã cu rezultatul operatorului new. In Java toate obiectele sunt alocate dinamic.println ("Eroare !"). cu diferenta cã numele functiei trebuie precedat de numele clasei din care face parte. In aceste exemple System este numele unei clase predefinite. Un obiect de tip T este o instantiere a clasei T si este referit printr-o variabilã de tip T. De exemplu. Exemple: System. metoda length.println ( “ Eroare “).length(). folosind operatorul new. // o variabila sir // un vector de siruri Un obiect Java corespunde unei variabile structurã din C. y=yi. de cãtre compilator. care contin doar metode statice (si eventual constante). double yi) { x=xi. Exemple: double xabs = Math. ale cãror adrese pot fi folosite în atribuiri sau initializãri la declarare. // alocare memorie pentru sir Pentru constantele de tip String se creeazã obiecte automat. Un alt exemplu este o constructie mult folositã în Java pentru afisarea la consolã (în mod text) a unor siruri de caractere: System.Clase instantiabile.sqrt(x). // String este o clasã predefinitã mesaj = new String (“ Eroare ! “) . Exemplu: String mesaj.out.Clase neinstantiabile . care contin date si metode (ne-statice). are ca rezultat (întreg) lungimea sirului continut în obiectul de tip String pentru care se apeleazã metoda.

writeln ("abc").size() ). s3="Rand". folosit între obiecte de tip String.out. System.util. In Java nu trebuie folositã o sintaxã specialã pentru declararea de variabile sau de parametri referintã.". Pot exista nume identice în spatii diferite. poate crea impresia cã variabilele de tip String contin chiar sirurile care se concateneazã si nu adresele lor. System. . Indirectarea prin variabila referintã este realizatã automat de compilator. O referintã la un tip clasã T este de fapt un pointer la tipul T dar care se foloseste ca si cum ar fi o variabilã de tipul T. fãrã a folosi un operator special. O variabilã referintã Java nu este un obiect.isDigit ( str.out. Nu se pot defini referinte la tipuri primitive.charAt(0)) ) . s2="util. cu simplificarea scrierii si utilizãrii acestor functii. // daca primul caracter e o cifra System. b=2 . Exemplu: System.println (s1+s2+s3). In cadrul unui spatiu nu pot exista douã sau mai multe nume identice (exceptie fac metodele supradefinite dintr-o aceeasi clasã).println ( a. // clasa Main. O variabilã referintã apare de obicei în stânga unei atribuiri cu operatorul new sau cu constanta null în partea dreaptã. // a = variabila referinta la un obiect de tip Vector Atunci când se apeleazã o metodã pentru un obiect. Spatii ale numelor în Java Un spatiu de nume ("namespace") este un domeniu de valabilitate pentru un nume simbolic ales de programator. atunci când se folosesc în alte clase. deoarece toate variabilele de un tip clasã sunt automat considerate ca variabile referintã.getName()). dar contine adresa unui obiect alocat dinamic. ca în C .out.Florian Moraru – Programare Orientata pe Obiecte Numele unei metode poate fi precedat de numele unei variabile clasã sau de numele unei clase. Tipul referintã a fost introdus în C++ în principal pentru a declara parametri modificabili în functii.println ( a + “+” + b + “=“ + (a+b)).println (obj + obj.getClass().". // scrie: 3 + 2 = 5 Efectul operatorului '+' depinde de tipul operanzilor: dacã unul din operanzi este de tip String atunci este interpretat ca operator de concatenare iar rezultatul este tot String. . Este uzual în Java sã avem denumiri de variabile sau de metode care contin câteva puncte de separare a numelor folosite în precizarea contextului. Exemple: if ( Character.length(). Fiecare clasã creeazã un spatiu de nume pentru variabilele si metodele clasei. Exemplu: Vector a = new Vector( ). Exemple: String s1="java. se foloseste numele variabilei referintã ca si cum acest nume ar reprezenta chiar obiectul respectiv si nu adresa sa. Exemple: Main. // afisare dimensiune vector a Operatorul de concatenare '+'. metoda writeln 10 .out. ca urmare numele metodelor sau datelor publice dintr-o clasã trebuie precedate de numele clasei. dar întotdeauna caracterul separator este un punct. // scrie java. int maxdigits= (Integer.MAX_VALUE+""). Exemplu: int a=3.Rand Operatorul de concatenare “+” este singurul operator “supradefinit” în Java si el poate fi utilizat între operanzi de tip String sau cu un operand de tip String si un alt operand de orice tip primitiv sau de un tip clasã (pentru care existã o functie de conversie la tipul String ).

cu fisierele de tip “class” corespunzãtoare claselor din pachet.lang : public static void main (String arg[]) { java. fãrã a mai fi precedate de numele pachetului.util. Un nume de pachet corespunde unui nume de director.lang" ("language") este folosit de orice program Java si de aceea numele lui nu mai trebuie mentionat înaintea numelui unei clase din java. fac parte din pachetul java. Exemple : java.swing.Random rand =new java.i++) // scrie 10 numere aleatoare System.println ( rand. for (int i=1. separate prin puncte. // sau import java.tree Un pachet este un spatiu al numelor pentru numele claselor din acel pachet. } Pachetul cu numele "java.Random().util.regex . este posibil si chiar uzual ca în componenta unui pachet sã intre clase aflate în fisiere sursã diferite. De observat cã un fisier sursã nu creeazã un spatiu de nume. variabila out Clasele înrudite ca rol sau care se apeleazã între ele sunt grupate în "pachete" de clase ("package"). Clasele String.*. } // fisierul EOFException. Numele de pachete cu clase predefinite. Instructiunea anterioarã permite folosirea numelor tuturor claselor dintr-un pachet cu numele "pachet".out.Integer. public class EOFException extends IOException { .nextFloat()" exprimã apelul metodei "nextFloat" din clasa "Random" pentru obiectul adresat de variabila "rand". Numele unui pachet poate avea mai multe componente. In lipsa unei instructiuni package se considerã cã este vorba de un pachet anonim implicit.sqrt(x). atunci când este folosit într-un alt pachet.Florian Moraru – Programare Orientata pe Obiecte Math. . Exemplul urmãtor ilustreazã folosirea instructiunii "import": import java.java package java.Object.util.util.io. parte din JDK.io. situatia unor mici programe de test pentru depanarea unor clase. dar care au la început aceeasi instructiune "package".out // clasa Math. javax.lang.Random. Notatia "rand. încep prin java sau javax. . metoda sqrt // clasa System.i<=10. .awt.a.util".System s. Instructiunea import permite simplificarea referirilor la clase din alte pachete si poate avea mai multe forme. iar clasa Random este definitã în pachetul "java.io . public class IOException extends Exception { . Exemplu: // fisierul IOException.nextFloat()). System. Instructiunea package se foloseste pentru a specifica numele pachetului din care vor face parte clasele definite în fisierul respectiv. Exemplu care ilustreazã utilizarea unei clase dintr-un alt pachet decât java.util. . java. Cea mai folositã formã este: import pachet. In general numele unei clase (publice) trebuie precedat de numele pachetului din care face parte. java. class R { public static void main (String arg[]) { 11 . } Variabila cu numele "rand" este de tipul Random. ea trebuie sã fie prima instructiune din fisierul sursã Java.java package java.* .lang.

echivalente: tip nume [ ]. Alocarea de memorie pentru un vector se face folosind operatorul new urmat de un nume de tip si de o expresie (cu rezultat întreg) între paranteze drepte. .util. Exemplu de eroare: int a[100].i<=10. atunci când vectorul este initializat la declarare cu un sir de valori. Exemplu care aratã riscurile importului tuturor claselor dintr-un pachet: import java. deoarece alocarea de memorie nu se face niciodatã la compilare.out. Este posibilã si o alocare automatã. } } Uneori se preferã importul de clase individuale. import java. // o matrice de întregi // altã matrice de întregi In Java nu este permisã specificarea unor dimensiuni la declararea unor vectori sau matrice. Exemplu: int a[ ][ ] . C++ si Java. componentele unui vector sunt initializate automat. In Java. null pentru variabile referintã de orice tip. Exemplu: short prime[ ] = {1.List ? } } Definirea si utilizarea de vectori în Java Cuvântul “vector” este folosit aici ca echivalent pentru “array” din limba englezã si se referã la un tip de date implicit limbajelor C. // clasa java. tip [ ] nume. int [ ][ ] b.7}.*.println ( rand. // aloca memorie ptr 10 reali int n=10. atât pentru documentare cât si pentru evitarea ambiguitãtilor create de clase cu acelasi nume din pachete diferite.*.5. for (int i=1.2. // la fel ca in C si C++ // specific Java Declararea matricelor (vectori de vectori) poate avea si ea douã forme.nextFloat()). byte[ ][ ] graf = new byte [n][n].util. expresia determinã numãrul de componente (nu de octeti !) pe care le poate contine vectorul.3. Exemple: float x[ ] = new float [10].List sau interfata java. In lipsa unei initializãri explicite. cu valori ce depind de tipul lor: zerouri pentru elemente numerice. class test { public static void main (String av[ ]) { List list. declararea unei variabile (sau unui parametru formal) de un tip vector se poate face în douã moduri. .awt. 12 .i++) // scrie 10 numere aleatoare System. . Acest tip este diferit de tipul definit de clasa JDK Vector (vectori ce se pot extinde automat) si de aceea vom folosi si denumirea de vector intrinsec (limbajului) pentru vectori ca cei din C.awt. // corect: int a[] = new int[100]. O variabilã vector este automat în Java o variabilã referintã iar memoria trebuie alocatã dinamic pentru orice vector.Florian Moraru – Programare Orientata pe Obiecte Random rand =new Random().

i<=10. [C pentru char[]. încadrarea indicilor între limitele declarate. se verificã automat. // initializare maxim partial for (String s: v) // repeta pentru s cu valori in vectorul v if ( max. pentru a determina numãrul de coloane.Florian Moraru – Programare Orientata pe Obiecte Un vector intrinsec cu componente de un anumit tip este considerat ca un obiect de un tip clasã. [F pentru float[] s. Functia care determinã sirul de lungime maximã dintr-un vector de siruri se poate scrie astfel: static String maxLen ( String v[ ]) { String max =0. Exemplu: int [ ] a= new int [10]. Exemplu: // functie de copiere a unui vector public static void copyVec ( int a [ ] . } O matrice este privitã si în Java ca un vector de vectori. la executie.length.length() < v[i].length.length() ) // compara lungimile a doua siruri max=v[i].length() ) // compara lungimile a doua siruri max=s. iar variabila "length" se poate folosi pentru fiecare linie din matrice. // retine adresa sirului cel mai lung return max. 13 . deci în exemplul anterior se produce exceptia de depãsire a limitelor la valoarea i=10 . tip recunoscut de compilator dar care nu este definit explicit în nici un pachet de clase.i<v. In Java. i++) // a. iar numãrul de elemente din vector se transmite ca argument la functii.m. atunci când diferã de capacitatea sa.length() < s. for (int i=1. Variabila predefinitã cu numele length poate fi folositã ( ca membru al claselor vector ) pentru a obtine dimensiunea alocatã pentru un vector (“capacitatea vectorului”).length =dimensiune vector a b[i] = a[i].a. for (int i=0.i < a. } De retinut cã length este dimensiunea alocatã si nu dimensiunea efectivã a unui vector.i++) if ( max. // exceptie la a[10]=10 Numerotarea componentelor unui vector este de la zero la (length-1). atribuind succesiv lui “ob” valorile din vectorul “a” (valori ce pot fi de un tip primitiv sau de un tip clasã). Variabila length nu trebuie confundatã cu metoda "length()" din clasa String.int b[ ] ) { for (int i=0.d. // retine in max sirul cel mai lung return max. iesirea din limite produce o exceptie si terminarea programului. [B pentru byte[]. [Z pentru boolean[]. Functia urmãtoare determinã sirul cel mai lung dintr-un vector de siruri: static String maxLen ( String v[ ]) { String max =v[0]. } Din versiunea 5 se poate itera printr-un vector la fel ca si printr-uo colectie de obiecte folosind un ciclu de forma: for ( tip ob: a) { …} // “tip” este tipul elementelor vectorului a cu semnificatia: “repetã ceva pentru fiecare valoare a variabilei “ob” din vectorul “a”. Numele acestor clase este format din caracterul ‘[‘ urmat de o literã ce depinde de tipul componentelor vectorului: [I pentru int[].i++) a[i]=i.

2. printmat (mat).Arrays.clone(). transmiterea parametrilor se face prin valoare. 5."5"."2". Exemplu: // creare vector cu divizorii unui întreg static int divizori (int n. j++) System. // cautare in vector ordonat float x[ ] = { 3.println ().length. printVec(x). Deci o functie Java nu poate transmite rezultate prin argumente de un tip primitiv. // y este o copie a vectorului x System. înainte de executia functiei. "3").out. adicã se copiazã valorile parametrilor efectivi în parametrii formali corespunzãtori. div). int d[ ]) { int k=0. // numar de divizori } // utilizare functie int div[ ]= new int [m]. // pune divizorul i în vectorul d return k. 2. nu existã probleme la transmiterea unei matrice ca argument la o functie.out.length.equals (x. // afisare vector int k = Arrays.i<a.binarySearch (t. // ordonare vector t System.4. Nu este necesarã transmiterea dimensiunilor matricei la o functie dacã matricea este ocupatã complet (la capacitatea ei). // ordonare vector x float y[ ] = (float[ ]) x.i<n. // un vector de numere reale Arrays. // completare vector divizori Clasa Arrays din pachetul “java. Exemplu: class Matrice { public static void printmat (int a[ ][ ]) { for (int i=0. ca si în C.out.Florian Moraru – Programare Orientata pe Obiecte Deoarece orice matrice este alocatã dinamic. } } // functie de afisare matrice // utilizare functie O functie poate avea un rezultat de un tip vector (sau matrice). System. 7.toString(t)). for (int i=1."3".8. j<a[i]. // scrie "true" } } Din versiunea 5 s-au adãugat clasei Arrays metode statice pentru transformarea în sir de caractere a continutului unui vector intrinsec unidimensional sau multidimensional cu elemente de orice tip 14 . cãutare s. {4.println ( Arrays.sort (x). sortare."6". // aloca memorie ptr vector divizori int nd = divizori (m.i++) if ( n %i == 0) d[k++]=i. In Java. // un vector de siruri Arrays.println ( Arrays.6} }.util” reuneste functii pentru operatii uzuale cu vectori având elemente de orice tip (primitiv sau clasã): afisare.out.y)).5.3}. Exemplu: import java. class ExArrays { // utilizare functii din clasa Arrays public static void main ( String args[] ) { String t[ ] ={ "4".print (a[i][j]+ " "). } } public static void main (String [ ] arg) { int mat[ ][ ]= { {1."1" }.6 }.a.util.sort (t).i++) { for (int j=0.1. dar poate modifica componentele unui vector primit ca argument.

). {1. prin abatere de la cursul normal. Double b[ ][ ]= { {0. fie "aruncate" mai departe. System. pentru cã permite semnalarea la executie a unor erori uzuale. s. de ex. // afisare caracter citit } 15 .2} }. ce pot apãrea în anumite puncte din program si care afecteazã continuarea programului.toString(a)). Aceste exceptii corespund unor erori grave de programare.1.println ((char) ch). Exceptii program în Java O exceptie program este o situatie anormalã apãrutã în executie si care poate avea cauze hardware sau software.Florian Moraru – Programare Orientata pe Obiecte primitiv sau de tip Object. 2. deoarece nu s-au transmis argumente prin linia de comandã. vectorul "arg" are lungime zero si deci nu existã arg[0] (indice zero). {2.2" (argumentul arg[0] nu este un sir corect pentru un numãr întreg) produce o exceptie NumberFormatException. 0.read ( ). 1.0. Exceptiile Java sunt de douã categorii: . Urmãtorul program poate produce (cel putin) douã exceptii.out. 2. cum ar fi: erori de indexare a elementelor unui vector (indice în afara limitelor).2. // citeste un caracter de la tastatura System. evitând efectele imprevizibile ale acestor erori (în C. Existenta exceptiilor este un mare avantaj al limbajului Java.out.Exceptii care trebuie fie tratate. conversie prin "cast" între tipuri incompatibile. prin mesaje clare asupra cauzei si locului unde s-a produs eroarea. pentru cã altfel compilatorul marcheazã cu eroare functia în care poate apare o astfel de eroare ("Checked Exceptions": exceptii a cãror tratare este verificatã de compilator). Exemplu de folosire: double [ ] a= {1. Ambele exceptii mentionate erau exceptii "Runtime".3. erori la operatii de citire-scriere.1. Exceptiile generate de operatiile de intrare-iesire (inclusiv la consolã) trebuie fie aruncate. împãrtire prin zero. dar care pot fi interceptate si tratate de cãtre programator.4}. Exceptiile pot fi privite ca evenimente previzibile. 4. 2. dacã este folosit gresit: class Exc { public static void main (String arg[ ]) { System.0. care nu permit continuarea executiei si care apar frecvent în programe. utilizarea unei variabile referintã ce contine null pentru referire la date sau la metode publice.deepToString(b)).0.in. 0.parseInt(arg[0])).1. } } // afiseaza primul argument O comandã pentru executia programului de forma "java Exc" produce exceptia ArrayIndexOutOfBoundException.out.out. .2}. Exemplu: public static void main (String arg[ ]) throws Exception { int ch= System. dar nu produc neapãrat terminarea programului. 3. fie tratate pentru cã suntem obligati de cãtre compilatorul Java. date incorecte).1.a. System. 1.2}.Exceptii care nu necesitã interventia programatorului (numite "Runtime Exceptions"). O linie de comandã de forma "java Exc 1.println ( Integer. Dacã nu sunt tratate.println ( Arrays. aceste exceptii produc afisarea unui mesaj referitor la tipul exceptiei si terminarea fortatã a programului.Aceste exceptii corespund unor situatii speciale care apar la utilizarea unui program (fisiere negasite. Compilatorul stie care metode pot genera exceptii si cere ca functiile care apeleazã aceste metode sã arunce mai departe sau sã trateze exceptiile posibile. exceptie generatã în functia "parseInt".println(Arrays.

} catch(NumberFormatException ex) {return false.parseDouble(s). Existã si situatii când exceptia trebuie tratatã si altfel decât prin afisarea unui mesaj. care împiedicã aparitia unui mesaj de avertizare la producerea exceptiei. fãrã a examina fiecare caracter în parte: public static boolean isNumber (String s){ try { Double.parseFloat (s).} Variabilele folosite în blocul try-catch vor fi declarate în afara acestui bloc. cu acelasi efect la executie ca al exceptiilor “Runtime”: int ch. // afisare in ecou ca un caracter } catch(IOException e) {e.} return true. exemplul urmãtor aratã cum putem verifica dacã un sir de caractere reprezintã un numãr corect (în orice format permis pentru numere neîntregi). pentru a obliga programatorul sã ia în considerare exceptia ce poate apare la functia de citire "read". fie sã trateze exceptia printr-un bloc trycatch care sã includã apelul metodei. Exemplu de verificare a unui sir dacã reprezintã un numãr corect sau nu. Tratarea exceptiilor necesitã folosirea unui bloc try-catch pentru delimitarea sectiunii de cod pentru care exceptiile posibile sunt redirectate cãtre secvente scrise de utilizator pentru tratarea exceptiilor produse.out. } catch (NullPointerException e) { } } // exceptie daca ref==null // interzice afisare mesaj (nerecomandat!) Anumite functii sunt apelate de mai multe alte functii si deci aruncarea exceptiei obligã si functiile care le apeleazã sã arunce exceptie.printStackTrace(). Aruncarea exceptiei nu este posibilã în definirea unor functii din interfete sau mostenite de la alte clase (pentru cã ar modifica antetul functiei). try { while ( (ch = System. In aceste cazuri vom face o tratare minimalã a exceptiilor (afisarea cauzei cu metoda “printStackTrace” prezentã în orice clasã exceptie).hashCode(). // daca s-a produs exceptie } return true. try {int h = ref. pentru a fi accesibile si în afara blocului. // daca nu s-a produs exceptie } Este preferabilã aruncarea unei exceptii fatã de tratarea prin ignorarea exceptiei.println ((char)ch). } catch (NumberFormatException ex) { return false. } 16 . prin tratarea exceptiei: public static boolean isNumber (String s) { try { Float.in. O metodã care apeleazã o metodã ce aruncã exceptii verificate trebuie fie sã semnaleze mai departe posibilitatea aparitiei acestei exceptii (prin clauza throws). datoritã citirii caracterului EOF (sfârsit de fisier) sau unei erori la citire.read()) >=0) // citeste un octet de la consola System. Exemplu: public static void main (String arg[]) { Object ref=null.Florian Moraru – Programare Orientata pe Obiecte Absenta clauzei throws din functia "main" este semnalatã ca eroare de compilator.

// deschide fisier pentru citire while ( true) System.out. } .out. In exemplul urmãtor se foloseste un singur bloc try pentru diferite exceptii posibile: try { f= new RandomAccessFile(arg[0]. } try { while ( true) System.readInt()). dar alteori este preferabilã tratarea exceptiei (ca în cazul detectãrii existentei unui fisier înainte de a fi deschis.println("Eroare la citire fisier").printStackTrace().println ( f. while ( true) System.out. Un bloc try se terminã cu una sau mai multe clauze catch pentru diferite tipuri de exceptii care pot apare în bloc. de ex. } catch (FileNotFoundException e) { System.) combinatã cu tratarea altor exceptii (de exemplu exceptia de sfârsit de fisier). System.exit(-1). ca în exemplele urmãtoare: void fun (Object obj) { if (obj==null) { System. } Este posibilã si aruncarea unor exceptii putin probabile (exceptia de citire. } public static void main (String arg[ ]) { if (arg.out. System.println ("Fisier negasit"). // citeste si scrie un numar } catch (IOException e) { } // exceptie de sfarsit de fisier la citire Varianta urmãtoare foloseste un singur bloc try.out.println("Eroare la citire fisier").Florian Moraru – Programare Orientata pe Obiecte O functie poate contine unul sau mai multe blocuri try . iar un bloc try poate contine una sau mai multe instructiuni. sau a utilizãrii unor variabile referintã ce pot fi nule).println ( arg[0])."r"). Producerea unor exceptii poate fi prevenitã prin verificãri efectuate de programator. în care pot apare exceptii.println ( f.."r").println ("Null Reference"). } catch (EOFException e) { } catch (IOException e) { System.println ("No Argument"). dar separã fiecare tip de exceptie: try { f= new RandomAccessFile("numer".out. // deschide fisier pentru citire } catch (IOException e) { // exceptie de fisier negasit System.out."r").readInt()). } System. // citeste si scrie un numar } catch (IOException e) { e. } Varianta urmãtoare foloseste douã blocuri try pentru a trata separat exceptiile: try { f= new RandomAccessFile(arg[0].println ( f.exit(-1). // afiseaza primul argument } Uneori este preferabilã verificarea prin program (ca în cazul unor conversii de tip nepermise)..length == 0 ) { System.out.readInt()).out. 17 .

Aceste metadate permit obtinerea de informatii despre clase la executie. Ruby s. Semnalarea erorilor prin exceptii si informatiile furnizate despre exceptii se datoreazã tot executiei programelor Java sub controlul masinii virtuale. apeluri de metode determinate la executie si alte operatii imposibile pentru un limbaj compilat.Florian Moraru – Programare Orientata pe Obiecte Platforma Java Java a început ca un limbaj de programare dar în prezent a devenit o platformã de programare pe care se pot utiliza mai multe limbaje (Java. numele si tipul metodelor clasei. formele de constructori pentru obiectele clasei s.a.a. aceeasi masinã virtualã Java si aceleasi biblioteci de clase (extinse si cu alte clase). numele variabilelor din clasã. Impreunã cu codul clasei (metodele clasei în format compilat) se mai încarcã si informatii despre clasã. instantierea de clase cunoscute numai la executie. fãrã interventie în codul sursã (cu conditia ca aceste clase sã respecte anumite interfete). Acest mod de lucru face posibilã utilizarea de clase cu nume cunoscut doar la executie sau înlocuirea unor clase. numele superclasei. utilizatorii au posibilitatea de a modifica anumite aspecte ale comportãrii masinii virtuale Java. alãturi de independenta fatã de procesorul pe care se executã. Scala) care au în comun acelasi cod intermediar. Masina virtualã împreunã cu bibliotecile standard de clase Java formeazã “mediul de executie Java” (JRE=Java Runtime Environment). JRuby.) sunt interpretate. numite si metadate: tipul clasei (sau interfetei). C#. Groovy. ceea ce a dat nastere expresiei de cod controlat (“managed code”). Interpretorul încarcã automat sau la cerere clasele necesare pentru executia functiei “main”(din fisiere de tip “class” sau de tip “jar” locale sau aduse prin retea de la alte calculatoare). Codul intermediar generat de compilatoarele Java (numit “bytecode”) este interpretat sau executat într-o masinã virtualã Java (JVM). Mai nou. 18 . Aceste facilitãti. In plus. Codul intermediar Java este “asistat” (supravegheat) la executie de cãtre masina virtualã. explicã de ce limbajele orientate pe obiecte mai noi (Java. se pot adãuga metadate (“annotations”=adnotãri). utilizabile de cãtre diverse programe înainte de executie sau în cursul executiei.

fgetc. fclose care primesc ca date variabilele “in”. fclose(in).Florian Moraru – Programare Orientata pe Obiecte 2. In programarea orientatã pe obiecte programatorul creeazã obiectele necesare aplicatiei si apeleazã metode ale acestor obiecte pentru actiuni ce folosesc datele continute în obiectele aplicatiei. sub formã de proceduri (functii)."w"). iar datele prelucrate se transmit între functii prin argumente (sau prin variabile externe). FILE * in =fopen(src. In C++ o variabilã de un tip clasã este un nume pentru un obiect. “out” si “ch”. folosind operatorul new se si deschid cele douã fisiere (actiunea functiei “fopen” din C).close(). while ( (ch= in. // un obiect FileWriter out = new FileWriter (dst).write(ch). out. cu avantaje în dezvoltarea programelor mari. // scrie un caracter fclose(out). String dst) throws IOException { FileReader in = new FileReader (src). din clasele respective.read()) != -1) // cere obiectului “in” operatia “read” out. Exemplul urmãtor este o functie Java de copiere octeti dintr-un fisier sursa “src” intr-un fisier destinatie “dst”: public static void filecopy (String src. octet cu octet: // copiere fisier in C void filecopy ( char * src. // cere obiectelor operatia “close” } In acest exemplu se folosesc douã obiecte: un obiect de tip FileReader (prin variabila “in”) si un obiect de tip FileWriter (prin variabila “out”). Obiectele “in” si “out” contin informatii despre fisierele “src” si “dst” necesare prelucrãrii lor. Definitia unei clase poate fi privitã ca o extindere a definirii unui tip structurã din C. // cere obiectului “out” operatia “write” in. o metodã poate fi aplicatã numai obiectelor din clasa care contine si metoda. prin metoda “read” se cere obiectului “in” sã citeascã si sã furnizeze un caracter."r"). De aceea. Un program orientat pe obiecte este o colectie de obiecte care interactioneazã prin apeluri de functii specifice fiecãrui tip de obiect. fputc. La crearea obiectelor. out). Aceste functii se numesc si metode ale clasei. // deschide fisier destinatie while ( (ch=fgetc (in)) != -1) // citeste un caracter fputc (ch. In programarea proceduralã sarcina programatorului este de a specifica actiuni de prelucrare.close(). // alt obiect int ch. Pentru obiectele “in” si “out” se pot apela si alte metode. dar în Java o variabilã de un tip clasã contine un pointer cãtre un obiect (corespunde unei variabile referintã din C++). } Actiunile necesare copierii se realizeazã prin functiile fopen. In Java si în C++ functiile (metodele) sunt subordonate claselor. Un obiect corespunde unei variabile structurã din C. cam aceleasi care se memoreazã într-o structurã de tip FILE în limbajul C. // deschide fisier sursa FILE * out =fopen(dst. char * dst) { char ch. mai corectã este exprimarea ‘se apeleazã 19 . Introducere în programarea orientatã pe obiecte Clase si obiecte Programarea cu clase si obiecte reprezintã un alt mod de abordare a programãrii decât programarea proceduralã (în limbaje ca C sau Pascal). iar prin metoda “write” se cere obiectului “out” sã scrie în fisier caracterul primit ca argument. care contine si functii pentru operatii cu datelor continute în clasã. Un program procedural (scris în C de exemplu) este o colectie de functii. Exemplul urmãtor este o functie C de copiere a unui fisier.

out. In exemplele anterioare si în cele ce vor urma se poate observa mutarea accentului de pe actiuni (functii) pe obiecte (date) în programarea orientatã pe obiecte. out = new FileWriter (dst).Florian Moraru – Programare Orientata pe Obiecte metoda “read” pentru obiectul adresat prin variabila “in”’. In general.close(). cu nume dat la construirea obiectului. trebuie spus cã utilizarea unei functii statice de copiere nu este în spiritul POO. Eliberarea memoriei se face automat si nu cade în sarcina programatorului Java. Obiectele Java sunt create dinamic. Exemplu: public class FileCopier { // datele clasei private FileReader in. Functiile statice Java corespund functiilor C si pot fi folosite fãrã ca sã existe obiecte (ele fac parte totusi din anumite clase). ce trebuia sã fie foarte simplu.a.write(c). // creare obiect “fc” fc. } // o metoda de copiere public void copy () throws IOException { int c. // destinatia datelor // constructor public FileCopier (String src. realizate prin metodele clasei din care fac parte. clasa StringTokenizer folositã la extragerea cuvintelor dintr-un sir s.close(). apelatã implicit de operatorul new.read()) != -1) out. iar un obiect este un caz concret (o realizare a conceptului sau o instantiere a clasei). arg[1]). folosind de obicei operatorul new. Mai aproape de stilul propriu POO. trebuie privitã doar ca un prim exemplu. String dst) throws IOException { in = new FileReader (src). } } // clasa pentru verificarea clasei FileCopier class UseFileCopier { public static void main (String arg[]) throws IOException { FileCopier fc = new FileCopier (arg[0]. // cere obiectului “fc” operatia “copy” } } Clasa “FileCopier”. while ( (c= in. 20 . cu sau fãrã argumente. si nu ca un model de clasã realã Java. dar toate obiectele suportã aceleasi operatii. ar trebui definitã o clasã copiator de fisiere. care are ca rezultat o referintã la obiectul alocat si initializat printr-o functie constructor. In Java existã obiecte comparator (de un subtip al tipului Comparator) folosite în compararea altor obiecte. obiecte diferite contin date diferite. Un obiect de tip FileReader corespunde unui anumit fisier. Clasele sunt module de program reutilizabile Definirea si utilizarea de module functionale permite stãpânirea complexitãtii programelor mari si reutilizarea de module prin crearea de biblioteci. // sursa datelor private FileWriter out. având si o metodã (nestaticã) de copiere. O clasã corespunde unei notiuni abstracte cum ar fi “orice fisier disc”.copy(). in. clasa Enumerator folositã la enumerarea elementelor unei colectii. Este posibil ca mai multe variabile de un tip clasã sã continã adresa aceluiasi obiect. Relativ la exemplul Java. uneori derivate din verbul ce defineste principala actiune asociatã obiectelor respective. Numele de clase sunt substantive. definitã anterior.

realizati prin colaborarea mai multor functii. // pune in stiva un element de tip T T pop (Stiva * sp ). Stiva s . dar în Java si C++ un modul este o clasã. ca posibilitãti oferite). care sã fie folositã împreunã cu programul de utilizare a stivei. In Java acelasi program de exersare a operatiilor cu stiva aratã astfel: 21 .Se poate realiza un cuplaj mai slab între module. Un exemplu de utilizare a unei stive: #include "stiva. care reuneste în general mai multe functii în jurul unor date. de exemplu nu se poate folosi o metodã de scriere pentru un fisier deschis numai pentru citire sau dintr-o clasã ce permite numai citirea de date. Existenta unui numãr mare de clase predefinite în Java reduce mult timpul de dezvoltare a unor noi aplicatii.Sunt posibile verificãri de utilizare corectã a unor metode sau asupra succesiunii de apelare a unor functii.Metodele unei clase necesitã mai putine argumente. . // scoate din stiva un element de tip T Definitia tipului “Stiva” si definitiile functiilor depind de implementare. pop). // pune x pe stiva while ( ! emptySt (&s) ) // cat timp stiva contine ceva printf("%d \n". efectul unei metode este fie de a face accesibile date din clasã. în spatele cãrora pot sta clase cu implementãri diferite dar cu acelasi mod de utilizare. prin argumente (ca niste variabile externe metodelor. In limbajul C se pot defini functiile pentru operatii cu stiva astfel ca utilizarea lor sã nu depindã de implementarea stivei. s.a. dar interne clasei). // scoate din stiva si afiseaza } Modificarea tipului de stivã necesitã un alt fisier “stiva. în sensul cã modificarea anumitor module nu va afecta restul programului. pentru operatii cu anumite structuri de date (arbori binari cu auto-echilibrare). fie printr-o listã înlãntuitã. scoate datele din vârful stivei si test de stivã goalã.h” si o altã bibliotecã de functii (push. . unele interne clasei. printre care folosirea de interfete Java. O stivã poate fi realizatã fie printr-un vector. x++) // genereaza date ptr continut stiva push (&s.Florian Moraru – Programare Orientata pe Obiecte In limbajul C un modul de program este o functie. pentru arhivare-dezarhivare. fie sunt disponibili prin biblioteci de functii destul de greu de utilizat. Variabilele unei clase sunt implicit accesibile metodelor clasei si nu mai trebuie transmise explicit. dar operatiile cu stiva sunt aceleasi. .x). Aceastã decuplare sau separare între module se poate realiza prin mai multe metode. // test stiva goala int push (Stiva * sp. initSt (&s). Pentru concretizare vom prezenta solutiile C si Java pentru câteva probleme. // initializare stiva for (x=1. fie de a modifica variabile din clasã pe baza argumentelor primite. pop (&s) ) .h" #define T int void main () { int x. // initializare stiva int emptySt (Stiva * s).Solutii mai simple pentru functii al cãror efect depinde de stare (de context). .O clasã poate încapsula algoritmi de complexitate ridicatã. iar aceste argumente nu sunt modificate în functie. Utilizarea de clase ca module componente ale programelor are o serie de avanaje fatã de utilizarea de functii independente: . Exemple sunt algoritmi pentru lucrul cu expresii regulate. Primul exemplu se referã la utilizarea structurii de tip stivã (“stack”) în aplicatii. x<10. T x). astfel de algoritmi fie nu sunt disponibili în C. indiferent de implementare: pune date pe stivã. prin reutilizarea acestor clase (care trebuie cunoscute. cum ar fi de apeluri anterioare ale aceleeasi functii sau ale altor functii pregãtitoare. prin folosirea unui pointer la o structurã: void initSt ( Stiva * sp).

// d=0 cand nu mi exista un numar corect In Java existã clasa de bibliotecã StringTokenizer. double d. deci starea sau contextul în care se executã functia ce dã urmãtorul cuvânt. astfel cã se pot analiza în paralel mai multe siruri. } // primul cuvânt din “str”.println (token). x++) // pune 10 numere in stiva s.pop()). prin definirea unei alte clase “Stack” nu necesitã modificãri în functia anterioarã.out. In plus. // str = sir analizat do { d= strtod (p.delim). primul apel diferã de urmãtoarele apeluri ale functiei. Exemplu de utilizare: char * p = str.nextToken(). // afiseaza obiectul scos din stiva } Modificarea implementãrii stivei. cuv=strtok(0. dar pretul plãtit este modificarea sirului analizat si imposibilitatea de a analiza în paralel mai multe siruri (pentru cã adresa curentã în sirul analizat este o variabilã staticã internã a functiei). la construirea obiectului).println ( s. // afisare cuvint } La crearea unui obiect StringTokenizer se specificã sirul analizat. while (cuv !=NULL) { puts(cuv). // lista separatori de cuvinte StringTokenizer st = new StringTokenizer (sir. folositã dupã cum urmeazã: String sep = new String (" .hasMoreTokens()) { // daca mai sunt cuvinte in sirul analizat String token = st. pentru fiecare folosind un alt obiect. // “sir” = sir analizat while (st. } while (d != 0).push(x) in Java 5 while ( ! s. // extrage urmatorul cuvint din linie System. In limbajul C se pot întâlni mai multe solutii ale acestei probleme în diferite functii de bibliotecã: Functia “strtok” se foloseste relativ simplu. Metodele “nextToken” si “hasMoreTokens” folosesc în comun o variabilã a clasei care contine pozitia curentã în sirul analizat (initializatã cu adresa sirului. separate prin anumite caractere date. de exemplu pentru a putea scrie într-un fisier. Problema este aceea cã dupã fiecare cuvânt extras se modificã adresa curentã în sirul analizat. Exemplu: cuv=strtok(str. sep= sir de separatori // daca s-a gasit un cuvant // afisare cuvant // urmatorul cuvant din “str” Functia “strtod” extrage urmãtorul numãr dintr-un sir si furnizeazã adresa imediat urmãtoare numãrului extras. In prelucrarea fisierelor apar situatii când executia cu succes a unei functii depinde de folosirea anterioarã a altor functii (cu anumite argumente). // creare obiect stiva for (int x=1.\n\t"). O situatie asemãnãtoare apare la utilizarea unor functii care compun o interfatã graficã si care trebuie folosite 22 . d). Un al doilea exemplu este cel al extragerii de cuvinte succesive dintr-un sir de caractere ce poate contine mai multe cuvinte.out.Florian Moraru – Programare Orientata pe Obiecte public static void main (String arg[ ]) { Stack s = new Stack().. acesta trebuie anterior deschis pentru creare sau pentru adãugare (extindere fisier existent). modificarea tipului datelor puse în stivã necesitã modificãri mai mici în Java decât în C. In plus. x<10. sep). // s. ci doar punerea noii clase în cãile de cãutare ale compilatorului si interpretorului Java.sep)..empty()) // cat timp stiva nu e goala System.push ( new Integer(x)).&p). // cauta de la adresa p si pune tot in p adresa urmatoare printf ("%lf\n".

cu o singurã metodã “compare”. parte imaginarã) pentru un numãr complex: typedef struct { float re. tehnici specifice POO. vom defini o structurã cu douã variabile (parte realã. Double.a. Cel mai simplu exemplu din Java este interfata Comparator. precum si modul de construire a obiectelor de tip “Complex” (de initializare a pãrtii reale si pãrtii imaginare). operatiile necesare într-o clasã sunt fie mostenite de la o altã clasã. Exemple de metode des folosite din clasa String: 23 . în urma unor comenzi date de utilizator unui mediu vizual de dezvoltare a aplicatiilor. este refolositã si într-o clasã stivã vector. Multe din clasele de bibliotecã Java pot fi privite ca având drept scop extinderea limbajului cu noi tipuri de date. Astfel de conditionãri reciproce nu se pot verifica automat în C. fie prin definirea clasei stivã ca o clasã derivatã din vector. Pentru fiecare tip de obiecte (comparabile) va exista o altã definitie a functiei “compare”. pentru compararea a douã obiecte (dupã modelul comparatiei de siruri din C). de modificare a proprietãtilor si de conectare cu alte clase (prin apeluri de metode sau prin evenimente). In Java operatiile sunt metode dintr-o aceeasi clasã si se poate verifica printro variabilã a clasei succesiunea corectã de folosire a metodelor. utile în mai multe aplicatii. fie delegate spre executie metodelor unei alte clase. prin asamblarea de componente prefabricate (în special pentru interfata graficã.Florian Moraru – Programare Orientata pe Obiecte într-o anumitã ordine. Boolean. Character. se pot defini noi tipuri de date ca grupuri de variabile de alte tipuri predefinite. extinderea automatã a unui vector. De exemplu. O componentã poate contine una sau mai multe clase si poate fi reutilizatã si adaptatã fãrã interventie în codul sursã al componentei (care nici nu este disponibil). In Java. Date etc. float im. In POO adaptarea unei clase la cerinte specifice unor aplicatii nu se face prin interventie în codul clasei ci prin derivare sau prin delegare. care va specifica si operatiile permise cu date de acest tip. dar interfetele cu mai multe metode creeazã posibilitãti inexistente în C. O clasã creeazã un spatiu de nume pentru metodele clasei: pot exista metode cu acelasi nume (si aceleasi argumente) în clase diferite. BigDecimal. String. Exemple: BigInteger. } Complex . In acest fel. fie prin agregare (compunere). Cel mai folosit tip de date definit printr-o clasã este tipul String. O componentã JavaBeans poate fi (re)utilizatã fãrã a scrie cod. Interfata Collection defineste câteva operatii ce trebuie sã existe în orice colectie de obiecte. Pachetul de clase (“package”) este o altã unitate Java care creeazã un spatiu de nume pentru clasele continute în el. O interfatã cu o singurã metodã corespunde unui pointer la o functie din limbajul C. fie prin folosirea unei variabile Vector în clasa stivã. un obiect de tip String contine ca date un vector de caractere si lungimea sa si suportã un numãr mare de metode ce corespund unor operatii uzuale cu siruri (realizate prin functii de bibliotecã în limbajul C). Float. introducerea unui tip “Complex” se va face prin definirea unei clase. necesarã uneori dupã adãugarea la vector. Functionalitatea unei clase poate fi reutilizatã în alte clase fie prin derivare. Short. dar nu numai). indiferent de structura colectiei: adãugare obiect la colectie s. fiind vorba de functii independente. O interfatã contine una sau mai multe operatii (metode abstracte) cu rol bine definit. O notiune proprie programãrii cu obiecte este notiunea de componentã software. toate cu acelasi rol dar cu mod de lucru diferit. Pentru fiecare tip primitiv de date existã un tip clasã asociat: Integer. O componentã este de obicei o clasã care respectã anumite conditii. dar a cãror implementare nu poate fi precizatã. prin generarea automatã a operatiilor de instantiere. prin definirea de tipuri structurã. Clasele creeazã noi tipuri de date In limbajul C. O interfatã poate fi implementatã de mai multe clase. Long. De exemplu. Ideea este de a obtine rapid un prototip al aplicatiei fãrã a scrie cod sau cu un minim de programare.

i++) { for (int j=0. ca urmare. // caracterul din pozitia ‘i’ a acestui sir int indexOf (char c). de argumente de functii. “length” si “substring” din clasa String // inlocuire repetata in sirul s a subsirului s1 prin sirul s2 public static String replaceAll (String s. pentru tipuri de date necesare aplicatiilor. // comparatie la egalitate de siruri int compareTo (String s). Vom citi un apel de metodã de forma a.a. // concatenare subsiruri p=s. String s1. int j).indexOf(s1. trebuie sã redefineascã anumite metode mostenite: “toString”. // aloca memorie ptr matrice } // modifica valoare element public void setElement (int i. Noile tipuri de date se pot folosi în declaratii de variabile. p+s2. for (int i=0. // lungimea acestui sir (pentru care se apeleazã metoda) boolean equals (String s). } // citire valoare element public boolean getElement (int i.compareTo(b) astfel: comparã acest obiect (‘a’) cu obiectul ‘b’. // are ca rezultat sirul modificat prin inlocuire } Utilizatorii îsi pot defini propriile clase.indexOf(s1).int j. de exemplu. a= new boolean[n][n]. // o matrice patratica private int n. s=s+"\n".Florian Moraru – Programare Orientata pe Obiecte int length (). // indicele (pozitia) sirului ‘s’ in acest sir (prima aparitie) String substring(int i).length()). String s2) { int p = s.length()). // urmatoarea aparitie a lui s1 in s } return s. // extrage subsirul dintre pozitiile ‘i’ si ‘j’ De observat cã operatiile de comparatie (si alte metode) au un singur argument desi comparã douã obiecte sir: obiectul pentru care se executã metoda si obiectul primit ca argument.int j) { return a[i][j].j<n. // indicele (pozitia) caracterului ‘c’ in acest sir int indexOf (String s).i<n. } 24 . boolean b) { a[i][j]=b. // nr de linii si coloane // constructor de obiecte public BoolMatrix (int n) { this. // extrage din acest sir subsirul care incepe in pozitia ‘i’ String substring(int i. In Java orice clasã este automat derivatã dintr-o clasã genericã Object si. } // sir cu elementele din matrice public String toString () { String s="".n=n.p)+ s2 + s. Exemplu de clasã minimalã pentru o matrice de biti: // matrice cu elemente de tip boolean public class BoolMatrix { private boolean a[ ][ ].substring(p+s1. “equals” s. putem defini o clasã “BoolMatrix” pentru o matrice cu elemente de tip boolean. Exemplu de functie staticã care foloseste metodele “indexOf”. // p = pozitia primei aparitiii a lui s1 in s while ( p >=0 ) { // indexOf are rezultat –1 daca s nu contine pe s1 s = s. de functii. // comparatie de siruri cu acelasi rezultat ca “strcmp” (C) char charAt (int i).substring(0.j++) s=s + ( a[i][j]==true ? "1 ": "0 ") .

La fel cum un argument formal de tip double poate fi înlocuit cu un argument efectiv de tip int. System. Tipul unei clase derivate este subtip al tipului clasei din care derivã. Tipurile de date definite prin clase pot forma ierarhii de tipuri compatibile (care se pot înlocui prin atribuire sau la transmitere de argumente). cu argumente de un tip general si utilizabile cu o multitudine de tipuri de argumente (asa cum functia “sqrt” se poate apela cu argument de orice tip numeric din C). indiferent de tipul datelor care vor fi memorate în vector (în listã). pentru cã acestor variabile li se pot atribui variabile de orice alt tip clasã (care contin adresele unor obiecte). De exemplu. Metodele clasei sunt de obicei publice pentru a putea fi apelate din alte clase. Colectiile cu elemente de tip Object au existat de la început în Java. dar lungimea nu poate fi modificatã direct de cãtre functii din alte clase (si nici continutul vectorului de caractere).i<4.prin colectii ce contin un supertip al tuturor tipurilor clasã (tipul Object în Java).Florian Moraru – Programare Orientata pe Obiecte return s. iar colectiile cu tipuri de date ca parametri au apãrut din versiunea 5 si sunt numite generice (“generics”). Se mai spune cã datele sunt ascunse sau sunt încapsulate în fiecare obiect.i. Genericitatea colectiilor de obiecte poate fi realizatã în limbajele cu clase prin douã metode: . iar tipul float ca un subtip al tipului double.prin clase ce au ca parametri tipurile de date folosite (numite “templates” în C++). Deoarece datele dintr-un obiect (variabile private) nu sunt direct accesibile din afara clasei si pot fi modificate numai prin intermediul metodelor clasei. orice modificare a vectorului de caractere dintr-un obiect StringBuffer (StringBuilder) este însotitã de modificarea lungimii sirului (în metodele care pot modifica lungimea sirului). 25 .true). Clasele permit programarea genericã Programarea genericã ne permite sã avem o singurã clasã pentru un vector (sau pentru o listã). clasa D fiind derivatã din clasa B. care este rãdãcina ierarhiei de clase Java. In Java nu se pot supradefini operatori.out. Variabilele dintr-o clasã sunt declarate de obicei cu atributul private. } } // sau System. O colectie de variabile de tip Object poate contine referinte la obiecte de orice tip.toString()).i++) mat. dar la extragerea din vector trebuie trecut de la tipul void* la tipul de pointer folosit la adãugare (prin operatorul de conversie de tip “cast”). utilizarea tipurilor de date definite prin clase este mai sigurã decât a celor definite prin structuri.println ( mat. for (int i=0. Pentru a permite colectii cu date de orice tip limbajul Java considerã cã toate clasele predefinite sau care urmeazã a fi definite de utilizatori sunt implicit derivate dintr-o clasã Object. asa cum tipul int poate fi considerat ca un subtip al tipului long.setElement (i. } // exemplu de utilizare public static void main (String arg[]) { BoolMatrix mat = new BoolMatrix(4). adãugarea la vector a unui pointer oarecare nu necesitã o conversie.out. Este la fel cum în limbajul C un vector de pointeri de tip void* poate fi folosit pentru a memora adrese ale unor date (alocate dinamic) de orice tip predefinit sau definit de utilizatori. In felul acesta se pot scrie functii generice.println (mat). ceea ce le face inaccesibile pentru metode din alte clase. deci operatiile cu noile tipuri de date se pot exprima numai prin metode asociate obiectelor (metode nestatice). tot asa un argument formal de un tip clasã B poate fi înlocuit cu un argument efectiv de un tip clasã D. . Tot programarea genericã ne permite sã folosim o singurã functie (metodã) pentru a parcurge elementele oricãrei colectii (indiferent de structura ei fizicã ) sau pentru a ordona orice listã abstractã (o colectie care suportã acces direct prin indice la orice element din colectie).

Intr-un program Java nu existã altceva decât obiecte si clase. k++) // genereaza numerele 1. for (int k=0.size().add ( new Integer(k)). variabile.k<v. Double. for (int k=0.2. // conversie automata de la “Integer” la “int” Clasele creeazã un model pentru universul aplicatiei Un program destinat unei aplicatii trebuie sã transforme notiunile si actiunile specifice aplicatiei în constructii specifice limbajului de programare folosit (functii.3… 10 v.Florian Moraru – Programare Orientata pe Obiecte Exemplu de calcul a sumei valorilor unor obiecte numerice dintr-un vector: Vector v = new Vector(). Float. // adauga numarul k la vector ca obiect de tip Integer int sum=0. // creare obiect vector de Integer for (int k=1. dar conversia în jos (de la Object la Integer. pentru rezultatul metodei “get”) trebuie cerutã în mod explicit prin operatorul de conversie ( ca si în C). // conversie automata de la “int” la “Integer” int sum=0.size(). // creare obiect vector (extensibil) for (int k=1. k<11.k++) sum += v. O altã simplificare privind utilizarea colectiilor generice de tipuri ce corespund tipurilor primitive (Byte.size().k++) { Integer obj = (Integer) v. k<11.intValue(). în sensul îndepãrtãrii progresive de masina fizicã prin introducerea de notiuni tot mai abstracte. Short. iar la extragere nu mai trebuie fãcutã nici o conversie de tip. Boolean. substantivele din 26 . k++) // genereaza numerele 1.add ( new Integer(k)). Acelasi exemplu dinainte cu colectii generice: Vector <Integer> v = new Vector<Integer>().3… 10 v.k<v. Exemplu reluat: Vector <Integer> v = new Vector<Integer>(). // adauga numarul k la vector ca obiect de tip Integer int sum=0. O colectie Java genericã specificã tipul obiectelor continute încã de la declarare (care poate fi orice subtip de Object sau de alt tip). Evolutia limbajelor de programare poate fi privitã si ca un progres al abstractizãrii. Character) este trecerea automatã de la tipul primitiv la tipul clasã corespunzãtor (“autoboxing”) si trecerea inversã (“unboxing”). // aduna numarul de tip “int” din obiectul “obj” } Conversia în sus de la subtipul Integer la supertipul Object se face automat (argumentul metodei “add” este de tip Object). etc. argumente de functii. Un program Java poate fi privit ca o descriere a unor obiecte si a interactiunilor dintre aceste obiecte.intValue().k<v.add ( k).3… 10 v. Integer.get(k). // creare obiect vector de Integer for (int k=1. k<11. // extrage din vector si conversie din Object în Integer sum += obj. k++) // genereaza numerele 1.k++) sum += v. Fiecare limbaj de programare oferã programatorilor o masinã virtualã (sau abstractã ) diferitã de masina concretã pe care se vor executa programele lor.).get(k). Identificarea obiectelor si actiunilor specifice unei aplicatii se face în faza de analizã orientatã obiect a problemei de rezolvat si implicã o abordare diferitã de cea anterioarã.2. for (int k=0. Programarea orientatã pe obiecte permite definirea de clase si obiecte ce corespund direct obiectelor din universul aplicatiei si modelarea relatiilor statice si dinamice dintre aceste obiecte.get(k).2. Analiza orientatã pe obiecte poate începe cu o descriere în limbaj natural a ceea ce trebuie sã facã programul.

length+1). Redimensionarea unui vector intrinsec se face printr-o nouã alocare de memorie si nu prin modificarea variabilei length (care are atributul final).) corespund direct unor obiecte din programele Java.Metoda “split” din clasa String produce un vector cu toate cuvintele dintr-un sir dat: String[] split (String regexp). ca în cazul obiectelor Vector. Multe aplicatii folosesc o interfatã graficã cu utilizatorii (operatorii) aplicatiei. Un obiect “Customer” va contine date de identificare ale clientului si metode pentru obtinerea sau modificarea acestor date. Exemplu: class Array { private Object a[]. Un vector intrinsec ocupã mai putinã memorie. } public Iterator iterator() { return Arrays.Florian Moraru – Programare Orientata pe Obiecte acest text corespund în general unor obiecte (clase). Obiectele de tipul “Account” sau “Customer” se numesc si obiecte din domeniul aplicatiei (“domain objects”). pentru jocuri. Exemple: .a. a[a. cu baze de date. pentru aplicatii bancare s.iterator(). meniuri. cu conexiuni între calculatoare s.a. s.a. pentru retragerea de bani din cont si pentru vizualizarea sumei de bani din cont. metoda staticã Arrays.asList (Object[]) transformã un vector intrinsec într-o colectie (de tip List). accesul la elementele vectorului este mai rapid si nu necesitã conversie prin “cast”.asList(a). Din versiunea 1. iar verbele din text corespund unor metode. } public void add (Object ob) { a=Arrays.a=a. Se recomandã atunci când vectorul nu îsi modificã dimensiunea initialã sau dimensiunea se modificã mai rar. Fiecare solutie de grupare a unor date are avantaje si dezavantaje si de aceea unele metode din clase de bibliotecã au ca rezultat sau primesc vectori intrinseci iar altele obiecte Vector. Intr-un astfel de vector se pot memora si date de un tip primitiv. O astfel de abordare este potrivitã pentru aplicatii grafice.Metodele “list” si “listFiles” din clasa File produc un vector cu toate fisierele dintr-un director dat: File[] listFiles().copyOf(a. Intr-o aplicatie bancarã vor exista clase si obiecte de genul “Account” (cont bancar) si “Customer” (client al bãncii).length-1]=ob. butoane. . cu colectii de obiecte.6 se poate folosi una din metodele statice “copyOf” din clasa Arrays pentru realocare vectori intrinseci.a. precum si metode pentru depunerea de bani în cont. Un obiect de tip “Account” va contine suma din cont (si alte date asupra operatiilor cu acel cont). Variabila publicã length are ca valoare dimensiunea alocatã pentru un vector intrinsec (numitã si “capacitate”). casete cu text. Aplicatiile mai pot contine obiecte ajutãtoare (“helper”) sau obiecte din clase predefinite pentru operatii cu anumite tipuri de date. Orice clasã colectie (deci si clasa Vector) contine o metodã “toArray” care produce un vector intrinsec cu obiectele din colectie. } } Obiectele din clasa Vector asigurã extinderea automatã a unui vector (realocarea dinamicã pentru mãrirea capacitãtii) la operatii de adãugare si au o multime de metode pentru operatii uzuale cu 27 . obiectele vizuale afisate pe ecran si care pot fi selectate sau actionate de operator (ferestre. public Array (Object a[]){ this. Vectori intrinseci si obiecte vector In Java coexistã vectorii mosteniti din limbajul C (ca tip de date predefinit si recunoscut de compilator) cu obiecte din clasele de bibliotecã Vector sau ArrayList.

System. Metoda “println” cu argument Object foloseste automat metoda “toString” pentru obiectul respectiv: System. Exemplu de functie similarã functiei “split”. return a. pentru clasa Vector. pentru extragere de cuvinte dintr-un sir (cuvintele sunt separate între ele printr-un numãr oarecare de spatii albe): class Unu { // cu rezultat vector intrinsec public static String[] split(String s) { StringTokenizer tok = new StringTokenizer(s).toString()). cãutarea unui obiect dat. // obiectul din pozitia i din acest vector boolean add (Object obj). // produce un vector intrinsec cu aceleasi elemente String toString(). eliminarea de elemente.out. // daca acest vector contine obiectul obj int indexOf (Object obj). int i). 28 .println (Arrays. while ( tok. String[] a = new String [tok. // adauga obj la sfarsitul acestui vector void addElement (Object obj).println(w). // dimensiunea acestui vector (nr de elemente) boolean contains (Object obj). Se recomandã pentru vectori de obiecte cu continut variabil (în decursul executiei programului). } } Afisarea continutului unui obiect Vector (ca si afisarea continutului oricãrei colectii) este simplificatã de existenta metodei “toString”. care are aceeasi functionalitate cu clasa Vector.toString (w)). // adauga obj la sfarsitul acestui vector void insertElementAt (Object obj. Clasa Vector contine un vector intrinsec de elemente de tip Object. // elimina elementul cu valoarea obj din vector Object remove (int i). // produce un sir cu elementele din vector. // inlocuieste elementull din pozitia i cu obj Object[] toArray(). // obiectul din pozitia i din acest vector Object get(int i). Incepând din versiunea 5 se poate preciza tipul elementelor din vector. } // utilizare functie public static void main (String arg[]) { String s = “ unu doi trei patru cinci “. // la fel cu: System.nextToken().println (w. String w[] = split(s).out. int i). // prima pozitie unde se afla obiectul obj sau –1 int indexOf (Object obj. // elimina elementul din pozitia i din acest vector void setElementAt (Object obj.out. // introduce obj in pozitia i din vector boolean remove (Object obj). int i=0. Exemple de metode utilizate frecvent: int size( ). s.a. // ultima pozitie din acest vector unde se afla obj Object elementAt (int i). // prima pozitie de dupa i unde se afla obiectul obj int lastIndexOf (Object obj). ca siruri Existenta unor metode diferite cu acelasi efect se explicã prin faptul cã din versiunea 2 clasa Vector a fost modernizatã pentru compatibilizare cu interfata List si a fost adãugatã clasa ArrayList.Florian Moraru – Programare Orientata pe Obiecte vectori: obtinerea numãrului de elemente. int i). plus variabile pentru capacitatea alocatã si pentru dimensiunea efectivã a vectorului.countTokens()]. folosind ceea ce se numeste un vector generic.hasMoreTokens()) a[i++]=tok. dar fãrã expresii regulate. ca si pentru orice altã clasã colectie.

in). Metode statice.". // citire cu format String param= sc. // functie statica din clasa Integer double r = Math. total).Florian Moraru – Programare Orientata pe Obiecte 3. Din versiunea 1. In felul acesta putem avea functii cu acelasi nume în clase diferite si se reduce posibilitatea unui conflict de nume.intValue (). Metodele statice sunt în general si publice.out.parseInt (args[0]).Metode statice.nextInt(). } } 29 .println (r). Exemple: System. Exemplu: class A { static String msg = "O. } } In Java se citesc din fisiere sau se preiau din linia de comandã siruri de caractere.out. // functie statica din clasa Math System.Metode aplicabile obiectelor (“Object Methods”) . utilizabile independent de obiecte (“Class Methods”) O metodã staticã Java corespunde unei functii din limbajul C.K. // afisare cu format Scanner sc=Scanner. // "static" este necesar aici public static void main (String arg[ ]) { System.println (msg). // apel metodã nestatica } // exemplu de utilizare pentru conversie de la String la int Integer a = new Integer (str).sqrt (x). pentru a putea fi apelate din orice altã clasã. name. Aceste clase nu sunt instantiabile. int x = getint (a). Utilizarea de clase si obiecte Clase fãrã obiecte.out. deci nu pot genera obiecte. Câteva clase Java nu sunt altceva decât grupãri de functii statice relativ independente.intValue().create(System.next(). Metodele statice au un parametru în plus fatã de metodele nestatice cu acelasi efect.printf ("%s %5d\n". // sau x = new Integer(str). Metodele Java sunt de douã categorii: . deoarece ele primesc un parametru de un tip primitiv (double). Pentru metodele cu un operand de un tip clasã avem de ales între o functie ne-staticã si o functie staticã. iar analiza lor si conversia în format intern pentru numere se face explicit de cãtre programator. Exemple: // o metoda statica pentru conversie numar din Integer in int public static int getInt (Integer a) { return a. // metoda ptr citirea sirului urmator int value=sc. Exemplu de utilizare a unor metode statice: // afisare radical dintr-un numar primit in linia de comanda class Sqrt { public static void main (String args[ ]) { int x = Integer. // metoda ptr citirea urmatorului numar intreg Pentru functiile matematice nu existã altã posibilitate de definire decât ca metode statice. O metodã staticã se poate referi numai la variabile statice din clase (variabile definite în afara functiilor si cu atributul static).5 au fost introduse în Java metode similare functiilor "scanf" si "printf" din C. dar poate fi folositã numai precedatã de numele clasei.

Metode aplicabile obiectelor Specific programãrii orientate pe obiecte este utilizarea de clase care contin si date si care pot genera obiecte. Metode statice pot fi întâlnite si în clase cu majoritatea metodelor nestatice.valueOf (int i). care poate genera obiecte ce contin fiecare un sir de caractere. care coboarã recursiv în fiecare subdirector întâlnit (metoda “listFiles” din clasa File nu furnizeazã decât fisierele si subdirectoarele de pe primul nivel): static void printFiles (File d.length. // creare obiect folosit la impartirea in cuvinte while ( st. Exemple: String s=”-123”. // conversie din int in String static boolean Character. In Java metodele statice sunt putine.Florian Moraru – Programare Orientata pe Obiecte Metoda “main” este staticã si de aceea variabilele referite si metodele apelate direct trebuie sã fie statice (adicã sã nu fie conditionate de existenta unor obiecte).nextToken ()). ca în exemplele urmãtoare: static Integer Integer. se iese File [] files=d. // un sir cu mai multe cuvinte StrTok st = new StrTok(str). String sp) { // d= director. int y = zero. de obicei cu operatorul new. // daca ch este litera O metodã staticã poate fi precedatã de numele clasei sau de numele unei variabile de tipul clasei din care face parte (caz în care se apeleazã la fel ca o metodã nestaticã).println(st. Una din cele mai folosite clase în Java este clasa String.hasMoreTokens()) // cat timp mai sunt cuvinte System. // afiseaza urmatorul cuvant } Metoda “main” de mai sus poate fi inclusã în clasa verificatã StrTok sau poate fi în altã clasã.valueOf (String s).isLetter (char ch). // conversie din String in Integer static int Integer.parseInt (String s).out. sp=spatii ptr indentare System. Pentru exemplificare vom defini o functie care sã afiseze fisierele dintr-un director dat si din subdirectoarele sale.i<files. // daca ch este cifra static boolean Character.isDigit (char ch). deci o “instantiere” a unei clase. Un obiect este un caz particular concret al unei clase. // afiseaza nume director primit if ( ! d. Intr-un program Java se creeazã obiecte si se apeleazã metode ale acestor obiecte. O astfel de clasã poate fi privitã ca un sablon pentru crearea de obiecte care au în comun aceleasi operatii (metode) dar contin date diferite. Un obiect String poate fi creat pe baza unui vector intrinsec de caractere sau de 30 . int x = Integer.listFiles(). // daca nu e subdirector. cu alta indentare } Clase instantiabile. desi nu este imposibil sã avem si metode nestatice recursive. // apel recursiv. // conversie din String in int static String String.i++) // pentru fiecare fisier din d printFiles (files[i]. Multe functii recursive se scriu în Java ca metode statice. iar variabile statice sunt în general constantele simbolice.parseInt(s).println (sp+d. Integer zero = new Integer(0). In practicã functia “main” creeazã unul sau câteva obiecte si apeleazã una sau câteva metode pentru obiectele create (de obicei “main” contine putine instructiuni).out.isDirectory()) return. // vector de fisiere din directorul d for (int i=0. sp+" ").parseInt(s). Exemplu de verificare a clasei “StrTok” definite anterior: public static void main (String[] arg) { String str = " unu doi trei ".getName()). Toate obiectele sunt create dinamic.

memoria ocupatã de un obiect este eliberatã atunci când obiectul respectiv devine inaccesibil si inutilizabil. atunci când existã alte posibilitãti.append (“ y=”)..append (“x=”). int p = fname. line = f. .out. sb. // creare sir cu un nume de fisier // pozitia primului punct din nume // creare sir cu extensia numelui In exemplul de mai sus. la executie.indexOf(‘. Ordinea de evaluare pentru operatorul punct este de la stânga la dreapta (ca în C). de aceea este posibil sã înlãntuim mai multe apeluri ale metodei “append” astfel: StringBuilder sb = new StringBuilder(). memorat la adresa din variabila "fext". In Java obiectele sunt create dinamic.Florian Moraru – Programare Orientata pe Obiecte octeti sau pe baza altui obiect String sau StringBuffer( StringBuilder). de tip Integer // nu: count[i]= new Integer(0). iar metoda "substring".substring(fname. In Java gestiunea memoriei dinamice este automatã iar programatorul nu trebuie sã aibã grija eliberãrii memoriei alocate pentru obiecte..append(x). pentru comparatie de siruri si pentru anumite operatii de modificare a unui sir. String line=null.append(y).readLine(). Variabilele de un tip clasã reprezintã singura posibilitate de acces la obiectele Java si ele corespund variabilelor referintã din C++.indexOf (‘. Teoretic. Metoda “append” din clasele StringBuffer si StringBuilder au ca rezultat un obiect de acelasi tip cu obiectul pentru care se aplicã metoda (este sirul rezultat prin adãugarea altor caractere sirului initial).equals(“java”) ) . dar momentul exact al recuperãrii memoriei nu poate fi precizat si depinde de modul de gestiune a memoriei dinamice. elimina spatii si trece in litere mari if ( fname. De exemplu. for (int i=0.println (sb). String fext = fname.toUpperCase().i++) count[i] = zero. folosind direct operatorul new (sau apelând o metodã “fabricã” de obiecte). // nu: line = new String().’). într-o aceeasi expresie. // un obiect de tip Integer // un vector cu elem. Pentru o scriere mai compactã se practicã uneori înlãntuirea de metode (“method chaining”).java “). adicã aplicarea unei metode asupra rezultatului unei alte metode. pentru extragere de subsiruri. Exemplu: RandomAccessFile f = new RandomAccessFile ("date. Exemple: String fname = new String (“test. Este posibilã si apelarea directã a colectorului de resturi de memorie (“garbage collector”).’)+1).toLowerCase(). System.. o variabilã de un tip clasã care va primi ulterior rezultatul unei functii nu va fi initializatã la declarare cu altceva decât cu constanta null.readLine().trim().txt". aplicatã aceluiasi obiect are ca rezultat un alt obiect. Adresa unui obiect se memoreazã într-o variabilã referintã de tipul clasei cãreia apartine obiectul. Clasa String contine metode pentru cãutarea într-un sir. i<n. metoda "indexOf" se aplicã obiectului cu adresa în variabila "fname" si are ca rezultat un întreg. // citeste o linie din fisierul f O altã situatie este cea în care un vector de obiecte trebuie initializat cu un acelasi obiect (de fapt cu o referintã la un obiect unic): Integer zero = new Integer (0).substring (p+1)."r"). Integer count[ ]= new Integer [n]. // citeste linie. 31 . Pentru utilizarea mai eficientã a memoriei si pentru reducerea timpului de executie se recomandã sã nu se creeze obiecte inutile.. Exemple: String line = f. dar nu se practicã decât foarte rar.

out. medie = new Float (med). boolean etc) sau variabile de un tip clasã (sau de un tip vector).println (v. System. int. // metode ale clasei Elev } Variabile referintã la un tip clasã Variabilele Java pot fi: variabile de un tip primitiv (char. float. fãrã a se folosit un operator . // utilizare variabilã neinitializatã Variabilele declarate în functii nu sunt initializate de compilator. de obicei este suficientã retinerea adresei obiectului primit ca parametru de constructor si nu trebuie creat un nou obiect. Float medie. dar variabilele declarate în afara functiilor sunt initializate automat cu zero sau cu null. Exemplu de eroare care nu este semnalatã la compilare si produce o exceptie la executie: class Eroare { static Vector v. // nu: nume = new String(unnume). care sunt variabile referintã. Secventa urmãtoare va provoca o eroare la compilare : String nume. } } // NullPointerException Pentru elementele unui vector intrinsec compilatorul nu poate stabili dacã au fost sau nu initializate si se produce exceptie la executie.length(). Nu se pot defini referinte la tipuri primitive sau parametri referintã de un tip primitiv. . // tab[i]=null in mod implicit // NullPointerException 32 . O referintã la un tip T este de fapt un pointer la tipul T care se foloseste ca si cum ar fi o variabilã de tipul T. Referinta Obiect Simpla declarare a unei variabile de un tip clasã nu antreneazã automat crearea unui obiect. Variabila care primeste rezultatul operatorului new nu contine chiar obiectul ci este o referintã la obiectul creat.out. } . . Compilatorul Java verificã si anuntã utilizarea unei variabile care nu a fost initializatã. Indirectarea prin variabila referintã este realizatã automat de compilator.Florian Moraru – Programare Orientata pe Obiecte O a treia situatie frecventã este la construirea unui obiect pe baza altor obiecte.size()).println (nume). int n=tab[0]. public static void main (String arg[]) { System. // datele clasei public Elev ( String unnume. Exemplu: String tab[ ] = new String[10]. float med) { // constructor al clasei Elev nume = unnume. Exemplu: class Elev { String nume.

. b. Incercarea a utiliza o variabilã referintã cu valoarea null pentru apelarea unei metode cauzeazã exceptia NullPointerException.Florian Moraru – Programare Orientata pe Obiecte Utilizarea operatorului de comparatie la egalitate "==" între douã variabile referintã are ca efect compararea adreselor continute în cele douã variabile si nu compararea datelor adresate de aceste variabile.readLine(). // citeste o linie din fisierul f // incorect. Float.println (s1).equals (“. Date etc. // adresa sirului “unu” s2.intValue()). . cum sunt clasele String.”)) break. s2=s1. if (linie == “. // exceptie ! 33 . // scrie: unudoi s1 unud s2 Copierea datelor dintr-un obiect într-un alt obiect de acelasi tip se poate face: a) Prin construirea unui nou obiect pe baza unui obiect existent (dacã existã constructor): String s1 = “unu”.clone(). Un rezultat nedorit este aparitia unor obiecte cu date comune. Clasele JDK care contin variabile de un tip referintã au metoda “clone” redefinitã pentru a face o copiere “profundã” a datelor din obiectul clonat în obiectul clonã. dar numai pentru obiecte din clase care implementeazã interfata Clonable.. b) Prin construirea unui nou obiect care preia datele din vechiul obiect (daca existã metode de extragere a datelor dintr-un obiect). Exemplu: Integer m1 = new Integer(1). Exemplu: StringBuffer s2. Copierea este superficialã în sensul cã se copiazã variabile referintã (pointeri) si nu datele la care se referã acele variabile. int len = s. Exemplu: String s=null.out. // “linie” de tip String Metoda “equals” existã în orice clasã Java iar metoda “compareTo” (cu rezultat “int”) existã numai în clasele cu obiecte “comparabile”. Integer m2 = new Integer ( m1. b= (Vector) a. Exemplu: Vector a. // referinte la doi vectori // creare b si copiere date din a în b In metoda “clone” se alocã memorie pentru un nou obiect si se copiazã datele din obiectul vechi în noul obiect ( rezultat al metodei “clone”).append (”doi”). String s2 = new String (s1). Operatorul de atribuire se poate folosi numai între variabile referintã de acelasi tip sau pentru atribuirea constantei null la orice variabilã referintã. c) Folosind metoda generalã “clone” (mostenitã de la clasa Object). Atribuirea între variabile referintã duce la multiplicarea referintelor cãtre un acelasi obiect.”) break. Exemplu de eroare: String linie=f. se compara adrese ! Comparatia la egalitate între obiecte Java se face prin metoda "equals" (de tip boolean).Exemplu : if (linie.length(). Integer. Efectul atribuirii între douã variabile referintã este copierea unei adrese si nu copierea unui obiect. s1=new StringBuffer (“unu”). System.

iar adresa sa este memoratã în variabila localã “t” (care continea initial adresa sirului dat). care sã permitã modificarea oricãrui vector. Un obiect creat într-o functie trebuie transmis ca rezultat al functiei. Intr-un vector sau într-o altã colectie se memoreazã referinte cãtre obiecte si nu cãtre clone ale obiectelor. Argumentele unei metode sunt de obicei date initiale. iar prelucrarea unui vector completat partial poate produce exceptia de utilizare a unui pointer (referintã) cu valoarea null. In cazul parametrilor de un tip clasã (parametri referintã) se copiazã adresa obiectului din functia apelantã în parametrul formal si nu se copiazã efectiv obiectul. ca si în C. cu alta adresa } Aici se creeazã un obiect String prin metoda “toUpperCase”.sort (b). b = (Object[]) a. deci o metodã nu poate transmite mai multe rezultate de un tip primitiv. a[1]="doi". cum ar fi transmiterea unui sir la construirea unui obiect de tip StrTok (sau StringTokenizer). Situatia este similarã unui vector de pointeri din limbajul C (cãtre siruri sau cãtre diferite structuri alocate dinamic): nu se cloneazã structurile sau sirurile. dar acest lucru nici nu este necesar. Argumente de functii de tip referintã In Java.sort(a). adicã se copiazã valoarea parametrului efectiv în parametrul formal corespunzãtor. Exemplu: static String toUpper (String s) { return s. Prin copierea adresei unui obiect în parametrul formal corespunzãtor apar referinte multiple la un acelasi obiect (multiplicarea referintelor). O functie nu poate trasmite în afarã adresa unui obiect creat în functie printr-un parametru referintã. // nu produce exceptie Clonarea de obiecte este o operatie folositã destul de rar în practica programãrii. Exemplu: Object [ ] b = new Object[10]. // se creeaza un nou obiect.toUpperCase(). Arrays. // 10 valori null a[0]="unu". Constructorul StrTok retine o referintã cãtre sirul de analizat si nu face o copie a acestui sir.gresit !!! static void toUpper (String t) { t = t. transmiterea unui parametru efectiv la apelarea unei functii se face prin valoare. pentru cã nu modificã sirul primit. retinând în vectorul clonã numai elementele nenule. Exemplu de functie fãrã efect în afara ei: // metoda statica pentru trecere sir in litere mari .toUpperCase(). a[2]="trei". pentru a permite modificarea obiectelor din colectie. iar efectul metodei este modificarea variabilelor clasei si nu modificarea argumentelor. } 34 . Un exemplu este un vector de vectori. Exemplu: Object a[] = new Object [10] . ca si a obiectelor memorate în acesti vectori. // exceptie aruncata de functia “sort” Metoda “clone” de copiere a unui vector intrinsec nu copiazã si valorile null. O situatie în care pare necesarã clonarea este la transmiterea de obiecte pentru construirea altor obiecte. iar adresele lor sunt memorate în vector). Argumentele de un tip primitiv nu pot fi modificate de o metodã Java. înainte de executia instructiunilor din functie.clone(). Arrays.Florian Moraru – Programare Orientata pe Obiecte Elementele unui vector de obiecte sunt initializate automat cu null la alocarea de memorie.

35 .clear(i). d si a se refera la acelasi obiect c. Exemplu de utilizare gresitã a metodei “replaceAll” din clasa String pentru înlocuirea unui subsir s1 cu un alt subsir s2 în sirul s: s. // atunci se face a[i]=0 } Dacã vrem sã pãstrãm multimea “a” atunci vom transmite o copie a ei BitSet aux= (BitSet) a. d=new BitSet(). // c= a*b dar s-a modificat si a ! d. deci nu se poate extinde cu metode de modificare a vectorului de caractere continut în fiecare obiect. In acest fel se protejeazã obiectul transmis ca argument fatã de modificarea sa nedoritã de cãtre functia care îl foloseste.a.s2).size().or(a). minus (aux. de aceea utilizarea corectã este urmãtoarea: s = s.i<b. Integer. // d= d / c dar s-a modificat si a ! return d. nu contin metode pentru modificarea datelor din aceste clase. deci o functie care primeste o referintã la un astfel de obiect nu poate modifica acel obiect. c. // c .xor(c). Float s. // c=a*b d. // d este multimea vidã ! } Urmeazã o variantã corectã pentru calculul diferentei a douã multimi de tip BitSet: public static BitSet minus (BitSet a. Avantajele si riscurile obiectelor modificabile trasmise ca argumente pot fi ilustrate prin clasa BitSet. BitSet b) { for (int i=0. } Clase cu obiecte nemodificabile Clasele String.i++) if (b.replaceAll (s1. Metodele “replaceAll” si “replace” au ca rezultat un nou sir obtinut dupã substituire si nu modificã obiectul pentru care se apeleazã. BitSet b) { BitSet c=new BitSet().or(a). BitSet b) { BitSet c=a. // copiaza pe a in aux // aux = aux-b In exemplul urmãtor diferenta A-B se obtine pe baza unor operatii existente: A-B = A / (A*B) unde A/B este diferenta simetricã a multimilor A si B.clone(). Diferenta a douã multimi A-B poate fi realizatã prin functia urmãtoare: public static void minus (BitSet a. d.get(i)) // daca b[i] este 1 a. // d=b-a return d. Functia modificã în mod nedorit multimile primite ca argumente din cauza multiplicãrii referintelor.and(b).d multimi initial vide c.Florian Moraru – Programare Orientata pe Obiecte O functie poate modifica un obiect a cãrui adresã o primeste ca argument numai dacã în clasa respectivã existã metode pentru modificarea obiectelor. public static BitSet minus (BitSet a. // c. d=a. Clasa String este o clasã read-only si finalã.replaceAll (s1.xor(c).and(b). Obiectele din clase fãrã metode de modificare a datelor (clase “read-only”) se numesc obiecte nemodificabile (“immutable objects”). pentru multimi de întregi realizate ca vectori de biti.b).s2).

Exemple de metode care modificã sirul continut întrun obiect de tip StringBuffer: append.out.5) sunt variante ale clasei String care contin în plus si metode pentru modificarea obiectului (sirului).length().replace (0. dar existã numai din versiunea 1. Eliminarea unui subsir dintr-un sir se poate face folosind metoda “delete” din clasa StringBuffer sau cu metode ale clasei String. setLength. Un obiect de tipul StringBuffer transmis unei functii ca argument poate fi modificat de cãtre functie. } Clasa StringBuilder este mai performantã decât StringBuffer pentru programele fãrã fire de executie multiple.length. int n =a.toUpperCase()). return new String (aux. b=“doi”. a= new String (am). Aceastã observatie poate fi folositã si pentru conversia unui numãr în sir de caractere. Metoda “println” folositã pentru afisarea pe ecran poate avea un singur argument de tip String. // s nemodificat ! 36 .sqrt(2).valueOf(x).append(bm). s. mai nou.Exemplu: System.5. // sau str = String. String str = ““+x. for (int i=0.length() ) return s. Dacã trebuie sã facem multe concatenãri de siruri este preferabil ca timp sã se foloseascã direct metoda “append” din clasa StringBuilder (StringBuffer). O instructiune de forma a=a+b. am. int from.i++) aux. se apeleazã metoda “append” si apoi creeazã un obiect String din obiectul StringBuffer rezultat din concatenare: String a=“unu”. ca alternativã a utilizãrii metodei “valueOf” din clasa String. atunci compilatorul Java face automat conversia celuilalt operand la tipul String (pentru orice tip primitiv si pentru orice tip clasã care redefineste metoda “toString”). insert. cu “a” si “b” de tip String este tratatã de compilator astfel: se transformã obiectele a si b în obiecte de tip StringBufer. setCharAt. Exemplu: static String delete1 (String s. Exemplu: float x = (float) Math. existã în clasa Arrays metoda staticã “toString” cu argument vector. Concatenarea de siruri este o operatie frecventã în Java.i<n-1. } De observat cã. StringBuffer am= new StringBuffer (a).Florian Moraru – Programare Orientata pe Obiecte Clasele StringBuffer si StringBuilder (din 1. Exemplu: public static String arrayToString ( int a[ ]) { // creare sir cu continutul unui vector StringBuilder aux = new StringBuilder (”[”). int to ) { // sterge din s intre pozitiile “from” si “to” if ( from > to || from < 0 || to > s.str.str. delete.append (a[n-1] +”]”) ) .println ( “x= “ + x). Pentru a scrie mai multe siruri acestea se concateneazã într-un singur sir cu operatorul ‘+’.”).append (a[i] + ”. Variantã pentru functia “toUpper”: static void toUpper (StringBuffer s) { String str= new String (s). dacã unul din operanzi este de tip String. // x de orice tip Intr-o expresie cu operatorul binar ‘+’.

Florian Moraru – Programare Orientata pe Obiecte
return s.substring(0,2) + s.substring(5); } // variantã cu StringBuffer static String delete2 (String s, int from, int to ) { StringBuffer sb = new StringBuffer(s); sb.delete (from,to); // exceptie daca argumente incorecte ! return sb.toString(); }

De observat cã trecerea de la tipul String la tipul StringBuffer se poate face numai printr-un constructor, dar trecerea inversã se poate face prin metoda “toString”, iar aceste transformãri pot fi necesare pentru cã în clasa StringBuffer nu se regãsesc toate metodele din clasa String. Metoda “toString()” existã în toate clasele ce contin date si produce un sir cu datele din obiectul pentru care se apeleazã (face conversia de la tipul datelor din obiect la tipul String). Orice tip de obiect se poate transforma într-un sir String prin metoda “toString”, apelatã explicit sau implicit, dar nu si prin “cast”. Exemplu:
Float x = new Float(3.14); String s = (String)x; // eroare la compilare String s =x.toString(); String s = x+””; // apel implicit “toString”

Metode statice si metode ale obiectelor Utilizarea abuzivã de metode statice este o prelungire a programãrii procedurale în limbajele orientate obiect. Utilizarea de metode obiect (nestatice) permite utilizarea unor tehnici specifice programãrii cu obiecte: derivare, mostenire, polimorfism, programare cu interfete, s.a. Avantajele metodelor obiect sunt mai evidente în cazul unor familii de clase deschise pentru extindere si atunci când se urmãreste reutilizarea metodelor unor clase în alte clase. Pentru a arãta diferentele sintactice dintre definirea si utilizarea celor douã tipuri de metode vom folosi exemplul unei clase cu rolul clasei StringTokenizer, dar fãrã posibilitatea de a specifica lista de caractere separator între atomi; atomii sunt separati numai prin spatii albe.
class StrTok { // clasa cu metode statice public static boolean hasMoreTokens (String str, int pos) { // daca mai sunt atomi in sirul analizat return pos < str.length(); } public static int nextToken (String str, int pos, String tok[]) { // urmatorul atom lexical din str while (pos < str.length() && Character.isSpace(str.charAt(pos))) ++pos; int p= str.indexOf (' ',pos); if ( p < 0) p= str.length(); tok[0]= str.substring (pos,p); return p; // pozitia dupa acest atom } } class UseStrTok { // utilizare clasa StrTok public static void main (String[] arg) { String str = " sigma = alfa + epsilon "; String tok[] = new String[1]; int k=0; while ( StrTok.hasMoreTokens(str,k)) { k= StrTok.nextToken ( str, k, tok); System.out.println(tok[0]); } } }

37

Florian Moraru – Programare Orientata pe Obiecte Am folosit un vector cu un singur element ca artificiu pentru a permite functiei “nextToken” sã transmitã unul din rezultate (urmãtorul atom extras) printr-un argument, lucru imposibil printr-un argument de tip String. Functia are douã rezultate: atomul extras si pozitia din sir de dupã acest atom. Urmeazã varianta cu metode ale obiectelor:
class StrTok { // variabile ale clasei private String str; // sirul analizat private int crt; // pozitia curenta in sir // constructor public StrTok (String s) { str=s; crt=0; } // daca mai sunt atomi in sirul analizat public boolean hasMoreTokens () { return crt < str.length(); } // extrage urmatorul atom lexical dintr-un sir public String nextToken () { while ( crt < str.length() && str.charAt(crt)==' ') ++crt; // ignora spatii int start= crt; // inceput de atom crt= str.indexOf (' ',start); if ( crt < 0) crt= str.length(); return str.substring (start,crt); } } // utilizare class UseStrTok { public static void main (String[] arg) { String str = " sigma = alfa + epsilon "; String token; StrTok st = new StrTok(str); // creare obiect “tokenizer” while ( st.hasMoreTokens()) { // apel de metoda a obiectului “st” token= st.nextToken (); System.out.println(token); } } }

Utilizarea unor clase de intrare-iesire Existã un numãr relativ mare de clase Java pentru operatii de intrare-iesire cu fisiere dar aici vom da câteva “retete” simple pentru realizarea de aplicatii Java de tip “Desktop” (de tip consolã) cu clase din pachetul java.io. Afisarea pe ecran si scrierea în fisiere text a oricãrui tip de date se face simplu folosind metodele “print” si “println” din clasele PrintStream sau PrintWriter. Aceste metode pot primi un singur argument de orice tip primitiv sau de tipul generic Object si realizeazã automat conversia numerelor din format intern în format extern (sir de caractere). Toate clasele de I/E pentru operatii cu fisiere disc au constructori cu argument String si respectiv File pentru a primi numele fisierului disc cu care lucreazã. Toate clasele au metode de citire a unui caracter (sau octet) si de scriere a unui caracter sau octet. Constructorii si metodele acestor clase pot genera exceptii IOException, care trebuie aruncate sau tratate (prin try… catch).

38

Florian Moraru – Programare Orientata pe Obiecte Pentru a scrie într-un fisier disc se creeazã mai întâi un obiect FileInputStream sau FileWriter si apoi se creeazã obiectul PrintStream sau PrintWriter. Exemple:
try { PrintStream ps = new PrintStream ( new FileOutputStream ("numere.txt")) ; for (int x=1; x<100;x++) ps.print (x+" "); // cu spatii intre numere ps.close(); } catch (IOException e) { e.printStackTrace();}

Pentru scrierea de siruri în fisiere text se poate utiliza direct metoda “write (String)” din clasa FileWriter, dar pentru a scrie linii de text trebuie adãugat explicit caracterul ‘\n’. Exemplu:
try { FileWriter fw = new FileWriter("t.txt"); for (int i=1;i<21;i++) fw.write (i+" "); // conversie din “int” in String si spatii intre numere fw.close(); } catch (IOException e) { e.printStackTrace();}

Nu existã o clasã de citire date corespondentã cu PrintStream sau PrintWriter, care sã facã conversie automatã de numere la citire (din sir de caractere zecimale în format intern binar), dar din versiunea 1.5 s-a introdus clasa Scanner în pachetul java.util pentru astfel de operatii. Citirea de siruri de la tastaturã sau dintr-un fisier text se poate face în douã moduri: - Folosind metoda “read” din clasa FileReader pentru citire într-un vector de octeti urmatã de conversie la tipul String; - Folosind metoda “readLine” din clasa BufferedReader sau BufferedInputStream pentru citire din fisiere text formate din linii (linii terminate prin caracterul ‘\n’). Exemple de citire integralã în memorie a unui fisier text:
try { File f = new File (arg[0]); // creare obiect File int flen = (int) f.length(); // lungime fisier (nr de caractere) char txt[]= new char[flen]; // aloca memorie ptr continut fisier FileReader fr = new FileReader (f); // creare obiect FileReader fr.read(txt,0,flen); // citeste flen caractere in txt System.out.println( new String(txt,0,len)); // creare obiect String din vector de caractere } catch (IOException e) { e.printStackTrace();}

Semnificatia si ordinea argumentelor metodei “read” sunt aceleasi ca într-un constructor al clasei String, astfel cã se poate trece simplu de la un vector de caractere de lungime cunoscutã la un String. Exemplu de citire linii de text:
String line; // aici se citeste o linie de text try { BufferedReader br = new BufferedReader (new FileReader(arg[0])); while ( (line = br.readLine()) != null) // readLine are rezultat null la sfarsit de fisier System.out.println (line); } catch (Exception e) { e.printStackTrace();}

Variabila publicã System.out este de tip PrintStream dar variabila System.in este de tip InputStream, pentru care se poate folosi metoda “read” de citire a unui octet sau a unui vector de octeti de lungime specificatã. Pentru a citi linii de text de la consolã vom folosi o clasã care contine o metodã “readLine”: BufferedReader sau DataInputStream.

39

Florian Moraru – Programare Orientata pe Obiecte
String line; try{ BufferedReader stdin = new BufferedReader (new InputStreamReader (System.in)); while ( (line=stdin.readLine()) != null) System.out.println (line); } catch( IOException e) { e.printStackTrace();}

Pentru citirea de numere de la consolã sau din fisiere text trebuie folositã o metodã de conversie cu argument String, cum sunt Integer.parseInt, Float.parseFloat, Double.parseDouble. Clasa RandomAccessFile are o serie de avantaje fatã de alte clase de I/E: - permite atât operatii de scriere cât si operatii de citire; - permite crearea si citirea de fisiere binare, cu numere în format intern (readInt, writeInt, etc.); - permite citirea de linii de text (readLine); - permite accesul direct la date din fisier pe baza adresei de octet în fisier (seek), aflarea adresei curente în fisier (getFilePointer) si aflarea lungimii unui fisier (length). - permite citirea unui întreg fisier în memorie (readFully). Exemplu de citire si scriere linii de text folosind obiecte de tip RandomAccessFile:
public static void main (String[] arg) throws IOException { String linie ; RandomAccessFile f1=new RandomAccessFile (arg[0],"r"); RandomAccessFile f2=new RandomAccessFile (arg[1],"rw"); while ( (linie=f1.readLine ()) != null) f2.writeBytes(linie+”\n”); f1.close(); f2.close(); }

// nu se poate doar “w” !

In general, metodele de citire din clasele Java raporteazã prin rezultatul lor când se ajunge la sfârsitul fisierului si nu existã metode care sã testeze sfârsitul de fisier. Aceastã regulã nu se aplicã si metodelor care citesc numere din fisiere binare deoarece rezultatul (valoarea cititã) poate fi orice configuratie binarã; solutia este sã se compare pozitia curentã în fisier cu lungimea fisierului:
while (f1.getFilePointer() < f1.length() ) { int x = f1.readInt(); // citeste un intreg din fisier in x … // prelucrare x }

Deoarece clasa RandomAccessFile este definitã în afara familiilor de clase de I/E, acest tip nu apare ca posibil argument în constructorii altor clase care folosesc fisiere disc: clase pentru compresie si arhivare de fisiere, clase parser de fisiere text sau de fisiere XML, s.a. Acesti constructori au fie un argument File, fie un argument clasã abstractã Reader (InputStream) sau Writer (OutputStream). Clasa Scanner are metode ca nextInt, nextFloat, nextDouble, s.a care se folosesc oarecum similar functiei “scanf” din limbajul C, deci programatorul trebuie sã stie ce tip de date urmeazã sã citeascã:
Scanner sc = new Scanner (new FileReader (arg[0])); int x, sum=0; while ( sc.hasNext( )) { x=sc.nextInt(); // citire de numere intregi sum += x; }

Existã si un corespondent al functiei “printf” sub forma metodei “format” din clasele String si java.util.Formatter, dar ca si în cazul functiei “printf” existã multe detalii legate de precizarea formatului dorit.

40

Nu se vor defini ca variabile ale clasei acele variabile care sunt folosite numai în una sau douã functii ale clasei. // metode ale clasei // adunare de numere complexe public void add ( Complex cpx) { re += cpx. Variabilele clasei sunt accesibile tuturor functiilor clasei pentru citire sau modificare si sunt initializate de obicei în constructorul clasei.cpx.cpx. în aceeasi clasã. dar în cazul unor clase cu un singur obiect (cum sunt clasele derivate din JFrame pentru fereastra aplicatiei) nu conteazã numãrul variabilelor clasei. im = im . c.im.Florian Moraru – Programare Orientata pe Obiecte 4. Toate variabilele numerice ale unei clase sunt initializate implicit cu zerouri si toate variabile referintã sunt initializate cu null. Datele clasei se declarã în afara metodelor clasei si vor fi prezente în fiecare obiect al clasei. return c. Exemplu de variabilã localã unei metode: public Complex conj ( ) { Complex c = new Complex(). Variabilele de interes numai pentru anumite metode vor fi definite ca variabile locale în functii si nu ca variabile ale clasei. im += cpx. } } // parte realã si parte imaginarã Se observã cã metodele unei clase au putine argumente. Ele sunt necesare mai multor metode si sunt de obicei putine. } // c este o variabilã localã 41 .im= -im. Ordinea în care sunt definiti membrii unei clase (date si functii) este indiferentã. ceea ce este tipic pentru multe clase cu date: metodele clasei au ca efect modificarea datelor clasei. Majoritatea claselor instantiabile grupeazã mai multe functii în jurul unor date comune. } // scadere de numere complexe public void sub ( Complex cpx) { re = re . Acesta este si un avantaj al programãrii cu clase fatã de programarea proceduralã: functii cu argumente putine si care nu sunt modificate în functie. In Java toate metodele trebuie definite (si nu doar declarate) în cadrul clasei. cum ar fi obiecte de tip nod de arbore sau obiecte ce corespund unor elemente din fisiere XML. iar argumentele sunt de obicei date initiale necesare pentru operatiile cu variabilele clasei.im. Definirea de noi clase Definirea unei clase în Java Definirea unei clase se face prin definirea variabilelor si functiilor clasei. c. Este posibil ca o metodã sã apeleze o altã metodã definitã ulterior.re= re. este important ca dimensiunea lor sã fie mentinutã la minimum. Clasele de uz general se declarã public pentru a fi accesibile din orice alt pachet. Ca exemplu vom schita o definitie posibilã pentru o clasã ale cãrei obiecte sunt numere complexe (nu existã o astfel de clasã predefinitã în bibliotecile JDK): public class Complex { // datele clasei private double re.im.re. Dimensiunea unui obiect este determinatã mai ales de numãrul si tipul variabilelor clasei si nu de numãrul metodelor clasei. Atunci când se creeazã multe obiecte dintr-o clasã. iar aceste argumente nu se modificã.re.

pentru cã metoda "toString" a colectiei apeleazã metoda "toString" a obiectelor din colectie. Exemplu: public class Complex { private double re. Aceste valori sunt preluate de fiecare obiect creat. deoarece nu pot fi apelati explicit (prin nume). // scrie (0.im. Complex c2= new Complex (0. System. return re==cobj. se poate afisa continutul unei colectii de obiecte "Complex". } Existenta metodei "toString" ne permite sã afisãm direct continutul unui obiect din clasa "Complex" astfel: Complex c = new Complex().out. pentru afisare public String toString ( ) { return ( "(" + re+ ". Un constructor este o functie fãrã tip si care are obligatoriu numele clasei din care face parte. dar contin o metodã cu numele “toString” care produce un sir de caractere ce reprezintã datele clasei si care poate fi scris pe ecran (cu metoda “System. im.println”) sau introdus într-un alt flux de date sau folosit de alte metode. Pentru a permite configurarea obiectelor create se practicã initializarea variabilelor clasei cu valori diferite pentru fiecare obiect în parte folosind unul sau mai multi constructori cu parametri.1).im= parte imaginarã public Complex ( double x.0) In plus. // re=parte realã . .Florian Moraru – Programare Orientata pe Obiecte Pentru utilizarea comodã a obiectelor clasei "Complex" mai sunt necesare functii pentru initializarea datelor din aceste obiecte (numite "constructori") si pentru afisarea datelor continute în aceste obiecte. Functia urmãtoare (metodã a clasei "Complex" ) trebuie inclusã în definitia clasei: // conversie în sir. im=y. . In Java clasele cu date nu contin metode de afisare pe ecran a datelor din obiectele clasei. } .out. 42 . } Functii constructor Orice clasã instantiabilã are cel putin un constructor public. Exemplu: public boolean equals (Object obj) { Complex cobj = (Complex) obj." + im+ ")"). Constructorii nu sunt considerati metode. // metode ale clasei } Exemple de creare a unor obiecte de tipul “Complex” : Complex c1 = new Complex (2.re && im==cobj. Variabilele oricãrei clase sunt initializate automat la crearea de obiecte (cu valori zero pentru variabile numerice si null pentru variabile de orice tip clasã). definit implicit sau explicit si apelat de operatorul new la crearea de noi obiecte.-3) . double y) { re=x. Redefinirea metodei "equals" în clasa "Complex" permite cãutarea unui obiect dat într-o colectie si alte operatii care necesitã comparatia la egalitate. dacã nu se fac alte initializãri prin constructori.println (c).

putem defini douã metode de adunare la un complex. // adresa vector private int count. String value) { this.add(t. // metode ale clasei } Variabilele unei clase pot fi initializate la declararea lor. // aloca memorie ptr vector count= 0. im += c. } In cazul când argumentele unui constructor sunt referinte la alte obiecte (si nu valori primitive) se retin de obicei aceste referinte în obiectul creat si nu se creeazã alte obiecte. dar au acelasi nume (un caz de supradefinire a unor functii). } … // metode ale clasei } Uneori metodele clasei chiar trebuie sã modifice obiectele ale cãror adrese le primeste. Exemplu: public Complex ( Complex c) { re=c. care diferã prin argumente."Center"). Exemplu: class Pair { private String key. iar alteori nu existã posibilitatea modificãrii acestor obiecte (din clase “read-only”). cu efect pentru toate obiectele clasei. // initial nici un element } . cu acelasi nume dar cu argumente diferite: // adunare cu un alt complex public void add ( Complex c) { re += c. im=c.key= new String(key)..Florian Moraru – Programare Orientata pe Obiecte Dacã nu se defineste nici un constructor atunci compilatorul genereazã automat un constructor implicit. // nu: this. public Pair (String key.value=value.re.. } Este posibilã si supradefinirea unor metode ale claselor. fãrã argumente si fãrã efect asupra variabilelor clasei (dar care apeleazã constructorul superclasei din care este derivatã clasa respectivã). public TextFrame () { getContentPane().im. Exemplu de initializare a unei variabile la declarare: class TextFrame extends JFrame { JTextField t = new JTextField (10). } // adunare cu un numar real public void add ( double x) { re = re + x.key=key.im. // initializare in afara constructorului // constructor clasa // adauga camp text la fereastra 43 . this.re. Acest fel de initializare se practicã pentru variabile membru de tip clasã si pentru clase cu un singur obiect. // nr de elemente in vector // constructori public IntVector( int n) { // n= capacitate initiala vector value= new int[n]. In cazul când variabilele clasei sunt vectori intrinseci sau clase colectie în constructor se alocã memorie pentru vectori sau colectii. De exemplu. Este uzual sã existe mai multi constructori într-o clasã.value. Exemplu: public class IntVector { // un vector de numere private float value[].

substring (k). Un prim caz de folosire curentã a variabilei this este la definirea unui constructor cu argumente formale având aceleasi nume cu variabile ale clasei. formale de var. Crearea obiectului câmp text JTextField nu se face printr-o instructiune ci printr-o declaratie cu initializare. Definitia metodei "length" ar putea fi rescrisã folosind variabila this astfel: public int length (this) { return this. instructiunile urmãtoare : int n = a. ca si o metodã. // ptr. dar un constructor cu mai putine argumente poate apela un constructor cu mai multe argumente. In Java nu sunt permise argumente formale cu valori implicite. iar atunci când este necesar acest apel (din aceeasi clasã) se foloseste cuvântul cheie this ca nume de functie. clasei } 44 ..0). a distinge arg. Atunci când se apeleazã o metodã pentru un anumit obiect. // lungimea sirului a // extrage subsir din pozitia k din sirul a corespund instructiunilor urmãtoare din limbajul C: int n = length(a). Altfel spus.re=re. float im) { this. Exemple: // constructor cu un parametru din clasa Complex public Complex (double re) { this (re.Florian Moraru – Programare Orientata pe Obiecte } .im=im. this.. într-un bloc static de initializare sau într-o metodã staticã private.k). } // apel constructor cu douã argumente // constructor fara parametri din clasa Complex public Complex () { this (0). dar trebuie sã fie inclusã într-o functie. // lungimea sirului a String b = substring (a. String b = a. // extrage subsir din pozitia k din sirul a Acest argument implicit poate fi folosit in interiorul unei metode nestatice prin intermediul variabilei predefinite this. Exemplu: public Complex ( float re. } // alte metode Intr-o aplicatie se va crea un singur obiect de tip "TextFrame".length(). iar obiectul de la adresa "t" va avea întotdeauna mãrimea 10. dintre care unele au valori implicite.count. Cuvântul cheie this este o variabilã referintã care desemneazã în Java adresa obiectului curent ("acest obiect"). Un constructor nu poate fi apelat explicit. Initializãri ale variabilelor clasei se mai pot face într-un bloc cuprins între acolade (inclus de compilator în orice constructor). metoda primeste ca argument implicit o referintã la obiectul respectiv. dar existã situatii când folosirea lui this este necesarã. } // apel constructor cu un argument Cuvântul cheie "this" Metodele nestatice dintr-o clasã actioneazã asupra unor obiecte din acea clasã. // "count" este o variabilã a clasei String } In mod normal nu trebuie sã folosim variabila this pentru referire la datele sau la metodele unui obiect. Instructiunea din constructor se va executa o singurã datã.

public static final Class TYPE = Class. dar clasele JDK folosesc aceleasi nume (si variabila this ) pentru cã argumentele unui constructor contin valori initiale pentru variabilele clasei.getPrimitiveClass(“byte”).arraycopy (value. // constructori si metode clasa Byte 45 . pentru metodele care modificã datele unui obiect si au ca rezultat obiectul modificat. Exemple: // un vector isi transmite adresa sa metodei statice de sortare class SortedVec extends Vector { public void addElement (Object obj) { // adaugare element si ordonare super. count--. au în Java mai multe atribute: tipul variabilei sau metodei ( tip primitiv sau tip clasã ). index. Exemple de constante din clasa Byte: public class Byte { public static final byte MIN_VALUE = -128. folositã atunci când nu se declarã explicit o altã valoare. // apel metoda din clasa Vector Collections. alte metode ale clasei StringBuffer } Un caz particular este metoda "toString" din clasa "String": public String toString () { return this. Un alt caz de folosire a variabilei this este în instructiunea return. // rezultatul este obiectul modificat de metodã } // . . public static final byte MAX_VALUE = 127.Florian Moraru – Programare Orientata pe Obiecte Astfel de situatii pot fi evitate prin alegerea unor nume de argumente diferite de numele variabilelor clasei. O variabilã staticã a clasei declaratã final este de fapt o constantã... abstract (numai pentru metode). Variabilele locale pot avea doar atributul final. Exemplu: public final class StringBuffer { private char value[ ]. final.. count-index-1). // un vector de caractere private int count.. Atribute ale membrilor claselor Membrii unei clase. return this. value. // ordonare obiect ptr care s-a apelat metoda “add” } } Variabila this nu poate fi folositã în metode statice. nemodificabilã prin operatii ulterioare primei initializãri. private) plus atributele static. un atribut de accesibilitate (public.sort(this).addElement (obj). Cu exceptia tipului. index+1. Cuvintele cheie folosite pentru modificarea atributelor implicite se numesc "modificatori". variabile si metode. protected. // caractere efectiv folosite din vector // metode public StringBuffer deleteCharAt (int index) { // sterge caracterul din poz index System. De exemplu orice membru este nestatic si nefinal atunci când nu se folosesc modificatorii static sau final. fiecare din aceste atribute are o valoare implicitã. } Uneori un obiect îsi trimite adresa sa metodei apelate (metodã staticã sau dintr-un alt obiect).

din cauza utilizãrii gresite a unui program Java: // program cu doua argumente în linia de comanda public static void main ( String arg[ ]) { if ( arg.. dar pot exista si câteva metode statice în fiecare clasã cu date. } Parte din definitia unei metode este si clauza throws.toString().int). // static int parseInt(String. Exemplu de metodã din clasa Integer. O metodã staticã poate apela un constructor. Uneori este posibilã anticiparea si prevenirea unor exceptii. anumite functii de citire a unui octet dintr-un fisier au rezultat negativ la sfârsit de fisier.println (“usage: java copy input output”).valueOf(value). Metodele unei clase instantiabile sunt de obicei nestatice. care specificã ce fel de exceptii se pot produce în metoda respectivã si care permite compilatorului sã verifice dacã exceptiile produse sunt sau nu tratate acolo unde se apeleazã metoda. pentru cã folosirea unei metode nesatice este conditionatã de existenta unui obiect. Exemplu: public static Integer valueOf (String s) throws NumberFormatException { return new Integer(parseInt(s)). . dar metoda staticã se poate folosi si în absenta unor obiecte. pentru cã initializarea se face la încãrcarea clasei. } . // prelucrare argumente primite } 46 .length < 2) { System. . } // sir echivalent unui obiect O metodã staticã poate fi apelatã de o metodã nestaticã. } Anumite situatii speciale apãrute în executia unei metode sunt uneori raportate prin rezultatul functiei (boolean) si nu prin exceptii. pentru conversia unui sir de caractere într-un întreg : public static int parseInt (String s) throws NumberFormatException { if (s == null) throw new NumberFormatException("null").out. iar functia de citire a unei linii (într-un obiect String) are rezultat null la sfârsit de fisier. // metoda statica din clasa String } O metodã staticã (inclusiv functia main) nu poate apela o metodã nestaticã. De exemplu. Exemplul urmãtor aratã cum se poate evita aparitia unei exceptii de iesire din limitele unui vector intrinsec. return.. Exemplu din clasa String: public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.Florian Moraru – Programare Orientata pe Obiecte } Se vede din exemplul anterior cã o variabilã staticã finalã poate fi initializatã si cu rezultatul unei functii. . Exemplu: public String toString() { // metoda din clasa "Integer" return String.

si este legat de ceea ce se numeste "încapsulare" sau "ascunderea datelor". iar accesul la aceste date este permis numai prin intermediul metodelor. Este posibilã modificarea implementãrii unei clase. In general. } . Exemplu: 47 . Incapsularea datelor în clase De obicei datele unei clase nu sunt direct accesibile utilizatorilor clasei. dar valoarea numãrului sau sirului cuvânt se aflã în variabile publice ale clasei. int h) { x0=x. putem folosi o stivã realizatã ca listã înlãntuitã în locul unei stive vector. care constã din totalitatea metodelor publice (accesibile) ale clasei si care aratã ce se poate face cu obiecte ale clasei respective.io”. în care nu se respectã principiul încapsulãrii iar variabilele unei clase sunt public accesibile. iar pe de altã parte este mai sigur ca aceste date sã fie modificate numai de metodele clasei si nu de cãtre orice utilizator. Metodele unei clase se pot apela unele pe altele. Object d.awt”: public class Rectangle { // dreptunghi de incadrare figura geometrica public int x. indiferent de ordinea definirii lor si de atributul de accesibilitate. adicã constructorii publici. cu mentinerea interfetei sale. ale clasei respective.. De exemplu. Documentatia unei clase prezintã numai interfata sa publicã. Toate obiectele unei clase au aceleasi drepturi. deci nu se pot crea obiecte pentru aceastã clasã (dar se pot defini variabile de un tip clasã abstractã). Exemplu din pachetul “java. protected. y. Variabilele public accesibile se mai numesc si proprietãti ale obiectelor. nume. care se implementeazã într-un alt limbaj decât Java (de obicei limbajul C). Datele unei clase au în general atributul private sau protected. O clasã care contine cel putin o metodã abstractã este o clasã abstractã si nu poate fi instantiatã.. int si. publice. Atributul de accesibilitate se referã la drepturile unei clase asupra membrilor unei alte clase. int w. care recunoaste tipul urmãtorului simbol extras dintr-un fisier (flux de date): numãr. Este vorba de clase cu mai multe variabile. declaratã dar nedefinitã în clasa unde apare pentru prima datã. int di. Definitia lor depinde de particularitatile sistemului de calcul pe care se implementeazã metodele. O metodã abstractã este o metodã cu atributul abstract. Atributul public se foloseste pentru functiile si/sau variabilele clasei ce urmeazã a fi folosite din alte clase. Tipul urmãtorului simbol este rezultatul unei metode. stabilite la definitia clasei. metodele publice (tip. fãrã ca programele ce folosesc stiva sã necesite vreo modificare. cel care foloseste o stivã nu are nevoie sã “vadã” vectorul stivã si alte detalii). Un obiect StreamTokenizer este un mic analizor lexical. deci nu sunt accesibile functiilor din alte clase. // alte metode } Un alt exemplu este clasa StreamTokenizer din pachetul “java. folosite relativ frecvent în alte clase si pentru care accesul prin intermediul metodelor ar fi incomod si ineficient. int length). înãltime public Rectangle (int x0. Exemplu de metodã nativã: public static native void arraycopy (Object s. int y0. Exista si cazuri. width=w. mai rare. y0=y. width. height. metodele si constructorii unei clase se declarã public. Pe de o parte utilizatorii nu sunt interesati de aceste date (de exemplu. lãtime. O clasã expune utilizatorilor sãi o interfatã publicã. cuvânt sau caracter special. Metodele unei clase pot folosi variabilele clasei. Metode declarate dar nedefinite în Java sunt si metodele "native" (cu atributul native). height=h.Florian Moraru – Programare Orientata pe Obiecte O metodã declaratã final nu mai poate fi redefinitã într-o subclasã si ar putea fi implementatã mai eficient de cãtre compilator. public). // coordonate: colt. indiferent de atributul de accesibilitate al acestor variabile (private. argumente) si variabilele public accesibile.

// afisare numar else System.Clasa “Node” este interioarã clasei “MyList”. De exemplu. . dacã anticipãm eventuale subclase derivate. // afisare caractere speciale } } Variabilele “sval” (de tip String) si “nval” (de tip double) sunt variabile publice.} // constructor } Metodele clasei listã trebuie sã aibã acces direct la variabilele clasei “Node”. numite uneori clase “prietene”. In continuare vom schita definitia unei clase pentru o listã înlãntuitã de numere întregi. In ambele cazuri variabilele clasei “Node” pot sã nu aibã nici un atribut explicit de accesibilitate.println((char)type). iar variabile cu atributul protected din clasa Vector sunt folosite direct în clasa VectorEnumerator (în Java 1. // tip atom while ( (type=st. // creare nod sentinela (cu orice date) } // adaugare la sfirsit de lista public void add (int v) { // adaugare intreg v la lista Node nou= new Node(v). Clasa “MyList” contine o variabilã de tip “Node” (adresa primului element din listã).TT_WORD) // daca e un cuvant System. // date din nod Node leg. O solutie alternativã ar fi fost obtinerea valorii cu o metodã publicã având un rezultat de tipul generic Object.sval). // adresa primului element public MyList ( ) { prim = new Node(0).println (type+" "+ st. // un obiect analizor lexical int type.println (type+" "+ st. Dacã nu se specificã explicit modul de acces la un membru al unei clase A atunci se considerã implicit cã el este accesibil pentru toate clasele definite în acelasi pachet cu A. // inaintare spre sfarsit de lista 48 . clasele VectorEnumerator si Vector sunt definite (în Java 1.TT_NUMBER) // daca e un numar System.nextToken()) != st. leg=null.2 clasa enumerator este inclusã în clasa vector).out. // afisare cuvant if (type == st. // creare nod nou cu valoarea v Node p=prim. dar utilizarea acestei valori ar fi necesitat o conversie la tipul indicat de metoda “nextToken”.0) în acelasi pachet si în acelasi fisier sursã. public class MyList { // lista inlantuita simpla (cu santinela) protected Node prim. Mai întâi trebuie definitã o clasã pentru un nod de listã. o clasã care poate sã nu aibã nici o metodã si eventual nici constructor explicit: class Node { // nod de lista inlantuita int val.Florian Moraru – Programare Orientata pe Obiecte public static void main (String arg[]) throws Exception { FileReader is = new FileReader(arg[0]).TT_EOF) { // repeta pana la sfarsit de fis if (type == st.Clasele “Node” si “MyList” se aflã în acelasi pachet.nval). care poate fi “private” sau “protected”. iar TT_EOF. Variabilele cu atributul protected dintr-o clasã sunt accesibile pentru toate clasele derivate direct din A (indiferent de pachetul unde sunt definite) si pentru clasele din acelasi pachet cu ea. // legatura la nodul urmator public Node (int v) { val=v.out.out. ceea ce se poate realiza în douã moduri: . TT_WORD si TT_NUMBER sunt constante (variabile publice statice si finale) din clasa StreamTokenizer. // un fisier cu numele in arg[0] StreamTokenizer st = new StreamTokenizer(is).

im. // continua cautarea in sublista } Functia “find” putea fi si staticã ( private static boolean find . continut lista public String toString () { String aux="".val se converteste automat in “String” // avans la nodul urmator // sirul cu toate valorile din lista // nou urmeaza ultimului element Pentru metoda “contains” vom defini o functie auxiliarã recursivã. “toString”. iar metodele de modificare a variabilelor au un nume care începe cu "set". // apel functie recursiva } // fct recursiva de cautare (din clasa MyList) private boolean find (Node crt.Metode care permit citirea datelor clasei ( metode accesor).leg. while ( p != null) { aux=aux + p.. p=p. } // sir ptr. . } return aux. // parte reala si imaginara // metode de citire a datelor public float getReal ( ) { return re. . Structura unei clase Java O clasã care poate genera obiecte contine în mod normal si date.Florian Moraru – Programare Orientata pe Obiecte while (p. // atunci x negasit if ( x==crt. pentru operatii generale. “hashCode”). definite în afara metodelor clasei (de obicei înaintea functiilor). } // extrage parte reale 49 . deci variabile ale clasei.Metode care permit modificarea datelor clasei ( si care lipsesc din anumite clase ). Exemplu: public class Complex { private double re. care se modificã de la un apel la altul (adresa primului nod din sublista în care se cautã): // cautare in lista (metoda a clasei MyList) public boolean contains (int x) { return find (prim. p.val + " ".leg=nou.leg. } } // rezultatul functiei toString // se pleaca de la primul nod din lista // cat timp mai exista un element urmator // intregul p. Majoritatea claselor instantiabile au unul sau mai multi constructori publici folositi pentru initializarea obiectelor si apelati de operatorul new. x).leg.).Metode mostenite de la clasa Object si redefinite.x). metodele de acces la variabile private au un nume care începe cu "get" si continuã cu numele variabilei.leg.. Node p=prim.val) return true. cu un argument suplimentar. Metodele unei clase pot fi clasificate în câteva grupe: . int x) { if (crt==null) // daca sfarsit de lista return false. // x gasit return find (crt.Metode pentru operatii specifice clasei respective. aplicabile oricãrui obiect Java ( “equals”. .leg != null) p=p. Conform unor cerinte mai noi ale standardului Java Beans.

Florian Moraru – Programare Orientata pe Obiecte
public float getImag ( ) { return im; } // extrage parte imaginara // metode de modificare a datelor public void setReal (float x) { re =x; } // modifica parte reala public void setImag (float y) { im=y; } // modifica parte imaginara // complex conjugat public Complex conj () { return new Complex(re,-im); } // alte operatii cu obiecte de tip Complex ... // metode din “Object” redefinite public boolean equals (Object obj) {. . . } public String toString () { . . . } }

Componentele JavaBeans sunt clase care se pot instantia de cãtre un operator prin intermediul unui mediu vizual de dezvoltare (tipic sunt componente de interfatã graficã: butoane, cãsute cu text, s.a.), dupã care se pot modifica proprietãtile lor (tot interactiv, în faza de proiectare a interfetei grafice). De aceea clasele JavaBeans au si un constructor fãrã argumente si, obligatoriu, metode pentru citirea si modificarea proprietãtilor (care sunt variabile private ale clasei). Clasele cu date care se instantiazã exclusiv prin program (prin operatorul new) vor avea constructori cu argumente, pentru cã este mai comod sã stabilim proprietãtile la construirea obiectului. Clasa String contine un vector de caractere si dimensiunea sa, mai multi constructori si diverse functii care realizeazã operatii uzuale asupra acestui vector: cãutarea unui caracter dat în vector, extragerea unui subsir din întregul sir, compararea cu un alt sir etc. Exemplu:
public class String { // variabilele clasei private char value[ ]; // un vector de caractere private int count; // numar de caractere folosite // un constructor public String( char [ ] str) { count= str.length; value= new char[count]; System.arraycopy (str,0,value,0,count); } // metodele clasei public int length () { // lungime sir return count; } public String substring (int start, int end) { // extrage subsir dintre start si end int n=end-start+1; // nr de caractere intre start si end char b[ ] = new char[n]; System.arraycopy (value,start,b,0,n); return new String (b); } public String substring (int pos) { // extrage subsir din pozitia pos return substring (pos, length()-1); // return this.substring (pos,length()-1); } // ... alte metode }

A se observa existenta unor metode cu acelasi nume dar cu argumente diferite si modul în care o metodã (nestaticã) apeleazã o altã metodã nestaticã din clasã: nu se mai specificã obiectul pentru care se apeleazã metoda “substring” cu doi parametri, deoarece este acelasi cu obiectul pentru care s-a apelat metoda “substring” cu un singur parametru (obiectul this = chiar acest obiect).

50

Florian Moraru – Programare Orientata pe Obiecte O clasã mai poate contine constante si chiar alte clase incluse. In limbajul C o declaratie de structurã este o definitie de tip care nu alocã memorie si de aceea nu este posibilã initializarea membrilor structurii. In Java o definitie de clasã creeazã anumite structuri de date si alocã memorie, ceea ce justificã afirmatia cã o clasã este un fel de sablon pentru crearea de obiecte de tipul respectiv. Definitia unei clase Java nu trebuie terminatã cu ‘;’ ca în C++. Metode care pot genera exceptii In antetul unor metode trebuie sã aparã clauza throws, dacã în aceste metode pot apare exceptii, a cãror tratare este verificatã de compilator. Intr-o metodã poate apare o exceptie fie datoritã unei instructiuni throw, fie pentru cã se apeleazã o metodã în care pot apare exceptii. La executia unei metode sau unui constructor este posibil sã aparã o situatie de exceptie, iar metoda trebuie sã anunte mai departe aceastã exceptie. Semnalarea unei exceptii program înseamnã în Java crearea unui obiect de un anumit tip clasã (derivat din clasa Exception) si transmiterea lui în afara functiei, cãtre sistemul care asistã executia ("Runtime system"). Exemplu de metodã care verificã dacã parametrul primit este eronat, caz în care produce o exceptie:
public char charAt (int index) { // caracterul din pozitia ‘index’ a unui sir if ((index < 0) || (index >= count)) // daca indice negativ sau mai mare ca lungimea sirului throw new StringIndexOutOfBoundsException(index); // arunca exceptie predefinita return value[index]; // daca indice corect, returneaza caracterul respectiv }

Instructiunea throw se foloseste pentru generarea unei exceptii într-o metodã si specificã un obiect “exceptie”(de obicei un obiect creat ad-hoc chiar în instructiune). Alegerea acestui cuvânt ("to throw"= a arunca) se explicã prin aceea cã la detectarea unei exceptii nu se apeleazã direct o anumitã functie (selectatã prin nume); functia care va trata exceptia (numitã "exception handler") este selectatã dupã tipul obiectului exceptie "aruncat" unui grup de functii. Metoda "charAt" aruncã o exceptie care nu trebuie declaratã. O metodã în care se poate produce o exceptie si care aruncã mai departe exceptia trebuie sã continã în definitia ei clauza throws. Cuvântul cheie throws apare în antetul unei metode, dupã lista de argumente si este urmat de numele unui tip exceptie (nume de clasã) sau de numele mai multor tipuri exceptie. In Java se considerã cã erorile semnalate de o metodã fac parte din antetul metodei pentru a permite compilatorului sã verifice dacã exceptia produsã este ulterior tratatã si sã oblige programatorii la tratarea anumitor exceptii. Exemplu de exceptie generatã de un constructor:
public static FileReader fopen ( String filename) throws IOException { return new FileReader (filename); // exceptie de fisier negãsit }

Orice functie (metodã) care apeleazã metoda “fopen” de mai sus trebuie fie sã arunce mai departe exceptia (prin clauza throws), fie sã o trateze (prin try-catch):
public static void fileCopy ( String src, String dst) throws IOException { FileReader fr = fopen (src); // aici poate apare exceptie de I/E …

Orice altã functie care apeleazã pe “fileCopy” trebuie sã trateze sau sã arunce mai departe exceptia; decizia se va lua în aplicatia care foloseste functia (dacã se afiseazã doar un mesaj sau se încearcã o repetare a operatiei de introducere nume fisier). O tratare minimalã afiseazã cauza exceptiei si locul unde s-a produs:
public static FileReader fopen ( String filename) {

51

Florian Moraru – Programare Orientata pe Obiecte
try { return new FileReader (filename); } catch (IOException ex) { ex.printStackTrace(); return null; } } // daca fisier negãsit

Pentru erori uzuale sunt predefinite o serie de tipuri exceptie, dar utilizatorii pot sã-si defineascã propriile tipuri exceptie, sub forma unor noi clase (derivate fie din clasa Exception fie din clasa RuntimeException). Pentru erorile care nu permit continuarea programului vom prefera exceptiile derivate din clasa RuntimeException sau din clasa Error, care nu obligã programatorul la o anumitã actiune ("prindere" sau aruncare mai departe). Existã chiar pãrerea cã toate exceptiile ar trebui sã fie de acest fel, pentru simplificarea codului (prin eliminarea clauzei throws din antetul metodelor) si pentru a evita tratarea lor superficialã (prin interzicerea oricãrui mesaj la aparitia exceptiei). Clase si obiecte Java în faza de executie Pentru fiecare clasã încãrcatã în masina virtualã Java este creat automat câte un obiect de tip Class, cu informatii despre clasa asociatã (metadate). Obiecte Class sunt create automat si pentru interfete, clase abstracte si vectori intrinseci Java. Prin “reflectie” (“reflection”) se întelege obtinerea de informatii despre o clasã sau despre un obiect în faza de executie (este “reflectatã” starea masinii virtuale). In plus, se pot crea si modifica dinamic obiecte în faza de executie. Reflectia este asiguratã în principal de clasa numitã Class, dar si de alte clase din pachetul “java.lang.reflect”: Constructor, Method, Field, s.a. O variabilã de tip Class contine o referintã la un obiect descriptor de clasã; ea poate fi initializatã în mai multe feluri: - Folosind cuvântul cheie class (literalul class) ca si cum ar fi un membru public si static al clasei sau tipului primitiv :
Class cF = Float.class, cS = Stiva.class, // clase predefinite sau proprii cf = float.class, cv =void.class, // tipuri primitive cN= Number.class, cI=Iterator.class ; // clase abstracte si interfete

- Folosind metoda staticã “forName” cu argument nume de clasã (ca sir de caractere):
Class cF = Class.forName(“java.util.Float”), cf = Class.forName (“float”);

- Folosind metoda “getClass” pentru o variabilã de orice tip clasã (metoda “getClass” este mostenitã de la clasa Object, deci existã în orice clasã):
Float f = new Float (3.14); Class cF = f.getClass();

Clasa Class contine metode care au ca rezultat numele clasei, tipul clasei (clasã sau interfatã sau vector), tipul superclasei, clasa externã, interfete implementate, tipul obiectelor declarate în clasã, numele câmpurilor (variabilelor clasei), numele metodelor clasei, formele functiilor constructor s.a. Metoda “getName” are ca rezultat un sir ce reprezintã numele clasei al cãrui tip este continut întrun obiect Class. Exemplul urmãtor aratã cum se poate afisa tipul real al unui obiect primit ca argument de tipul generic Object:
void printClassName (Object obj) { System.out.println ( obj.getClass().getName()); }

Crearea de obiecte de un tip aflat în cursul executiei dar necunoscut la compilare (cu conditia ca tipul respectiv sã fi fost definit printr-o clasã, iar fisierul “class” sã fie accesibil la executie) se poate face simplu dacã în clasã existã numai un constructor fãrã argumente . Exemplu:

52

Florian Moraru – Programare Orientata pe Obiecte
public static Object getInstance (String clsname) throws Exception { Class cl = Class.forName(clsname); // clsname = nume clasa (complet) return cl.newInstance(); }

Deoarece putem obtine toti constructorii unei clase, cunoscând tipul argumentelor, se pot “fabrica” obiecte si apelând constructori cu argumente. Exemplu:
public static Object newObject (Constructor constructor, Object [ ] args) { Object obj = null; try { obj = constructor.newInstance(args); } catch (Exception e) { System.out.println(e); } return obj; } // utilizare Float x; Class cls = Float.class; Class[] argsCls = new Class[ ] {float.class}; // tip argumente constructor Constructor constr = cls.getConstructor(argsCls); // obtinere constructor Object[] args = new Object[ ] { new Float(3.5) }; // argument efectiv ptr instantiere x =(Float) newObject (constr,args);

Prin reflectie un asamblor de componente dintr-un mediu vizual poate sã determine proprietãtile si metodele proprii unor obiecte, sã modifice proprietãtile acestor obiecte si sã genereze apeluri de metode între obiecte. In rezumat, reflectia permite operatii cu clase si cu obiecte necunoscute la scrierea programului, dar care pot fi determinate dinamic, în cursul executiei. De observat cã tipul unui obiect nu este neapãrat acelasi cu tipul variabilei prin care se fac referiri la obiect; tipul variabilei poate fi o interfatã, o clasã abstractã sau neabstractã din care este derivat tipul obiectului, deci tipul variabilei poate fi mai general decât tipul obiectului. De exemplu, rezultatul unei metode de extragere dintr-un vector (elementAt) este de tip Object, dar tipul real al obiectului poate fi orice tip clasã. Pentru apelarea metodelor specifice clasei respective se face o conversie prin operatorul “cast” la tipul cunoscut. Exemplu:
Vector v = new Vector(); v.add (new Integer(1)); // adauga la v un obiect de tip Integer ... int x = (Integer)v.elementAt(0).intValue(); // x= 1

Utilizarea metodei “intValue” fãrã conversie prealabilã este semnalatã ca eroare la compilare, deoarece metoda nu existã în clasa Object, iar compilatorul verificã dacã metodele (nestatice) apelate existã în clasa de tipul cãreia este variabila. Dacã se apeleazã metode polimorfice pentru obiecte de tip necunoscut, atunci se va apela automat varianta definitã în clasa respectivã, chiar dacã programatorul (si nici compilatorul) nu stie care este tipul exact al obiectului. Exemplu:
String s = v.elementAt(i).toString(); if ( v.elementAt(i).equals(obj)) ... // se apeleazã Integer.toString() // se apeleazã Integer.equals()

De aceea determinarea tipului unui obiect la executie, folosind obiectul Class asociat, este rareori necesarã în programele obisnuite.

53

Florian Moraru – Programare Orientata pe Obiecte 5. Cuvântul super. pentru a permite compararea de obiecte si conversia datelor din clasã într-un sir de caractere. Exemplu: public class SortedVector extends Vector { // vector ordonat // metoda redefinita ptr adaugare la vector ordonat public void addElement (Object obj) { // adaugare element si ordonare int i=indexOf (obj). De multe ori. La definirea unei clase derivate se foloseste cuvântul cheie extends urmat de numele clasei de bazã. Nu se poate extinde o clasã finalã (cu atributul final) si nu pot fi redefinite metodele din superclasã care au unul din modificatorii final. deci numele si tipul metodei. ca nume de functie. Majoritatea claselor Java care contin date redefinesc metodele “equals” si “toString” (mostenite de la clasa Object). Tipul D este un subtip al tipului A.i). dar se poate face folosind cuvântul cheie "super" în locul numelui superclasei. } } Principala modalitate de specializare a unei clase este redefinirea unor metode din superclasã. o particularizare a superclasei si nu extinde domeniul de utilizare al superclasei. O metodã redefinitã într-o subclasã "ascunde" metoda corespunzãtoare din superclasã. de la care “mosteneste” toate variabilele si metodele publice si “protected”. Exemplu: // vector ordonat cu adaugare la sfarsit si reordonare public class SortedVector extends Vector { public void addElement (Object obj) { // adaugare element si ordonare super. Redefinirea unei metode trebuie sã pãstreze "amprenta" functiei. Prin redefinire ("override") se modificã operatiile dintr-o metodã. private. if (i < 0) i=-i-1. iar o variabilã dintr-o subclasã poate "ascunde" o variabilã cu acelasi nume din superclasã. polimorfism Clase derivate Derivarea înseamnã definirea unei clase D ca o subclasã a unei clase A. } } In Java se spune cã o subclasã extinde functionalitatea superclasei.obj).binarySearch(this. Subclasa D poate redefini metode mostenite de la clasa pãrinte (superclasa) A si poate adãuga noi metode si variabile clasei A. mostenire. // apel metoda “addElement” din clasa Vector 54 .addElement (obj). numãrul si tipul argumentelor . Derivare. } // metoda redefinita: cautare binara in vector ordonat public int indexOf (Object obj) { // cauta in vector un obiect return Collections. dar nu si modul de utilizare al metodei. subclasele nu fac altceva decât sã redefineascã una sau câteva metode ale superclasei din care sunt derivate. In general o subclasã este o specializare. Exemplu: public class IOException extends Exception { public IOException() { super(). static. } public IOException(String s) { super(s). Apelarea metodei originale din superclasã nu este în general necesarã pentru un obiect de tipul subclasei. insertElementAt(obj. în sensul cã ea poate contine metode si date suplimentare. poate fi folosit numai într-un constructor si trebuie sã fie prima instructiune (executabilã) din constructorul subclasei.

* . int pos) { // ptr a nu modifica ordinea throw new UnsupportedOperationException().5 adnotarea @Override ca prefix al metodei: import java.i++) { 55 .size(). File [] files= listFiles().sort(this). } } Dacã metoda din subclasã nu are exact acelasi prototip (nume. public boolean empty() { // test de stiva goala return size()==0.i++) addElement(v. if ( ! isDirectory()) return.println (sp + getName()). fie indirect.util.i<files. for(int i=0. // vector ordonat cu adaugare la sfirsit si reordonare class SortVec extends Vector { public SortVec () { super(). } // afisare recursiva fisiere din subdirectoare // varianta 1: cu metoda listFiles public void printFiles (String sp) { System. } // metoda mostenita automat dar interzisa in subclasa public void insertElementAt (Object obj. // apel metoda din superclasa Collections. prin metode publice ale superclasei. } Lipsa adnotãrii @Override în exemplul anterior nu permite detectarea erorii cã metoda “addElement” cu argument String nu redefineste metoda “addElement” din superclasã.i<v.Florian Moraru – Programare Orientata pe Obiecte Collections.. Exemplu: public class Stack extends Vector { .sort(this). O subclasã se poate referi la variabilele superclasei fie direct prin numele lor. cu argument Object.} public SortVec ( Vector v) { super().out. // apelat implicit ! for (int i=0.tip.elementAt(i)). } @Override public void addElement ( String obj) { // adaugare element si ordonare super. Pentru a permite compilatorului sã verifice cã are loc o redefinire de metodã mostenitã s-a introdus din 1.addElement (obj).. dacã variabilele din superclasã sunt de tip public sau protected. } } Exemplul urmãtor aratã cum putem scrie o metodã recursivã într-o clasã derivatã: class FileExt extends File { public FileExt (String name) { super (name).length.argumente) cu metoda din superclasã atunci nu are loc o redefinire si doar se adaugã altã metodã la cele mostenite. // sau return elementCount==0.

out. deci fiecare clasã are propriile sale functii constructor (definite explicit de programator sau generate de compilator). O subclasã nu mosteneste constructorii superclasei. " înainte de prima instructiune din constructorul subclasei. definitã anterior.toString()). Initializarea variabilelor mostenite este realizatã de constructorul superclasei. // afisare continut vector Chiar si metoda “addElement” redefinitã în clasa derivatã. Este deci necesar ca un constructor din subclasã sã apeleze un constructor din superclasã. Apelul unui constructor din superclasã dintr-o subclasã se face folosind cuvântul cheie super ca nume de functie si nu este permisã apelarea directã. indexOf. f.printFiles (sp+" "). O solutie de preluare a unor functii de la o clasã la alta este derivarea.Florian Moraru – Programare Orientata pe Obiecte FileExt f = new FileExt (files[i]. Clasa “SortedVector”.println ( a. for(int i=0. In schimb. a acestui constructor. prin apelul metodei cu acelasi nume din superclasã. Exemplu: public class SortedVector extends Vector { 56 . trebuie definite functiile constructor..i++) { FileExt f = new FileExt (this+"/"+ files[i])..)".getAbsolutePath()). } } } Derivarea ca metodã de reutilizare Derivarea este motivatã uneori de necesitatea unor clase care folosesc în comun anumite functii dar au si operatii specifice. In Java atributele membrilor mosteniti nu pot fi modificate în clasa derivatã: o variabilã public din superclasã nu poate fi fãcutã private în subclasã. refoloseste extinderea automatã a vectorului. mosteneste metodele clasei Vector: elementAt. } } // varianta 2: cu metoda list public void printFiles (String sp) { // afisare recursiva cu indentare System.printFiles (sp+" "). metodele si datele suplimentare (dacã existã) si trebuie redefinite metodele care se modificã în subclasã.out. desi valorile folosite la initializare sunt primite ca argumente de constructorul subclasei. dacã nu existã un apel explicit si dacã nu se apeleazã un alt constructor al subclasei prin "this(.i<files. Metodele si variabilele mostenite ca atare de la superclasã nu mai trebuie declarate în subclasã si pot fi folosite fãrã prefixul super. Din acest motiv nu putem crea un obiect vector ordonat prin instructiunea urmãtoare: SortedVector a = new SortedVector(n). Exemplu: SortedVector a = new SortedVector(). if ( ! isDirectory()) return. Nu se genereazã automat decât constructori fãrã argumente. toString s.length. f. System. String [] files= list(). O clasa derivatã "mosteneste" de la superclasa sa datele si metodele nestatice cu atributele public si protected . // nu exista constructor cu argument ! In Java constructorul implicit (fãrã argumente) generat pentru o subclasã apeleazã automat constructorul fãrã argumente al superclasei.println (sp + getName()). prin numele sãu.a. Compilatorul Java genereazã automat o instructiune "super(). diferite în clasele înrudite.

} public SortedVector ( Comparator c) { super(). // text += line+”\n”. comp=c.. } // verificare metode clasa FileTokenizer public static void main (String arg[]) { FileTokenizer t = new FileTokenizer (arg[0]).hasMoreTokens()) System.out. // text va contine tot fisierul try { BufferedReader f= new BufferedReader( new FileReader(fname)). In exemplul anterior este necesarã definirea constructorului fãrã argumente. e generat automat } public void addElement (Object obj) { // adaugare element si ordonare super.printStackTrace(). care nu se mai genereazã automat dacã se defineste (cel putin) un constructor cu argumente. comp=c. pentru cã nu existã încã obiectul pentru care se apeleazã metoda): // extragere cuvinte separate prin spatii dintr-un fisier text class FileTokenizer extends StringTokenizer { public FileTokenizer ( String fname) { super(readFile(fname)).readLine()) !=null) text=text+line+"\n". atunci ele vor fi grupate într-o metodã staticã (metode nestatice nu pot fi apelate înaintea unui constructor.. } } 57 .nextToken()).Florian Moraru – Programare Orientata pe Obiecte public SortedVector () { // vector cu dimensiune implicita super(). } catch (IOException ex) { ex. // super() poate lipsi. // initializari in superclasa } public SortedVector (int max) { // vector cu dimensiune specificata la instantiere super(max).line. Exemplu: public class SortedVector extends Vector { private Comparator comp. Comparator c) { super(max). // max folosit la alocarea de memorie in superclasa } . // ordonare dupa comparatorul primit } } Apelul constructorului superclasei (“super”) trebuie sã fie prima instructiune din constructorul subclasei. while ( t. // variabila a subclasei // constructori public SortedVector ( int max.comp). atunci ele vor fi initializate în constructorul subclasei.sort(this. Dacã subclasa are variabile în plus fatã de superclasã.print(" | "+ t.addElement (obj). Dacã trebuie efectuate anterior anumite operatii. while ( (line=f. // apel metoda “addElement” din clasa Vector Collections.} return text. // argument de tip String } // citire continut fisier text intr-un sir ( functie statica !) static String readFile( String fname) { String text ="".

leg=p. Exemple de conversii între tipuri compatibile: MyList a . dar are acelasi efect: adãugarea unui nou element la sfârsitul listei. Prin derivare se creeazã subtipuri de date compatibile cu tipul din care provin. // adresa ultimului element din lista } // adaugare la sfirsit coada (mai rapid ca in MyList) public void add (Object v) { Node nou= new Node(). // initializare variabila “prim” ultim=prim. // primul nod cu date prim. MyQueue q= new MyQueue(). } // eliminare obiect de la inceputul cozii public Object del () { if ( empty()) return null. Conversia de tip între clase incompatibile produce exceptia ClassCastException. ultim. if (empty()) // daca coada este goala ultim=prim. în sensul cã o variabilã (sau un parametru) de un tip A poate fi înlocuit fãrã conversie explicitã cu o variabilã (cu un parametru) de un subtip D. } // test daca coada goala public boolean empty() { return prim. pentru adãugare la început de listã. nou. a = q. argumente sau functii de tipul respectiv. si cu o metodã de extragere (si stergere) obiect de la începutul listei. Avantajele reutilizãrii prin extinderea unei clase sunt cu atât mai importante cu cât numãrul metodelor mostenite si folosite ca atare este mai mare. iar trecerea de la o subclasã D la o superclasã A se poate face prin conversie explicitã (“cast”). // variabila prezenta numai in subclasa public MyQueue () { super(). care poate fi folosit în declararea unor variabile. Tipurile superclasã si subclasã sunt compatibile.leg=nou. // downcasting 58 .val=v. public class MyQueue extends MyList { // Lista coada private Node ultim. Derivare pentru creare de tipuri compatibile Definirea unei noi clase este echivalentã cu definirea unui nou tip de date. Nod p=prim.leg.val.leg==null. cu redefinirea metodei de adãugare “add”. return p.leg. Ideea este cã atât coada cât si stiva sunt cazuri particulare de liste. ultim=nou. } } Din clasa “MyList” se poate deriva si o clasã stivã realizatã ca listã înlãntuitã.Florian Moraru – Programare Orientata pe Obiecte In exemplul urmãtor clasa “MyQueue” (pentru liste de tip coadã) este derivatã din clasa “MyList” pentru liste simplu înlãntuite. // upcasting q = (MyQueue) a . Cele douã clase folosesc în comun variabila “prim” (adresa de început a listei) si metoda “toString”. Metoda “add” este redefinitã din motive de performantã.

a. Number Float Integer Procesul de extindere sau de specializare a unei clase poate continua pe mai multe niveluri. care include ca niste cazuri particulare fisiere text. In felul acesta programele au un caracter mai general si sunt mai usor de modificat. Putem constata în practica programãrii cu obiecte cã unele superclase transmit subclaselor lor putinã functionalitate (putine metode mostenite întocmai).a. In acest scop existã urmãtorii constructori cu argumente de tip interfatã: public Scanner (InputStream source). s. poate prelua textul de scanat si dintr-un flux de intrare-iesire. De exemplu clasa Scanner (java.a. Trecerea între cele douã tipuri nu se poate face prin operatorul de conversie. desi sunt derivate din tipul Number). MyList downcasting MyQueue upcasting De remarcat cã douã subtipuri ale unui tip comun A nu sunt compatibile ( de ex. float. în loc sã avem câte o functie pentru fiecare tip numeric (int. Conversia explicitã de la tipul de bazã la un subtip este necesarã pentru a putea folosi metodele noi ale subclasei pentru obiectele subclasei.). care recunoaste si converteste automat numere din format extern în format intern. O subclasã a clasei Exception nu adaugã metode noi si contine doar constructori: public class RuntimeException extends Exception { 59 . derivate direct din clasa Exception sau din subclasa RuntimeException. permitând sã existe câte o singurã functie matematicã (sqrt.). siruri sau vectori de octeti s.util). log s. Tipurile InputStream si Readable sunt tipuri foarte generale. Aplicarea repetatã a derivãrii produce ierarhii de tipuri si familii de clase înrudite. Un exemplu este familia claselor exceptie. unor argumente de functii sau componente ale unor colectii. Ierarhii de tipuri (primitive) existã si în limbajul C. Tipul superclasei este mai general decât tipurile subclaselor si de aceea se recomandã ca acest tip sã fie folosit la declararea unor variabile. Posibilitatea de creare a unor tipuri compatibile si de utilizare a unor functii polimorfice constituie unul din motivele importante pentru care se foloseste derivarea. NullPointerException s. si mai specializate. deci motivul extinderii clasei nu este reutilizarea unor metode ci relatia specialã care apare între tipurile subclasei si superclasei. Instructiunea throw trebuie sã continã un obiect de un tip compatibil cu tipul Exception. care sunt toate compatibile ca tip cu clasa de la baza ierarhiei. de obicei un subtip ( IOException.a. deci fiecare subclasã poate deveni superclasã pentru alte clase. In general se definesc familii de clase. de exemplu. tipurile Integer si Float nu sunt compatibile.Florian Moraru – Programare Orientata pe Obiecte Tipuri compatibile sunt tipurile claselor aflate într-o relatie de descendentã (directã sau indirectã) pentru cã aceste clase au în comun metodele clasei de bazã. exp. public Scanner (Readable source). unele derivate din altele. In felul acesta se reduce numãrul de constructori care ar fi fost necesar pentru fiecare clasã instantiabilã derivatã din clasa abstractã InputStream. dar se poate face prin construirea altui obiect.) pentru toate tipurile de numere (considerate ca subtipuri ale tipului double).

Clasa Object ca bazã a ierarhiei de clase Java In Java. // atunci se mareste contorul de aparitii else // daca cheia nu exista in dictionar cnt =1. care nu transmite metode subclaselor sale. System. In Java (si în alte limbaje) aceastã tehnicã a condus la crearea unui tip generic Object.a. put. dar creeazã clase compatibile cu clasa abstractã. de aceea în alte ierarhii de clase (din alte limbaje) rãdãcina ierarhiei de clase este o clasã abstractã. clasa Object (java."a". Exemplu de creare a unui dictionar cu numãrul de aparitii al fiecãrui cuvânt dintr-un vector: public static Dictionary wordfreq ( String v[]) { Dictionary dic = new Hashtable(). keys. } } // s= un mesaj suplimentar Uneori superclasa este o clasã abstractã (neinstantiabilã). clasele abstracte si interfetele Java sunt subtipuri implicite ale tipului Object. i< v. b=2. // pune in dictionar cheia si valoarea asociata } return dic. De asemenea. // valoarea asociata cheii key in dictionar if ( nr != null) // daca exista cheia in dictionar cnt = nr. Putem sã ne definim si alte clase care sã extindã clasa Dictionary. Exemplu de folosire a functiei: public static void main (String arg[]) { String lista[ ] = {"a". care este tipul din care derivã toate celelalte tipuri clasã si care este folosit pentru argumentele multor functii (din clase diferite) si pentru toate clasele colectie predefinite (Vector.println (d). Deoarece clasa Object nu contine 60 . s.Object) este superclasa tuturor claselor JDK si a claselor definite de utilizatori. cum ar fi un dictionar vector.lang. Dictionary d= wordfreq(lista). i++) { Object key = v[i]. } Rezultatul functiei “wordfreq” poate fi prelucrat cu metodele generale ale clasei Dictionary.get(key). // atunci este prima ei aparitie dic. fãrã sã ne intereseze ce implementare de dictionar s-a folosit în functie. } public RuntimeException(String s) { super(s). Orice clasã Java care nu are clauza extends în definitie este implicit o subclasã derivatã direct din clasa Object. // tipul Hashtable se poate inlocui cu alt tip int cnt. Clasa Object transmite foarte putine operatii utile subclaselor sale.out."b". Clasa de bibliotecã Hashtable extinde clasa Dictionary."c". // scrie {c=1. HashTable). care contine metode utilizabile pentru orice clasã dictionar."b". Un exemplu din Java 1.put (key. indiferent de implementarea dictionarului: get.length. fiind un caz particular de dictionar. Atunci când scriem un program sau o functie vom folosi variabile sau argumente de tipul Dictionary si nu de tipul Hashtable sau de alt subtip.Florian Moraru – Programare Orientata pe Obiecte public RuntimeException() { super(). new Integer(cnt)).1 este clasa abstractã Dictionary."a"}. // cuvintele sunt chei in dictionar Integer nr = (Integer) dic. remove. // contor de aparitii for (int i=0. realizat printr-un tabel de dispersie.intValue()+1. a=3} } Utilizarea unui tip mai general pentru crearea de functii si colectii cu caracter general este o tehnicã specificã programãrii cu obiecte. Alegerea unei alte implementãri se face prin modificarea primei linii din functie.

toString(). care are ca operanzi o variabilã de un tip clasã si tipul unei (sub)clase si un rezultat boolean.re == cpx.im == cpx. dar în practicã se redefinesc câteva metode. diferitã de metoda "equals" mostenitã de la clasa Object. deci trecerea de la orice tip la tipul String (dar nu prin operatorul de conversie ): Date d = new Date(). } atunci ea ar fi fost consideratã ca o nouã metodã. sau a metodei "Complex. La apelarea metodei "Complex. Din acest motiv. O variabilã sau un argument de tip Object (o referintã la tipul Object) poate fi înlocuitã cu o variabilã de orice alt tip clasã. Existã deci mai multe metode "equals" în clase diferite..equals" este de tip Object si nu este de tip "Complex". Exemplu din clasa “Complex”: public boolean equals (Object obj) { Complex cpx =(Complex) obj.Florian Moraru – Programare Orientata pe Obiecte nici o metodã abstractã.equals" cu argument de tip String se foloseste în Java operatorul instanceof.equals" cu argument de tip "Complex".equals" poate fi folosit un argument efectiv de tipul "Complex".. nu se impune cu necesitate redefinirea vreunei metode. } Pe de altã parte. Dacã am fi definit metoda urmãtoare: public boolean equals (Complex cpx) { . deci nu se pot schimba tipul functiei sau tipul argumentelor. toate cu argument de tip Object si care pot fi apelate cu argument de orice tip clasã. cum ar fi clasele flux de intrare-iesire. 61 . // dar nu si : String s = (String) d. pentru a produce un sir cu continutul obiectului. deoarece orice tip clasã este în Java derivat din si deci compatibil cu tipul Object (la fel cum în limbajul C o variabilã sau un argument de tip void* poate fi înlocuitã cu o variabilã de orice tip pointer). if ( obj != null && obj instanceof Complex) if (this. deoarece un argument formal de tip superclasã poate fi înlocuit (fãrã conversie explicitã de tip) printrun argument efectiv de un tip subclasã. Metoda "equals" din clasa Object considerã cã douã variabile de tip Object sau de orice tip derivat din Object sunt egale dacã si numai dacã se referã la un acelasi obiect: public boolean equals (Object obj) { return (this == obj). De aceea. In acest caz am fi avut o supradefinire ("overloading") si nu o redefinire de functii. Redefinirea unei metode într-o subclasã trebuie sã pãstreze aceeasi semnãturã cu metoda din superclasã. Metoda “toString” permite obtinerea unui sir echivalent cu orice obiect. Metoda “toString” din clasa Object transformã în sir adresa obiectului si deci trebuie sã fie redefinitã în fiecare clasã cu date. un utilizator al clasei String ( sau al altor clase cu date) se asteaptã ca metoda "equals" sã aibã rezultat true dacã douã obiecte diferite (ca adresã) contin aceleasi date. Metoda "equals" nu trebuie redefinitã în clasele fãrã date sau în clasele cu date pentru care nu are sens comparatia la egalitate.re && this.im) return true. cum ar pãrea mai natural. String s = d. metoda "equals" este rescrisã în clasele unde se poate defini o relatie de egalitate între obiecte. } Pentru a evita erori de programare de tipul folosirii metodei "String. return false. argumentul metodei "Complex.

la executie.. Variabile Obiecte Clase 62 .toString(). Object f = new Float (3. al cãror efect depinde de tipul obiectului pentru care sunt apelate (altul decât tipul variabilei referintã). Legarea dinamicã are loc în Java pentru orice metodã care nu are atributul final sau static.equals() s.14). Metodele “equals”.Florian Moraru – Programare Orientata pe Obiecte Polimorfism si legare dinamicã O functie polimorficã este o functie care are acelasi prototip.toString(). apeluri de forma a..add(). // apel Date. Exemple: Object d = new Date(). adicã un apel de metodã este tradus într-o instructiune de salt care contine adresa functiei apelate. q. q. Clasa MyQueue va avea o altã adresã pentru functia "add" decât clasa MyList. // apel Float. Fiecare clasã are asociat un tabel de pointeri la metodele (virtuale) ale clasei. Pentru metodele finale si statice legarea se face la compilare.add(). Legarea se poate face la compilare (“legare timpurie”) sau la executie ("legare târzie" sau "legare dinamicã"). Object equals toString equals toString toString . Asocierea unui apel de metodã cu functia ce trebuie apelatã se numeste "legare" ("Binding"). In Java majoritatea metodelor sunt polimorfice. dar implementãri (definitii) diferite în clase diferite dintr-o ierarhie de clase. “toString” sunt exemple tipice de functii polimorfice.a. metodã numitã polimorficã..toString() Apelul functiilor polimorfice este mai putin eficient ca apelul functiilor cu o singura formã.toString(). String ds = d. q prim aprim ultim MyQueu uee equals aprim apri toStrin add add del totoStri del .. q. toString a prim aprim apri MyList equals toString toString add toString add totoStrin totoStri .toString() String fs = f.. Metodele polimorfice Java corespund functiilor virtuale din C++. Figura urmãtoare aratã cum se rezolvã..

O colectie genericã poate fi realizatã ca o colectie de obiecte de tip Object iar o functie genericã este o functie cu argumente formale de tip Object si/sau de un tip colectie genericã. q. apelul metodei “toString” pentru un obiect colectie alege functia ce corespunde tipului de colectie (vector.length. q = new MyQueue().Florian Moraru – Programare Orientata pe Obiecte Fiecare obiect contine.toString” conduce la metoda “toString” din superclasã (“MyList”) deoarece este o metodã mostenitã si care nu a fost redefinitã. Fie urmãtoarele variabile: MyList a. un pointer la tabela de metode virtuale (polimorfice). este o metodã care efectueazã aceleasi operatii în toate subclasele clasei A. în functie de tipul variabilei pentru care se apeleazã metoda. care contin toate metode (nestatice) cu aceeasi semnaturã. Exemplu: public final boolean contains (Object elem) { return indexOf (elem) >=0 . Exemplu de functie pentru cãutare secventialã într-un vector de obiecte: public static int indexOf (Object[] array.i++) if ( obj. deoarece nici o subclasã a clasei Object nu redefineste metoda “equals”. Structuri de date generice în POO O colectie genericã este o colectie de date (sau de referinte la date) de orice tip. De exemplu. Apelul “q. pe lângã datele nestatice din clasã.sort” apeleazã functia polimorficã “compareTo” (sau “compare”).add” pleacã de la obiectul referit de variabila "q" la tabela metodelor clasei "MyQueue" si de acolo ajunge la corpul metodei "add" din clasa "MyQueue". chiar dacã este redefinitã în subclase. etc). // indice unde a fost gasit return -1. a = new MyList(). în sensul cã selectarea metodei apelate se face în functie de tipul obiectului pentru care se apeleazã metoda.i<array. Pentru a memora 63 . Compilatorul traduce un apel de metodã polimorficã printr-o instructiune de salt la o adresã calculatã în functie de continutul variabilei referintã si de definitia clasei. care este membrã a unei familii de clase. Apelul “q. etc. Alegerea automatã a variantei potrivite a unei metode polimorfice se poate face succesiv. datoritã modului de apelare. lista înlãntuitã. pentru a ordona un vector cu orice fel de obiecte.equals (array[i])) // “equals” este polimorfica return i. O metodã staticã. } O metodã staticã poate apela o metodã polimorficã.). Numai metodele obiectelor pot fi polimorfice. pe mai multe niveluri. Solutia functiilor polimorfice este specificã programãrii orientate pe obiecte si se bazeazã pe existenta unei ierarhii de clase. dar cu efecte diferite în clase diferite. Object obj) { for (int i=0. // negasit } Pentru functia anterioarã este esential cã metoda “equals” este polimorficã si cã se va selecta metoda de comparare din clasa cãreia apartine obiectul referit de variabila “obj” (aceeasi cu clasa elementelor din vectorul “array”). O metodã finalã (atributul final) dintr-o clasã A. Integer. ea nu mai poate fi redefinitã. al cãrei efect depinde de tipul obiectelor comparate.equals” merge în sus pe ierahia de clase si ajunge la definitia din clasa Object. în functie de tipul acestor obiecte (String. la rândul ei metoda “toString” dintr-o clasã vector apeleazã metoda “toString” a obiectelor din colectie. de exemplu “Arrays. Apelul “q. nu poate fi selectatã astfel. Legãtura dinamicã se face de la tipul variabilei la adresa metodei apelate. Date. iar un algoritm generic este un algoritm aplicabil unei colectii generice.

Exemplu de functie care creazã un sir cu valorile dintr-un vector.toString()+”\n”). Double. // sau StringBuilder în Java 5 for (int i=0. double.i<v. Hashtable si Stack dar din Java 1. poate fi o colectie).2 sunt mai multe clase si sunt mai bine sistematizate.i<n. pe linii separate: public static String arrayToString ( Object a[]. for (int k=1. Primele colectii generice din Java au fost clasele Vector. la rândul lui.Boolean.size(). In plus. ”3”.asList”) si invers (metoda “toArray” din orice clasã colectie): String a[]={“1”. dar aceastã realitate este ascunsã de sintaxa Java.elementAt(i). dar timpul de acces la elemente este important. cu prelungirea actiunii unor functii polimorfice la subcolectii. for (int i=0.toString(). return sb. metoda “toString” din orice colectie apeleazã repetat metoda “toString” pentru fiecare obiect din colectie (care. } public static String vectorToString ( Vector v) { // obiect vector StringBuffer sb=new StringBuffer(). 64 . Char.i<10. int n) { // vector intrinsec StringBuffer sb=new StringBuffer().add ( k+””). Clasa Vector oferã extinderea automatã a vectorului si metode pentru diferite operatii uzuale: cãutare în vector. ”2”. Un alt avantaj al utilizãrii de clase colectie fatã de vectori intrinseci este acela cã se pot creea colectii de colectii.toString()+"\n"). Float. Existã posibilitatea de a trece de la un vector intrinsec la o clasã vector (metoda “Arrays. ele vor fi preluate de metoda “asList” în obiectul vector si pot produce exceptii la operatii asupra lor (si la ordonare).a.toArray().append ( v. Byte. De exemplu. Vectorul intrinsec de obiecte generice se foloseste atunci când continutul sãu nu se modificã sau se modificã foarte rar. return sb. Object[] b = v.asList(a)). boolean s.add (String.k++) v. ”4”}. Cea mai simplã colectie genericã este un vector cu componente de tip Object: Object [ ] a = new Object[10].toString(). Dacã existã valori null în vectorul intrinsec. afisare în vector. Exemplu de adãugare a unor siruri (referinte la siruri) la o colectie de tip Vector: Vector v = new Vector (10).valueOf(k)).k<11.) se pot folosi clasele “anvelopã” ale tipurilor primitive: Integer.i++) sb.a. numãrul de elemente din vector se obtine prin metoda “size” si nu trebuie transmis separat ca argument functiilor. // aloca memorie pentru vector // sau v.Florian Moraru – Programare Orientata pe Obiecte într-o colectie de obiecte si date de un tip primitiv (int. } Ceea ce numim o colectie de obiecte este de fapt o colectie de pointeri (referinte) la obiecte alocate dinamic.i++) sb. ca în cazul unui vector intrinsec (variabila “length” nu spune câte elemente sunt în vector ci doar capacitatea vectorului). Vector v = new Vector (Arrays. Short.append ( a[i]. insertie într-o pozitie datã s. // sau StringBuilder în Java 5 for (int i=0.i++) a[i]= new Integer(i).

i<v. Numãrul de sinonime din fiecare vector nu poate fi cunoscut si nici estimat. // afisare vector } In Java.i++) v. Metoda de dispersie are ca rezultat restul împãrtirii codului de dispersie (produs de metoda “hashCode”) prin dimensiunea vectorului principal. modificarea sirurilor si afisarea vectorului : public static void main (String args[]) { Vector v = new Vector() .contains } // adaugare obiect la multime (daca nu exista deja) public boolean add (Object el) { // redefineste metoda “add” din Vector 65 .setElementAt ( ( (String)v.i<v. având drept elemente obiecte de tip Vector (clasa Vector este si ea subclasã a clasei Object). // trece sir in litere mici } System.out.i++) v.n=n.toLowerCase(). // alocare memorie ptr vector principal this.length.i++) { String str = (String)v. este super. Tabelul de dispersie va fi realizat ca un vector de vectori. // dimensiune implicita vector principal } // apartenenta obiect la multime public boolean contains (Object el) { int k= (el.i<args.size().toLowerCase(). în care se foloseste un vector de liste înlãntuite.i++) addElement (new Vector()). Fiecare vector (sau listã) contine un numãr de “sinonime”. i). în vector principal (nr pozitiv) return ((Vector)elementAt(k)). ptr un vector de sinonime } public HSet () { // alt onstructor this(11). pentru a putea aplica metode specifice tipului respectiv.elementAt(i). Exemplu: for (int i=0.Florian Moraru – Programare Orientata pe Obiecte La extragerea unui element dintr-o colectie genericã trebuie efectuatã explicit conversia la tipul obiectelor introduse în colectie. fãrã a mai utiliza variabile intermediare.i<n. adicã elemente cu valori diferite pentru care metoda de dispersie a produs un acelasi indice în vectorul principal. for (int i=0. Sã considerãm o multime de obiecte realizatã ca tabel de dispersie (“hashtable”). Exemplu de creare a unui vector de siruri.println(v). // sir din pozitia i v.i). dar notatiile se simplificã în Java 5 dacã se folosesc clase generice (cu tipuri declarate). for (int i=0. înainte de a folosi acest element pentru apelul unei metode. public class HSet extends Vector { // tabel de dispersie ca vector de vectori private int n.add (args[i]).hashCode() & 0x7FFFFFFF) % n. ca si în C. deci ca un obiect de tip Vector.elementAt(i)). // adauga sir la vector // modificare vector for (int i=0.contains(el).size(). // poate lipsi. sunt posibile expresii ce contin apeluri succesive de metode. Operatorul de conversie (“cast”) are prioritate mai micã decât operatorul punct si de aceea trebuie folosite paranteze pentru elementul cu tip schimbat. O solutie mai bunã este cea din clasa JDK Hashtable. // apel Vector. // poz.setElementAt( str. Expresiile cu metode înlãntuite aplicate unor elemente din colectii Java necesitã astfel de paranteze. dar clasa Vector asigurã extinderea automatã a unui vector atunci când este necesar. // aloca mem.size() ! public HSet (int n) { // un constructor super(n).

Aceastã situatie este destul de frecventã în Java si face necesarã utilizarea operatorului de conversie (tip) ori de câte ori o variabilã de un tip mai general (tip clasã sau interfatã) se referã la un obiect de un subtip al tipului variabilei. “contains” si “size” din clasa “HSet” apeleazã metodele cu acelasi nume din clasa Vector.Florian Moraru – Programare Orientata pe Obiecte int k = (el.5). } // numar de obiecte din colectie public int size() { // redefineste metoda “size” din Vector int m=0.size(). Object obj = f.add (el).contains (el)) return false. } Metodele polimorfice “toString”. Exemplu: Object obj = v. // aduna dimensiune vector return m. chiar dacã ele se referã la variabile de un alt tip (Integer. // sau: Float f = (Float) v. String s = (String) obj. în vector principal (nr pozitiv) Vector sin = (Vector)elementAt(k). for (int k=0. String. astfel nu se declarã doar un obiect Vector ci se declarã un vector de obiecte String sau Float sau de un alt tip: Vector<String> a = new Vector<String>. dar o parte din ele pot avea valoarea null si genereazã exceptii la folosirea lor în expresii de forma v.k<n. printr-o exceptie.toString()+"\n". copilatorul Java nu “vede” decât tipul declarat (Object) si nu permite utilizarea de metode ale clasei din care fac parte obiectele reunite în colectie. Redefinirea metodei “toString” a fost necesarã deoarece metoda “toString” mostenitã de la clasa Vector (care o mosteneste de la o altã clasã) foloseste un iterator. De remarcat cã astfel de conversii nu modificã tipul obiectelor ci doar tipul variabilelor prin care ne referim la obiecte (tipul unui obiect nu poate fi modificat în cursul executiei). // vector de sinonime if ( sin. String s = (String) f. 66 . if (v !=null) s=s + v.k<n.elementAt(i).k++) // ptr fiecare din cei n vectori m=m+((Vector)elementAt(k)). // poz. Pentru a evita astfel de erori la executie s-au introdus din Java 1.hashCode() & 0x7FFFFFFF) % n. Subliniem cã variabilele unei colectii au tipul Object. // eroare la compilare // exceptie la executie In al doilea caz tipul String este subtip al tipului Object si deci compilatorul Java permite conversia (nu stie tipul real al obiectului referit de variabila “obj”). } // conversie continut colectie în sir public String toString () { // redefineste metoda din Vector String s="". Incercarea de conversie între tipuri incompatibile poate fi semnalatã la compilare sau la executie. care parcurge secvential toate elementele vectorului. etc. } return s.toString() .). // adauga la vector din pozitia k return true. sin. // obiectul din vector poate avea orice tip Float f = (Float) obj.5 colectii generice care permit specificarea tipului obiectelor din colectie la definirea colectiei.k++) { Vector v=(Vector)elementAt(k). for (int k=0. Exemple: Float f = new Float(2.elementAt(i).

Derivarea dintr-o clasã abstractã se face la fel ca derivarea dintr-o clasã neabstractã folosind cuvântul extends. O clasã care contine cel putin o metodã abstractã trebuie declaratã ca abstractã. . } … // alte metode } 67 . // nr de elemente in colectie public MyCollection (int maxsize) { array = new Object[maxsize].Clase abstracte. dar poate specifica ce operatii (metode) ar fi necesare pentru toate subclasele sale. Short. O clasã abstractã nu este instantiabilã. In Java avem douã posibilitãti de a defini clase care contin doar metode statice si. tipul Number este o generalizare a tipurilor clasã numerice (Double. ci de a transmite anumite metode comune pentru toate subclasele derivate din clasa abstractã (metode implementate sau neimplementate). care contine atât metode abstracte cât si metode definite. Byte). ca urmare. O clasã abstractã este de multe ori o implementare partialã a unei interfete. csize=0. Exemplu: public class MyCollection extends AbstractCollection { private Object [ ] array. // verifica daca colectia este goala … // alte metode } public abstract class AbstractCollection implements Collection { public abstract int size(). de exemplu.} // metoda implementata … } Scopul unei clase abstracte nu este acela de a genera obiecte utilizabile. nu pot fi instantiate (nu pot genera obiecte): . cu constructor private. O superclasã defineste un tip mai general decât tipurile definite prin subclasele sale.Clase ne-abstracte. O clasã abstractã constituie o bazã de plecare pentru definirea altor clase. dar ar putea contine metode statice sau nestatice utilizabile în subclase. Interfete si clase abstracte Clase abstracte în Java Generalizarea si abstractizarea în programarea cu obiecte sunt realizate si prin clase abstracte. care nici nu au nevoie de un constructor Spre deosebire de interfete. Exemplu: public interface Collection { // declara metode prezente in orice colectie int size(). Metodele abstracte pot apare numai în interfete si în clasele declarate abstracte. // o colectie bazata pe un vector private int csize. Integer. Ea urmeazã a fi definitã într-o subclasã a clasei (interfetei) care o contine. Uneori superclasa este atât de generalã încât nu poate preciza nici variabile si nici implementãri de metode. O metodã abstractã este declaratã cu atributul abstract si nu are implementare. dar nu este obligatoriu ca o clasã abstractã sã continã si metode abstracte. In astfel de cazuri superclasa Java este fie o clasã abstractã.Florian Moraru – Programare Orientata pe Obiecte 6. // metoda abstracta public boolean isEmpty ( return size()==0. clasele abstracte pot contine variabile si metode neabstracte. Float. // nr de obiecte in colectie } public int size() { return csize. // dimensiune colectie boolean isEmpty(). fie o interfatã. care împiedicã instantierea.

O clasã (abstractã sau instantiabilã) poate implementa una sau mai multe interfete. pentru cã nu se stie care din ele sunt efectiv necesare în subclase. } } 68 . Pentru a obtine clase instantiabile dintr-o clasã abstractã trebuie implementate toate metodele abstracte mostenite. existã deosebiri importante între interfete si clase abstracte. Totusi. Metodele declarate într-o interfatã sunt implicit publice si abstracte. public boolean hasMoreElements() { return hasMoreTokens(). respectând declaratiiile acestora ( ca tip si ca argumente). In bibliotecile de clase Java existã câteva clase adaptor. unele redefinite în subclase. Ele sunt clase abstracte pentru a nu fi instantiate direct. } // daca mai sunt elemente in colectie // elementul curent din colectie Enumerarea unor obiecte poate fi privitã ca o alternativã la crearea unui vector cu obiectele respective. interfata mai veche Enumeration contine douã metode comune oricãrei clase cu rol de enumerare a elementelor unei colectii : public interface Enumeration { boolean hasMoreElements(). Dintre clasele care implementeazã aceastã interfatã. // folosire sau afisare caracter ch } } Interfete Java O interfatã Java ar putea fi consideratã ca o clasã abstractã care nu contine decât metode abstracte si. constante simbolice. care implementeazã o interfatã prin metode cu definitie nulã. dar metodele lor nu sunt abstracte. deci poate contine si metode neabstracte si/sau variabile în afara metodelor abstracte. fãrã sã se preocupe de celalte metode nefolosite de subclasã: class KListener extends KeyAdapter { public void keyPressed(KeyEvent e) { char ch = e. Object nextElement(). sunt clasele enumerator pe vector si enumerator pe un tabel de dispersie Hashtable.getKeyChar(). care face o enumerare a cuvintelor dintr-un text: public class StringTokenizer implements Enumeration { .. prin definirea metodelor declarate în interfetele respective.Florian Moraru – Programare Orientata pe Obiecte O clasã abstractã poate face o implementare partialã. Exemplu de clasã adaptor utilã în definirea de clase ascultãtor la evenimente generate de tastaturã: public abstract class KeyAdapter implements KeyListener { public void keyTyped(KeyEvent e) { } // la apasare+ridicare tasta public void keyPressed(KeyEvent e) { } // numai la apasare tasta public void keyReleased(KeyEvent e) { } // numai la ridicare tasta } O subclasã (care reactioneazã la evenimente generate de taste) poate redefini numai una din aceste metode. eventual.. // caracter generat de tasta apasata . Ulterior s-au adãugat metode din aceastã interfatã si clasei StringTokenizer.. De exemplu. în vederea prelucrãrii succesive a unui grup de obiecte. } public Object nextElement() { return nextToken()..

} // utilizare public static void main(String arg[]) { String a[]={“2”.hasMoreElements() ) if (enum.”1”)). return m. buf.toString().”2”.println (count (v. // indice element curent din enumerare public VectorEnumerator (Vector v) { 69 .asList(a)). } } In exemplul anterior. deci cu referinte la obiecte care implementeazã interfata sau care extind clasa abstractã. i++) { String s = e. Exemplu: // variantã pentru metoda Vector. se pot defini variabile.elements(). // utilizarea variabilei interfata buf. de tipul Enumeration. i <= size() . private int crt = 0.append(". Exemplu de metodã care calculeazã de câte ori apare un obiect într-o colectie de obiecte care are un enumerator: public static int count (Enumeration enum. Exemplu de posibilã implementare a metodei “elements” din clasa “Vector” : public Enumeration elements() { return new VectorEnumerator (this). Enumeration e = elements().Florian Moraru – Programare Orientata pe Obiecte Utilizarea unei interfete comune mai multor clase permite unificarea metodelor si modului de utilizare a unor clase cu acelasi rol.toString public final String toString() { StringBuffer buf = new StringBuffer(). Prin definirea unei interfete sau clase abstracte se creeazã un tip de date comun mai multor clase deoarece o variabilã (sau un argument) de un tip interfatã poate fi înlocuitã fãrã conversie explicitã cu o variabilã de orice subtip.”4”. Vector v = new Vector (Arrays. for (int i = 0 .").”1”. // variabila de tip interfata ! buf.nextElement(). dar si scrierea unor metode general aplicabile oricãrei clase care respectã interfata comunã. Object obj) { int m=0. metoda “elements” din clasa Vector are ca rezultat un obiect “enumerator pe vector”.equals(obj) ) m++. Astfel de variabile vor fi înlocuite (prin atribuire sau prin argumente efective) cu variabile de un tip clasã care implementeazã interfata respectivã. } // definirea clasei iterator pe vector class VectorEnumerator implements Enumeration { private Vector v. } Nu pot fi create obiecte de un tip interfatã sau clasã abstractã. while (enum. argumente formale si functii de un tip interfatã sau clasã abstractã.nextElement(). } buf.append("\b]").out. dar se pot declara variabile.”1”.append(s).toString().”3”.”1”. return buf. functii sau argumente de functii de un tip interfatã. Ca si în cazul claselor abstracte.”1”} .append("["). System.

a.v=v. Clonable.elementAt (crt++). O interfatã este consideratã ca un contract pe care toate clasele care implementeazã acea interfatã se obligã sã îl respecte. dar este aplicabilã numai pentru o parte din clase. care toate implementeazã interfata Comparable: String. în faza de executie. Serializable (obiectele lor pot fi salvate sau serializate în fisiere disc sau pe alt mediu extern). int index). mai multe clase predefinite SDK. De exemplu. putând contine si alte metode.} public class Date implements Serializable. implementeazã simultan interfetele Comparable (obiectele lor pot fi comparate si la mai mic – mai mare). folositã numai de clasele care declarã cã implementeazã interfata Clonable. cum este cazul interfetei Comparator..size()) return v. Clonable (obiectele lor pot fi copiate). definitã astfel: public interface Clonable { } Clasa Object contine metoda "clone". Un exemplu tipic este interfata Clonable. s.Florian Moraru – Programare Orientata pe Obiecte this. Date. Un exemplu sunt clasele cu obiecte comparabile. 70 . O interfatã fãrã nici o metodã poate fi folositã pentru a permite verificarea utilizãrii unor metode numai în anumite clase. iar interfata MutableTreeNode o extinde pe prima cu metode de modificare nod de arbore (dupã crearea unui arbore poate fi interzisã sau nu modificarea sa): public interface MutableTreeNode extends TreeNode { void insert(MutableTreeNode child. Clasele care implementeazã o aceeasi interfatã pot fi foarte diverse si nu formeazã o ierarhie (o familie).. Comparable { . Altfel spus. O clasã poate simultan sã extindã o clasã (alta decât clasa Object.. implicit extinsã) si sã implementeze una sau mai multe interfete. // adauga un fiu acestui nod void remove(int index). BigDecimal. interfata TreeNode reuneste metode de acces la un nod de arbore. o clasã poate mosteni date si/sau metode de la o singurã clasã. dar poate mosteni mai multe tipuri (poate respecta simultan mai multe interfete).size(). } public Object nextElement() { if (crt < v.. } } O interfatã poate extinde o altã interfatã (extends) cu noi metode abstracte. Metoda neabstractã "clone" este mostenitã automat de toate clasele Java. Serializable { .. Exemple: public class String implements Comparable.} Este posibil ca în momentul definirii unei interfete sã nu existe nici o singurã clasã compatibilã cu interfata. Integer. // elimina fiu cu indice dat al acestui nod . } Diferente între interfete si clase abstracte O clasã poate implementa (implements) o interfatã sau mai multe si poate extinde (extends) o singurã clasã (abstractã sau nu). deci o clasã îsi asumã obligatia de a defini toate metodele din interfetele mentionate în antetul ei. cu date.. Ca exemplu. } public boolean hasMoreElements() { return crt < v.

iar clasa abstractã este o implementare partialã a interfetei. Interfata este extinsã de alte interfete. toate clasele cu date sunt serializabile. deoarece depind de tipul evenimentului. Pot fi comparate cu metoda “compareTo” numai clasele care implementeazã interfata Comparable si deci care definesc metoda abstractã “compareTo”: 71 . Aceste metode au fost reunite înainte de Java 1. dar dacã s-ar fi decis ca metoda "compareTo" sã facã parte din clasa Object. dar poate fi o functie polimorficã.a. O interfatã care stabileste un tip comun poate fi atât de generalã încât sã nu continã nici o metodã. dar metodele de tratare a evenimentului nu pot fi precizate nici ca prototip. compatibile cu interfata dar care beneficiazã de metode mostenite de la clasa abstractã. } public interface ItemListener extends EventListener { public void itemStateChanged(ItemEvent e). tip si argumente dar cu definitii diferite. iar uneori sunt folosite împreunã într-un “framework” cum este cel al claselor colectie sau cel al claselor Swing (JFC): interfata defineste metodele ce ar trebui oferite de mai multe clase. pentru a permite anumite verificãri. pentru a distinge clasele ale cãror obiecte sunt serializabile (care contin metode de salvare si de restaurare în / din fisiere) de clasele ale cãror obiecte nu pot fi serializate ( fãrã obiecte "persistente"). } Interfetele sunt preferabile în general claselor abstracte. care nu trebuie definite însã de la zero. Un exemplu este interfata EventListener (pachetul "java. dar nu ar putea extinde si clasa abstractã Dictionary . In Java. Interfetele si clasele abstracte nu trebuie opuse. obtine valoarea asociatã unei chei date s. In felul acesta se obtine o familie de clase deschisã pentru extinderi ulterioare cu clase compatibile. Compararea de obiecte în Java In unele operatii cu vectori de obiecte (sortare. determinare maxim sau minim s. Incepând cu Java 2 aceleasi metode (plus încã câteva) au fost grupate în interfata Map. se produce o exceptie de tip CloneNotSupportedException atunci când ea este apelatã pentru obiecte din clase care nu aderã la interfata Clonable. douã obiecte pot fi comparate dupã mai multe criterii (criteriile sunt variabile sau combinatii de variabile din aceste obiecte). pentru a facilita definirea de noi clase prin extinderea clasei abstracte. Un exemplu simplu este cel al metodelor considerate esentiale pentru orice clasã dictionar: pune pereche cheie-valoare in dictionar. Avantajul solutiei interfatã în raport cu o clasã abstractã este evident atunci când definim un dictionar realizat ca un vector de perechi cheie-valoare: o clasã derivatã din clasa Vector ar putea mosteni metode utile de la superclasã. cu acelasi nume.2 într-o clasã abstractã (Dictionary) care continea numai metode abstracte. care stabileste tipul “ascultãtor la evenimente”. Interfete ca Serializable si Clonable se numesc interfete de "marcare" a unui grup de clase ("tagging interfaces"). atunci ar fi fost necesarã o interfatã de "marcare" pentru a distinge clasele cu obiecte comparabile de clasele cu obiecte necomparabile. Un exemplu este interfata Set (sau Collection) si clasa AbstractSet (AbstractCollection) care permit definirea rapidã de noi clase pentru multimi (altele decât HashSet si TreeSet).Florian Moraru – Programare Orientata pe Obiecte Pentru a semnala utilizarea gresitã a metodei "clone" pentru obiecte ne-clonabile. In plus. specifice anumitor ascultãtori (pentru anumite evenimente): public interface ActionListener extends EventListener { public void actionPerformed(ActionEvent e). functia “compareTo” este destinatã comparatiei dupa criteriul cel mai “natural” (cel mai frecvent iar uneori si unicul criteriu). Practic.) este necesarã compararea de obiecte (de acelasi tip). Clasa Object nu contine o metodã de comparare pentru stabilirea unei relatii de precedentã între obiecte. pentru cã oferã mai multã libertate subclaselor.util"). O utilizare asemãnãtoare o are interfata Serializable. Functia de comparare depinde de tipul obiectelor comparate.a.

sort” cu un argument foloseste implicit metoda “compareTo”.cost.).((Byte) obj). String. for (int i=0. } public int compareTo (Object obj) { Arc a = (Arc) obj. } Functia “Arrays. .length.v && w==a.compareTo(a[i]) < 0) // daca maxim < a[i] maxim=(Comparable)a[i].a.int y) { v=x. . // arce egale dacã au aceleasi extremitati } public String toString () {return v+"-"+w.Florian Moraru – Programare Orientata pe Obiecte public interface Comparable { int compareTo (Object obj). cu argument de tip Object.binarySearch”). 72 . // cost arc public Arc (int x. BigDecimal s. Putem considera cã interfata Comparable adaugã metoda “compareTo” clasei Object si astfel creeazã o subclasã de obiecte comparabile.value. . // noduri capete arc float cost. Exemple de clase Java cu obiecte comparabile : Integer. Este posibil ca egalitatea de obiecte definitã de metoda “compareTo” sã fie diferitã de egalitatea definitã de metoda “equals”.w. } // rezultat <0 sau = 0 sau >0 Clasele care declarã implementarea acestei interfete trebuie sã continã o definitie pentru metoda "compareTo". // arce egale dacã au costuri egale ! } } Metoda “equals” este folositã la cãutarea într-o colectie neordonatã (“contains”.w).value . Exemplu: // determinare maxim dintr-un vector de obiecte oarecare public static Object max (Object [ ] a) { Comparable maxim = (Comparable) a[0]. w=y. iar metoda “compareTo” este folositã la cãutarea într-o colectie ordonatã (“Collections. } } Metoda ”compareTo” se poate aplica numai unor obiecte de tip Comparable si de aceea se face o conversie de la tipul general Object la subtipul Comparable. } public boolean equals (Object obj) { Arc a= (Arc)obj.a. return cost – a. Float. s. Date.i++) if ( maxim. metode specifice public int compareTo( Object obj) { // compara doua obiecte Byte return this. “add” pentru multimi. return (v==a. clasa “Arc” produce obiecte de tip “arc de graf cu costuri”: class Arc implements Comparable { int v. ceea ce poate conduce la rezultate diferite pentru cele douã metode de cãutare într-o listã de obiecte “Arc”.i<a. De exemplu. return maxim. Exemplu: public class Byte extends Number implements Comparable { private byte value. // constructori.

// linia o1."3". k<a. {"1".i<3. } // implicit abstracta.length() . return maxim. for (int i=0. . coloana c return c1. // afisare matrice a } 73 .length."6"}. .new CompCol(i)). De exemplu. } // constructor cu un argument public int compare(Object o1."2"}. Functia “Arrays. Object t2) { return ((String)t1). ne-statica ! Interfata Comparator putea fi si o clasã abstractã cu o singurã metodã. Comparator c) { Object maxim = a[0].”trei”.”unu”}. vom defini urmãtoarea clasã comparator: class LengthComparator implements Comparator { public int compare (Object t1.k++) if ( c. coloana c Comparable c2 =(Comparable) ((Object[ ])o2)[c]."1".sort( a. // compara valorile din coloana c (liniile o1 si o2) } } Exemplu de utilizare a acestui comparator în functia de sortare : Object a[ ][ ] = { {"3".sort” are si o formã cu douã argumente. // linia o2."5". for (int k=1. // ordonare dupa coloana i printMat (a). {"7". Pentru ordonarea unei matrice de obiecte dupã valorile dintr-o coloanã datã putem defini o clasã comparator cu un constructor care primeste numãrul coloanei: class CompCol implements Comparator { int c. new LengthComparator()). } } . Functia care determinã maximul dintr-un vector de obiecte poate fi scrisã si astfel: public static Object max (Object a[ ]. // numar coloana ce determina ordinea public CompCol (int col) { c=col. // ordonare dupa lungime String [ ] a = {”patru”.i++) { // pentru fiecare coloana i Arrays. pentru cã este improbabil ca o clasã comparator sã mai mosteneascã si de la o altã clasã. } Functia “max” cu douã argumente este mai generalã si poate fi folositã pentru a ordona un acelasi vector dupã diferite criterii (dupã diverse proprietãti). a[k]) < 0) // c este un obiect comparator maxim = a[k].Florian Moraru – Programare Orientata pe Obiecte Pentru comparare de obiecte dupã alte criterii decât cel “natural” s-a introdus o altã functie cu numele “compare”."4"} }. pentru ordonarea unui vector de siruri dupã lungimea sirurilor. Object t2). Arrays.sort(a.compare (maxim.compareTo(c2).length().((String)t2). al doilea argument este de tipul Comparator si precizeazã functia de comparare (alta decât “compareTo”). Object o2) { Comparable c1 =(Comparable) ((Object[ ])o1)[c]. parte din interfata Comparator: public interface Comparator { int compare (Object t1.

Utilizatorul are sarcina de a defini o clasã care implementeazã una din aceste interfete si de a transmite o referintã la un obiect al acestei clase fie metodei “list” fie metodei “listFiles”. Metodele “list” si “listFiles” au si o variantã cu un argument de un tip interfatã ce specificã filtrul aplicat fisierelor.out.println (files[i]).File(dir).i++) System. Clasa File din pachetul “java.metoda “list” cu argument de tip FilenameFilter .*. dar pot fi folosite si în alte situatii. Un filtru poate contine o singurã metodã cu rezultat boolean. Interfetele FileFilter si FilenameFilter contin o singurã metodã.io.getAbsolutePath()).0 boolean accept (File path. Ea este destinatã operatiilor de listare sau prelucrare a fisierelor dintr-un director si nu contine functii de citire-scriere din/în fisiere. class Dir { public static void main (String arg[]) throws IOException { String dir =".io.println ("Directory of "+ d.Florian Moraru – Programare Orientata pe Obiecte Interfete pentru filtre Un filtru este un obiect care permite selectarea (sau respingerea) anumitor obiecte dintr-o colectie sau dintr-un vector. cu numele fisierelor din directorul specificat la construirea obiectului File. // path=director(cale) } public interface FileFilter { // prezenta din jdk 1.File d = new java. for (int i=0. // java.".list().out. care sã spunã dacã obiectul primit ca argument este acceptat sau respins de filtru. String filename). // vector cu fisierele din directorul curent System. numitã “accept”: public interface FilenameFilter { // prezenta din jdk 1. Exemplu de folosire a unui obiect File pentru afisarea continutului directorului curent: import java. Cea mai folositã metodã a clasei File este metoda “list” care produce un vector de obiecte String.io” poate genera obiecte ce corespund unor fisiere sau directoare.metoda “listFiles” cu argument de tip FileFilter sau FilenameFilter. Exemplu de listare selectivã cu mascã a directorului curent: // clasa pentru obiecte filtru class FileTypeFilter implements FileFilter { String ext. pentru extragere selectivã de fisiere din director: .length.2 boolean accept (File path).ext = ext. // "numele" directorului curent File d =new File(dir). String [ ] files = d. i< files. // extensie nume fisier public FileTypeFilter (String ext) { this. // path=nume complet fisier (cu cale) } Metoda “accept” va fi apelatã de “list” sau “listFiles” pentru fiecare fisier din director si spune dacã acel fisier este sau nu este acceptat în vectorul creat de functie.io. In bibliotecile Java filtrele se folosesc în clasa File pentru listare selectivã de fisiere dintr-un director. } } Metoda “listFiles” produce un vector de obiecte File ce corespund fisierelor din directorul pentru care se apeleazã metoda. } public boolean accept (File f) { 74 . Cel mai folosit constructor din clasa File primeste un argument de tip String ce reprezintã numele unui fisier de date sau unui fisier director.

} } Acelasi filtru (dupã extensie fisier) în varianta cu interfata FilenameFilter: class DirFilter implements FilenameFilter { String ftype.out.nextElement(). deoarece este apelatã de metode existente (Collections. if ( c. String name) { return name. int pp = fname.indexOf('.getName().a) dar trebuie furnizatã de aplicatia care foloseste una din aceste metode.max.i++) System. i< files. La scrierea functiei de sortare (Collections. } } // Listare fisiere de un anumit tip class Dir { public static void main (String arg[ ]) throws IOException { File d =new File(“.println (files[i]). // primul element din enumerare while (enum. iar “sort” foloseste adresa continutã în obiectul comparator pentru a se referi înapoi la functia "compare". Metoda “sort” primeste adresa functiei “compare” (printr-un obiect dintr-o clasã care implementeazã interfata Comparator) ceea ce-i permite sã se refere "înapoi" la functia de comparare.'). tip si numãr de argumente). care va fi apelatã înapoi de B.equals(fname. Comparator c) { Object maxim = enum. } 75 . public DirFilter (String ft) { ftype=ft. Adresa functiei X este primitã de B de la functia A.substring(pp+1)) .listFiles(new FileTypeFilter("java")).length.Florian Moraru – Programare Orientata pe Obiecte String fname = f.sort) nu se cunostea exact definitia functiei de comparare (pentru cã pot fi folosite diverse functii de comparare). de ex. De fiecare datã când A apeleazã pe B îi transmite si adresa functiei X.) apeleazã o functie B ("sort". dar s-a putut preciza prototipul functiei de comparare.nextElement(). dar care respectã toate un prototip (tip rezultat. // din directorul curent File [ ] files = d. Situatia mai poate fi schematizatã si astfel: o functie A ("main". iar B apeleazã o functie X ("compare") dintr-un grup de functii posibile. for (int i=0. ca metodã abstractã inclusã într-o interfatã. } } // name= nume fisier // daca numele contine tipul ca subsir Functii “callback” Functia "compare" poartã numele de functie "callback".compare (maxim. s. Collections. return ext. adresa obiectului comparator este transmisã “sort”. In limbajul C se transmite un pointer explicit la o functie. se transmite un pointer printr-un obiect ce contine o singurã functie. Putem deci sã scriem o functie care apeleazã functii încã nedefinite.). de ex.hasMoreElements()) { Object next = enum. Exemplu: // determinare maxim dintr-o enumerare de obiecte necunoscute public static Object max (Enumeration enum.sort. if (pp==0) return false.”). Practic.} public boolean accept (File dir.indexOf(ftype) != -1. next) < 0) // apel functie de comparare maxim=next.

. Ca suport concret al unui flux de date Java se considerã urmãtoarele variante: fisier disc. apelatã de metodele “list” si “listFiles” din clasa de bibliotecã File. Clasele Reader si Writer sunt mai noi si sunt preferabile claselor mai vechi InputStream si OutputStream pentru cã se lucreazã cu caractere în loc de octeti (byte). transmise tuturor subclaselor. Pentru fiecare tip de flux existã implementãri diferite ale metodelor abstracte “read” si “write” pentru citire/scriere de octeti. pentru un flux vector de octeti metoda "read" preia urmãtorul octet din vector. public int read ( byte b[ ]) throws IOException { return read (b. } Tehnica “callback” este folositã la scrierea unor clase de bibliotecã. Operatiile elementare de citire si de scriere octet sunt aplicabile oricãrui tip de flux si de aceea au fost definite douã clase abstracte: o sursã de date InputStream (Reader) si o destinatie de date OutputStream (Writer).. canal de comunicare între fire de executie (în memorie). pentru cã ea comparã date din aplicatie. sir de caractere (în memorie).b. care recunoaste marcaje dintr-un fisier XML si apeleazã metode ce vor fi scrise de utilizatori pentru interpretarea acestor marcaje (dar care nu pot fi definite la scrierea analizorului lexical). ale cãror functii apeleazã functii din programele de aplicatii. care apeleazã metodele de citire/scriere octet. vãzutã ca sir de octeti. cu suport neprecizat. Clasa Reader diferã de InputStream prin utilizarea tipului char în loc de byte: public abstract class Reader { 76 . scrise ulterior de utilizatorii pachetului. dar definitã ca parte din aplicatie. Exemplu: public abstract class InputStream { public abstract int read ( ) throws IOException. Functia “accept” din interfetele filtru este tot o functie “callback”. care contin metodele abstracte “read” si “write”. ceea ce se reflectã si în numele lor: FileInputStream. } public int available( ) throws IOException { return 0. In acest caz parserul SAX stie sã recunoascã atomi XML dar nu stie ce sã facã cu acesti atomi.0. precum si alte metode care nu sunt definite dar nici nu sunt abstracte. Clase abstracte si interfete pentru operatii de I/E Un flux de date Java ("input/output stream") este orice sursã de date (la intrare) sau orice destinatie (la iesire). Un alt exemplu este un analizor lexical SAX. vector de octeti (în memorie). dar functia de comparare face parte din aplicatie. // alte metode } // citeste un octet (-1 la sfarsit de fisier) // citeste un bloc de octeti // nr de octeti rãmasi de citit Clasele concrete de tip flux de citire sunt toate derivate din clasa InputStream. în schimb aplicatia stie cum sã prelucreze atomii XML dar este scutitã de analiza documentelor XML (realizatã de parser). etc. } . Un algoritm de sortare foloseste o functie de comparare.length). Analizorul SAX impune aplicatiei sã foloseascã functii care respectã interfetele definite de analizor si apeleazã aceste functii “callback”.Florian Moraru – Programare Orientata pe Obiecte return maxim. Functiile “callback” sunt definite de cei care dezvoltã aplicatii dar sunt apelate de cãtre functii din clase predefinite (de bibliotecã). Clasele abstracte InputStream si OutputStream contin si metode neabstracte pentru citire si scriere blocuri de octeti. Metoda "read()" este redefinitã în fiecare din aceste clase si definitia ei depinde de tipul fluxului: pentru un flux fisier "read" este o metodã "nativã" (care nu este scrisã în Java si depinde de sistemul de operare gazdã). Ideea generalã este cã o parte din algoritmul implementat de clasa de bibliotecã depinde de specificul aplicatiei care foloseste acest algoritm. ByteInputStream.

. int readInt () throws IOException. In acest tip de fisiere (binarer) numerele se reprezintã în formatul lor intern (binar virgulã fixã sau virgulã mobilã) si nu se face nici o conversie la citire sau la scriere din/în fisier... boolean write) throws IOException. } public final short readShort() throws IOException { 77 . 0. .in=in. Operatiile cu fisiere depind de sistemul de operare gazdã. ). abstract public void close() throws IOException. 1) == -1) return -1. long readLong () throws IOException. 0. int off. Exemplu de variantã modificatã a clasei DataInputStream : public class DataInputStream extends InputStream implements DataInput { InputStream in. String readLine () throws IOException. . . cbuf. if (ch < 0) throw new EOFException(). dar numãrul lor este mentinut la minim. } // constructor public final byte readByte() throws IOException { int ch = in. Aceste clase simplificã crearea si exploatarea de fisiere text si de fisiere binare. . } Clasa RandomAccessFile implementeazã ambele interfete si deci asigurã un fisier în care se poate scrie sau din care se poate citi orice tip primitiv.. // alte metode } Clasele DataInputStream si DataOutputStream implementeazã interfetele DataInput si respectiv DataOutput.a. } public int read(char cbuf[ ]) throws IOException { // citire bloc de octeti return read(cbuf. } // ptr a interzice instantierea clasei public int read() throws IOException { // citire caracter urmator (-1 la sfarsit de fisier) char cb[ ] = new char[1]. Un fragment din clasa RandomAccessFile aratã astfel: public class RandomAccessFile implements DataOutput. return (byte)(ch).length). public native void write (int b) throws IOException.Florian Moraru – Programare Orientata pe Obiecte protected Reader() { . else return cb[0]. int len) throws IOException. DataInput { private native void open (String name.. if (read(cb. // “in” poate fi orice fel de flux (ca suport fizic) public DataInputStream (InputStream in) { this.. public native long length () throws IOException. ce contin date de un tip primitiv ( numere de diferite tipuri s. public native int read () throws IOException..read(). float readFloat () throws IOException. public native void close () throws IOException. // alte metode } Interfata DataInput reuneste metode pentru citire de date de tipuri primitive: public interface DataInput { char readChar () throws IOException. } abstract public int read(char cbuf[ ]. deci o parte din metodele claselor de I/E sunt metode "native".

readLine()) != null) { // dacã nu s-a introdus ^Z (EOF) System. int ch1 = in. Pentru a citi linii de la consolã (terminate cu Enter)..in” este de un subtip (anonim) al tipului InputStream si corespunde tastaturii ca flux de date. while ( (line=stdin.in). // asamblare octeti pe 16 biti } public String readLine ( ) { . return (short)((ch1 << 8) + (ch2 << 0)).read(). Clasa DataInputStream nu are constructor fãrã argumente si nici variabile care sã specifice suportul datelor.in)).readLine().out. } // citeste o linie public int readInt ( ) { . Pentru obiectul adresat de variabila “System. String line= f.. } } 78 .. System.read(). // alte metode } Obiecte de tip DataInputStream nu pot fi create decât pe baza altor obiecte.. String line. int ch2 = in. care suportã metoda “readLine”. .println (“ S-a citit: "+ line).in” se poate folosi metoda “read” pentru citirea unui octet (caracter) dar nu se poate folosi o metodã de citire a unei linii. care precizeazã suportul fizic al datelor. putem crea un obiect de tip DataInputStream . } // citeste un numar real . Variabila publicã “System.println (line). Exemplu: // citire linie de la tastatura public static void main (String arg[]) throws Exception { DataInputStream f = new DataInputStream(System. } // citeste un numar intreg public float readFloat ( ) { ... } Varianta pentru citire de linii de la tastaturã : public static void main (String arg[ ]) throws IOException { BufferedReader stdin = new BufferedReader(new InputStreamReader (System. // citeste doi octeti if ((ch1 | ch2) < 0) throw new EOFException(). .out.in.Florian Moraru – Programare Orientata pe Obiecte InputStream in = this.

Colectiile Java sunt structuri de date generice. Colectiile se folosesc pentru memorarea si regãsirea unor date sau pentru transmiterea unui grup de date de la o metodã la alta. realizate fie cu elemente de tip Object. variabil de obiecte. // copiere colectie intr-un vector } Din interfata Collection sunt derivate direct douã interfete pentru tipurile abstracte: . din colectia c boolean addAll(Collection c). Un dictionar (o asociere ) este o colectie de perechi cheivaloare (ambele de tip Object). care respectã aceste interfete impuse si sunt compatibile cu cele existente (pot fi înlocuite unele prin altele).Set pentru multimi de elemente distincte. Urmeazã descrierea completã a interfetei Collection : public interface Collection { // operatii generale ptr orice colectie int size(). listã dublu înlãntuitã. Structurile de date concrete folosite pentru implementarea tipurilor de date abstracte sunt : vector extensibil. în sensul Java. Nu toate aceste operatii trebuie implementate obligatoriu de clasele care implementeazã interfata Collection. // adauga un element la colectie boolean remove(Object element). toate conforme cu anumite interfete ce reprezintã tipuri abstracte de date (liste. Un utilizator îsi poate defini propriile clase colectie. // verifica daca colectie vida boolean contains(Object element). Pentru fiecare din cele 3 structuri de date abstracte (listã. // retine in colectie numai elem. care depind de structura fizicã a colectiei). Colectii de obiecte Object Infrastructura claselor colectie O colectie este un obiect ce contine un numãr oarecare.Ierarhia care are la bazã interfata Collection. o clasã care nu defineste o metodã optionalã poate semnala o exceptie la încercarea de apelare a unei metode neimplementate. // sterge continut colectie Object[ ] toArray(). fiecare pereche trebuie sã aibã o cheie unicã. // daca colectia contine un obiect dat boolean add(Object element). numite si elemente ale colectiei. fie cu clase sablon (cu tipuri parametrizate) . deci nu pot fi douã perechi identice.Florian Moraru – Programare Orientata pe Obiecte 7. //elimina din colectie elem. din c la colectie boolean removeAll(Collection c). în care fiecare element are un succesor si un predecesor si este localizabil prin pozitia sa în listã (un indice întreg). // adauga elem. Clasele abstracte existente fac uz de iteratori si aratã cã aproape toate metodele unei clase colectie pot fi scrise numai folosind obiecte iterator (cu exceptia metodelor de adãugare obiect la colectie si de calcul numãr de elemente din colectie. colectiei c boolean retainAll(Collection c). Interfata Collection contine metode aplicabile pentru orice colectie de obiecte. din c void clear().dictionar) sunt prevãzute câte douã clase concrete care implementeazã interfetele respective. . multime. O colectie. // daca colectia contine elem. Infrastructura colectiilor (Collection Framework) oferã clase direct utilizabile si suport pentru definirea de noi clase (sub formã de clase abstracte). Familia claselor colectie din Java este compusã din douã ierarhii de clase : . // elimina un element din colectie Iterator iterator(). 79 . tabel de dispersie si arbore binar.List pentru secvente de elemente. multimi. // dimensiune colectie (nr de elemente) boolean isEmpty(). este un tip de date abstract care reuneste un grup de obiecte de tip Object. // produce un iterator pentru colectie boolean containsAll(Collection c).Ierarhia care are la bazã interfata Map. dictionare). .

// aux identic cu sv O a treia ierarhie are la bazã interfata Iterator. Comparator cmp). } public void addArc (int v.”trei”}. Clasa Collections contine metode statice pentru mai multi algoritmi "generici" aplicabili oricãrei colectii ("min".w)). iar arborele echilibrat permite pãstrarea unei relatii de ordine între elemente si un timp de cãutare bun. public static void sort (List lst). De asemenea sunt prevãzute metode de trecere de la vectori intrinseci de obiecte (Object [] ) la colectii de obiecte si invers: functia “Arrays. // nu este nici ArrayList. Exemple în forma mai veche dar mai simplã: public static Object min (Collection col ).asList(sv).asList”. Exemplul urmãtor foloseste o multime de tipul HashSet pentru un graf reprezentat prin multimea arcelor (muchiilor) : class Graph { // graf reprezentat prin multimea arcelor private int n. realizatã ca arbore binar echilibrat automat (“Red-Black Tree”). “reverse”. Interfata Set contine exact aceleasi metode ca si interfata Collection.toArray(). Metoda de adãugare a unui obiect la o multime "add" verificã dacã nu existã deja un element identic.w) 80 . pentru ca obiecte diferite sã nu aparã ca fiind egale la compararea cu “equals”. List list = Arrays.toString()). de tip Object[].. Tabelul de dispersie asigurã cel mai bun timp de cãutare. pentru metodele specifice oricãrui iterator asociat unei colectii sau unui dictionar. "max") sau numai listelor (“sort”. “shuffle”). Comparator cmp). compareTo s. Toate colectiile au iteratori dar nu si clasele dictionar (se poate însã itera pe multimea cheilor sau pe colectia de valori). care produce un sir cu toate elementele colectiei. cu un argument vector intrinsec de obiecte si rezultat de tip List si functia “toArray” din clasa AbstractCollection.n =n. Exemplu: String sv[] = {“unu”.int w) { // adauga arcul (v. “binarySearch”. } public boolean isArc (int v. Multimi de obiecte O multime este o colectie de elemente distincte pentru care operatia de cãutare a unui obiect în multime este frecventã si trebuie sã aibã un timp cât mai scurt.int w) { // daca exista arcul (v. pentru a nu se introduce duplicate în multime. // numar de noduri in graf private Set arc. Ei foloseau initial tipul generic Object si metodele polimorfice equals. De aceea obiectele introduse în multime trebuie sã aibã metoda “equals” redefinitã.”doi”. // System. // ordonare lista lst public static void sort (List lst. String aux[] = (String[ ]) list.out. nici LinkedList ! System. Afisarea continutului unei colectii se poate face printr-o singurã instructiune.w) la graf arc.a. Clasele multime predefinite si care implementeazã interfata Set sunt: HashSet : pentru o multime neordonatã realizatã ca tabel de dispersie TreeSet : pentru o multime ordonatã.Florian Moraru – Programare Orientata pe Obiecte Toate clasele colectie instantiabile redefinesc metoda “toString”. arc = new HashSet(). separate prin virgule si încadrate de paranteze drepte.add (new Arc (v. dar implementãrile acestei interfete asigurã unicitatea elementelor unei multimi. dar acum folosesc tipuri parametrizate (din versiunea 5). // obiect minim din colectia col public static Object min (Collection col .println (list).out.println (list. // lista de arce public Graph ( int n) { this.

Object to).hasNext() ) { // repeta cat mai sunt cuvinte in fisier word= sc.implementatã de clasa TreeSet. last()) 81 . pentru a permite afisarea cuvintelor în ordine lexicograficã. // reuniune s1+s2 // intersectie s1*s2 in aux // reuniune minus intersectie Exemplul urmãtor aratã cum putem folosi douã multimi pentru afisarea cuvintelor care apar de mai multe ori si celor care apar o singurã datã într-un text. dar are aceleasi performante la cãutare ca HashSet. // scoate urmatorul cuvant if ( ! toate.addAll(s2). } public String toString () { return arc. } } In general se recomandã programarea la nivel de interfatã si nu la nivel de clasã concretã.println ("unice: " + unice). Set aux = new HashSet(s1). // submultimea definita prin 2 valori SortedSet headSet(Object to). public static void main (String arg[ ]) throws IOException { Set toate = new HashSet (). // este o aparitie multipla } System. // afiseaza cuvinte cu o singura aparitie } Nici una din multimile HashSet sau TreeSet nu pãstreazã ordinea de adãugare a elementelor la multime. // subSet (first(). // toate cuvintele distincte din text Set dupl =new TreeSet (). se vor folosi pe cât posibil variabile de tip Collection sau Set si nu variabile de tip HashSet sau TreeSet (numai la construirea obiectului colectie se specificã implementarea sa).containsAll (s2) s1. Object last().retainAll (s2) s1.add (word).toString(). String word="". dar clasa LinkedHashSet contine o metodã “toString” care produce un sir cu elementele multimii în ordinea adãugãrii lor la multime. extinde interfata Set cu câteva metode aplicabile numai pentru colectii ordonate: Object first(). Interfata SortedSet. Asadar.removeAll (dupl).retainAll (s2).to) SortedSet tailSet (Object from) // subSet (from.out. // primul si ultimul element din multime SortedSet subSet(Object from.contains (new Arc(v. // elimina cuvinte multiple System.println ("multiple: "+ dupl). aux. unice. S-a folosit o multime ordonatã.removeAll(aux). // afisare cuvinte repetate Set unice = new TreeSet (toate). sdif.add (word) ) // daca a mai fost in “toate” dupl.Florian Moraru – Programare Orientata pe Obiecte return arc. simdif.out.w)). // cuvintele cu aparitii multiple Scanner sc = new Scanner (new File(arg[0])). // un cuvant din fisier while ( sc.next(). Operatiile cu douã colectii sunt utile mai ales pentru operatii cu multimi: s1.removeAll (s2) // true dacã s1 contine pe s1 (includere de multimi) // reuniunea multimilor s1 si s2 (s1=s1+s2) // intersectia multimilor s1 si s2 (s1=s1*s2) // diferenta de multimi (s1= s1-s2) Diferenta simetricã a douã multimi se poate obtine prin secventa urmãtoare: Set sdif = new HashSet(s1).addAll (s2) s1.

Noile metode au nume mai scurte si o altã ordine a argumentelor în metodele "add" si "set": Forma veche (1.1) Object elementAt (int) Object setElementAt (Objext. // a. In plus. remove (index) .set (i. în caz de obiect negãsit.get(j)). deci cãutarea primei si ultimei aparitii a unui obiect într-o listã: indexOf (object). Existã douã implementãri pentru interfata List: ArrayList listã vector. lastIndexOf (object) . int) Forma nouã (1. public static int binSearch (List list. int) void insertElementAt (Object.elementAt(i) a. // acces prin indice int cmp = ((Comparable)midVal). nu si pentru adãugare de noi elemente. rezultatul este pozitia unde ar trebui inserat obiectul cãutat astfel ca lista sã rãmânã ordonatã ( decalatã cu 1 si cu minus). Urmeazã o variantã a metodei “binarySearch”. else if (cmp > 0) high = mid .setElementAt (aux . else return mid. high = list. int i. if (cmp < 0) low = mid + 1.elementAt(j) .Object) Exemplu de functie care schimbã între ele valorile a douã elemente i si j: static void swap (List a.Florian Moraru – Programare Orientata pe Obiecte Liste secventiale Interfata List contine câteva metode suplimentare fatã de interfata Collection : . Metoda “set” se poate folosi numai pentru modificarea unor elemente existente în listã.Metode pentru determinarea pozitiei unde se aflã un element dat.Metode pentru acces pozitional. // a. // negasit } 82 .setElementAt (a.Metode pentru crearea de iteratori specifici listelor: listIterator (). care a fost modificat sau eliminat. int j) { // din clasa Collections Object aux = a.1. LinkedList listã dublu înlãntuitã. set (index.compareTo(key). while (low <= high) { int mid =(low + high)/2. j) } De observat cã metodele "set" si "remove" au ca rezultat vechiul obiect din listã.aux). preferatã când sunt multe inserãri sau stergeri de elemente în listã.Metodã de extragere sublistã dintr-o listã: List subList(int from. clasei Vector i-au fost adãugate noi metode pentru a o face compatibila cu interfata List. Object key) { int low = 0. listIterator (index) . de aceea metodele “sort” si “binarySearch” din clasa Collections au argument de tip List si nu Collection.get(mid). Object midVal = list. preferabilã pentru acces aleator frecvent la elementele listei. Algoritmii de ordonare si de cãutare binarã sunt exemple de algoritmi care necesitã accesul direct.2) Object get(int) Object set (i.a. Object) void add (i. i) a. prin indici. object).size()-1.object). // a. int to).set (j. pe baza unui indice întreg care reprezintã pozitia : get (index).get(i). la elementele listei. // gasit } return -(low + 1). add (index.

care garanteazã ordinea de enumerare. cu cel mai bun timp de cãutare. Indiferent de ordinea de adãugare la coadã.): getFirst(). // scoate si sterge din coada Clase dictionar Interfata Map contine metode specifice operatiilor cu un dictionar de perechi cheie valoare. TreeMap dictionar realizat ca arbore echilibrat. “poll” si “remove” cu eliminare din coadã).w. // se adauga key inainte de primul element mai mare ca el Accesul pozitional este un acces direct.Florian Moraru – Programare Orientata pe Obiecte Exemplu de utilizare a functiei de cãutare într-o secventã care adaugã un obiect la o listã ordonatã. astfel ca lista sã rãmânã ordonatã: int pos = Collections. în pachetul java. “poll” sau “remove”) este cel cu prioritatea minimã.cost)).remove()).util. offerLast. removeFirst. Din versiunea 5 au mai fost adãugate interfetele Queue si Deque cu câteva metode noi. alãturi de alte clase.binarySearch (alist.add (new Arc(v. getLast si variantele care nu produc exceptii: offer. Cele mai multe clase de tip coadã (ca BlockingQueue si SynchronousQueue) au fost introduse. rapid la vectori dar mai putin eficient în cazul listelor înlãntuite. utile pentru cazuri particulare de liste (stive.concurrent pentru programare cu fire de executie concurente. key). … pq. ArrayDeque Interfata Queue extinde interfata Collection cu o metodã de adãugare “offer” care nu produce exceptie dacã nu mai este loc în colectie si. în plus fatã de clasa abstractã AbstractList.add (-pos-1. urmãtoarele metode.…). Prioritatea este determinatã de cãtre metoda “compareTo” a obiectelor introduse în coadã (constructor fãrã argumente) sau de cãtre metoda “compare” a obiectului comparator transmis ca argument la construirea unei cozi. Interfata Deque extinde interfata Queue cu metode de acces si la primul si la ultimul element din listã: addFirst. clasa LinkedList implementeazã acum si interfata Deque si de aceea nu existã o clasã LinkedDeque. printre care AbstractQueue. în ipoteza cã metoda “compareTo” din clasa “Arc” comparã costuri: PriorityQueue<Arc> pq = new PriorityQueue<Arc>(). Existã trei implementãri pentru interfata Map: HashMap dictionar realizat ca tabel de dispersie.isEmpty()) System. Pentru aplicatiile cu structuri de date sunt interesante clasele ArrayDeque si PriorityQueue. De aceea s-a definit o clasã abstractã AbstractSequentialList. cu metode de acces la primul element din colectie (din coadã) cu si fãrã exceptie în caz cã nu existã (“element” si “peek” fãrã eliminare din coadã. getLast(). elementul din fatã (obtinut prin una din metodele “peek”. LinkedList. LinkedHashMap tabel de dispersie cu mentinere ordine de introducere (din vers. 1.key). getFirst. peek ( offerFirst. // adauga arc la coada pq … // afisare coada in ordinea prioritatilor (costului arcelor) while ( ! pq. Coada cu prioritãti este implementatã ca un vector “heap” extensibil (fãrã limite de capacitate).binarySearch. Clasa LinkedList contine. In exemplul urmãtor se adaugã si se scoate dintr-o coadã de arce ordonatã dupã costuri.out. care este extinsã de clasa LinkedList dar nu si de clasa ArrayList. recunoaste tipul de listã si face o cãutare binarã în vectori. cu parametru de tipul general List. cozi etc. Metoda staticã Collections. PriorityQueue. if (pos < 0) // daca key nu exista in lista alist. poll. addFirst(Object). interfete implementate de mai multe clase. mai important. removeLast. dar o cãutare secventialã în liste secventiale. în care cheile sunt unice .4) Definitia simplificatã a interfetei Map este urmãtoarea: 83 . removeLast().println( pq. addLast. addLast(Object). removeFirst().

// eliminã pereche cu cheie datã boolean containsKey(Object key).get(word).println (dic).ONE). if (nrap == null) dic. // multimea cheilor Collection values(). Integer nrap = (Integer) dic. fie indirect prin verificarea rezultatului operatiei “get”. astfel încât sã se poatã face cãutarea la egalitate dupã codul de dispersie. // elimina toate perechile din dictionar Set keySet(). Efectul metodei “put (k.Metoda “entrySet” produce o multime echivalentã de perechi "cheie-valoare".put (word. String text =“ trei unu doi trei doi trei “. // multimea perechilor cheie-valoare } Metoda “get” are ca rezultat valoarea asociatã unei chei date sau null dacã cheia datã nu se aflã în dictionar. String word. orice modificare în dictionar se va 84 . // prima aparitie else dic.out. // pune o pereche cheie-valoare Object get(Object key).hasMoreTokens()) { word = st. folosind un dictionar-arbore pentru afisarea cuvintelor în ordine. De observat cã metodele “entrySet”. // verifica daca exista o valoare data int size().“keySet” si “values” (definite în AbstractMap) creeazã doar imagini noi asupra unui dictionar si nu alte colectii de obiecte. // afisare dictionar } } Cheile dintr-un dictionar pot fi extrase într-o multime cu metoda “keySet”.Florian Moraru – Programare Orientata pe Obiecte public interface Map { Object put(Object key. // o constanta public static void main (String arg[]) { Map dic = new TreeMap ().intValue()+1)). // dimensiune dictionar (nr de perechi) boolean isEmpty(). Metoda “put” adaugã sau modificã o pereche cheie-valoare si are ca rezultat valoarea asociatã anterior cheii date (perechea exista deja în dictionar) sau null dacã cheia perechii introduse este nouã.nextToken(). new Integer (nrap. void clear().v)” în cazul cã existã o pereche cu cheia “k” în dictionar este acela de înlocuire a valorii asociate cheii “k” prin noua valoare “v” (valoarea înlocuitã este transmisã ca rezultat al metodei “put”). Clasele care genereazã obiecte memorate într-un obiect HashMap sau HashSet trebuie sã redefineascã metodele "equals" si "hashCode". class FrecApar { // frecventa de aparitie a cuvintelor intr-un text private static final Integer ONE= new Integer(1). StringTokenizer st = new StringTokenizer (new String (text)). iar valorile din dictionar pot fi extrase într-o colectie (o listã) cu metoda “values”. // verifica daca exista o cheie data boolean containsValue(Object value). // colectia valorilor din dictionar Set entrySet().put (word. Object value). Testul de apartenentã a unei chei date la un dictionar se poate face fie direct prin metoda “containsKey”. while ( st. // extrage valoare asociatã unei chei date Object remove(Object key). “getValue” si “setValue”. Entry este o interfatã inclusã în interfata Map si care are trei metode: “getKey”. In exemplul urmãtor se afiseazã numãrul de aparitii al fiecãrui cuvânt distinct dintr-un text. unde clasa pereche are tipul Entry. // alta aparitie } System.

// colectia nemodificata ramane ordonata if ( cmp==null) Collections. sau alte alte structuri compatibile cu interfata List si care asigurã ordinea (un arbore binar. cmp=comp. pentru a mentine lista ordonatã. // ordonare cu comparator primit din afara return modif. Pentru pãstrarea ordinii de adãugare se foloseste o listã înlãntuitã. } } Problema clasei anterioare este cã ar trebui redefinitã si metoda “set” care modificã elemente din listã. // adresa obiectului comparator public SortedArray () { super(). Listele sunt implicit neordonate (se adaugã numai la sfârsit de listã) si pot fi ordonate numai la cerere. // retine adresa obiectului comparator } public boolean add (Object obj) { // adaugare la vector ordonat boolean modif= super.trei. Metodele clasei imagine sunt definite apoi pe baza iteratorului.doi.println (lista).sort (this. System. în timp ce la HashMap ordinea este aparent întâmplãtoare (ea depinde de capacitatea tabelei de dispersie.sort (lista). care dã acces la datele din dictionar. Exemplu de încercare de a defini o clasã pentru o multime ordonatã implementatã printr-un vector (dupã modelul clasei TreeSet): public class SortedArray extends ArrayList implements List { Comparator cmp=null. O imagine este creatã printr-o clasã care defineste un iterator pe datele clasei dictionar si nu are propriile sale date. Collections. List lista = Arrays. Multimile.Florian Moraru – Programare Orientata pe Obiecte reflecta automat în aceste “imagini”. iar tabelul de dispersie asigurã un timp bun de regãsire dupã cheie.out.”patru”. // ordonare dupa criteriul natural else Collections. Exemplu de ordonare a unei liste: public static void main (String arg[ ]) { String tab[ ] = {"unu". Colectii ordonate Problema ordonãrii este rezolvatã diferit pentru liste fatã de multimi si dictionare. au o variantã de implementare (printr-un arbore binar ordonat) care asigurã mentinerea lor în ordine dupã orice adãugare sau eliminare de obiecte.”cinci”}.} public SortedArray (Comparator comp) { super()."doi".sort (this).add(obj).patru. de exemplu). ca si dictionarele.unu] } Putem sã ne definim vectori sau liste ordonate automat. fãrã ca sã apelãm din nou metodele respective.sort).asList (tab). // daca s-a modificat colectia dupa adaugare if ( ! modif) return modif."trei". prin apelul unei metode statice (Collections.sort” deoarece aceasta apeleazã metoda “set” si apare o recursivitate indirectã infinitã de forma set -> sort -> set -> sort -> set ->… 85 .cmp). Acest lucru nu este posibil prin folosirea metodei “Collections. // [cinci. de functia “hashCode” si de ordinea de introducere a cheilor). Diferenta dintre clasele HashMap si LinkedHashMap apare numai în sirul produs de metoda “toString” a clasei: la LinkedHashMap ordinea perechilor în acest sir este aceeasi cu ordinea de introducere a lor în dictionar.

e2= (Map. este ordinea numericã fãrã semn pentru caractere si este ordinea lexicograficã pentru obiecte de tip String. Obiectele introduse într-o colectie TreeSet sau TreeMap trebuie sã apartinã unei clase care implementeazã interfata Comparable si contine o definitie pentru metoda “compareTo”. Pentru ordonarea dupã un alt criteriu decât cel natural si pentru ordonarea dupã mai mlte criterii se va folosi o clasã compatibilã cu interfata Comparator.entrySet(). Rezultatul metodei "compare" este acelasi cu al metodei "compareTo". care implementeazã (optional) interfata SortedSet. min.e2=prechi cheie-val return ((Integer)e2. max) ce necesitã compararea de obiecte. deci un numãr negativ dacã ob1<ob2. trebuie implementatã interfata Comparable prin definirea metodei "compareTo". Pentru alte clase. realizate ca douã interfete diferite si care au aplicabilitate distinctã. iar un obiect al acestei clase se transmite ca argument functiei.sort (entries. Clasele JDK cu date (String.compareTo ((Integer) e1. în ordinea inversã a numãrului de aparitii: Set entset = dic.Entry)o1. Exemplu de ordonare a unei liste de nume (distincte) prin crearea si afisarea unei multimi ordonate: SortedSet lst = new TreeSet (lista). ArrayList entries = new ArrayList(entset). Object o2) { Map. Cursorul este un indice întreg în 86 . Un argument de tip Comparator apare în constructorii unor clase si în câteva metode din clasa Collections (sort. // e1. zero dacã ob1==ob2 si un numãr pozitiv dacã ob1>ob2. Anumite metode statice (sort.Entry e1= (Map. Se pot defini si alte tipuri de colectii sau dictionare ordonate. într-o anumitã ordine (pentru colectii neliniare). Exemplu de ordonare a dictionarului de cuvinte-frecventã creat anterior.a. respectiv interfata SortedMap. Adãugarea sau modificarea valorilor dintr-un arbore se face cu mentinerea ordinii si nu necesitã reordonarea multimii sau dictionarului. // sau se adauga cu metoda addAll Iteratorul unei colectii ordonate parcurge elementele în ordinea dictatã de obiectul comparator folosit mentinerea colectiei în ordine. Realizarea concretã a enumerãrii depinde de tipul colectiei si foloseste un cursor care înainteazã de la un element la altul. O problemã comunã colectiilor ordonate este criteriul dupã care se face ordonarea. // comparator pentru ordine inversa lui cmp Clase iterator Una din operatiile frecvente asupra colectiilor de date este enumerarea tuturor elementelor colectiei (sau a unei subcolectii) în vederea aplicãrii unei prelucrãri fiecãrui element obtinut prin enumerare. deci functia de comparatie. max s. Date s. Pentru a utiliza o functie “compare” trebuie definitã o clasã care contine numai metoda "compare".) implementeazã interfata Comparable si deci contin o metoda "compareTo" pentru o ordonare "naturalã" ( exceptie face clasa Boolean.a.) si unele metode din clase pentru multimi ordonate apeleazã în mod implicit metoda "compareTo". ale cãrei obiecte nu sunt comparabile).. . new Comp()). Sunt prevãzute douã solutii pentru aceastã problemã. dacã obiectele clasei pot fi comparate (si sortate)..Entry)o2. definite de utilizatori.Florian Moraru – Programare Orientata pe Obiecte O multime ordonatã este de tipul TreeSet iar un dictionar ordonat este de tipul TreeMap. Ordinea naturalã este ordinea valorilor algebrice (cu semn) pentru toate clasele numerice. parte a interfetei Comparable. Integer. min. Collections.getValue()). care depinde de tipul obiectelor comparate.getValue()). // clasa comparator obiecte Integer in ordine inversa celei naturale class Comp implements Comparator { public int compare (Object o1. } } Din versiunea 5 s-a introdus în clasa Collections si un comparator pentru ordinea inversa: Comparator reverseOrder(Comparator cmp).

listIterator(). it. i. // ordonare vector intrinsec (mai eficienta) ListIterator i = list.add( "x"). } } Pentru enumerarea unei colectii se va folosi o variabilã de tip Iterator si nu metoda iterator(). cum ar fi vectori de liste. fie prin metoda “remove” a clasei iterator. // transforma lista in vector intrinsec Arrays. pentru cã fiecare obiect iterator are o variabilã cursor proprie. iar obiectele iterator se obtin prin apelarea unei metode a clasei colectie (metoda “iterator”).next(). } // daca exista un element urmator in colectie // extrage element curent si avans la urmãtorul // elimina element curent din colectie (optional) De observat cã modificarea continutului unei colectii se poate face fie prin metode ale clasei colectie.Detectarea sfârsitului colectiei (test de terminare a enumerãrii).length. Pentru colectii mai complexe. Un dictionar nu poate avea un iterator. deoarece fiecare apel al metodei reinitializeazã iteratorul prin pozitionare pe primul element.toArray().hasNext()) { a. care progreseazã în mod diferit în cadrul colectiei. un vector) pot exista mai multi iteratori.sort(a). 87 . Toate clasele iterator rebuie sã includã urmãtoarele operatii: . for (int j=0.next().set(a[j]).Pozitionarea pe urmãtorul element din colectie . j<a. dar nu ar trebui folosite simultan ambele moduri de modificare. Exemplu: public static void sort (List list) { Object a[] = list. Generalizarea modului de enumerare a elementelor unei colectii pentru orice fel de colectie a condus la aparitia claselor cu rol de “iterator” fatã de o altã clasã colectie. void remove(). while (it. j++) { // modificare elemente din lista i.Pozitionarea pe primul element din colectie . Clasele iterator nu sunt direct instantiabile (nu au constructor public). Mai multi algoritmi generici realizati ca metode statice (în clasa Collections) sau ca metode ne-statice din clasele abstracte folosesc un obiect iterator pentru parcurgerea colectiei. In exemplul urmãtor apare o exceptie de tip ConcurrentModificationException: ArrayList a = new ArrayList(). Interfata Iterator contine metode generale pentru orice iterator: public interface Iterator { boolean hasNext(). enumerarea poate folosi indici (pentru vectori) si pointeri (pentru noduri legate prin pointeri). Orice clasã colectie Java 2 poate avea o clasã iterator asociatã.Obtinerea elementului curent din colectie . dar multimea perechilor si multimea cheilor din dictionar au iteratori.Florian Moraru – Programare Orientata pe Obiecte cazul unui vector sau un pointer (o referintã) pentru o listã înlãntuitã sau pentru un arbore binar. Iterator it = a. } Pentru fiecare clasã concretã de tip colectie existã o clasã iterator.iterator(). Un obiect iterator este singura posibilitate de enumerare a elementelor unei multimi si o alternativã pentru adresarea prin indici a elementelor unei liste. In felul acesta. Object next(). Pentru un acelasi obiect colectie (de ex. programatorul este obligat sã creeze întâi obiectul colectie si numai dupã aceea obiectul iterator.

next(). } return modified.previous()). O clasã dictionar (Map) nu are un iterator asociat. hasPrevious().out.hasPrevious(). it.println ( e. ) System.next())) return true. i++) result[i] = e.) { Map. precum si douã colectii performante pentru elemente de un tip enumerat: EnumSet si EnumMap 88 .Entry e = (Map. Secventa urmãtoare face o enumerare a perechilor cheie-valoare dintr-un dictionar “map”.getKey()+”:”+e. next(). System. e. while (e. add(Object o). return false. folosind interfata publicã Entry. previousIndex(). definitã în interiorul interfetei Map: for (Iterator it= map. } } Interfata ListIterator contine metode pentru traversarea unei liste în ambele sensuri si pentru modificarea elementelor enumerate : hasNext(). Exemplu de parcurgere a unei liste de la coadã la capãt: List a = new LinkedList(). Iterator e = c.hasNext(). remove().iterator().listIterator (a. iar metoda “listIterator(index)” pozitioneazã initial cursorul pe indicele specificat. Iterator e = iterator(). for (ListIterator i= a.entrySet().size()).hasNext()) if (o. dar este posibilã extragerea unei multimi de chei sau unei multimi de perechi cheie-valoare dintr-un dictionar. } Definirea de noi clase colectie Din versiunea 1. } public boolean addAll (Collection c) { boolean modified = false.hasNext()) { if(add(e.println (i. Metoda “listIterator()” pozitioneazã cursorul pe începutul listei.out.hasNext().next(). i.Entry) it. iar aceste multimi pot fi enumerate.Florian Moraru – Programare Orientata pe Obiecte Fragmentul urmãtor din clasa AbstractCollection aratã cum se pot implementa metodele unei clase colectie folosind un iterator pentru clasa respectivã: public abstract class AbstractCollection implements Collection { public boolean contains(Object o) { // fara cazul o==null Iterator e = iterator().equals(e. while (e. set (Object o).iterator(). } public Object[] toArray() { Object[] result = new Object[size()].next())) modified = true.5 s-a introdus tipul de date definit prin enumerare (si cuvântul cheie enum). nextIndex(). previous(). for (int i=0. return result.getValue()).

de exemplu o multime de obiecte realizatã ca vector sau ca listã simplu înlãntuitã. elementele unei colectii pot fi de orice tip derivat din Object. Desi ele au fost create special pentru clasa JTree care asigurã vizualizarea acestor arbori.toString().int w) { // adauga arcul (v.n =n. // lista vecinilor lui v return vlist.swing. Set. // multime-vector de perechi cheie-valoare public ArrayMap (int n) { entries = new ArraySet(n). Exemplu de clasã pentru obiecte dictionar realizate ca vectori de perechi: public class ArrayMap extends AbstractMap { // dictionar vector de perechi private ArraySet entries .a. folosind iteratori : isEmpty.v<n. cu facilitãti de expandare/contractie noduri. ceea ce simplificã definirea si utilizarea colectiilor de colectii. compatibilã cu interfata Collection. pot fi folosite si separat de clasa JTree. în pachetul “javax.get(v). Map.tree”: clasa instantiabilã DefaultMutableTreeNode pentru un nod de arbore. Aceste noi clase colectie ar trebui sã se conformeze cadrului creat de interfetele Collection. // adauga o lista vida de vecini } public void addArc (int v.contains (new Integer(w)). In Java. Biblioteca de clase Java mai contine si clase pentru crearea si prelucrarea de arbori multicãi. contine implementãri pentru o serie de operatii care pot fi realizate indiferent de tipul colectiei. // numar de noduri in graf private List a. si pot sã refoloseascã metode definite în clasele abstracte si în clasele instantiabile deja existente. Definirea unor noi clase colectie poate fi necesarã din mai multe motive: . selectie de noduri.. Iterator s. addAll. Set. deci pot fi la rândul lor colectii.int w) { // verifica daca arc de la v la w List vlist = (List)a. a = new ArrayList (n). // daca contine nodul w } public String toString () { // un sir cu toate listele de adiac. // vector sau lista de noduri public Graph ( int n) { this. Clasele AbstractSet si AbstractList extind clasa AbstractCollection. Map).add (new LinkedList()). containsAll. return a. // aloca memorie pentru vector for (int v=0. desi unele din ele sunt redefinite din motive de performantã. Astfel se mostenesc toate functiile mentionate.v++) a. colectie de multimi disjuncte. . toArray.get(v). removeAll. List. } } Clasa abstractã AbstractCollection.a. Exemplul urmãtor este o clasã pentru grafuri memorate prin liste de adiacente. retainAll. } public Object put ( Object key.a. // adauga w la vecinii lui v } public boolean isArc (int v. clear. iar clasele instantiabile pentru multimi si liste extind aceste clase abstracte.Florian Moraru – Programare Orientata pe Obiecte (implementate prin vectori de biti). // lista de vecini ai nodului v vlist. Object value) { 89 .w) la graf List vlist = (List)a. clasa DefaultTreeModel pentru un (model de) arbore si interfete pentru alte eventuale implementãri. s. Prin extinderea claselor abstracte se pot defini noi clase colectie cu un efort de programare minim si cu pãstrarea compatibilitãtii cu interfetele comune (List. remove.Sunt necesare alte tipuri abstracte de date neimplementate în SDK: graf. toString. contains.add(new Integer(w)).Cerinte de performantã (de memorie ocupatã sau timp de acces) impun alte implementãri pentru tipurile abstracte de date existente. Se foloseste un vector de liste înlãntuite: public class Graph { // graf reprezentat prin liste de adiacente private int n. arbore binar s.

size().toString().getValue(). } } public Set entrySet ( ) { return entries. return v. } } // multime-vector de obiecte class ArraySet extends AbstractSet { public int size() { return entries. } public Object getValue() { return val. // modifica perechea return v.set (i.indexOf( new MEntry(key. } public void remove () { entries. // se adauga o noua pereche return null. if (i<0) { // daca cheia key nu exista anterior in dictionar entries.get(i). } public boolean hasNext() { return i < entries. } public String toString() { return key+":"+val.} } 90 . ASIterator () { i=0. } } // multimea cheilor // mostenita din AbstractMap dar redefinita Clasa “Entry” implementeazã interfata “Map.getKey().equals(key). // valoare asociata anterior cheii entries. public Entry (Object k.Entry” si redefineste metoda “equals” (eventual si metoda “toString”): class Entry implements Map. } else { // daca cheia key exista deja in dictionar Object v = ((MEntry) entries.remove(i-1).value)). } public String toString( ) { return entries.} public Object setValue (Object v) { val=v. } public Object getKey() { return key. val=v.add (new MEntry(key.Florian Moraru – Programare Orientata pe Obiecte int i = entries.} public Object next() { Object e= (Entry)entries. return e. } public Iterator iterator () { return new ASIterator().value)).Entry { private Object key.} } // clasa iterator (inclusa in clasa ArrayMap) class ASIterator implements Iterator { int i.val. i++. new MEntry(key.} public boolean equals (Object obj) { return ((Entry)obj).value)).size().get(i)). Object v) { key=k.

Asadar. o variabilã a clasei AbstractMap retine o referintã la obiectul creat numai la primul apel. // referinta la multimea de perechi cheie-valoare public KSet (Set entrySet) { eset=entrySet. } 91 . iar clasa iterator se poate defini prin parcurgerea multimii de perechi cheie-valoare (“entrySet”) si extragerea câmpului dorit din fiecare pereche (fie cheia. Pe lângã consumul inutil de memorie ar exista si problema mentinerii în concordantã a dictionarului cu cele douã colectii dupã orice modificare efectuatã în dictionar (sau a refacerii colectiilor la fiecare nou apel). dictionar definit complet prin multimea perechilor cheie-valoare. dar aici vom folosi clase exterioare. dar aceste clase nu vor contine date. Un alt exemplu îl oferã metodele “keySet” si “values” din clasele dictionar. size) pe baza datelor existente în vectorul initial. In Java problema se pune de a privi un vector intrinsec sau o colectie ca fiind de un alt tip.Florian Moraru – Programare Orientata pe Obiecte Colectii virtuale Vom folosi denumirea de colectie virtualã pentru ceea ce în literatura de limbã englezã se numeste “view”. iar la un apel ulterior se verificã doar valoarea acestei variabile (initial valoarea null): private transient Set keySet = null. } Clasa KSet implementeazã o multime virtualã a cheilor dintr-un dictionar. care trebuie sã furnizeze o multime a cheilor si respectiv o colectie a valorilor din dictionar. // atunci se creeazã obiectul unic de tip KSet return keySet.asList() care ne permite sã vedem un vector intrinsec ca o colectie nemodificabilã de un tip (anonim) compatibil cu interfata List. Solutia acestei probleme foloseste ideea urmãtoare: o nouã colectie (multime) se poate defini prin extinderea unei clase abstracte cu definirea unei clase iterator. fie valoarea). } public int size() { return eset. } public Iterator iterator () { // iterator pe multimea cheilor return new KIter(eset). dar fãrã a copia datele într-un alt tip de colectie. Clasa nouã extinde o clasã abstractã (AbstractList) si defineste metodele abstracte (get. O altã observatie utilã pentru întelegerea codului este aceea cã metodele “keySet” si “values” au rol de metode fabricã de obiecte (“Factory Methods”) si nu vor produce câte un nou obiect la fiecare apel. pentru cã metoda “next” a iteratorului clasei va furniza urmãtoarea cheie sau valoare din dictionarul existent (într-o ordine care depinde de tipul clasei dictionar). tabel care însã nu este creat efectiv pe disc ci este doar o imagine diferitã a unor date existente prezentatã utilizatorilor bazei de date pentru afisare sau pentru cãutare. se vor defini noi clase pentru multimea cheilor si colectia valorilor. pentru a facilita întelegerea ideii de colectie virtualã. dar fãrã sã creeze noi colectii (fãrã sã copieze referintele din dictionar în aceste noi colectii). Exemplul cel mai simplu este metoda staticã Arrays. Implementarea acestei idei în clasa AbstractMap foloseste clase incluse anonime. Prima utilizare a acestui termen a apãrut în domeniul bazelor de date unde existã posibilitatea prezentãrii datelor din mai multe tabele fizice sub forma unui nou tabel ( cu o parte din coloanele tabelelor fizice). multime generatã de metoda “entrySet” (specificã fiecãrui tip de dictionar): class KSet extends AbstractSet { Set eset. // adresa obiectului creat la primul apel keySet public Set keySet() { if (keySet == null) // daca este primul apel keySet = new KSet(entrySet()). adicã o altã imagine (perspectivã) asupra unei colectii de date din memoria RAM sau de pe un suport extern.size().

tree.} public TNode (Object info) { super(info). O idee ar fi înlocuirea vectorului extensibil de succesori cu o listã înlãntuitã de succesori. multicãi. Un arbore este complet definit prin nodul rãdãcinã. prin metode ale clasei dictionar (“put”. // scoate cheia dintr-o pereche Map. Dintre metodele publice mai des folosite mostenite de clasa TNode mentionãm: void add (TNode child). Se pot defini si alte variante dar care ar trebui sã implementeze interfata MutableTreeNode pentru a fi folosite de un obiect JTree.hasNext().Entry) i.Florian Moraru – Programare Orientata pe Obiecte public boolean contains(Object k) { return eset.contains(k). // iterator pe multimea de perechi cheie-valoare } public boolean hasNext() { return i. Este o clasã cu multe metode si posibilitãti de utilizare si în aplicatii fãrã interfatã graficã. multimea cheilor nu este modificatã direct ci numai indirect. TNode getParent(). în principal). Colectii arborescente Bibliotecile de clase Java contin o singurã clasã care poate fi utilizatã ca arbore general. } } // elimina o cheie atunci cand se elimina perechea care o contine In mod similar se procedeazã la metoda “values” pentru a crea o colectie virtualã a valorilor din dictionar. prin iterare pe aceeasi multime de perechi “entrySet”.Entry } public void remove() { i.iterator().getKey(). // adauga acestui nod fiul “child” // numarul fiilor acestui nod // parintele acestui nod 92 . Vom folosi în continuare un nume mai scurt pentru clasa DefaultMutableTreeNode astfel: class TNode extends DefaultMutableTreeNode { public TNode() { super(). } public Object next() { return ((Map. int getChildCount().next()). sub numele de DefaultMutableTreeNode în pachetul javax.swing. Clasa iterator parcurge multimea de perechi si extrage cheia din fiecare pereche: class KIter implements Iterator { private Iterator i.} } Clasa nod de arbore foloseste o implementare în care fiecare nod contine un vector extensibil de referinte la nodurile fii (lista fiilor este un obiect de tip Vector). } } Metoda de adãugare la multime (“add”) nu poate fi definitã pentru multimea KSet dar nici nu este necesar (pentru cã nu este metodã abstractã în AbstractSet).remove(). deci un obiect arbore este de fapt un obiect nod de arbore. folositã pentru pãstrarea datelor care vor fi afisate într-o componentã graficã JTree. public KIter (Set eset) { i=eset. o referintã la nodul pãrinte si un obiect cu date (de tipul generic Object).

// r este nodul radacina DataInputStream cin= new DataInputStream(System. // datele din acest nod void remove (TNode child). r.Florian Moraru – Programare Orientata pe Obiecte Enumeration children(). in adancime Enumeration breadthFirstEnumeration(). // p = adresa nod parinte in arbore if (p==null) { // daca nu exista nod parinte r=p=new TNode(parent). } return null. fie folosim un dictionar în care cheile sunt valori din noduri si au asociate adresele nodurilor în arbore (dacã valorile din noduri sunt distincte). // noduri din subarbore. parcurs in latime Exemple de utilizare a unor obiecte de tip DefaultMutableTreeNode: // clasa ptr un nod de arbore ( cu un nume mai scurt) class TNode extends DefaultMutableTreeNode { public TNode() { super(). c.} public TNode (Object info) { super(info).hasMoreElements()){ TNode n = (TNode) e. Dacã aceste date vin sub forma unor perechi parinte-fiu atunci fie utilizãm metoda “find” de mai înainte pentru cãutarea adresei nodului pãrinte. while ( e. // si pune adresa lui in dictionar } p. // pune adresa nod fiu in dictionar } } catch (IOException ex) { ex.nextElement(). if (n. Object obj) { Enumeration e = root. // fiii directi ai acestui nod Object getUserObject(). // atunci creeaza nod parinte h.readLine()) != null) { StringTokenizer tok = new StringTokenizer(line). // noduri din subarborele acestui nod. // creare si adaugare nod fiu h. try { while ( (line=cin. Exemplu: private TNode buildTree() { TNode p. String child. parent = tok. // valoare nod parinte child =tok.c).printStackTrace().nextToken().put(child.p).preorderEnumeration().in).} } // cauta in arborele cu radacina root nodul ce contine un obiect dat obj public static TNode find (TNode root. // elimina fiul “child” din lista fiilor acestui nod Enumeration preorderEnumeration().TNode>().} return r.equals(obj)) return n. line. Hashtable<String.TNode> h = new Hashtable<String. parent. } Crearea unui arbore depinde de modul cum sunt furnizate datele ce vor fi introduse în arbore.get(parent).put(parent. // valoare nod fiu p = h. } Crearea unui arbore cu numele fisierelor dintr-un director dat si din subdirectoarele sale este un caz particular important de creare a unui arbore general: // subclasa ptr arbori multicai de fisiere class FTree extends DefaultMutableTreeNode { 93 .add(c=new TNode(child)).nextToken().getUserObject().

// creste indentarea la un nou nivel while ( e. folosind metode ale clasei DefaultMutableTreeNode. String ind) { // ind = indentare la fiecare apel final String inc =" ".nextElement()).i<files. // adauga continut subdirector f } } } Se poate folosi si un constructor recursiv de nod. // afisare nod radacina Enumeration e = r.toString()). } static void printTree (FTree r.setParent(this).i++){ // ptr fiecare fisier File f = new File(d+"\\"+files[i]).i<files.isDirectory( ) ) return. kid.isDirectory() ) return. cu indentare diferitã la fiecare nivel din arbore: public void print () { // metoda a clasei FTree printTree ( (FTree) getRoot().setParent(r).getName()).length.i++) { FileNode s=new FileNode(files[i]). // adaugare recursiva noduri la acest arbore } // creare arbore cu fisierele dintr-un director d si din subdirectoarele sale void dirtree ( FTree r. // creare nod ptr nume fisier r.length. // iesire daca d nu e fisier director String [ ] files=d. // fiii nodului r ind=ind+inc. ""). dir). // adauga fiecare fiu la radacina } } } Exemplu de afisare în mod text. // nume fisiere din directorul d for(int i=0. s.Florian Moraru – Programare Orientata pe Obiecte public FTree() { super().} public FTree (String info) { super(info).listFiles().} public FTree (File dir) { // arbore cu fisierele din directorul “dir” this (dir.getName()). // leaga nod la arbore dirtree (kid. } 94 .add ( kid). // fisiere din directorul d for (int i=0. // radacina arbore.list(). // creare obiect File FTree kid = new FTree (f. // creare nod radacina if ( ! d. File [] files=d. // cu cat creste indentarea la un nou nivel System. f). care construieste întreg arborele: class FileNode extends DefaultMutableTreeNode { public FileNode (File d) { super (d).ind). cu nume fisier director dirtree (this.children(). // noduri fii ai radacinii add(s).println (ind+ r.hasMoreElements()) // afisare recursiva fii printTree ((FTree) (e. File d) { if ( ! d.out.

out.5. } Exemplu de utilizare dictionar cu valori multiple pentru problema listei de referinte încrucisate . while ((line=in.add (new Integer (nl)).List<Integer>>( ).i++) { Integer val= list.Florian Moraru – Programare Orientata pe Obiecte 8. lst=new LinkedList<Integer>()).hasMoreTokens() ) { word=st. Exemplu: 95 . ca si la apelul de functii. Colectii generice Clase generice în Java 5 Clasele colectie cu tipuri parametrizate au fost introduse în versiunea 1. Aceste clase au fost numite generice (“generics”) si nu clase “sablon” (“templates”) pentru cã desi se definesc si se folosesc aproape la fel cu clasele sablon din C+ + ele diferã prin implementare: în C++ din clasa sablon se genereazã diverse clase prin substitutia parametrilor tip din clasa sablon. Exemplu cu un vector de obiecte Integer: ArrayList<Integer> list = new ArrayList<Integer>().add( new Integer(i) ). } } System. word. ca solutii alternative pentru colectiile de obiecte Object. // initializata ulterior Avantajele sunt acelea cã se poate verifica la compilare dacã se introduc în colectie numai obiecte de tipul declarat pentru elementele colectiei.println (cref).size(). if (lst==null) cref. dar în Java existã o singurã clasã genericã (sursã si compilatã) în care se înlocuiesc parametri formali cu parametrii efectivi.get (word).put (word. BufferedReader in = new BufferedReader (new FileReader (arg[0])). int nl=0.în ce linii dintr-un fisier text apare fiecare cuvânt distinct : public static void main (String[ ] arg) throws IOException { Map<String.5. lst. iar la extragerea din colectie nu mai este necesarã o conversie de la tipul generic la tipul specific aplicatiei. List<Integer> lst = cref. procese numite “autoboxing” si “unboxing”. ArrayList<LinkedList<Integer>> graf.intValue().List<Integer>> cref = new TreeMap<String.nextToken(). i< list. } Trecerea de la un tip primitiv la tipul clasã corespunzãtor si invers se face automat în versiunea 1.i<10. while ( st.get(i). // fara conversie de tip ! sum += val. StringTokenizer st = new StringTokenizer (line). for (int i=1. // nu se poate adauga alt tip decat Integer int sum=0.i++) list. for (int i = 0.readLine()) != null) { ++nl. Exemple de declarare a unor vectori cu elemente de diferite tipuri : ArrayList<Integer> a = new ArrayList<Integer>(). ArrayList<TNode> c = ArrayList<TNode> (n). ArrayList<String> b = ArrayList<String> (100). String line.

} public boolean contains(Object elem) { return indexOf(elem) >= 0.Florian Moraru – Programare Orientata pe Obiecte ArrayList<Integer> list = new ArrayList<Integer>(). for (int i=1.. return elementData[index]. sau sum += it. } public boolean add(E o) { ensureCapacity(size + 1). Cloneable.x++) list.5 toate interfetele si clasele colectie au fost redefinite pentru a permite specificarea tipului elementelor colectiei. Exemplu: for (Integer it : list) sum += it. cu o listã de siruri : LinkedList<String> list = new LinkedList<String>(). Exemplu: // transforma un vector intr-o colectie (gresit) static void arrayToCol (Object[] a.add(o).. Un alt exemplu.add(o). // trecere automata de la int la Integer int sum=0.next(). for (String s : list) sb += s.) sum += i. String sb="". } . for (int x=1. public E get(int index) { RangeCheck(index).io. } Nu numai clasele pot avea parametri tip dar si metodele (numite metode generice). i. private int size. Pentru exemplificare redãm un fragment din clasa ArrayList.iterator().intValue(). return true.add(x+""). RandomAccess.add(10*i). // concatenare de siruri extrase din vector In versiunea 1. // Increments modCount!! elementData[size++] = o. unde “E” este tipul neprecizat al elementelor colectiei : public class ArrayList<E> extends AbstractList<E> implements List<E>.x<9.i++) list. Collection<T> c) { for (T o : a) c. // suma valorilor din vector for (Iterator i = list. java.Serializable { private transient E[] elementData. Collection<?> c) { for (Object o : a) c.hasNext().5 se poate utiliza o formã mai simplã pentru enumerarea elementelor unei colectii care implementeazã interfata Iterable. // correct 96 . // it este iterator pe colectia list de obiecte Integer // sau sum += (int) it. // trecere automata de la Integer la int Tot din versiunea 1. // eroare la compilare } // transforma un vector intr-o colectie (corect) static <T> void arrayToCol (T[] a.i<10.

Exemplu: class OldClass { // metoda statica fara generice public static void addToMap (Map m.put(k. System. // T presupus a fi String La utilizarea unui cod anterior versiunii Java 5 (cod fãrã generice) de cãtre programe Java cu generice pot apãrea mesaje la compilare inevitabile (care pot fi suprimate numai prin adnotarea @SuppressWarning )."unu". arrayToCol (sa.add(new Object())..out.V> { . Exemplu: List<String> ls = new ArrayList<String>(). } } Avertismente la compilare apar si la redefinirea unor metode din clasa Object în alte clase.Entry<K.V> extends Object implements Map. Object k. String s = ls. } Functia urmãtoare nu produce avertisment la compilare: public boolean equals (Object obj) { MEntry e = (MEntry) obj. } } class Generic2 { public static void main (String arg[]){ Map<String.v). OldClass. Forme posibile pentru parametri tip O colectie Collection<Object> nu este un supertip al unei colectii Collection<T> desi Object este un supertip al lui T.println(m).Florian Moraru – Programare Orientata pe Obiecte } // utilizare (fara argumente efective de tip la apel) String[] sa = new String[100]. // eroare la compilare ! Exemplul urmãtor de utilizare aratã de ce nu este corectã atribuirea anterioarã: lo. List<Object> lo = ls.. public boolean equals (Object obj) { MEntry<K.V>) obj.Integer> m = new HashMap<String. cs). Exemplu: class MEntry<K.Integer> (). Object v) { m.get(0). Collection<String> cs = new LinkedList<String>(). // adauga la lista lo // incercare de atribuire a unui Object la un String Exemplul urmãtor aratã de ce se foloseste caracterul ? (wildcard) ca parametru de tip: 97 . } Compilatorul Java poate verifica numai utilizarea corectã de tipuri statice.V> e = (MEntry<K.addToMap (m.1). pe baza numelor de tip.

Florian Moraru – Programare Orientata pe Obiecte // functie naiva de afisare a unei colectii generice static void printCol (Collection<Object> c) { for (Object e : c) System.out..doubleValue(). } // utilizare incorecta HashSet<String> m = new HashSet<String> (). a.add(2L). System.println(e).add(3L).add(new Object()). for (Object x: c) s+= ((Number)x). Dacã argumentul functiei "sum" ar fi ‘?' atunci nu am putea folosi metoda "doubleValue" decât dupã conversie de la Object la Number: static double sum (Collection<?> c){ double s=0. printCol (m).. return s. Fie functia urmãtoare pentru calculul sumei numerelor dintr-o colectie: static double sum (Collection<Number> c){ double s=0. a. // eroare ! Compilatorul nu stie daca "Object" este un subtip al tipului necunoscut '?'. Functia "printC" poate fi folositã numai cu colectii de Object si nu cu colectii de un subtip al lui Object. return s. c. Declaratiile urmãtoare nu sunt echivalente: Collection<Object> Collection<?> In linia 2 din exemplul urmãtor apare eroare la compilare: Collection<?> c = new ArrayList<String>(). } 98 .out.println( (long)sum(a)). for (Number x: c) s+= x. } // utilizare incorecta ArrayList<Long> a = new ArrayList<Long> (). Urmeazã varianta corectã de definire a functiei de afisare a unei colectii generice: // functie generica de afisare a oricarei colectii static void printCol (Collection<?> c) { for (Object e : c) System.println(e). iar caracterul '?' are sensul de "tip necunoscut". Tipurile necunoscute pot fi limitate inferior sau superior (Bounded Wildcard) folosind sintaxa <? extends T> pentru tipuri limitate inferior si <? super T> pentru tipuri limitate superior. } In concluzie Collection<?> este supertipul oricãrei colectii de obiecte.doubleValue().out.

public TreeSet (Comparator<? super E>) { . { . Se spune ca Number este limita superioara a tipului necunoscut '?'. Tipurile limitate superior se folosesc în general pentru rezultatele functiilor.. Exemplu de situatie care produce eroare la compilare desi nu ar trebui: class A implements Comparable<Object> {. Necesitatea unor limite multiple (“Multiple bounds”) este ilustratã de urmãtoarea posibilã definire a functiei "Collections..out..Florian Moraru – Programare Orientata pe Obiecte Desi nu apar mesaje la compilare pot apare exceptii la executie.max" : public static <T extends Comparable<T>> T max(Collection<T> coll) {.. O colectie ordonatã poate primi un comparator folosit la compararea elementelor din colectie: class TreeSet<E> . . public TreeSet (Comparator<E>) { . 99 .. long x) { c..} Definitia este prea restrictivã deoarece permite numai compararea de obiecte de tip T între ele.println(sum(a))... Float.. .} } // utilizare class MyComp<String> implements Comparator<String> { . { . De observat cã urmãtoarea functie se compileazã cu erori: static void addLong (Collection<? extends Number> c.. A am = Collections. Short.add (new Long(x))..} class MyComp<? super E> implements Comparator<? super E> { . . } TreeSet<String> a = new TreeSet<String> ( new MyComp<Object>()). Long.max(a).} Collection<A> a = . . Tipurile limitate superior se folosesc în general pentru argumente de functii. } deoarece compilatorul nu poate verifica dacã tipul necunoscut '?' este un supertip al tipului Long. Double). Exemplul urmãtor aratã necesitatea unor tipuri necunoscute limitate inferior (? super T)...... Integer.. Pentru a permite utilizarea unui comparator mai general se va scrie: class TreeSet<E> .} deci functia "sum" poate opera cu o colectie de obiecte de orice subtip al lui Number (Byte. System... } TreeSet<String> a = new TreeSet<String> ( new MyComp<String>()). // exceptie la conversia din Date in Number Solutia sigurã pentru definirea functiei "sum" este: static double sum (Collection<? extends Number> c) { . ca în exemplul urmãtor: ArrayList<Date> d = new ArrayList<Date> ()... ..

for (int v=1. for (int i=0.v<=n.get(v). } } Exemplul urmãtor este o clasã pentru un tabel (o matrice) de obiecte memorat ca un vector de vectori: class MyTable extends AbstractCollection { private Vector<Vector<Object>> data.i<nl. } public boolean isArc (int v.add(new Integer(w)). vlist.add (new LinkedList<Integer>()).Florian Moraru – Programare Orientata pe Obiecte Eroarea provine din aceea cã tipul A nu implementeazã tipul <Comparable><A> si ar trebui ca tipul T sã fie comparabil cu orice supertip al lui T : public static <T extends Comparable<? super T>> T max(Collection<T> coll) Aceastã definire nu este compatibilã cu definitia anterioarã versiunii Java 5: public static Object max (Collection coll).int w) { LinkedList<Integer> vlist = a. // numar de noduri in graf private ArrayList<LinkedList<Integer>> a. a = new ArrayList<LinkedList<Integer>> (n). Exemplele care urmeazã ilustreazã beneficiile claselor generice comparativ cu colectiile mai vechi. return vlist.add (new Vector<Object>(nc)). // constructori public MyTable(int nl. de variabile Object. Definitia urmãtoare rezolvã aceastã compatibilitate : public static <T extends Object & Comparable<? super T>> T max(Collection<T> coll) Exemple comparative Discutia anterioarã aratã cã este mai simplu sã folosim clase si metode generice existente decât sã definim clase generice noi aplicabile în diverse situatii si care sã nu producã erori la compilare. int nc) { data= new Vector<Vector<Object>>(nl). Exemplu de clasã pentru un graf reprezentat printr-un vector de liste (de noduri vecine): class Graph { private int n.size(). } public void addArc (int v.v++) a. } public int arcs (int v) { // nr de vecini (de arce) ptr nodul v return a.int w) { LinkedList<Integer> vlist = a.i++) data.get(v)).contains (new Integer(w)). } public MyTable (Object a[][]){ 100 . // vector de liste de intregi public Graph ( int nn) { n =nn+1.get(v).

data. int j) { return data.get(i).get(i).length).size()) lin.get(i)).get(j) } public void setValueAt (Object v.size(). } } 101 .add(a[i][j]).Florian Moraru – Programare Orientata pe Obiecte data = new Vector<Vector<Object>>(a. return getValueAt (i/c.set (j.length).length. } public void remove(){} } public int getColumns() { return data.add (j.size().v).i<a. if (j<lin. for (int i=0. } } // metode public int size() { return data. } } Fragmente dintr-o altã variantã care nu produce avertismente “unchecked or unsafe operation”: class MyTable<T> extends AbstractCollection<T> { private Vector<Vector<T>> data. i%c). ++i.get(j).j++) col.size()) lin. for (int j=0.length.v).j<a[i]. // metode public T getValueAt(int i. else lin. } public void setValueAt (T v. int j) { Vector<T> lin = data. int j) { return data. // ((Vector)data.get(0)).v).set (j.} public Iterator iterator(){ return new TIterator (). } public Object next() { int c= getColumns(). // ((Vector)data.i++){ Vector<Object> col = new Vector<Object> (a[0].add(col). } public Iterator<T> iterator(){ return new TIterator<T> (this).get(i).get(0).size() } public Object getValueAt(int i. int j) { Vector<Object> lin = data. } class TIterator implements Iterator { int i=-1. else lin.get(i). if (j<lin.int i.add (j. public boolean hasNext () { return i<size(). int i.get(j).v).

prin indice (ca la liste).b.2]. } public void remove(){} } } Colectii Google In 2007 firma Google a publicat un pachet de clase colectie care vin sã completeze clasele Java de bibliotecã.getColumns().t=t. public T next() { int c= t.Florian Moraru – Programare Orientata pe Obiecte class TIterator<T> implements Iterator<T> { int i=-1. ++i. [a. [c. // elimina n aparitii ale unui element.c. eliminarea tuturor elementelor cu aceeasi valoare si compararea la egalitate a douã colectii cu elemente multiple.d} se va memora sub forma : { [d. MyTable<T> t.i%c). return t. Set<E> elementSet(). int n). Interfata “MultiSet” este: public interface Multiset<E> extends Collection<E> { int count (Object element). // valoare element int getCount().c. } public boolean hasNext () { return i< t. // adauga de n ori un element int remove( Object element. public TIterator (MyTable<T> t) { this. “BiMap” pentru dictionare bidirectionale. // multime de perechi “Entry” @Override public int size() { // numar de elemente din multime long sum = 0L.d.d. în diverse variante de implementare. // metode din Collection redefinite … interface Entry<E> { // o pereche element si contor de aparitii E getElement(). int n). for (Entry<E> entry : entrySet()) { 102 . “MultiMap” pentru dictionare cu chei multiple. String toString(). // numar de aparitii al unui element dat boolean add (E element. // daca doua obiecte MultiSet sunt egale int hashCode().a. care nu permite accesul direct. // multime virtuala de perechi element-contor de aparitii boolean equals(Object object). int hashCode().4].getValueAt(i/c. dar toate cu generice. dar permite realizarea eficientã a unor operatii cum sunt obtinerea numãrului de elemente cu o valoare datã. // multime virtuala a elementelor distincte (view) Set<Entry<E>> entrySet().b.size(). daca count(elem)>n int removeAllOccurrences(Object element). Dintre ele mentionãm clase “MultiSet” pentru multimi cu valori multiple. O multime cu valori multiple este o colectie în care elementele nu sunt neapãrat distincte.1].c. Astfel multimea de elemente {d. String toString(). [b. adicã are toate metodele definite în functie de multimea de perechi furnizatã de metoda abstractã “entrySet”: public abstract class AbstractMultiset<E> extends AbstractCollection<E> implements Multiset<E> { public abstract Set<Entry<E>> entrySet(). } } Toate implementãrile interfetei “MultiSet” se bazeazã pe un dictionar în care cheia este elementul din multime iar valoarea asociatã cheii este numãrul de repetãri ale elementului respectiv. // numar de aparitii boolean equals(Object o).3] } Clasa “AbstractMultiSet” este definitã dupã exemplul clasei AbstractMap.

Integer>> backingEntries = backingMap.next(). } // alte metode … } Clasa AbstractMapBasedMultiMap defineste metoda abstractã “entrySet” în functie de un dictionar primit de constructorul clasei si care poate avea orice implementare: abstract class AbstractMapBasedMultiset<E> extends AbstractMultiset<E> implements Serializable { private final Map<E.get(getElement()). @Override public Set<Multiset.Entry<E>> entrySet() { if (entrySet == null) { entrySet = new EntrySet().Florian Moraru – Programare Orientata pe Obiecte sum += entry. this.getValue().MAX_VALUE). } public int getCount() { int count = mapEntry. return new AbstractMultisetEntry<E>() { public E getElement() { return mapEntry. AtomicInteger> backingMap) { this.Entry<E. } } return count. // pentru eficienta // constructor protected AbstractMapBasedMultiset(Map<E. } // entrySet creeazã obiect numai la primul apel private transient volatile EntrySet entrySet.min(sum.hasNext().Entry<E.getKey(). } public void remove() { checkState(toRemove != null. Atomic> toRemove. } // clasa inclusa private class EntrySet extends AbstractSet<Multiset. if (frequency != null) { count = frequency. } }. return new Iterator<Multiset. } public Multiset.Entry<E>>() { Map. Integer> mapEntry = backingEntries. if (count == 0) { Integer frequency = backingMap.get().size = super.size(). AtomicInteger> backingMap. "no calls to next() since the last call to remove()"). // dictionarul folosit de acest multiset private long size. } return (int) Math.getCount(). public boolean hasNext() { return backingEntries.Entry<E> next() { final Map. toRemove = mapEntry.Entry<E>> { @Override public Iterator<Multiset.iterator().Entry<E>> iterator() { final Iterator<Map.get().Entry<E.entrySet(). Integer. } return entrySet.backingMap = backingMap. 103 .

} // metode redefinite pentru multimi ordonate @Override public SortedSet<E> elementSet() { return (SortedSet<E>) super. } } // alte metode mostenite si redefinite ptr eficienta } Clasele HashMultiSet. } backingMap.getValue(). size = 0L. TreeMultiSet apeleazã la implementãri existente pentru dictionarul folosit: public final class TreeMultiset<E> extends AbstractMapBasedMultiset<E> { // constructori public TreeMultiset() { super(new TreeMap<E. } @Override public void clear() { for (AtomicInteger frequency : backingMap. Integer>(comparator)). Integer>()). backingEntries.Florian Moraru – Programare Orientata pe Obiecte size -= toRemove.size(). toRemove = null.remove().clear().getAndSet(0).elementSet(). } }. Integer>) backingMap()). } @Override protected Set<E> createElementSet() { return new SortedMapBasedElementSet((SortedMap<E. } // clase incluse si alte metode … } 104 .values()) { frequency.set(0). } @Override public int size() { return backingMap. } public TreeMultiset(Comparator<? super E> comparator) { super(new TreeMap<E.

Altfel spus. iar schema de proiectare ce realizeazã extinderea functionalitãtii unei clase se numeste “decorator”. o tehnicã specificã POO este adãugarea de noi metode unor clase existente. desi continutul claselor este atât de asemãnãtor. Metodele de reutilizare a codului sunt delegarea si derivarea. care vor fi definite în subclasele acestei clase abstracte.isEmpty(). In exemplul urmãtor se defineste o clasã pentru stive prin delegarea operatiilor cu stiva cãtre metodele listei înlãntuite de obiecte LinkedList: public class LinkedStack { private LinkedList stack. Varianta definirii clasei "LinkedStack" ca o 105 . prin delegarea cãtre metodele clasei B a metodelor clasei A. } public Object push (Object obj) { stack. pentru reutilizarea functionalitãtii clasei B în noua clasã A.toString(). Clasa urmãtoare din colectiile Google ilustreazã esenta delegãrii pentru “decorarea” unei clase: public abstract class ForwardingObject implements Serializable { private final Object delegate. protected ForwardingObject(Object delegate) { this. } protected Object delegate() { return delegate. } public Object pop () { return stack. } } // stiva lista (obiect continut) // constructor // metoda clasei LinkedStack // foloseste metoda clasei LinkedList // metode cu acelasi nume si tip De observat cã tipurile “LinkedStack” si LinkedList nu sunt compatibile si nu se pot face atribuiri între ele.toString(). } public boolean isEmpty () { return stack. In cazul delegãrii o clasã A contine o variabilã de un tip clasã B. } public String toString () { return stack. Reutilizarea codului în POO Reutilizarea codului prin delegare Unul din avantajele programãrii orientate pe obiecte este posibilitatea de reutilizare simplã si sigurã a unor clase existente în definirea altor clase. } } De remarcat cã nu sunt delegate si operatiile “equals” sau “hashCode”. return obj. } @Override public String toString() { return delegate(). fãrã a modifica clasele initiale. de la imaginea unor decoratiuni adãugate unui obiect existent pentru a-i spori utilitatea sau estetica.delegate = checkNotNull(delegate).removeFirst().addFirst (obj). public LinkedStack () { stack= new LinkedList(). simultan cu reutilizarea unor metode mostenite.Florian Moraru – Programare Orientata pe Obiecte 9.

val). } public Object push (Object obj) { stack. } return str.).put (key. // vector // lista inlantuita In exemplul urmãtor se defineste o clasa dictionar cu valori multiple. care poate fi înlocuitã la executie (la construirea unui obiect.toString() + "\n". // apel HashMap. } } 106 . compatibil cu tipul variabilei din clasã. în care fiecare cheie are asociatã o lista de valori.next()).keySet()..get } public String toString () { String str="". Delegarea de metode între obiecte poate fi o relatie dinamicã..get(key).obj). Vom relua exemplul cu stiva adãugând un grad de generalitate prin posibilitatea utilizatorului de asi alege tipul de listã folosit pentru stivã (vector sau lista înlãntuitã): public class StackList { private List stack.add (0.keySet(). modificabilã la executie.put } public List get (Object key) { return (List) m. “pop” etc. public StackList (List list) { stack=list. public void put (Object key.hasNext() ) { List lst = get(ik. StackList st2 = new StackList (new LinkedList()). mai ales cã douã dintre metode pot fi mostenite ca atare ("isEmpty" si "toString"). de exemplu) printr-o referintã la un obiect de un alt tip. // apel HashMap. // alte metode } // adresa vector sau lista inlantuita // constructor // retine adresa obiect stiva La construirea unui obiect “StackList” trebuie precizat tipul de listã folosit (vector sau altceva): StackList st1 = new StackList (new ArrayList()). Clasa preia o mare parte din functionalitatea clasei HashMap. public class MultiMap { HashMap m = new HashMap(). stabilitã la scrierea programului si care nu mai poate fi modificatã. List val) { m. Iterator ik=m. // apel HashMap. } . Clasa compusã poate contine o variabilã de un tip interfatã sau clasã abstractã.Florian Moraru – Programare Orientata pe Obiecte subclasã a clasei LinkedList este preferabilã aici.”removeFirst” ale clasei LinkedList) la un alt set de metode publice (“push”. Clasa “LinkedStack” este numitã si clasã “adaptor” pentru cã face trecerea de la un set de metode publice (“addFirst”.iterator(). spre deosebire de relatia staticã de derivare. return obj. prin delegarea unor operatii cãtre metode din clasa HashMap. // lista de valori a unei chei str = str+ key + " : " + lst.keySet while (ik. } public Set keySet () { return m.

return obj.add (val). lst=new ArrayList()). vom defini douã metode noi: public class LinkedStack extends LinkedList { public Object push (Object obj) { addFirst (obj). Solutia este de a redefini aceste metode în subclasã. fie dupã "ajustãri" si "adaptãri" cerute de rolul subclasei.a. } public Object pop () { return removeFirst(). cu efectul de aruncare a unor exceptii. "isEmpty" si altele din clasa LinkedList. care pot exista si dupã disparitia obiectului compus (desi s-ar putea pierde referintele la ele). Vom relua exemplul clasei pentru stive realizate ca liste înlãntuite. } Iatã si o altã solutie de definire clasei "MultiMap" (dictionar cu valori multiple). Extinderea unei clase permite reutilizarea unor metode din superclasã. De asemenea. cãutare în stivã s. In UML se face distinctie între termenii “compozitie” si “agregare”: în compozitie distrugerea unui obiect compus are ca urmare distrugerea obiectele continute (care nu mai pot exista independent de obiectul care le contine). De aceea. nefiind necesarã rescrierea sau apelarea metodelor mostenite. // se introduce o pereche cheie-lista lst. existã un constructor implicit care apeleazã constructorul superclasei si care initializeazã lista stivã. fie direct.put (key. // lista anterioara de valori } } 107 . Exemplu: public Object remove (int index) { throw new UnsupportedOperationException(). dar interzise pentru stive: citire si modificare orice element din stivã. // extrage lista de valori asociata cheii key List result=lst. Operatiile cu o stivã se numesc traditional “push” si “pop”. Reutilizarea codului prin derivare Prin derivare se face o adaptare sau o specializare a unei clase mai generale la anumite cerinte particulare fãrã a opera modificãri în clasa initialã. O problemã în acest caz ar putea fi posibilitatea utilizatorilor de a folosi pentru obiecte "LinkedStack" metode mostenite de la superclasã. Superclasa transmite subclasei o mare parte din functiile sale. In Java nu se face aceastã diferentã doarece obiectul compus contine referinte la obiectele componente. pe baza observatiei cã lista de valori asociatã unei chei (de tip List) este tot un obiect (compatibil cu tipul Object) si deci se poate pãstra interfata publicã a clasei HashMap: public class MultiMap extends HashMap { public Object put (Object key. Object val) { List lst = (List) get(key). // adauga prima valoare la lista return result. } } De observat cã subclasa “LinkedStack” mosteneste metodele "toString". // rezultatul va fi lista anterioara if (lst==null) // daca cheia key nu exista in dictionar super.Florian Moraru – Programare Orientata pe Obiecte Delegarea se mai numeste “agregare” sau “compozitie”: clasa agregat (compusã) contine unul sau mai multe obiecte cãtre care deleagã anumite operatii. dar clasa LinkedList foloseste alte nume pentru operatiile respective.

O situatie care apare la utilizarea unor API standard. Un alt exemplu este cel al claselor flux de date cu sumã de control. definit prin interfata Node) nu putem folosi derivarea si rãmâne doar delegarea. In plus. care va primi la instantiere adresa obiectului ce contine metoda de calcul a sumei de control.update(b).a. // adresa obiect cu metoda de calcul suma control public CheckedOutputStream(OutputStream out. . care contin o variabilã interfatã (Checksum). la care adaugã câteva metode specifice noii clase. clase de intrare-iesire s. Derivarea din clase instantiabile sau abstracte este solutia folositã în cazul unor infrastructuri de clase (“Frameworks”) pentru crearea unor ierarhii de tipuri compatibile: clasele colectii de Object. AbstractList.a.). . Exemplu: public class CheckedOutputStream extends FilterOutputStream { private Checksum cksum. dar interfetele celor douã clase sunt diferite.O clasã pentru o interfatã graficã Swing extinde de obicei clasa JFrame de la care mosteneste metode de adaugare a unor componente Swing. metoda “paint” dintr-o subclasã a clasei JApplet. Dacã vrem sã redefinim o metodã dintr-o astfel de clasã anonimã (cum ar fi metoda “toString” asociatã unui nod de arbore DOM. Derivarea (un B este un fel de A) se recomandã atunci când vrem sã reutilizãm o mare parte din (sau toatã) interfata clasei A si pentru clasa B. In plus. } public void write(int b) throws IOException { out. AbstractMap) o mare parte din metode. Exemple: .O clasã ce defineste un fir de executie Java nu se poate defini decât prin extinderea clasei de bibliotecã Thread sau prin implementarea interfetei Runnable. fie prin delegare (prin apelarea metodelor obiectului continut). Un exemplu este cel al claselor flux de I/E care deleagã operatiile elementare de citire/scriere de caractere individuale cãtre obiecte ce depind de suportul fizic al fluxului de date (fisier disc. iar o altã parte prin metode ale unor clase definite de programatori si care este specificã fiecãrei aplicatii (sau pãrti de aplicatie). Aceste exemple (si altele) pot fi interpretate si astfel: o parte din functionalitatea unei aplicatii este realizatã prin metode ale unor clase predefinite (de bibliotecã). Delegarea se foloseste la definirea unor clase adaptor de la o interfatã la o altã interfatã dar si atunci când tipul obiectului delegat se poate modifica la executie (adresa obiectului este primitã de constructorul fiecãrui obiect). etc.O clasã aplet (sau servlet) este definitã de programator prin extinderea unei clase de bibliotecã (JApplet. cksum. this. Atât derivarea cât si compozitia permit reutilizarea metodelor unei clase. Legãtura dintre un obiect flux si obiectul de calcul a sumei este stabilitã la executie si asigurã o flexibilitate sporitã. de stabilire asezare (“Layout”) si de vizualizare.Florian Moraru – Programare Orientata pe Obiecte Comparatie între compozitie si derivare Delegarea (un B contine un A) se recomandã atunci când vrem ã folosim (sã reutilizãm) functionalitatea unei clase A în cadrul unei clase B. // metoda din interfata Checksum } 108 .cksum = cksum. de exemplu) de la care mosteneste metode absolut necesare functionãrii sale ca aplet. derivarea creeazã tipuri compatibile: putem sã înlocuim o variabilã sau un argument de tipul A printr-o variabilã (sau argument) de tipul B . In felul acesta clasele definite de programatori pot mosteni un numãr mare de metode (si date) cu un efort minim de programare. Checksum cksum) { super(out). . este aceea cã nu se cunosc decât nume de interfete si nu se cunosc nume de clase care implementeazã aceste interfete (pot fi mai multe astfel de implementãri si chiar unele care urmeazã sã aparã în viitor). unele din metodele definite de programatori trebuie sã respecte anumite interfete impuse. pentru cã sunt apelate din alte clase predefinite (metoda “run” dintr-o subclasã a clasei Thread. s.).write(b). o zonã de memorie. fie prin mostenire.O nouã clasã colectie poate mosteni de la AbstractCollection (AbstractSet. cum ar fi DOM (XML).

f1().read(). care extinde clasa AbstractSet si contine o variabilã de tip ArrayList : public class ArraySet extends AbstractSet { private ArrayList set. Exemplu de mostenire functii de la 3 clase: class A { public void f1 () { System. m.f2"). } } Un exemplu real de mostenire multiplã poate fi o clasã pentru o multime realizatã ca vector.f2().f1"). fie sunt mostenite de la clasa A. a si b se poate face intr-un constructor private B b = new B (). // initializarea var.. Mai mult. nici nu se recomandã accesul direct la orice element dintr-o stivã (prin metodele clasei Vector). // o clasa care implementeaza interfata Checksum CheckedOutputStream out. 109 . } } class C { public void f3 () { System. m. } // Exemplu de utilizare CRC32 Checker = new CRC32(). } } class M extends C { private A a = new A (). } } class B { public void f2 () { System. Clasa A este de multe ori o clasã abstractã.. out = new CheckedOutputStream (new FileOutputStream("date").out.} // delegare obiect a pentru operatia f1 public void f2 () { b. In Java o subclasã nu poate avea decât o singurã superclasã. metodele din M fie apeleazã metode din clasa B. Checker). } Colectiile Google folosesc mai mult delegarea ca metodã de reutilizare. iar clasa B este instantiabilã sau abstractã.out. dar uneori alegerea între compozitie si derivare nu este evidentã si chiar a condus la solutii diferite în biblioteci de clase diferite.f1().write(c).println ("B. comparativ cu clasele colectie din Java care se bazeazã mai mult pe derivare.Florian Moraru – Programare Orientata pe Obiecte .f3().f2(). Un exemplu este cel al claselor pentru vectori si respectiv pentru stive. O clasã M poate prelua metode de la douã clase A si B astfel: clasa M extinde pe A si contine o variabilã de tip B. Ce este o stivã ? Un caz particular de vector sau un obiect diferit care contine un vector ? In Java clasa Stack este derivatã din clasa Vector.println ("A. dar uneori este necesar ca o clasã sã preia functii de la douã sau mai multe clase diferite. desi un obiect stivã nu foloseste metodele clasei Vector ( cu exceptia metodei "toString").println ("C. m. out. public void f1 () { a.out. while (in. De cele mai multe ori metoda de reutilizare a unor clase se impune de la sine.available() > 0) { int c = in.} // delegare obiect b pentru operatia f2 } class X { public static void main (String arg[]) { M m = new M(). Implementarea mai multor interfete de cãtre o clasã nu este o solutie pentru mostenirea multiplã de functii.f3").

unde A este o clasã instantiabilã: class B extends A { private A a . Un model de listã este un vector cu posibilitãti de generare evenimente (de apelare receptori) la modificãri operate în vector.add(obj).a=a. Combinarea delegãrii cu derivarea îmbinã avantajele ambelor metode de reutilizare: mostenirea interfetei si posibilitatea modificãrii obiectului delegat. în acelasi timp. ca un ambalaj pentru acel obiect. De exemplu. // delegare pentru operatia de adaugare return false. Combinarea compozitiei cu derivarea Derivarea pãstreazã interfata clasei de bazã. Clasa anvelopã care urmeazã este compatibilã cu tipul List si. removeAll.Florian Moraru – Programare Orientata pe Obiecte public ArraySet() { set = new ArrayList().a.obj). deoarece "decoreazã" cu noi functii o clasã existentã.. deoarece adaugã functii obiectului continut. Totusi nu se justificã o constructie de forma urmãtoare. foloseste metode definite în clasa AbstractList : class StackList extends AbstractList { private AbstractList stack. // adresa obiect stiva public StackList (List list) { // constructor stack=(AbstractList)list. putem defini o clasã stivã mai generalã.size(). containsAll. // metode ale clasei B } Clasele ce contin un obiect de acelasi tip sau de un tip compatibil cu al superclasei sale se mai numesc si clase “anvelopã” (“wrapper”). care sã poatã folosi fie un vector. O clasa anvelopã este numitã si clasã “decorator”. 110 . } } Pentru multimi de tipul "ArraySet" se pot folosi toate metodele mostenite de la clasa AbstractSet: toString. addAll. clasa DefaultListModel preia metode de la superclasa AbstractListModel si deleagã unei variabile interne de tip Vector operatii cu un vector de obiecte. de exemplu. // delegare pentru creare obiect iterator } public int size() { return set. public B (A a) { this. } public boolean add (Object obj) { if (! set.. retainAll s. fie o listã înlãntuitã. } public Iterator iterator() { return set. Uneori variabila din subclasã este chiar de tipul superclasei. iar delegarea permite mai multã flexibilitate la executie.add (0. // retine adresa obiect stiva } public Object push (Object obj) { stack. dupã cum doreste programatorul. contains. } .contains(obj) ) return set. tip clasã abstractã sau interfatã.iterator(). Aceeasi solutie de mostenire multiplã este folositã în câteva clase JFC (Swing) de tip “model”.

dacã nu se defineau metodele "add". 111 . } Incercarea de adãugare a unui nou obiect la o colectie nemodificabilã produce o exceptie la executie.add(o). } public int size() { return stack. // metode care nu modifica continutul colectiei public int size() {return c. stack. dar compatibile cu acestea ca tip. } public Object pop () { Object obj= get(0). // metode care ar putea modifica continutul colectiei public boolean add (Object o){ throw new UnsupportedOperationException().} public boolean contains(Object o) {return c. } } Exemple de combinare a derivãrii si compozitiei se gãsesc în clasa Collections. pentru definirea de clase colectie cu functionalitate putin modificatã fatã de colectiile uzuale.. } public boolean remove (Object o) { throw new UnsupportedOperationException().toString().. Exemplu: class UnmodifiableCollection implements Collection. atunci apelarea metodei "add" era semnalatã ca eroare la compilare. . return obj.a. Serializable { Collection c.} public boolean isEmpty() {return c.. Exceptia poate fi tratatã fãrã a împiedica executia programului.size().size().size(). "remove" s. } .} } . care diferã de colectiile generale prin interzicerea operatiilor ce pot modifica continutul colectiei. . // colectia de baza public int size() { synchronized(this) {return c.contains(o).} } public boolean add(Object o) { synchronized(this) {return c.Florian Moraru – Programare Orientata pe Obiecte return obj.. // alte metode } In realitate. în clasa derivatã.} . Al doilea grup de clase sunt clasele pentru colectii sincronizate. clasele prezentate sunt clase incluse statice si sunt instantiate în metode statice din clasa Collections: static class UnmodifiableList extends UnmodifiableCollection implements List { private List list.remove(obj). Serializable { Collection c.} public String toString() {return c. Exemplu: class SynchronizedCollection implements Collection.isEmpty(). Primul grup de clase este cel al colectiilor nemodificabile.

Principalele clase filtru de intrare-iesire sunt: FilterInputStream. BufferedWriter. PipedWriter. citire-scriere de numere în format intern. Dacã s-ar fi utilizat derivarea pentru obtinerea claselor direct utilizabile atunci ar fi trebuit generate.Buffer de siruri (în memorie): StringBufferInputStream. // alte metode } public static List unmodifiableList (List list) { return new UnmodifiableList(list). care sunt numite si clase “filtru” de intrare-iesire."while". . Dupã facilitãtile oferite avem de ales între: . List list = Collections.a. CharArrayWriter.unmodifiableList (Arrays. “write”). din care sunt derivate clase care adaugã operatii specifice (de “prelucrare”): citire de linii de text de lungime variabilã. FilterOutputStream si FilterReader. scriere numere cu conversie de format s. combinatii ale celor 8 clase cu cele 4 optiuni.synchronizedSet (new HashSet()). "do". Writer si celelalte sunt clase abstracte. Clasele de tip filtru sunt clase intermediare. .list = list. FileOutputStream. Combinarea celor 8 clase sursã/destinatie cu optiunile de prelucrare asociate transferului de date se face prin intermediul claselor anvelopã . BufferedOutputStream. .a. deci 32 de clase (practic.. . "for"}.Citire-scriere pe octeti dar cu zonã buffer: BufferedInputStream.Citire însotitã de numerotare automatã a liniilor citite (LineNumberInputStream). . Dupã suportul fizic al fluxului de date se poate alege între: . Numãrul de clase instantiabile de I/E este relativ mare deoarece sunt posibile diverse combinatii între suportul fizic al fluxului de date si facilitãtile oferite de fluxul respectiv.Fisiere disc : FileInputStream. } Exemple de utilizare a claselor colectie "speciale" : String kw[] ={"if".Florian Moraru – Programare Orientata pe Obiecte UnmodifiableList(List list) { super(list). PipedReader.Citire cu punere înapoi în flux a ultimului octet citit (PushBackInputStream).io”. deoarece unele optiuni nu au sens pentru orice flux). FilterWriter. Toate clasele flux de intrare sunt subtipuri ale tipului InputStream (Reader) si toate clasele flux de iesire sunt subtipuri ale tipului OutputStream (Writer). cu citire-scriere de caractere. . FileReader. ByteArrayOutputStream CharArrayReader. Pentru a folosi mai multe optiuni cu acelasi flux ar fi trebuit mai multe niveluri de derivare si deci ar fi rezultat un numãr si mai mare de clase. . PipedOutputStream. "else".asList(kw)). } . Solutia claselor anvelopã permite sã se adauge unor clase de bazã diverse functii în diferite combinatii. prin derivare. StringBufferOutputStream.Canal pentru comunicarea sincronizatã între fire de executie : PipedInputStream. Set s = Collections. this. FileWriter s. DataOutputStream.Citire-scriere la nivel de linie si pentru numere de diferite tipuri (fãrã conversie): DataInputStream. Existã douã familii de clase paralele : familia claselor “flux” (“Stream”) cu citire-scriere de octeti si familia claselor “Reader-Writer”. O clasã anvelopã de I/E contine o variabilã 112 . mai putine. .Vector de octeti sau de caractere : ByteArrayInputStream. Clase de intrare-iesire cu derivare si delegare Combinarea derivãrii cu delegarea a fost folositã la proiectarea claselor din pachetul “java. Clasele Reader. BufferedReader. .Citire-scriere la nivel de octet sau bloc de octeti (metode “read”.

Exemplu de citire linii . Decorarea unei clase flux de I/E se poate face repetat. PushbackInputStream. care va fi înlocuitã cu o variabilã de un tip flux concret (FileOutputStream. Un decorator nu adaugã functii (metode) noi clasei decorate dar modificã actiunea unor metode existente prin delegare cãtre metode ce provin din obiectul transmis ca argument la construirea unui obiect din clasa decorator (si care înlocuieste o variabilã de tip interfatã din clasa decorator). } De obicei nu se mai folosesc variabile intermediare la construirea unui obiect flux. Nu se pot crea obiecte de tipul FilterInputStream deoarece constructorul clasei este de tip protected.).. // alte metode } Metoda "read" este o metodã polimorficã. // adresa obiectului "flux" } // citirea unui octet public int read () throws IOException { return in.in=in.out.. dar se pot crea obiecte din subclase ale clasei FilterInputStream. BufferedInputStream. Cea mai folositã este clasa DataInputStream care adaugã metodelor de citire de octeti mostenite si metode de citire a tuturor tipurilor primitive de date: "readInt".println (lnis. LineNumberInputStream lnis= new LineNumberInputStream (bis). Clasele filtru de I/E fac parte din categoria clasele “decorator”. iar selectarea metodei necesare se face în functie de tipul concret al variabilei "in" (transmis ca argument constructorului). DataInputStream dis = new DataInputStream (fis).. protected FilterInputStream (InputStream in) { // constructor (in clasa abstracta) this. sau “deleagã” operatia de citire cãtre clasa folositã la construirea obiectului filtru.getLineNumber()+" "+linie). String linie. care aplicã diverse “decoratiuni” (functionalitãti) unor clase existente. astfel pentru a citi linii dintr-un fisier folosind o zonã tampon si cu numerotare de linii vom folosi urmãtoarea secventã de instructiuni: public static void main (String arg[]) throws IOException { FileInputStream fis= new FileInputStream (arg[0]). LineNumberInputStream sunt derivate din clasa FilterInputStream si contin metode de prelucrare a datelor citite. // citirea depinde de tipul fluxului } .. dintr-un fisier disc: 113 . readFloat". La crearea unui obiect de tipul DataInputStream constructorul primeste un argument de tipul InputStream (sau un tip derivat direct din InputStream sau din FilterInputStream) prin care se specificã suportul fizic al datelor si modul concret de citire din flux. Pentru citire linii dintr-un fisier disc metoda “readLine” deleaga citirea de octeti cãtre metoda “read” din FileInputStream: FileInputStream fis= new FileInputStream (“t. . etc. BufferedInputStream bis = new BufferedInputStream (fis). la construirea unui obiect de un tip flux direct utilizabil. DataInputStream dis = new DataInputStream (lnis).readLine()) != null) System.read (). Clasa FilterInputStream este derivatã din InputStream si contine o variabilã de tip InputStream: public class FilterInputStream extends InputStream { protected InputStream in. "readLine". "readBoolean". while ( (linie=dis. Metoda “read” din FilterInputStream preia functionalitatea metodei “read” din clasa delegat.Florian Moraru – Programare Orientata pe Obiecte de tipul abstract OutputStream sau InputStream. cu buffer.txt”). Clasele DataInputStream.

btc = btc. Codul urmãtor ilustreazã esenta clasei adaptor: public class InputStreamReader extends Reader { private ByteToCharConverter btc. metode cu numele “print” sau “println”. private InputStream in. this. pentru a putea folosi metode ca "readLine" si altele.in = in. // conversie din byte in char return ch. off.readLine()) != null) System. // caracter coresp. len). } public int read(char cbuf[ ]. this. this.getDefault()). } public void close() throws IOException { in. int off. int len) throws IOException { return in.read(). Clasele adaptor extind o clasã Reader (Writer) si contin o variabilã de tip InputStream (OutputStream). } public int read() throws IOException { int byte = in.read(cbuf.println ( linie). Constanta “System.out” este de tipul PrintStream si corespunde ecranului.out.in” este de un subtip al tipului InputStream si corespunde tastaturii. private InputStreamReader(InputStream in. ByteToCharConverter btc) { super(in).in = in. // citire octet char ch = btc.read(). octetului citit } } 114 . } public InputStreamReader(InputStream in) { this(in. iar constanta “System. String linie. } } Clasele PrintStream si PrintWriter adaugã claselor filtru metode pentru scriere cu format (cu conversie) într-un flux de iesire. o parte din operatiile impuse de superclasã sunt realizate prin apelarea operatiilor pentru variabila flux (prin “delegare”). Si familia claselor Reader-Writer foloseste clase decorator: public abstract class FilterReader extends Reader { protected Reader in. ByteToCharConverter. Clasa BufferedReader adaugã clasei Reader o metodã “readLine” pentru a permite citirea de linii din fisiere text. Clasele InputStreamReader si OutputStreamWriter sunt clase adaptor între clasele “Stream” si clasele “Reader-Writer”.convert(byte). } public int read() throws IOException { return in. while ( (linie=dis. Este deci un alt caz de combinare între extindere si agregare.close(). Un obiect InputStreamReader poate fi folosit la fel ca un obiect Reader pentru citirea de caractere.Florian Moraru – Programare Orientata pe Obiecte public static void main (String arg[ ]) throws IOException { DataInputStream dis = new DataInputStream ( new BufferedInputStream (new FileInputStream (arg[0]))). } Ordinea în care sunt create obiectele de tip InputStream este importantã : ultimul obiect trebuie sã fie de tipul DataInputStream. dar în interior se citesc octeti si se convertesc octeti la caractere. protected FilterReader(Reader in) { super(in).

Florian Moraru – Programare Orientata pe Obiecte 10. alte clase numite clase incluse (“nested classes”).Entry: public class ArrayMap extends AbstractMap { // clasa interioara static class Entry implements Map. In cazul unei singure clase incluse. Object put (Object key.. } } // alti membri ai clasei ArrayMap private ArraySet entries .} public Object getKey() { return key. Un exemplu de interes pentru definirea de noi clase dictionar este interfata Entry.. structura va arãta astfel: public class Outer { .Entry { private Object key. // valoarea Object setValue(Object value).. // alti membri ai clasei Outer } Clasele incluse cu nume primesc de la compilator un nume compus din numele clasei exterioare. sau o clasã abstractã sau o interfatã. cu metode asociate unei perechi cheie-valoare. return v. ambele de tip Object: public interface Map { Object get (Object key). // date si/sau metode ale clasei Inner } ..val..getKey(). ca membri ai clasei. } public String toString() { return key+"="+val. // alte metode abstracte din interfata Map public interface Entry { // acces la cheia si valoarea dintr-o pereche Object getKey(). Object v) { key=k. } public Object getValue() { return val.. Clasa inclusã poate fi si o clasã staticã. // date si/sau metode ale clasei Outer public class Inner { . // modifica valoarea boolean equals(Object o). public Entry (Object k. . Clasa inclusã “Entry”implementeazã interfata inclusã Map. // multime de perechi cheie-valoare public ArrayMap (int n) { // constructor 115 .. inclusã în interfata Map.} public Object setValue (Object v) { val=v. Clase incluse Clase incluse O clasã Java poate contine.} public boolean equals (Object obj) { return ((Entry)obj). // daca doua perechi sunt egale } } In exemplul urmãtor clasa inclusã “Entry” este folositã numai în definitia clasei “ArrayMap”. val=v. caracterul ‘$’ si numele clasei interioare (Outer$Inner). Object value). Clasele care nu sunt incluse în alte clase se numesc clase de nivel superior (“top-level classes”).equals(key).. // cheia Object getValue().

} Clasa interioarã staticã ReverseComparator din clasa Collections. In acest fel clasa iterator are acces la variabile private ale colectiei. while (i. return key. nu poate fi instantiatã direct ci numai prin intermediul colectiei (nu poate exista obiect iterator fãrã o colectie).add (new Entry(key. } private static final Comparator REVERSE_ORDER = new ReverseComparator(). clasã interioarã: public class Vector extends AbstractList implements List. Exemplu: public Object get(Object key) { Iterator i = entrySet(). Cloneable { protected int count.iterator().Serializable { public int compare(Object o1. } } Tipul Entry este folosit în mai multe metode ale clasei AbstractMap. } public Object put ( Object key. // vector de obiecte 116 . // nr de elemente in vector protected Object elementData[]..getValue().getKey())) return e. private static class ReverseComparator implements Comparator. } } .next(). cuvântul “inner” se referã la relatia dintre obiectele claselor incluse: un obiect al clasei exterioare contine în el un obiect al clasei incluse.compareTo(o2). Object o2) { Comparable c1 = (Comparable)o1. } public Set entrySet () { return entries. Object value) { entries.value)).Florian Moraru – Programare Orientata pe Obiecte entries = new ArraySet(n). Exemplu de iterator pe vector.equals(e. Cuvântul “nested” se referã la relatia sintacticã dintre clase: clasa inclusã este definitã în cadrul definitiei clasei exterioare.. if (key.hasNext()) { Entry e = (Entry) i. este folositã de metoda staticã “reverseOrder” prin intermediul unei variabilei statice: public class Collections { public static Comparator reverseOrder() { // din clasa Collections return REVERSE_ORDER. // alte metode si clase incluse din clasa Collections } Clase interioare O clasã inclusã nestaticã este numitã si clasã interioarã (“inner class”). Un exemplu este o clasã iterator inclusã în clasa colectie pe care actioneazã. fiind ascunsã altor clase. pentru cã fiecare obiect din clasa exterioarã va contine un obiect din clasa interioarã. } return null. return -c1.

} } // . 117 . fãrã a scoate obiecte din stivã (fãrã a folosi o altã stivã). else return null. atunci variabila comparator primeste o valoare implicitã. } } Un exemplu din Java Tutorial aratã cã uneori iteratorul trebuie sã aibã acces la datele colectiei (private).compareTo(c2). . alte metode din clasa Vector } Clasa iterator se poate defini si ca o clasã top-level.size(). Object e2) { Comparable c1=(Comparable)e1. Este cazul unui iterator pe o stivã vector.v=v. dacã primeste o referintã la clasa colectie (ca parametru în constructor) si foloseste metode publice ale colectiei: class VectorEnum implements Enumeration { private int count = 0. Dacã se foloseste constructorul fãrã argumente. return c1. care face o enumerare a elementelor stivei de la vârf spre baza stivei. } public Object nextElement() { if (count < elementCount) return elementData[count++]. Comparable c2=(Comparable)e2. ea este utilã numai clasei în care este definitã (clasa exterioarã "SortedArray"): public class SortedArray extends ArrayList { // clasa interioara private class DefaultComp implements Comparator { public int compare (Object e1. // indice element curent din enumerare private Vector v. public VectorEnum (Vector v) { this. ca referintã la un obiect comparator dintr-o clasã interioarã. Clasa "DefaultComp" nu mai trebuie definitã de utilizatori si transmisã din afarã. nefiind suficiente metodele publice de acces la acestea. In exemplul urmãtor o clasã pentru un vector ordonat ("SortedArray") contine o variabilã comparator ("cmp").Florian Moraru – Programare Orientata pe Obiecte // metoda care produce obiect iterator public Enumeration elements() { return new VectorEnumeration() } // definirea clasei iterator pe vector (inclusa) class VectorEnumeration implements Enumeration { int count = 0. } public boolean hasMoreElements() { return count < v. // indice element curent din enumerare public boolean hasMoreElements() { return count < elementCount. } public Object nextElement() { if (count < v. . care poate fi initializatã de un constructor al clasei.elementAt(count++).size()) return v.

.getValue()).add(k.out.obj.out. B si C 118 . Exemplu de clasã pentru un obiect comparator inclusã în functia de ordomare: // ordonarea unui dictionar în ordinea valorilor din dictionar (nu a cheilor) static void sortByValue (Map m) { // clasa inclusa class VComp implements Comparator { // compara doua perechi cheie-val public int compare (Object o1. // comparator implicit public SortedArray () { super().f3"). // ordonare vector System.println ("B.Entry)o2. return true.cmp).Pentru reducerea numãrului de clase de nivel superior si deci a conflictelor între nume de clase (ca urmare a unor instructiuni “import” pentru pachete în care se aflã clase cu nume identice).getValue()).Pentru clase de interes local : clasa interioarã este necesarã numai clasei exterioare. Exemplu: class A { void f1 () { System.binarySearch (this.Entry e2= (Map. } } class B { void f2 () { System. } } Set eset = m.f1"). Aceste mosteniri se pot combina în obiecte ale clasei exterioare. pentru care se pot folosi metode mostenite de toate clasele incluse.Entry)o1. } public boolean add (Object obj) { int k=indexOf(obj).println (entries). } } class M extends C { // M preia metode de la clasele A. // multime de perechi cheie-valoare ArrayList entries = new ArrayList(eset). // alta pereche cheie-valoare return ((Integer) e1.Pentru ca o clasã sã poatã mosteni functii de la câteva clase (mostenire multiplã). new VComp()). } } class C { void f3 () { System.} public SortedArray (Comparator comp) { super(). if (k < 0) k= -k-1. super. cmp=comp.out. } public int indexOf (Object obj) { return Collections. .Florian Moraru – Programare Orientata pe Obiecte } } // alti membri ai clasei SortedArray Comparator cmp=new DefaultComp(). } O altã formã de clasã interioarã este o clasã definitã într-o metodã a clasei externe.compareTo ((Integer) e2.Pentru a permite claselor interioare accesul la variabile ale clasei exterioare si deci o comunicare mai simplã între clasele incluse (prin variabile externe lor).out. O clasã interioarã poate mosteni de la o implementare (nu de la o interfatã) în mod independent de mostenirea clasei exterioare si de mostenirile altor clase incluse. // afisare perechi ordonate dupa valori } Motivele definirii de clase interioare pot fi diverse: .Pentru a permite clasei exterioare accesul la membri private ai clasei interioare.sort (entries. // o pereche cheie-valoare Map.println ("C.f2").println ("A. // vector de perechi cheie-valoare Collections.Entry e1= (Map. obj). .entrySet(). Object o2) { Map. .

next=null. public Node (Object ob) { val=ob.f1(). // var. } } 119 .next.i<k. // inceput lista private int n.f2(). din clasa inclusa while (p. din clasa inclusa p.Florian Moraru – Programare Orientata pe Obiecte class AltA extends A { } class AltB extends B { } void f1 () { new AltA(). public class SimpleList extends AbstractList { // clasa pentru liste simplu inlantuite // clasa inclusa in clasa SimpleList class Node { private Object val.val. Node p=head. din clasa inclusa for (int i=0. din clasa inclusa return p. // var.next.next=nou. din clasa inclusa } public boolean add (Object el) { // adauga la sfarsit daca nu exista deja Node nou = new Node(el). m. m. // var.next != null) // var.f1().f2(). din clasa inclusa p=p. } public Object get (int k) { if ( k > n) return null. // santinela n=0. // var. return true. Node p =head.next. } void f2 () { new AltB(). } } // variabile ale clasei SimpleList private Node head. } // clasa inclusa derivata din A // clasa inclusa derivata din B // metoda a clasei M // metoda a clasei M } class X { public static void main (String arg[]) { M m = new M(). } } Pentru clasa M se pot apela functii mostenite de la clasele A. B si C la care se pot adãuga si alte functii suplimentare. Simplificarea comunicãrii între clase In exemplul urmãtor metodele clasei exterioare “SimpleList” se pot referi direct la variabile din clasa inclusã “Node” (si nu prin intermediul unor metode publice). din clasa inclusa n=n+1. m. // nr de noduri in lista // functii membre ale clasei SimpleList public SimpleList () { head= new Node(null). } public int size() { return n. // var.i++) p=p. // var.f3(). private Node next.

din clasa Node pos=pos. Urmeazã mai întâi solutia cu clase de acelasi nivel: class Producer { // proces producator Queue q. // alti membri ai clasei exterioare: clase. Obiectul coadã este folosit atât de obiectul producãtor cât si de obiectul consumator. } } class Consumer { // proces consumator Queue q.val. Instantierea clasei interioare se face prin metoda “iterator” din clasa exterioarã: public class SimpleList extends AbstractList { private Node head.q=q. } public void remove () { throw new UnsupportedOperationException(). .next. Includerea a douã clase A si B într-o aceeasi clasã C permite simplificarea transmiterii de date între clasele A si B prin variabile ale clasei C. // var. accesul direct la aceste variabile este simplificat dacã se include clasa iterator în clasa colectie. } } // metoda a clasei SimpleList public Iterator iterator () { return new SListIterator().Florian Moraru – Programare Orientata pe Obiecte O clasã iterator poate lucra cu variabile ale clasei colectie pe care face enumerarea. alãturi de obiectul coadã. } public Object next() { Object obj =pos.Includerea claselor producãtor si consumator într-o clasã gazdã. } 120 . pentru acoperirea diferentei dintre ritmul de producere si ritmul de consumare a mesajelor.q=q. // var. // cursor= adresa nod curent SListIterator () { // constructor pos=head. variabilele fiind componente ale instantei curente.Transmiterea unei referinte la acest obiect la construirea obiectelor producãtor si consumator . public Consumer (Queue q) { this. // var “head” din clasa SimpleList } public boolean hasNext () { return pos != null. } public void put(Object x) { // pune un obiect in coada q. Avem cel putin douã solutii pentru a permite accesul la obiectul comun coadã: .add(x). } .next.. din clasa Node return obj. // inceput lista // clasa iterator inclusa class SListIterator implements Iterator { private Node pos. public Producer (Queue q) { this.. variabile. O astfel de situatie apare în cazul a douã clase cu rol de producãtor si respectiv de consumator care-si transmit date printr-o coadã de obiecte. metode } Clasele interioare cu date comune O clasã inclusã într-o altã clasã este un membru al acelei clase si are acces la ceilalti membri ai clasei (variabile si metode).

add(new Byte(k)). return q.Florian Moraru – Programare Orientata pe Obiecte public Object get() { if ( q. Producer p=new Producer(q).out. dar se pot defini ad-hoc clase incluse anonime. // obiectul coada static Producer p . // proces consumator static byte k=1.out.del(). folosind cuvântul class.put(new Integer(k)). q Consumer c=new Consumer(q). // obiectul p se refera la ob. break. case 1: c. c=new Consumer().println("Consumator " + " scoate " + q. } } } Clase interioare anonime In exemplele anterioare clasele incluse au fost definite explicit. deoarece nu se folosesc cuvintele extends sau implements). switch (r) { case 0: p.isEmpty()) return null.get()). k++.random()*2)) { case 0: p. k++. // activare consumator } } // daca coada goala Pentru comparatie urmeazã solutia cu clase interioare. k=0. while ( k <=20) switch ((int)(Math. break.del()). } } // simulare procese producator-consumator class Simulare { public static void main(String[] args) throws Exception { int qm=0. Clasele incluse au fost declarate statice pentru a putea fi instantiate din metoda staticã “main”. p=new Producer().println(c. break. // proces producator static Consumer c . class Simulare { static class Producer { // clasa inclusa public void run() { q. Integer x. r.run(). // simulare procese producator-consumator public static void main(String[] args) { q= new Queue(). // activare producator case 1: System.run().ql. // obiectul c se refera la ob. break. Queue q= new Queue(). } } static Queue q .isEmpty()) System.random() * 2)). q while ( k < 21) { r= ((int)(Math. 121 . pe baza unei alte clase sau a unei interfete (prin extindere sau prin implementare implicitã. } } static class Consumer { // clasa inclusa public void run() { if ( ! q.

val. In plus. Sintaxa definirii unei clase anonime este urmãtoarea: new Interf ( ) { . } }. în care clasa iterator pentru liste este anonimã si este definitã în expresia care urmeazã lui new. pentru a crea un singur obiect de acest tip. new Comparator( ) { // ordonare descrescatoare public int compare (Object t1. return obj. } 122 .. pos=pos. Comparable c2=(Comparable)e2.obj. } public int indexOf (Object obj) { return Collections.sort (list. public boolean hasNext () { return pos != null.next.cmp). // rezultat invers metodei compareTo } }). return . Object e2) { Comparable c1=(Comparable)e1. public Iterator iterator () { return new Iterator() { // definirea clasei anonime iterator ca o clasa inclusa private Node pos=head. Exemplu de sortare a unei liste de obiecte în ordine descrescãtoare Collections. Object t2) { // incepe definitia clasei anonime Comparable c1=(Comparable)t1.} public SortedArray (Comparator comp) { super(). } . printr-un bloc care urmeazã operatorului new cu un nume de interfatã sau de clasã abstractã.. O situatie tipicã pentru folosirea unei clase anonime definitã simultan cu crearea unui obiect de tipul respectiv este cea în care transmitem unei functii un obiect de un subtip al interfetei Comparator (adresa unei functii de comparare). // aici se termina definitia clasei si instructiunea Alt exemplu de clasã comparator definitã ca o clasã interioarã anonimã: class SortedArray extends ArrayList { private Comparator cmp=new Comparator () { // comparator implicit public int compare (Object e1. // definitie clasa inclusa } . unde "Interf" este un nume de interfatã (sau de clasã abstractã sau neabstractã) din care este derivatã (implicit) clasa inclusã anonimã. O astfel de clasã nu poate avea un constructor explicit si deci nu poate primi date la construirea unui obiect din clasa anonimã.Florian Moraru – Programare Orientata pe Obiecte Uneori numele unei clase incluse apare o singurã datã..compareTo(c2). Pentru astfel de situatii se admite definirea ad-hoc de clase anonime.next. c2=(Comparable)t2. cmp=comp. return c1. public SortedArray () { super(). // alte metode } Iatã si o altã definitie a metodei “iterator” din clasa “SimpleList”. } public Object next() { Object obj =pos. clasa inclusã implementeazã o interfatã sau extinde o altã clasã si contine numai câteva metode scurte.compareTo(c2)..c1.binarySearch (this.

getValue().this. inclusiv argumente formale ale functiei definite prin blocul respectiv. } public Object next() { return ((Entry)i. acest cuplaj poate fi un dezavantaj la restructurarea (refactorizarea) unei aplicatii.this. Exemplul urmãtor este o functie similarã functiei “iterator” dintr-o clasã colectie.hasNext(). In prima variantã a acestei functii se defineste o clasã cu nume interioarã unui bloc si care foloseste un argument al functiei care contine definitia clasei. dar poate fi exploatat în definirea unor clase de bibliotecã (care nu se mai modificã). dar pot exista dificultãti la întelegerea codului si erori de utilizare a acoladelor si parantezelor.next()). Exemplu: public abstract class AbstractMap implements Map { public Collection values() { // o colectie a valorilor din dictionar if (values == null) { // ptr apeluri repetate ale metodei values values = new AbstractCollection() { // subclasa interioara anonima public Iterator iterator() { return new Iterator() { // alta clasa interioara anonima private Iterator i = entrySet(). // this = obiect din clasa anonima } public boolean contains(Object v) { return AbstractMap. // iterator pentru vectori intrinseci 123 . dar itereazã pe un vector intrinsec de obiecte. public static Iterator arrayIterator (final Object a[] ) { // clasa interioara functiei class AI implements Iterator { int i=0. Intre clasa interioarã si clasa exterioarã existã un "cuplaj" foarte strâns. } } Probleme asociate claselor incluse O clasã inclusã într-un bloc poate folosi variabile locale din acel bloc. deoarece aceastã variabilã este copiatã în fiecare instantã a clasei incluse si toate aceste copii trebuie sã aibã aceeasi valoare (nemodificatã în cursul executiei). } public void remove() { i. return new Iterator … } public int size() { // din subclasa lui AbstractCollection return AbstractMap. // aici se termina instr.size(). values= new … } return values.Florian Moraru – Programare Orientata pe Obiecte public void remove () { } }.containsValue(v). Orice variabilã sau parametru formal folosit într-o clasã inclusã trebuie declarat cu atributul final. // aici se termina instr. } }. public boolean hasNext() { return i.remove(). } }.iterator(). // sfârsit instructiune return new Iterator ( ) } // sfarsit metoda iterator Prin definirea de clase anonime codul sursã devine mai compact iar definitia clasei apare chiar acolo unde este folositã.

val$a = val$a. } public Object next() { return a[i++]. } public void remove() { throw new UnsupportedOperationException(). // C este numele clasei ce contine metoda } class C$1 implements Iterator { private Object val$a[]. Numele claselor incluse anonime sunt generate de compilator prin adãugarea la numele clasei exterioare a caracterului ‘$’si a unui numãr (a câta clasã inclusã este). fie sã implementeze o interfatã (ca în exemplul anterior) cu aceeasi sintaxã. i=0. } public boolean hasNext () { return i < val$a.length && a[i] != null . cu nume generate automat de compilator si transmise ca argumente constructorului clasei interioare. } public Object next() { return a[i++]. Pentru functia anterioarã compilatorul Java genereazã un cod echivalent de forma urmãtoare: public static Iterator arrayIterator (final Object a[]) { return new C$1(a).length. } public Object next() { return val$a[i++]. return } O clasã anonimã nu poate avea un constructor explicit si poate fie sã extindã o altã clasã. // aici se termina instr. OuterClass$1 (Object val$a[]) { // constructor this. sau sã implementeze mai multe interfete. O clasã definitã într-un bloc nu este membrã a clasei ce contine acel bloc si nu este vizibilã din afara blocului.} }. iar lista de argumente trebuie sã fie vidã. public boolean hasNext() { return i < a. } Clasa AI poate sã fie definitã ca o clasã inclusã anonimã deoarece acest nume nu este folosit în afara functiei “arrayIterator”. O clasã anonimã nu poate simultan sã extindã o altã clasã si sã implementeze o interfatã. } public void remove() { } // neimplementata } return new AI(). Exemplu de clasã anonimã definitã într-un bloc: public static Iterator arrayIterator (final Object a[] ) { // iterator pentru vectori intrinseci return new Iterator () { // urmeaza definitia clasei anonime int i=0. } public void remove() { throw new UnsupportedOperationException().Florian Moraru – Programare Orientata pe Obiecte public boolean hasNext() { return i < a.length && a[i] != null . Un nume de interfatã poate urma cuvântului cheie new numai la definirea unei clase anonime care implementeazã acea interfatã.} } 124 . Variabilele locale din clasa exterioarã sau din blocul exterior sunt copiate de compilator în câmpuri private ale clasei interioare. int i.

Containere de nivel superior (“top-level”) pentru fereastra principalã a aplicatiei. Majoritatea aplicatiilor actuale preiau datele de la operatorul uman în mod interactiv. . Colectia claselor GUI constituie un cadru pentru dezvoltarea de aplicatii cu interfatã graficã.Butoane de diverse tipuri: butoane simple.Componente pentru selectarea unei alternative (optiuni) . Clasele JFC asigurã 125 .Componente cu text de diverse complexitãti: câmp text. aspectul (“Look and Feel”) componentelor vizuale poate fi ales dintre trei (patru. iar modificarea unor caractere din text genereazã un alt tip de eveniment. Uneori. Componentele container sunt si ele de douã feluri: . incluse în alte containere si care permit operatii cu un grup de componente vizuale (de exemplu. Limbajul Java permite. Clase pentru o interfatã graficã Programarea unei interfete grafice (GUI) Comunicarea dintre un program de aplicatie si operatorul (beneficiarul) aplicatiei poate folosi ecranul în modul text sau în modul grafic.Componente “atomice”. dar existã o fereastrã initiala cu care începe aplicatia. folosind atât tastatura cât si mouse-ul pentru introducere sau selectare de date afisate pe ecran.Elemente de dialog . Multe din clasele JFC extind sau folosesc clase AWT. folosite ca atare si care nu pot contine alte componente (un buton este un exemplu de componentã atomicã). componentele vizuale sunt de douã categorii: . De exemplu.Componente “container”. Linux cu X-Windows etc). MacOS sau Java).Indicatoare de progres a unor activitãti de duratã . în sensul cã asigurã o bazã de clase esentiale si impune un anumit mod de proiectare a acestor aplicatii si de folosire a claselor existente. în mod text. butoane radio .Panouri cu derulare verticalã sau orizontalã (pentru liste sau texte voluminoase) . care grupeazã mai multe componente atomice si/sau containere. documente . Tipul evenimentelor este determinat de componenta vizualã implicatã dar si de operatia efectuatã. Programele cu interfatã graficã sunt controlate prin evenimente produse fie de apãsarea unei taste fie de apãsarea unui buton de mouse. Acest cadru (“Framework”) mai este numit si infrastructurã sau colectie de clase de bazã (“Foundation Classes”). programarea mai simplã si mai versatilã a interfetei grafice prin numãrul mare de clase si de facilitãti de care dispune. care reprezintã o evolutie fatã de vechile clase AWT (Abstract Window Toolkit). Un eveniment des folosit este cel produs de pozitionarea cursorului pe suprafata unui “buton” desenat pe ecran si clic pe butonul din stânga de pe mouse. pe parcursul programului se deschid si alte ferestre. Ca infrastructurã pentru aplicatiile Java cu interfatã graficã vom considera clasele JFC (Java Foundation Classes). numite si “controale” pentru cã permit operatorului sã controleze evolutia programului prin introducerea unor date sau optiuni de lucru (care. Interfata graficã cu utilizatorul (GUI = Graphical User Interface) este mai sigurã si mai "prietenoasã". . zonã text.Florian Moraru – Programare Orientata pe Obiecte 11. se transmit programului prin linia de comandã). pusã la dispozitie de sistemul gazdã (Microsoft Windows. într-un câmp cu text terminarea unei linii de text (cu “Enter”) genereazã un tip de eveniment. mai nou) variante (Windows.Meniuri si bare de instrumente In POO fiecare componentã a unei interfete grafice este un obiect dintr-o clasã predefinitã sau dintr-o subclasã a clasei de bibliotecã. printr-o interfatã graficã. fatã de alte limbaje. Componentele atomice pot fi grupate dupã rolul pe care îl au : . In termenii specifici Java. De exemplu. indiferent de sistemul de operare gazdã. pozitionarea întregului grup). O interfatã graficã simplã constã dintr-o singurã fereastrã ecran a aplicatiei pe care se plaseazã diverse componente vizuale interactive. numite si “Swing”.Containere intermediare (panouri).

300) frm. // sau frm. Controalele JFC pot fi inscriptionate cu text si/sau cu imagini (încãrcate din fisiere GIF sau definite ca siruri de constante în program).show(). atrãgãtoare si personalizate dupã cerintele aplicatiei si ale beneficiarilor. AbstractTableModel etc. JTextField. Un program minimal cu clase JFC. } } Fereastra principalã se afiseazã initial într-o formã redusã la bara de titlu cu cele 3 butoane generale. JColorChooser etc. JRadioButton. JCheckBox. DefaultTreeModel. JLabel. DefaultListSelectionModel. JInternalFrame. fãrã sã trateze evenimentele asociate acestor componente. navigatoare pe retea si alte utilitare folosite frecvent). fie pentru crearea de spatii controlabile între componente vecine. JTable. concepute conform arhitecturii MVC (“Model-View-Controller”): DefaultButtonModel. . JMenuBar. Clasele JFC container de nivel superior sunt numai trei: JFrame.setVisible (true). In jurul componentelor pot fi desenate borduri .Clase JFC noi sau extinse: JSlider. dupã care poate fi mãritã. se stabilesc dimensiunile ferestrei principale (metoda “setSize” sau “pack”) si se comandã afisarea ferestrei principale (metoda “setVisible” sau “show”). dar fãrã alte componente vizuale : import javax. se creazã componente atomice si se adaugã la panou obiectele grafice create de programator (cu metoda “add”). Fereastra principalã a aplicatiei este în general de tipul JFrame si contine o barã de titlu si trei “butoane” standard în coltul dreapta-sus al ferestrei pentru operatii de micsorare (minimizare). Toate celelalte clase JFC sunt subclase directe sau indirecte ale clasei JComponent. JTree. mãrire (maximizare) si închidere fereastrã .pack(). JProgressBar. fie pentru delimitarea lor. JFileChooser. JPanel. Primele douã sunt subclase (indirecte) ale clasei Window din AWT. JPopUpMenu.*. JSplitPanel. JScrollBar. Exemplul urmãtor afiseazã o fereastrã cu titlu. JButton. O parte din clasele JFC sunt folosite ca atare (prin instantiere) iar altele asigurã doar o bazã pentru definirea de clase derivate. reducând substantial efortul de programare a unor astfel de aplicatii (inclusiv editoare de texte. frm. . JToolBar.Clase JFC care au corespondent în clasele AWT. Cea mai mare parte dintr-un astfel de program creazã în memorie structurile de date ce contin atributele componentelor vizuale si relatiile dintre ele: se creazã un obiect fereastrã (panou). Clase JFC pentru interfatã graficã O altã clasificare posibilã a claselor Swing este urmãtoarea: .Florian Moraru – Programare Orientata pe Obiecte elementele necesare proiectãrii de interfete grafice complexe. JTabbedPane. JTextArea. // sau frm.setSize(500.Clase de tip “model”. JScrollPane. In exemplul urmãtor se extrage "panoul" ferestrei cu "getContentPane" si se adaugã o componentã JLabel (o etichetã) la acest panou: 126 .swing. JList. creazã si afiseazã componentele vizuale pe ecran. care constituie fundalul pentru celelalte componente. având aproape acelasi nume (cu prefixul 'J' la clasele JFC) si acelasi mod de utilizare: JComponent. class EmptyFrame { public static void main ( String args[]) { JFrame frm = new JFrame(“EmptyFrame”). inclusiv clasa container intermediar JPanel. JMenu. Adãugarea de componente vizuale la fereastra principalã JFrame se poate face în douã moduri. JDialog si JApplet. Pentru afisarea continutului ferestrei chiar de la început se poate folosi metoda “pack” (din clasa JFrame) sau metoda “setSize” (din clasa Container). In final. JComboBox.

127 . JTable s.add (panel).Gruparea componentelor atomice în containere intermediare.).show ().*.Crearea unui obiect fereastrã principalã.200).Adãugarea containerelor intermediare la fereastra aplicatiei si stabilirea modului de asezare a acestora. tip chenar etc. // un obiect “panou” panel. class LabelFrame { // fereastra cu o eticheta in ea public static void main (String args[]){ JFrame frame = new JFrame(). nu era posibilã adãugarea de componente atomice (JLabel. Efectuarea unui clic pe butonul de închidere al ferestrei principale (X) are ca efect închiderea ferestrei.*. // afisare continut fereastrã } } Dupã apelul metodei “setVisible” sau “show” nu mai trebuie create si adãugate alte componente vizuale ferestrei JFrame. de tip JFrame sau de un subtip al tipului JFrame.) . de ex. } } // fereastra cu o eticheta in ea // fereastra aplicatiei // creare eticheta // adauga eticheta la fereastrã // dimensiuni fereastra // afisare continut fereastrã Inainte de versiunea 6. sau terminarea programului de cãtre operator. Solutii de programare a interfetelor grafice In general. iar panoul este adãugat ulterior la fereastra JFrame: // adaugare la un panou introdus apoi in obiectul JFrame import javax. Incepând cu versiunea 1. frame.) direct la un obiect JFrame. // adauga eticheta la fereastra principala In exemplul urmãtor se creeazã un panou JPanel. extras din JFrame cu metoda “getContentPane”: frame.setVisible(true). chiar dacã se modificã date prezentate în unele din aceste componente (cum ar fi JList. culoare.Crearea componentelor atomice si stabilirea proprietãtilor acestora (dimensiuni.swing.) . // fereastra aplicatiei JPanel panel = new JPanel(). . care sunt obiecte de tip JPanel sau de un subtip al acestei clase.a. De aceea este necesarã tratarea acestui eveniment.setSize(200. pe care se plaseazã eticheta. prin Ctrl-C.swing. culoare.add(label). text afisat.setDefaultCloseOperation (JFrame. crearea si afisarea unei interfete grafice necesitã urmãtoarele operatii din partea programatorului aplicatiei: . ci numai la un obiect Container. frame. dacã nu se preferã modul implicit de dispunere în fereastrã. dar nu se terminã aplicatia dacã nu estre tratat evenimentul produs de acest clic. class LabelFrame { public static void main (String args[ ]){ JFrame frame = new JFrame(). // sau: frame.EXIT_ON_CLOSE). frame. si stabilirea proprietãtilor ferestrei (titlu.add(label).3 mai existã o posibilitate de terminare a aplicatiei la închiderea ferestrei principale: frame.add (new JLabel ("Eticheta")) . JLabel label = new JLabel ("Eticheta").setContentPane (panel). // adaugã eticheta la panou frame.Florian Moraru – Programare Orientata pe Obiecte import javax. dimensiuni etc.getContentPane(). frame.

swing. eventual. Vom prezenta în continuare trei variante uzuale de definire a clasei GUI în cazul simplu al unui câmp text însotit de o etichetã ce descrie continutul câmpului text.swing. class GUI2 { private JFrame frame. Exemplele anterioare nu reprezintã solutia recomandatã pentru programarea unei interfete grafice din urmãtoarele motive: . // txt1. prin definirea de clase de tip “ascultãtor” la evenimentele generate de componentele vizuale. init(). prin metoda “setVisible” sau “show” a clasei JFrame.*. // constructor public GUI2 ( String title) { frame = new JFrame(title). la apelarea unei metode pentru acel obiect.show().awt.Tratarea evenimentelor asociate componentelor si ferestrei principale.awt. // constructor public GUI1 ( String title) { super(title). continut de clasa GUI: import javax.*.*. private JTextField txt1 = new JTextField (16). } // initializare componente private void init() { setLayout(new FlowLayout()).show(). frame. private JTextField txt1 = new JTextField (16). init(). private JLabel lbl1 = new JLabel ("Directory"). add(txt1).Metoda staticã “main” trebuie redusã la crearea unui obiect si. setSize (300. inclusiv metode activate prin evenimente. setDefaultCloseOperation (JFrame. } // initializare componente private void init() { 128 . .100).Variabilele referintã la obiecte JFC nu vor fi locale metodei “main” pentru cã ele sunt folosite si de alte metode.*. import java. import java. class GUI1 extends JFrame { private JLabel lbl1 = new JLabel ("Directory").Florian Moraru – Programare Orientata pe Obiecte .EXIT_ON_CLOSE). } } Varianta a doua foloseste “delegarea” sarcinilor legate de afisare cãtre un obiect JFrame. . Prima variantã foloseste o subclasã a clasei JFrame: import javax. } // activare interfata grafica public static void main (String arg[]) { new GUI1("GUI solution 1").Afisarea ferestrei principale.addActionListener (new TxtListener()). Obiectul apartine unei clase definite de programator si care foloseste clasa JFrame sau JPanel. add (lbl1).

deoarece ele nu vor mai fi vizibile pe ecran. add(txt1). // constructor public GUI3 () { init().setLayout(new FlowLayout()). frame.*. se practicã modificarea continutului afisat în componentele deja existente. } } Clasele ascultãtor la evenimente ( “TxtListener” si altele) sunt de obicei clase incluse în clasa GUI pentru a avea acces la variabilele ce definesc obiecte JFC.EXIT_ON_CLOSE).setSize(300.addActionListener (new TxtListener()). } // activare interfata grafica public static void main (String arg[]) { new GUI2("GUI solution 2"). ca rãspuns la evenimente generate de operatorul uman). obiect selectat prin metoda “setLayout” si care stabileste automat dimensiunile 129 .show().setSize (300. // txt1. frame. de exemplu. Variabilele de tipuri JFC ( JLabel. deoarece va exista un singur obiect GUI. frame. // frame.EXIT_ON_CLOSE).setContentPane(new GUI3()). Metoda “init” de initializare a componentelor JFC poate lipsi dacã are numai câteva linii. dar este mult mai simplu sã apelãm la un obiect de control al asezãrii în panou (“Layout Manager”).add (new GUI3()). Dispunerea componentelor într-un panou Plasarea componentelor grafice pe un panou se poate face si prin pozitionare în coordonate absolute de cãtre programator. Dupã executia metodei “setVisible” nu se vor mai crea alte obiecte JFC (de exemplu. Dacã sunt putini ascultãtori. } // initializare componente private void init() { add (lbl1). In schimb. frame.add (lbl1).Florian Moraru – Programare Orientata pe Obiecte frame. frame. } public static void main (String arg[]) { JFrame frame = new JFrame ("GUI solution 3"). frame. frame.add(txt1). s.setDefaultCloseOperation (JFrame. class GUI3 extends JPanel { private JLabel lbl1 = new JLabel ("Directory"). } } Varianta 3 defineste clasa GUI ca o subclasã a clasei JPanel: import javax.100). atunci clasa GUI poate implementa una sau câteva interfete de tip ascultator la evenimente si sã continã metodele de tratare a evenimentelor (dispare necesitatea unor clase ascultãtor separate).a) pot fi initializate la declarare sau în constructorul clasei GUI.setDefaultCloseOperation (JFrame. JTextField.100). frame. // txt1.addActionListener (new TxtListener()). metoda “setText” din clasele JTextField si JLabel poate modifica textul afisat în astfel de componente JFC. private JTextField txt1 = new JTextField (16). De observat cã pentru clasele GUI constructorul este cea mai importantã functie si uneori singura functie din clasã.swing.

add (. Acest efect se poate obtine folosind un GridLayout cu douã coloane sau un BoxLayout cu asezare pe orizontalã.add(new JButton(“ 1 ”).add (. O altã solutie este alegerea modului FlowLayout.BorderLayout.add(new JButton(“ 3 ”). Alegerea modului de dispunere depinde de specificul aplicatiei : .In cazul unor componente de diferite dimensiuni BoxLayout sau GridBagLayout permite un control mai bun al plasãrii componentelor si al intervalelor dintre ele. // cp.WEST). cp.setVisible(true). // cp. Exemplu de plasare a trei butoane: class DefaultLayout { // exemplu de asezare butoane in fereastra public static void main (String args[ ]) { JFrame frame = new JFrame().In cazul a câteva componente ce trebuie sã aparã în mãrimea lor naturalã si cât mai compact se va folosi BoxLayout sau BorderLayout: pentru câteva butoane sau câmpuri text.In cazul mai multor componente de aceeasi mãrime se va folosi GridBagLayout: grupuri de butoane.i++) // 6 butoane cu cifre cp. Alte modalitãti de dispunere a componentelor într-un panou sunt GridLayout (o matrice de componente egale ca dimensiune). 130 .i<7.BorderLayout. GridBagLayout. frame. Dacã nu se specificã pozitia la adãugare. mod care foloseste un al doilea parametru în metoda “add”. atunci componenta este centratã în fereastrã. . “West”) frame. // sau new GridLayout() for (int i=1.”East”) cp. De exemplu. cp. . frame. Pentru panoul de “continut” al ferestrei JFrame este activ implicit “BorderLayout”. Container cp = frame.EAST). Container cp = frame.”Center”) cp.add (.CENTER).valueOf(i)) )..add(new JButton(“ 2 ”).setSize(400. iar dacã sunt mai multe componente.200). Existã diverse metode de a mentine pozitia relativã a douã sau mai multe componente vizuale.setVisible(true). atunci ele sunt suprapuse pe centrul ferestrei.Florian Moraru – Programare Orientata pe Obiecte si pozitia fiecãrei componente într-un panou. } } De observat cã pentru un panou JPanel asezarea implicitã este FlowLayout.. Exemplu: class FlowLayoutDemo { // Asezare butoane in fereastra public static void main (String args[ ]) { JFrame frame = new JFrame()..add (new JButton (String. BoxLayout ( asezare compactã pe verticalã sau pe orizontalã. de exemplu. Asezarea componentelor într-un panou se poate modifica automat atunci când se modificã dimensiunile panoului.. indiferent de dimensiunile panoului unde sunt plasate. // cp. . de exemplu... BorderLayout. de exemplu.getContentPane(). care aseazã componentele una dupã alta de la stânga la dreapta si de sus în jos în functie de dimensiunile lor si ale ferestrei principale. o etichetã (obiect JLabel) trebuie sã aparã întotdeauna la stânga unui câmp text sau deasupra unei zone text. la alegere) si CardLayout ( componente / panouri care ocupã alternativ acelasi spatiu pe ecran).getContentPane()..In cazul unei singure componente în panou care sã foloseascã la maximum suprafata acestuia se va alege GridBagLayout sau BorderLayout : pentru o zonã text sau o listã de selectie JList. } } De observat cã obiectul extras cu "getContentPane" are tipul Container.. dimensiunile sau numãrul componentelor (în modul FlowLayout)..setLayout(new FlowLayout()).

folosind alte panouri si margini de separare între aceste panouri.setVisible(true).add (new JTextField (20)).getAbsolutePath() ). // directorul curent pane.100). // comanda afisarea pe ecran } } Realizarea unui formular cu mai multe rubrici este în general mai complicatã decât în exemplul anterior pentru cã necesitã controlul dispunerii câmpurilor text si etichetelor asociate. // adauga panou la fereastra principala f. La construirea unui obiect JTextField se foloseste ca parametru un sir de caractere sau un întreg pentru dimensiunea ferestrei text. O zonã text (JTextArea) permite afisarea mai multor linii de text. // o eticheta ptr rubrica varsta p.setText ( dir. // rubrica pentru nume p."). // rubrica pentru vârstã f.setVisible(true). // comandã afisarea } } Principala utilizare a unui câmp text este pentru introducerea de date de cãtre operatorul aplicatiei.setContentPane(pane). JPanel p =new JPanel(). public static void main (String args[]) { JPanel pane = new JPanel().add(pane) frame. introducerea si editarea unei linii de text în cadrul unei ferestre text. într-un formular de introducere date: class InputForm { public static void main (String arg[]) { JFrame f = new JFrame(). f. // o eticheta pentru rubrica nume p. Un câmp text (JTextField) permite afisarea.add(tf). Cele mai importante metode ale clasei JTextField sunt : setText(String txt) getText() setEditable(boolean b) // afisare text din program // citire text introdus de utilizator in câmp // permite sau interzice editarea de text In exemplul urmãtor se foloseste un câmp text (nemodificabil de la tastaturã) pentru afisarea numelui directorului curent: // afisare nume director curent class UseTextField { static JFrame frame = new JFrame().100). // adaugã câmp text pe panou frame. care pot fi introduse sau 131 .Florian Moraru – Programare Orientata pe Obiecte Componente vizuale cu text Un text scurt (nu mai lung de o linie) poate fi afisat în mod grafic (într-o zonã de pe ecran) folosind diverse componente vizuale: o etichetã (obiect JLabel) contine un text constant (nemodificabil din exterior).setSize(300.add (new JTextField (8)) . // nume cu cale completa tf.add (new JLabel("Nume")). // adaugã etichetã pe panou tf. // interzice editare camp text pane.add (new JLabel("Current Directory")).setEditable(false). Pentru a informa operatorul asupra semnificatiei unui câmp text se poate adãuga o etichetã fiecãrui câmp. // sau frame.setContentPane(p).setSize(300. iar un câmp text (obiect JTextField) contine un text si permite modificarea textului de cãtre operator sau prin program.add (new JLabel("Vârsta")). p. File dir =new File(". static JTextField tf = new JTextField (20). frame. cu etichete asociate. Exemplu de utilizare câmpuri text.

// comanda afisarea } Operatorul poate deplasa cursorul în cadrul zonei text si poate modifica textul. // creare obiect ComboBox JLabel et = new JLabel ("Sort By: "). Exemplu cu o zonã text pentru afisarea numelor fisierelor din directorul curent: public static void main (String av[ ]) { File dir = new File(". Selectarea componentei focalizate se poate face fie prin program (metoda “requestFocus” sau “grabFocus”).append (" "+files[i]+"\n")."Ext".add (cbox.i<files. Dimensiunile zonei text se stabilesc la construirea unui obiectului JTextArea. existã o singurã componentã care primeste date de la tastaturã."Date".show()."West"). JComboBox si JSpinner permit selectarea unei linii (unei optiuni) din mai multe linii afisate (simultan la JList. win. Tratarea unui eveniment se face într-o metodã cu nume si argumente impuse (functie de tipul evenimentului).i<21. metodã inclusã într-o clasã care implementeazã o anumitã interfatã (functie de eveniment).getContentPane(). // comanda afisarea } Pentru ca programul sã poatã reactiona la selectarea sau modificarea unor linii trebuie adãugate programelor anterioare secvente de tratare a evenimentelor produse de aceste actiuni exterioare programului. dar prin mouse se poate cere afisarea tuturor liniilor continute. frm. frm."Center"). // fisiere din directorul curent JFrame win= new JFrame("Current Directory"). // adauga nume fisiere la zona win. frm. // directorul curent String files[ ] = dir.5). JTextArea ta = new JTextArea(10.append (100*i+"\n"). frm.setVisible(true).add (new JScrollPane(ta)). // o zona text for (int i=0.add (et. } // max 10 linii cu max 5 caractere // adauga siruri de cifre // fiecare numar pe o linie separata In AWT o zonã text este automat plasatã într-o fereastrã cu derulare pe verticalã si pe orizontalã. // titlul ferestrei JTextArea ta = new JTextArea(20. // în panou cu derulare win. fie de cãtre operator prin clic pe mouse dupã pozitionare pe componentã sau prin tasta Tab (care mutã 132 . Exemplu: public static void main (String arg[ ]) { JFrame frm = new JFrame().Florian Moraru – Programare Orientata pe Obiecte modificate de la tastaturã sau prin program (cu metoda “append”). In cazul unui panou cu mai multe componente text. // adauga eticheta si ComboBox frm. // texte afisate in ComboBox JComboBox cbox = new JComboBox(s).getContentPane().pack().pack().setVisible(true). cp. se spune cã tastatura este focalizatã pe acea componentã sau cã acea componentã detine controlul tastaturii.getContentPane(). Exemplu de utilizare zonã text : public static void main ( String args [ ]) { JFrame frm = new JFrame (). cp. dar în JFC componenta JTextArea trebuie inclusã într-un panou cu derulare (JScrollPane) pentru a putea aduce în fereastra vizibilã elemente ce nu pot fi vãzute din cauza dimensiunii limitate a zonei text.i++) ta. Un obiect JComboBox afiseazã pe ecran numai linia selectatã. Clasele JList.20).i++) ta. cum ar fi un formular de introducere date sau o foaie de calcul.add (ta).length. for (int i=1. pentru o altã selectie."Size"}. // o eticheta asociata Container cp = frm.").pack(). succesiv la JSpinner sau la cerere).list(). String s[ ]={"Name".

setLayout (new FlowLayout()). f. p1. Exemplu cu douã panouri independente afisate simultan class MFrame extends JFrame { JPanel p1 = new JPanel().Florian Moraru – Programare Orientata pe Obiecte focalizarea pe urmãtoarea componentã din fereastrã). p2. Orice obiect Swing care poate primi intrãri de la tastaturã ( inclusiv butoane) poate detine controlul (“focus”).add (p2.k++) b[k]= new JButton(“ “+ k +” “). public MFrame () { Container w = getContentPane().Panouri multiple afisate simultan si partial suprapus: JLayeredPane. In fiecare dintre panourile unei aplicatii putem avea un panou cu derulare JScrollPane. p1.”North”).HORIZONTAL_SPLIT.k<4.add (b[3]). w. . p2. In cazul unui formular cu multe câmpuri text sau al unui tabel (JTable) pierderea focalizãrii poate însemna terminarea introducerii de date în acel câmp sau celulã si deci poate înlocui evenimentul produs de tasta Enter (terminare introducere text). JPanel p2 = new JPanel(). dar el poate fi modificat astfel ca în panoul din dreapta sã se afiseze continutul fisierului director selectat din lista afisatã în panoul din stânga . JSplitPane sp = new JSplitPane(JSplitPane. } // utilizare MFrame public static void main ( String args []) { JFrame f = new MFrame ().add (b[0]).").add (p1.setSize (100. f.”South”).Panouri divizate în douã (pe orizontalã sau pe verticalã): JSplitPanel. JScrollPane p2 = dirlist("c:\\").Panouri multiple afisate succesiv in aceeasi zonã ecran : JTabbedPane. getContentPane(). JButton b[] = new JButton[4].add (b[2]). Panourile pot fi independente unele de altele sau pot fi legate. astfel ca selectarea unui element dintr-un panou sã aibã ca efect modificarea continutului celuilalt panou. p2. Pentru inserarea de spatii între panouri se pot crea margini (borduri) în jurul fiecãrui panou. Panouri multiple In cadrul unei ferestre principale avem urmãtoarele posibilitãti de lucru cu panouri: .setLayout (new FlowLayout() ).show (). p2). w. for (int k=0. p1.Panouri multiple afisate simultan.100). fãrã suprapunere între ele. } } // un panou cu doua butoane // alt panou cu butoane // 4 butoane // panou principal al aplicatiei // creare butoane // dispunere in panoul p1 // butoane din panoul p1 // dispunere in panoul p2 // butoane din panoul p2 // adauga panouri la panou princ Exemplul urmãtor afiseazã în douã panouri "legate" douã liste de fisiere diferite. . p1. Gruparea unor componente în (sub)panouri permite manipularea unui panou separat de celelalte si alegerea altui mod de dispunere în fiecare panou. class SplitPane extends JFrame { public SplitPane() { JScrollPane p1 = dirlist(".add(sp). Câstigarea sau pierderea controlului de cãtre o componentã produce evenimente specifice (“FocusEvent”) si apelarea de metode “focusGained” sau “focusLost”. 133 .add (b[1]). .

frame. JScrollPane p1 = dirlist(t1). } Apleti Java Cuvântul aplet (“applet”) desemneazã o micã aplicatie care foloseste ecranul în mod grafic. null.*. p1.setSize(400. add(tabbedPane). Programarea unei interfete grafice într-un aplet este putin mai simplã decât într-o aplicatie deoarece apletul mosteneste de la clasa Panel (si de la clasele Container si Component) o serie de metode utile (inclusiv metoda “windowClosing”).class) poate fi adus de cãtre browser si de la un alt calculator decât cel pe care se executã.Label. Din punct de vedere sintactic un aplet este o clasã Java. 1)). dar care depinde de un alt program “gazdã” pentru crearea fereastrei principale (care nu trebuie creatã de programatorul apletului). public void init () { add (et). Exemplu de aplet scris în varianta AWT: import java. adicã o micã portiune cu numele panoului. class TabbedPane extends JFrame { public TabbedPane() { String t1=". Clasa JApplet este indirect derivatã din clasa Panel . frame. JTabbedPane tabbedPane = new JTabbedPane().. setLayout(new GridLayout(1. Codul unui aplet (fisierul . public class LabelAplet extends Applet { Label et= new Label ("Eticheta".awt. tabbedPane. p2. destinat vizualizãrii rezultatului executiei unui aplet. Programul gazdã este fie un program navigator (“browser”). } .*. } } 134 .200).". derivatã din clasa Applet sau din JApplet. afisatã permanent pe ecran alãturi de “tab”-urile celorlalte panouri selectabile). prin fereastra programului browser. care asigurã oricãrui aplet o fereastrã cu butoane de închidere. mãrire si micsorare. } } In exemplul urmãtor cele douã panouri sunt afisate alternativ.applet. tabbedPane.addTab("Dir of "+t2.list(). fie programul “appletviewer”. Fereastra de afisare a unui aplet nu poate fi manipulatã direct de operatorul uman ci numai indirect. t2="C:\\". } public static void main(String s[]) { JFrame frame = new SplitPane(). import java.CENTER). ""). JList lst = new JList(files).Florian Moraru – Programare Orientata pe Obiecte } public static JScrollPane dirlist (String dirname) { String files[] = new File(dirname). JScrollPane p2 = dirlist(t2). return new JScrollPane(lst). null.addTab("Dir of "+t1.. în functie de selectia operatorului (fiecare panou are un “tab” de prindere. în aceeasi zonã.setVisible(true). "").

f. Functia “init” este înlocuitã cu functia “main” la trecerea de la un aplet la o aplicatie. De remarcat cã o clasã care corespunde unui aplet trebuie sã aibã atributul public si nu contine o metodã “main”. O clasã aplet poate fi transformatã într-o aplicatie prin adãugarea unei functii “main” în care se construieste un obiect JFrame.CENTER). care trebuie (re)definite de utilizator si sunt apelate de programul gazdã. adicã la actionarea butonului din stânga de pe mouse dupã mutare mouse pe zona ecran ocupatã de buton. ceea ce conduce la instructiuni de forma urmãtoare comp. “start”.*. împreunã cu dimensiunile ferestrei folosite de aplet. Este posibil ca anumite programe de navigare mai vechi (“Browser”) sã nu recunoascã clase JFC si din acest motiv s-a dat si varianta AWT pentru aplet. f. Clasa aplet mosteneste si redefineste de obicei metodele “init”.addXXXListener(this). apelate de programul gazdã la producerea anumitor evenimente.init().swing. } // se adauga la clasa JAplet O altã posibilitate este crearea unei clase separate în care se preia codul din aplet si se adaugã crearea si afisarea ferestrei principale a aplicatiei (de tip JFrame). Exemplu de fisier “html” necesar pentru executia apletului precedent: <applet code="JAplet. aplet.class" width="250" height="100"> </applet> In comanda “appletviewer” este specificat numele fisierului “html” si nu apare direct numele fisierului “class”. Din punct de vedere functional un aplet contine câteva functii. public void init () { getContentPane(). public class Aplet extends JApplet implements ActionListener { 135 . Dimensiunile ferestrei folosite de aplet se dau în fisierul de tip "html" si nu în codul Java. // comp este numele unei componente din aplet Exemplul urmãtor este un aplet care afiseazã un buton în centrul ferestrei puse la dispozitie de programul gazdã si emite un semnal sonor ("beep") la "apãsarea" pe buton.setVisible (true). Fisierul “class” generat de compilator pentru un aplet este specificat într-un fisier “html”.add (et). Obiectul ascultãtor la evenimente este chiar obiectul aplet.getContentPane(). la care se adaugã un obiect aplet si se apeleazã metoda “init”: public static void main (String args[ ]) { JFrame f = new JFrame().JLabel. pentru cã nu se admit alte clase ascultãtor. între marcajele <applet> si </applet>.Florian Moraru – Programare Orientata pe Obiecte Acelasi aplet în varianta JFC aratã astfel: import javax.add (aplet). public class JAplet extends JApplet { JLabel et= new JLabel ("Eticheta". } } Clasa JApplet este derivatã din clasa Applet si permite în plus folosirea unui meniu într-un aplet si a componentelor vizuale noi din JFC (fãrã echivalent în AWT). JAplet aplet = new JAplet(). separate de clasa aplet. Un aplet care trateazã evenimente externe trebuie sã continã si metodele de tratare a evenimentelor. "paint" si alte câteva metode.

// afisare text } 136 . la încãrcarea codului apletului în memorie. BorderLayout. getSize(). // margini fereastra g..1.Florian Moraru – Programare Orientata pe Obiecte JButton button.. getContentPane().CENTER). // obiectul receptor este chiar apletul } public void actionPerformed(ActionEvent e) { // tratare eveniment buton Toolkit. getSize(). 0.1). iar metoda "start" este apelatã de fiecare datã când programul browser readuce pe ecran pagina html care contine si marcajul <applet .beep(). // semnal sonor } } Metoda "init" este apelatã o singurã datã.drawRect (0. Metoda "paint" are un parametru de tip Graphics.drawString ("text in aplet".addActionListener(this). button.height . 10.>.width . public void init() { button = new JButton("Click Me").add(button. iar clasa Graphics contine metode pentru desenarea de figuri geometrice diverse si pentru afisarea de caractere cu diverse forme si mãrimi: public void paint (Graphics g) { g.getDefaultToolkit(). 30).

care primeste ca argument un obiect “eveniment”. etc. activare. ci sunt apelate ca urmare a producerii unor evenimente. .Sã defineascã clasele ascultãtor. Structura unui program dirijat prin evenimente diferã de structura unui program obisnuit prin existenta functiilor speciale de tratare a evenimentelor (“Event handlers”).a.Evenimente asociate fiecãrei componente vizuale (buton. Operatia de înregistrare se face prin apelarea unei metode de forma “addXListener” (din obiectul generator). un clic pe butonul de închidere a unei ferestre JFrame genereazã un alt eveniment decât un clic pe butonul de micsorare a ferestrei. prin definirea metodei sau metodelor interfetei pentru tratarea evenimentele observate. interfata WindowListener contine sapte metode pentru tratarea diferitelor evenimente asociate unei ferestre (deschidere. care nu sunt apelate direct din program. actionarea unui buton de “mouse”. Un program controlat prin evenimente nu initiazã momentul introducerii datelor. închidere. Notiunea de eveniment a apãrut ca o abstractizare a unei întreruperi externe .Florian Moraru – Programare Orientata pe Obiecte 12. . care implementeazã interfete JFC. fie din taste (apãsare buton. 137 . deci trebuie sã continã anumite metode cu nume si semnãturã impuse de clasele JFC. Programare bazatã pe evenimente Evenimente Swing Programarea dirijatã de evenimente (“Event Driven Programming”) se referã la scrierea unor programe care reactioneazã la evenimente externe programului (cauzate de operatorul uman care foloseste programul). Evenimentele JFC pot fi clasificate astfel: . micsorare). Declansarea unui eveniment are ca efect apelarea de cãtre obiectul generator a unei metode din obiectul ascultãtor. independent de evolutia programului si al cãrui moment de producere nu poate fi prevãzut la scrierea programului. Clasele generator sunt în general clase JFC sau AWT si ele creeazã obiecte “eveniment” ca efect al actiunii operatorului pe suprafata componentei respective.a.Sã înregistreze obiectele ascultãtor la obiectele generatoare de evenimente. De exemplu.). deplasare “mouse” s. tastare “Enter”. De exemplu. câmp text. Aceste clase trebuie sã implementeze anumite interfete JFC. dar alte interfete contin mai multe metode ce corespund unor evenimente diferite asociate aceleeasi componente vizuale. Prin “eveniment” se întelege aici un eveniment asincron.Obiecte generatoare de evenimente (sursa unui eveniment). dezactivare. Intr-un program dirijat de evenimente existã douã tipuri principale de obiecte: .Evenimente asociate dispozitivelor de introducere ( mouse sau tastaturã). In Java un eveniment este un obiect de un tip clasã derivat din clasa EventObject. . Tipul obiectului eveniment este determinat de tipul componetei GUI care a generat evenimentul dar si de actiunea operatorului uman. dar poate reactiona prompt la orice eveniment produs de o actiune a operatorului. generate fie prin “mouse”. Evenimente tipice sunt: apãsarea unei taste. La fiecare obiect generator de evenimente se pot “înregistra” (se pot înscrie) mai multe obiecte ascultãtor interesate de producerea evenimentelor generate. unde ‘X’ este numele (tipul) evenimentului si care este totodatã si numele unei interfete. Anumite interfete ascultãtor (“listener”) contin o singurã metodã.Obiecte receptoare de evenimente (obiecte ascultãtor). Functiile care preiau date nu sunt apelate direct si explicit de alte functii din program. s. Clasele receptor de evenimente sunt scrise de cãtre programatorul aplicatiei pentru cã metodele de tratare a evenimentelor observate sunt specifice fiecãrei aplicatii.). Tratarea evenimentelor Swing Programatorul unei aplicatii Java cu evenimente trebuie: .

In exemplul urmãtor este tratat numai evenimentul de tastã apãsatã prin afisarea caracterului corespunzãtor tastei: class KeyEvent1 { static JFrame f= new JFrame(). import javax. majoritatea cu definitie nulã (fãrã efect). class KeyHandler extends KeyAdapter { public void keyPressed(KeyEvent e) { ta. f. Dacã s-ar defini o clasã care sã implementeze aceastã interfatã atunci ar trebui sã definim toate cele sapte metode ale interfetei.event.*.addWindowListener ( new WindExit()).swing.exit(0). Pentru a simplifica sarcina programatorului este prevãzutã o clasã “adaptor” WindowAdapter care contine definitii cu efect nul pentru toate metodele interfetei.addWindowListener ( new WindowAdapter() { public void windowClosing( WindowEvent e) { System.awt.append("\n key typed: " + e. } } class FrameExit { public static void main (String args[ ]) { JFrame f = new JFrame (). static JTextField tf. In exemplul urmãtor se defineste o clasã separatã. Metoda “windowClosing” din aceastã clasã nu are nici un efect si trebuie redefinitã pentru terminarea aplicatiei la închiderea ferestrei principale.swing. care extinde clasa WindowAdapter si redefineste metoda “windowClosing”: class WindExit extends WindowAdapter { public void windowClosing( WindowEvent e) { System.exit(0). } }). } } Putem folosi si o clasã inclusã anonimã pentru obiectul ascultãtor necesar: public static void main (String args[ ]) { JFrame f = new JFrame (). import java. static JTextArea ta.*.Florian Moraru – Programare Orientata pe Obiecte Pentru a trata numai evenimentul “închidere fereastrã” este suficient sã scriem numai metoda “windowClosing”. iar clasa KeyAdapter contine definitii nule pentru cele trei metode.show (). iar programatorul trebuie sã redefineascã doar una sau câteva metode.*. f.getKeyChar() ).show (). 138 . f. f.event.*. Pentru a compila exemplele care urmeazã se vor introduce la început urmãtoarele instructiuni: import java. import javax. } Evenimente de mouse si de tastaturã Interfata KeyListener contine trei metode : “keyPressed”. “keyTyped” si “keyReleased”.awt.

getContentPane().Florian Moraru – Programare Orientata pe Obiecte } } public static void main(String args[]) { KeyEvent1 kd = new KeyEvent1().getKeyChar() ). static JTextArea ta.new KeyHandler()). MouseInputAdapter oferã o implementare nulã pentru toate cele 7 metode. } } // tratare eveniment buton class ActHandler implements ActionListener { public void actionPerformed(ActionEvent e) { ta. } Programele anterioare lucreazã corect numai pentru taste cu caractere afisabile. // refocalizare pe TextField } } ..requestFocus(). tf. cp. “mousePressed”. // numar de apasari pe mouse public static void main (String args[]) { JFrame frame = new JFrame(). ta = new JTextArea(20. class KeyEvent2 { static JFrame f= new JFrame(). } Evenimentele de la tastaturã sunt asociate componentei selectate prin mouse.”mouseDragged”). cp. // modifica afisarea } 139 .append("\n key typed: " + e.add(new JScrollPane (ta)). static JButton button = new JButton("Clear"). label. tf = new JTextField(20). // o comp. class KeyHandler extends KeyAdapter { // tratare eveniment tastatura public void keyPressed(KeyEvent e) { ta.setText((new Integer(clicks)).20). a cãrui apãsare are ca efect focalizarea tastaturii pe câmpul text. cp. neinteractiva label. class MouseClicks { static int clicks=0.show( ).setText(""). Este posibilã si selectarea prin program a componentei pe care se focalizeazã tastatura folosind metoda care existã în orice componentã JFC numitã “requestFocus”..setText(""). f. // aici se introduc caractere tf. dar ele pot fi extinse pentru a prezenta orice caracter introdus. // aici se afiseaza caracterele introduse Container cp = f.addMouseListener (new MouseInputAdapter() { // receptor evenimente public void mouseClicked(MouseEvent e) { ++clicks. “mouseReleased”) si metode apelate la deplasare mouse (“mouseMoved”.setLayout(new GridLayout()).toString()).addKeyListener(kd. In exemplul urmãtor s-a adãugat un buton de stergere a continutului zonei text.add(tf). Interfata MouseInputListener contine metode apelate la actiuni pe mouse (apãsarea sau eliberare buton: “mouseClicked”. static JTextField tf. Exemplul urmãtor aratã cum se poate reactiona la evenimentul de “clic” pe mouse prin numãrarea acestor evenimente si afisarea lor. final JLabel label= new JLabel("0"). tf.

Se poate spune cã se produc efectiv numai evenimentele pentru care s-au definit ascultãtori si deci metode de tratare a evenimentelor. Astfel de clase “adaptor” au rolul de intermediar între componentã si aplicatie.setVisible(true). “remove*Listener”. Evenimentele generate de componentele JFC pot fi clasificate în: . KeyEvent. pozitia sau vizibilitatea componentei.setLayout (new FlowLayout()). pot fi descrise astfel: . ChangeEvent (dacã s-a modificat ceva în starea butonului) s.getContentPane(). mostenite de la clasa Component. In general nu se trateazã toate evenimentele ce pot fi generate de o componentã JFC. cauzate de un clic pe imaginea componentei sau de apãsarea unei taste. Un buton este un obiect Swing de tip JButton care genereazã câteva tipuri de evenimente: ActionEvent (dacã s-a actionat asupra butonului).MouseEvent : produs de apãsare sau deplasare mouse pe suprafata componentei.Florian Moraru – Programare Orientata pe Obiecte }). ListDataEvent. MenuEvent.getContentPane().add(label). MouseEvent. Desi un buton poate genera peste 5 tipuri de evenimente. .Evenimente specifice fiecãrui tip de componentã: ChangeEvent. Metodele de tratare sunt grupate în interfete.ComponentEvent : produs de o modificare în dimensiunea. La apãsarea unui buton se creeazã un obiect de tip ActionEvent si se apeleazã metoda numitã “actionPerformed” (din interfata ActionListener) pentru obiectul sau obiectele înregistrare ca receptori la acel buton (prin apelul metodei “addActionListener”).KeyEvent : produs de apãsarea unei taste si asociat componentei pe care este focalizatã tastatura. frame. în mod uzual se foloseste numai ActionEvent si deci se apeleazã numai metoda “actionPerformed” din obiectele înregistrate ca receptori pentru butonul respectiv.ActionEvent : produs de actiunea asupra unei componente prin clic pe mouse. Fiecare componentã genereazã anumite tipuri de evenimente. Programatorul trebuie sã defineascã clase ce implementeazã aceste interfete cu metode de tratare a evenimentelor. DocumentEvent etc.a. De asemenea editarea textului dintrun câmp text sau dintr-o zonã text produce evenimente. în sensul cã dupã ce se afiseazã forma si continutul componentei este posibil un clic cu mouse-ul pe suprafata componentei sau deplasarea unor elemente ale componentei. frame. ceea ce genereazã evenimente. Evenimentele comune. si pentru fiecare tip de eveniment este prevãzutã o metodã de tratare a evenimentului. pentru ca ea sã poatã primi intrãri de la tastaturã. Majoritatea componentelor vizuale sunt interactive.getDefaultToolkit().Evenimente comune tuturor componentelor JFC: ActionEvent. . Tratarea evenimentului înseamnã scrierea unei metode cu numele “actionPerformed” care sã producã un anumit efect ca urmare a “apãsãrii” butonului (prin clic pe suprafata sa). Exemplu de tratare a evenimentului de clic pe un buton. frame.beep().FocusEvent: produs de “focalizarea” claviaturii pe o anumitã componentã JFC. FocusEvent. } } Evenimente asociate componentelor JFC Orice componentã vizualã AWT sau JFC poate fi sursa unui eveniment sau chiar a mai multor tipuri de evenimente. // produce semnal sonor (beep) } } 140 . ComponentEvent . Numele evenimentelor apar în clasele pentru obiecte “eveniment” si în numele unor metode: “add*Listener”. . . prin emiterea unui semnal sonor la fiecare clic: // clasa ptr obiecte ascultator de evenimente class Listener implements ActionListener { public void actionPerformed(ActionEvent e) { Toolkit. ListSelectionEvent.

setVisible(true). String [ ] aa = {"A". frame.setMnemonic (KeyEvent.addItemListener (myListener)."C"}. // ascultator casute for (int i=0. cp.VK_C).i++) cp. pentru a crea un singur obiect.beep(). for (int i=0.getSource(). cp. // ptr a evita repetarea selectiei 141 . JButton buton = new JButton("Click Me"). button. Exemplul urmãtor foloseste componente de tip JCheckBox (cãsute cu bifare) care genereazã alt tip de evenimente (ItemEvent). Se observã cã am folosit clasa “Listener” o singurã datã.setLayout(new GridLayout(0.i<3.i++) if (source == cb[i]) { source.getContentPane().removeItemListener(this). } // Ascultator la casute cu bifare.i<3. class CheckBoxListener implements ItemListener { public void itemStateChanged(ItemEvent e) { JCheckBox source = (JCheckBox)e. // litere afisate in casute public CheckBox1() { CheckBoxListener myListener = new CheckBoxListener(). for (int i=0. în timp ce al doilea apare indiferent de pozitia mouse pe ecran. JTextField a = new JTextField(30). In plus.CENTER). } }). // definitia clasei receptor Diferenta dintre evenimentul generat de un buton si un eveniment generat de clic pe mouse este cã primul apare numai dacã dispozitivul mouse este pozitionat pe aria ocupatã de buton.Florian Moraru – Programare Orientata pe Obiecte // creare buton si asociere cu ascultator class Beeper1 { public static void main ( String args[]) { JFrame frame = new JFrame().add(buton. ilustreazã un ascultãtor la mai multe surse de evenimente si utilizarea unei metode “removeXXXListener”: class CheckBox1 extends JFrame { JCheckBox cb[] = new JCheckBox[3]."B". evenimentele generate de componente JFC contin în ele sursa evenimentului si alte informatii specifice fiecãrei componente.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ Toolkit. } } Pentru a obtine acelasi efect si prin apãsarea unei taste asociate butonului (de exemplu combinatia de taste Ctrl-C) este suficient sã adãugãm o instructiune: buton.i++) { cb[i] = new JCheckBox (aa[i]). De aceea se practicã frecvent definirea unei clase “ascultãtor” anonime acolo unde este necesarã: button.getDefaultToolkit().i<3. 1)). // creare 3 casute cb[i]. BorderLayout.add(cb[i]). // inscriere receptor la buton frame.add(a).addActionListener(new Listener()). // acelasi ascultator la toate casutele } Container cp = getContentPane().

addActionListener (new TFListener()).add (tf. } } } // afisare nume casutã selectata Evenimente produse de componente text Pentru preluarea caracterelor introduse într-un câmp text trebuie tratat evenimentul ActionEvent. } public void actionPerformed (ActionEvent ev) { JComboBox cb = (JComboBox) ev. return true.isDigit(str."Center"). f. Pânã la apãsarea tastei “Enter” este posibilã si editarea (corectarea) textului introdus.tf=tf.pack(). Exemplu de validare a continutului unui câmp text la terminarea introducerii. } public static void main ( String av[]) { JFrame f = new MFrame ().getDefaultToolkit(). Exemplu: // ascultator la ComboBox class CBListener implements ActionListener { JTextField tf.getSelectedItem()). public CBListener (JTextField tf) { this. tf.i++) if ( ! Character.beep(). // sursa eveniment // text selectat 142 .length(). tf = new JTextField(10). } // verifica daca un sir contine numai cifre static boolean isNumber (String str) { for (int i=0. // text introdus de operator if ( ! isNumber(text)) // daca nu sunt doar cifre Toolkit.getText()+aa[i]). Selectarea unui text dintr-o listã de optiuni JComboBox produce tot un eveniment de tip ActionEvent. în locul tastei “Enter” sau ca o alternativã posibilã.getSource(). class TFListener implements ActionListener { // clasa interioara public void actionPerformed (ActionEvent ev) { String text = tf.setVisible(true).setText(a. generat de un obiect JTextField la apãsarea tastei "Enter".getText(). f. } } Unele aplicatii preferã sã adauge un buton care sã declanseze actiunea de utilizare a textului introdus în câmpul text.Florian Moraru – Programare Orientata pe Obiecte a. w. // emite semnal sonor } } public MFrame () { Container w = getContentPane().charAt(i)) ) return false. cu emiterea unui semnal sonor în caz de eroare : class MFrame extends JFrame { JTextField tf. String seltxt = (String) (cb.i<str.

unde cuvântul “model” are sensul de model logic al datelor care vor fi prezentate vizual. public String getActionCommand()."South"). // ptr afisare rezultat selectie Container cp = frm. redatã mai jos într-o formã mult simplificatã (prin eliminarea a cca 70 % dintre metode): public interface ButtonModel { boolean isPressed(). // primeste sursa evenimentului public Object getSource(). care era un vector de referinte la obiecte de tipul Object (in JDK 1. O clasã model contine date si poate genera evenimente la modificarea sau selectarea unor date continute.getContentPane()."Trei".pack(). O sursã de evenimente trebuie sã continã o listã de receptori ai evenimentelor generate. public void setPressed(boolean b). Un eveniment este încapsulat într-un obiect de un tip subclasã a clasei generale EventObject. Inregistrarea unui obiect ascultãtor la obiectul sursã de evenimente se face prin apelarea metodei “addActionListener”. void addActionListener(ActionListener l). chiar dacã nu sunt componente “vizuale”).addActionListener ( new CBListener (txt)).setText (seltxt)."Doi". } } class A { public static void main (String arg[]) { JFrame frm = new JFrame(). definite în pachetul “java. cp. // ascultator la ComboBox frm. // produce sursa evenimentului public String toString(). în Java. public void setActionCommand(String s). folosind alte clase si interfete JDK. frm. dupã cum este posibil ca un acelasi obiect sã poatã “asculta” evenimente produse de mai multe surse."Center").3 s-a introdus clasa EventListenerList pentru a manipula mai sigur aceastã listã). cp."Cinci"}. deoarece evenimentele fac parte din standardul pentru JavaBeans (componentele standard comunicã între ele si prin evenimente.util” astfel: public class EventObject extends Object implements Serializable { public EventObject (Object source). JTextField txt = new JTextField(10). cbox. String s[]={"Unu". Acest lucru este necesar atunci când trebuie creatã o componentã standard Java (“bean”).add (txt. La o sursã de evenimente se pot conecta mai multi “ascultãtori” interesati de aceste evenimente. Modelul de buton (DefaultButtonModel) este o sursã de evenimente si contine o listã de “ascultãtori” sub forma unui obiect de tipul EventListenerList. } } // afisare in campul text Mecanismul de generare a evenimentelor De cele mai multe ori. care este în esentã un vector de referinte la obiecte ce implementeazã interfata EventListener."Patru".add (cbox. 143 . Un model de buton trebuie sã respecte interfata ButtonModel. folosim surse de evenimente prefabricate (componente AWT sau JFC) dar uneori trebuie sã scriem clase generatoare de evenimente.Florian Moraru – Programare Orientata pe Obiecte tf. // un sir echivalent obiectului } Mecanismul de producere a evenimentelor si de notificare a obiectelor “ascultãtor” poate fi urmãrit în cadrul claselor JFC de tip “model”.show(). // lista de optiuni JComboBox cbox = new JComboBox(s).

class) ((ActionListener)listeners[i+1]). l). } } } } 144 . } if (b) stateMask |= PRESSED.getListenerList().class. } // notificare receptori despre producere eveniment protected void fireActionPerformed (ActionEvent e) { Object[] listeners = listenerList. } // eliminare receptor la evenimente buton public void removeActionListener(ActionListener l) { listenerList. public final static int PRESSED = 1 << 2. protected String actionCommand = null. getActionCommand()) ). // declansare eveniment “buton actionat” if(!isPressed() && isArmed()) { fireActionPerformed( new ActionEvent (this . // lista de ascultatori la evenimente generate de aceasta clasa protected EventListenerList listenerList = new EventListenerList(). i>=0.length-2.getListenerList(). Serializable { protected int stateMask = 0.stateChanged(changeEvent).actionPerformed(e). for (int i = listeners. // actionare buton prin program public void setPressed(boolean b) { if((isPressed() == b) || !isEnabled()) { return.class) { if (changeEvent == null) changeEvent = new ChangeEvent(this). considerate semnificative pentru aceastã discutie: public class DefaultButtonModel implements ButtonModel. } Sirul numit “ActionCommand” reprezintã inscriptia afisatã pe buton si permite identificarea fiecãrui buton prin program (este diferit de numele variabilei JButton ). Secventa urmãtoare contine o selectie de fragmente din clasa model de buton. } fireStateChanged(). ((ChangeListener)listeners[i+1]).length-2.Florian Moraru – Programare Orientata pe Obiecte void removeActionListener(ActionListener l).remove(ActionListener. } // notificare receptori despre schimbarea stãrii protected void fireStateChanged() { Object[ ] listeners = listenerList. protected transient ChangeEvent changeEvent = null. for (int i = listeners.add(ActionListener. l). i>=0. ActionEvent. } // adaugare receptor la evenimente generate de buton public void addActionListener(ActionListener l) { listenerList.class.ACTION_PERFORMED. i-=2) if (listeners[i]==ActionListener. else stateMask &= ~PRESSED. i-=2) { if (listeners[i]==ChangeListener.

acest exemplu aratã necesitatea interactiunii dintre componentele vizuale si obiectele “ascultãtor”. De multe ori clasele ascultãtor trebuie sã comunice între ele. fiecare cu avantaje si dezavantaje. Intr-o formã mult simplificatã. l }. primul buton (‘+’) are ca efect mãrirea cu 1 a numãrului afisat iar al doilea buton (‘-’) produce scãderea cu 1 a numãrului afisat. Obiectele ascultãtor la butoanele ‘+’ si ‘-‘ trebuie sã actioneze asupra unui câmp text si deci trebuie sã primeascã o referintã la acest câmp text (în constructor). fie prin intermediul unei alte clase. // modifica continut camp text } 145 . clasa pentru liste de obiecte ascultãtor aratã astfel: public class EventListenerList { protected transient Object[ ] listenerList = null.valueOf(n+1)). In astfel de situatii avem de ales între mai multe posibilitãti de grupare a claselor din program.parseInt(text. Prima variantã foloseste numai clase de nivel superior (“top-level”): o clasã pentru fereastra principalã a aplicatiei si douã clase pentru tratarea evenimentelor de butoane.getText()). dar câte un nou obiect ActionEvent pentru fiecare clic. // adaugare la vectorul "tmp" listenerList = tmp. EventListener l) { if (listenerList == null) listenerList = new Object[ ] { t. 0. dar în listã se memoreazã si tipul (clasa) obiectului ascultãtor.length. fie direct. adicã declanseazã un eveniment de tip “ActionEvent” si deci apeleazã metodele “actionPerformed” din toate obiectele ascultator înregistrate la butonul respectiv. Desi foarte simplu. i). tmp. împreunã cu adresa sa. Existã un singur obiect eveniment ChangeEvent transmis ascultãtorilor la orice clic pe buton.setText(String. } public void actionPerformed (ActionEvent ev) { int n =Integer. // valoarea din campul text text. // referinta la campul text folosit public B1L (JTextField t) { text=t. In câmpul text se afiseazã un numãr întreg (initial zero). Lista de ascultãtori la diferite evenimente este unicã pentru un obiect EventListenerList.Florian Moraru – Programare Orientata pe Obiecte Apelarea metodei “setPressed” pentru un buton are acelasi efect cu actiunea operatorului de clic pe imaginea butonului. else { int i = listenerList. // ascultator la buton "+" class B1L implements ActionListener { JTextField text. tmp[i+1] = l. deoarece sirul “actionCommand” poate fi modificat prin apelul metodei “setActionCommand”. 0. // realocare pentru extindere vector System. Structura programelor dirijate de evenimente Intr-un program care reactioneazã la evenimente externe trebuie definite clase ascultãtor pentru aceste evenimente. } } } O solutie mai simplã dar mai putin performantã pentru clasa EventListenerList poate folosi o colectie sincronizatã în locul unui vector intrinsec extins mereu.arraycopy(listenerList. // copiere date in "tmp" tmp[i] = t. // lista de obiecte ascultator // adauga un obiect ascultator la lista public synchronized void add (Class t. Producerea unui eveniment ActionEvent se traduce în apelarea metodei “actionPerformed” pentru toate obiectele de tip ActionListener înregistrate. Pentru a ilustra variantele posibile vom folosi un exemplu simplu cu douã butoane si un câmp text. Object[ ] tmp = new Object[i+2].

146 . c. Se poate defini o clasã “Int” ce contine o variabilã de tip int si o metodã pentru modificarea sa : class Int { int val. Avem o situatie în care trei clase trebuie sã foloseascã în comun o variabilã si sã o modifice. } // pentru verificare public static void main (String args[ ]) { JFrame f = new MFrame(). } public void actionPerformed (ActionEvent ev) { int n =Integer.Florian Moraru – Programare Orientata pe Obiecte } // ascultator la buton "-" class B2L implements ActionListener { JTextField text. JTextField text. f.parseInt(text.setVisible(true). desi este oricum memorat ca sir de caractere în câmpul text. } // modificare valoare public String toString(){return String.setText(“0”). constructorul primeste o copie a valorii sale. } // citire valoare public void setValue (int n) { val=n. public Int (int n) { val=n.add(b1).pack(). text.add (text). public MFrame() { Container c = getContentPane(). JButton b2 = new JButton (" .getText()). JTextField text = new JTextField (6)."). // modifica continut camp text } } // Clasa aplicatiei: butoane cu efect asupra unui camp text class MFrame extends JFrame { JButton b1 = new JButton (" + ").valueOf(n-1)).setText(String. } // pentru afisare } Clasele pentru obiecte ascultãtor la evenimente sunt aproape la fel: // receptor la butonul de incrementare class B1L implements ActionListener { Int n. // referinta la campul text folosit public B2L (JTextField t) { text=t. } // constructor public int getValue () { return val. } } Numãrul afisat în câmpul text ar putea fi util si altor metode si deci ar putea fi memorat într-o variabilã. c. c.setLayout (new FlowLayout()). // valoarea din campul text text.valueOf(val). b2. f. iar metodele clasei nu pot modifica valoarea originalã. care nu poate fi extinsã cu noi metode.add(b2). Dacã variabila este de un tip primitiv (int în acest caz).addActionListener (new B1L(text) ). b1. c. O solutie uzualã este ca variabila externã sã fie transmisã la construirea unui obiect care foloseste aceastã variabilã (ca parametru pentru constructorul clasei).addActionListener (new B2L(text) ). Clasa Integer nu este nici ea de folos doarece nu contine metode pentru modificarea valorii întregi continute de un obiect Integer si este o clasã finalã.

add(b1). JTextField text = new JTextField (6). b2.setText (n. ceea ce eliminã clasele separate cu rol de ascultãtor : class MFrame extends JFrame implements ActionListener { JButton b1 = new JButton (" + "). else --n.Florian Moraru – Programare Orientata pe Obiecte public B1L (JTextField t.n) ).toString()).addActionListener (this). public MFrame() { Container c = getContentPane(). JButton b2 = new JButton (" .addActionListener (this). b2."). text. } } Clasa cu fereastra principalã a aplicatiei : // Doua butoane care comanda un camp text class MFrame extends JFrame { JButton b1 = new JButton (" + "). c. text. } public void actionPerformed (ActionEvent ev) { String b = ev. text. Int n) { text=t. b1. } public void actionPerformed (ActionEvent ev) { int x=n.toString()).setLayout (new FlowLayout()). c.n=n. text. mai scurtã. this. JTextField text = new JTextField (6). n. public MFrame() { Container c = getContentPane(). int n=0.n) ). if (b. } } O variantã cu numãr minim de clase este definirea clasei cu fereastra aplicatiei ca ascultãtor la evenimente. Int n= new Int(0). public BListener (JTextField t) { text=t.setText (" "+ n). b1. // afiseaza val initiala n } } O altã solutie. JTextField text. c.getValue().add(b2).").addActionListener (new B1L(text.setValue(++x). porneste de la observatia cã metodele de tratare a evenimentelor diferã foarte putin între ele si cã putem defini o singurã clasã ascultãtor pentru ambele butoane: class BListener implements ActionListener { static int n=0.getActionCommand().setText(n.addActionListener (new B2L(text. 147 . JButton b2 = new JButton (" .add (text).indexOf('+')>=0) ++n. c.setText(" "+ n).

"). c. b2.setText(" "+n). c.Florian Moraru – Programare Orientata pe Obiecte c. else if (source==b2) --n. public MFrame() { Container c = getContentPane(). } class B1L implements ActionListener { // clasa inclusa in MFrame public void actionPerformed (ActionEvent ev) { text. } } class B2L implements ActionListener { // clasa inclusa in MFrame public void actionPerformed (ActionEvent ev) { text.add(b2). int n= 0. Exemplul anterior rescris cu clase incluse anonime: class MFrame extends JFrame { JButton b1 = new JButton (" + ").add(b1).setLayout (new FlowLayout()). text. c. text. c.addActionListener (new B2L()). } Utilizarea de clase incluse Pentru reducerea numãrului de clase de nivel superior si pentru simplificarea comunicãrii între clasele ascultãtor la evenimente putem defini clasele receptor ca niste clase interioare cu nume. c. if (source==b1) ++n. dar ele sunt mai greu de citit si de extins în cazul unor interactiuni mai complexe între componentele vizuale.add(b1). O problemã poate fi si limitarea la constructori fãrã argumente pentru clasele anonime. b1.setText(" "+ ++n).").setText(" "+ --n).add (text).add(b2). JButton b2 = new JButton (" . b2 = new JButton (" .setText(" "+n). JTextField text = new JTextField (6).setLayout (new FlowLayout()).getSource().addActionListener (new ActionListener(){ // definire clasa anonima public void actionPerformed (ActionEvent ev) { 148 . JTextField text = new JTextField (6). c. } public void actionPerformed (ActionEvent ev) { Object source =ev. b1.add (text).addActionListener (new B1L()).setText(" "+n). incluse în clasa cu fereastra aplicatiei: class MFrame extends JFrame { JButton b1 = new JButton (" + "). c. public MFrame() { Container c = getContentPane(). } } Definirea de clase incluse anonime reduce si mai mult lungimea programelor. int n= 0. text.

valueOf(++x)). b2. b2= new DecBut(text).add(b2). De aceea. txt. } }). b1= new IncBut(text). public IncBut (JTextField t) { super(" + ").b2. JTextField text = new JTextField (6). dar care apare într-o altã clasã. diferitã de clasa buton.setText(“0”). } // Doua butoane care comanda un camp text class MFrame extends JFrame { JButton b1. public MFrame() { text. Clase generator si clase receptor de evenimente Rolul unui buton sau unei alte componente se realizeazã prin metoda “actionPerformed” de tratare a evenimentelor generate de buton. c. Exemplu: // Buton "+" class IncBut extends JButton implements ActionListener { JTextField txt.setText(" "+ --n). } public void actionPerformed (ActionEvent ev) { int x=Integer.parseInt(txt. deoarece este probabil ca mai multe metode sã se refere la aceste variabile. Container c = getContentPane(). Pe de altã parte. sa propus definirea de subclase specializate pentru fiecare componentã Swing folosite într-o aplicatie.getText()).setText(" "+ ++n).Obiectele acestor clase sunt în acelasi timp sursa evenimentelor dar si ascultãtorul care trateazã evenimentele generate. } }).. Initializarea lor se poate face în afara functiilor (la încãrcarea clasei) deoarece se foloseste un singur obiect fereastrã. c. dacã aceste variabile nu sunt definite ca variabile statice. functia “main” sau alte metode statice nu pot folosi direct variabilele referintã la butoane si la alte componente vizuale.Florian Moraru – Programare Orientata pe Obiecte text.setText(String.setLayout (new FlowLayout()).add (text). O astfel de clasã contine si metoda “actionPerformed”. text. 149 .. c.setText(" "+n).add(b1). addActionListener(this).addActionListener (new ActionListener(){ // alta clasa anonima public void actionPerformed (ActionEvent ev) { text. } } // Buton "-" class DecBut extends JButton implements ActionListener { . txt=t. } } // clasa MFrame poate contine si metoda "main' Variabilele referintã la componente vizuale “b1”si “b2” sunt definite de obicei în afara functiilor (ca variabile ale clasei).setLayout (new FlowLayout()). c. c.

c.execute(). // functia “main” } Comunicarea între obiectele acestor clase specializate se realizeazã fie prin referinte transmise la construirea obiectului (sau ulterior. } public void execute () { int x=Integer. b1. } // Buton de incrementare class IncBut extends JButton implements Command { JTextField txt. public IncBut (JTextField t) { super(" + "). } } // Un buton care comanda un camp text class MFrame extends JFrame implements ActionListener { JButton b1. } } Reducerea cuplajului dintre clase In examinarea unor variante pentru aplicatia anterioarã am urmãrit reducerea lungimii codului sursã si al numãrului de clase din aplicatie. care apeleazã o altã metodã din obiectele buton (sau alt fel de obiecte vizuale specializate). Container c = getContentPane(). txt.add(b2). dar acestea nu reprezintã indicatori de calitate ai unui program cu obiecte si aratã o proiectare fãrã perspectiva extinderii aplicatiei sau reutilizãrii unor pãrti si în alte aplicatii.setLayout (new FlowLayout())..getSource(). fie prin includerea claselor respective într-o clasã anvelopã. } public void actionPerformed (ActionEvent ev) { Command cmd = (Command) ev. printr-o metodã a clasei).add(b1).setText(String. Exemplu : // interfata comuna obiectelor vizuale care executã comenzi interface Command { public void execute(). c.add (text). txt. O variantã a acestei solutii este cea în care de defineste o singurã metodã “actionListener” ( mai exact. Alegerea metodei “execute” de tratare efectivã a unui tip de eveniment se face prin polimorfism. câte o singurã metodã pentru fiecare tip de eveniment). Un dezavantaj comun solutiilor anterioare este cuplarea prea strânsã între obiectele “ascultãtor” la butoane si obiectul câmp text unde se afiseazã rezultatul modificãrii ca urmare a actionãrii unui buton: metoda “actionPerformed” apeleazã direct o metodã dintr-o altã clasã (“setText” din JTextField). } .parseInt(txt. Metoda dispecer de tratare evenimente poate fi inclusã în clasa derivatã din JFrame. c.add (text). cmd.valueOf(++x)).addActionListener(this).. public MFrame() { b1= new IncBut (text). txt=t. functie de sursa evenimentului.setText("0"). Desi 150 .getText()).Florian Moraru – Programare Orientata pe Obiecte c.add(b1). c. JTextField text = new JTextField (6). c.

parseInt(txt. Mediator m = new Mediator(). } public void actionPerformed (ActionEvent ev) { med. addActionListener(this). } public void actionPerformed (ActionEvent ev) { med. } } // clasa mediator class Mediator { private JTextField txt. } public void dec() { int x=Integer. } public void inc() { int x=Integer. JTextField text = new JTextField (6).dec().setText("0"). public void registerTxt (JTextField t) { txt=t. Fiecare din obiectele cu rol în aplicatie nu trebuie sã comunice decât cu obiectul mediator. public DecBut (Mediator m) { super(" .valueOf(x+1)). 151 . Fiecare dintre obiectele care comunicã prin mediator trebuie sã se înregistreze la mediator. care va retine câte o referintã la fiecare obiect cu care comunicã. totusi tratarea evenimentului de buton este specificã acestei aplicatii iar clasele ascultãtor nu pot fi reutilizate si în alte aplicatii. } } // Doua butoane care comanda un cimp text class MFrame extends JFrame { JButton b1.Florian Moraru – Programare Orientata pe Obiecte programarea este mai simplã. addActionListener(this). Pentru reducerea lungimii codului sursã înregistrarea obiectului câmp text la mediator se face în constructorul clasei “MFrame”: // Buton "+" class IncBut extends JButton implements ActionListener { Mediator med. txt. } } // Buton "-" class DecBut extends JButton implements ActionListener { Mediator med. Intr-o aplicatie cu multe componente vizuale care interactioneazã este mai dificil de urmãrit si de modificat comunicarea directã dintre obiecte. med=m. In exemplul cu douã butoane si un câmp text. public IncBut (Mediator m) { super(" + ").getText()). care va transmite comenzile necesare modificãrii unor componente de afisare. txt. med=m.getText()). singura care cunoaste rolul jucat de celelalte clase.setText(String.setText(String.parseInt(txt.valueOf(x-1)).b2. } public void init() { txt. obiectul mediator este informat de actionarea unuia sau altuia dintre butoane si comandã modificarea numãrului afisat în câmpul text. De aceea s-a propus o schemã de comunicare printr-o clasã intermediar (“mediator”).").inc().

In exemplul nostru clasa model trebuie sã memoreze un singur numãr (un obiect) si putem alege între DefaultListModel (care contine un vector de obiecte Object) sau DefaultSingleSelectionModel (care contine un indice întreg). c. m. ci sunt obiecte observate. this. O clasã “model” seamãnã cu o componentã vizualã prin aceea cã poate genera evenimente. c. b2= new DecBut (m). Programul urmãtor foloseste clasa DefaultSingleSelectionModel.Florian Moraru – Programare Orientata pe Obiecte public MFrame() { b1= new IncBut (m). 152 . în functie de obiectul observat (butonul) care l-a notificat de modificarea sa. JTextField txt. Interactiunea dintre un obiect vizual si mediator poate avea loc în ambele sensuri.init(). m. } public int getElement () { return getSelectedIndex(). } } Comunicarea dintre obiecte se face aici prin apel direct de metode: obiectele buton apeleazã metodele “inc” si “dec” ale obiectului mediator. mod=m. numãrul continut poate fi citit cu “getSelectedIndex” si modificat cu metoda “setSelectedIndex”. Inregistrarea unui obiect ascultãtor la un obiect model se face la fel ca si la o componentã vizualã. O variantã de realizare a aplicatiei oarecum asemãnãtoare foloseste schema de clase "observatobservator": butoanele nu actioneazã direct asupra câmpului text. Container c = getContentPane(). care diferã de un mediator prin aceea cã modelul genereazã evenimente în loc sã apeleze direct metode ale obiectelor comandate.setLayout (new FlowLayout()). iar mediatorul apeleazã metoda “setText” a obiectului JTextField folosit la afisare. Putem folosi o clasã model existentã sau sã ne definim alte clase model cu o comportare asemãnãtoare dar care diferã prin datele continute si prin tipul evenimentelor. se genereazã eveniment de tip ChangeEvent la modificarea valorii din obiectul model.add(b2). c. dar diferã de o componentã Swing prin aceea cã evenimentele nu au o cauzã externã (o actiune a unui operator uman) ci sunt cauzate de mesaje primite de la alte obiecte din program. Pentru scurtarea codului am definit o subclasã a clasei model. Obiectul observator actioneazã asupra câmpului text.registerTxt (text). cu nume mai scurt: class SModel extends DefaultSingleSelectionModel { public void setElement (int x) { setSelectedIndex(x).add(b1). Comunicarea prin evenimente si clase “model” O alternativã la obiectul mediator este un obiect “model”. c.add (text). } } // Buton "+" class IncBut extends JButton implements ActionListener { SModel mod.txt=txt. public IncBut (SModel m. JTextField txt) { super(" + "). de exemplu un buton care comandã mediatorului o actiune dar care poate fi dezactivat de mediator.

text). // main } Pentru exemplul anterior putem sã ne definim o clasã model proprie.trim()). } public void actionPerformed (ActionEvent ev) { int n = Integer. b2= new DecBut (m. c.setText("0").getElement().b2. c. m.getText().addChangeListener (text). int x = m..valueOf(x)). setText (String. public MFrame() { text. c. TF text = new TF(6).text). c. care sã continã o variabilã de tipul general Object si sã genereze evenimente de tip ActionEvent.add (text). 153 . SModel m = new SModel(). } public void stateChanged (ChangeEvent ev) { SModel m = (SModel) ev.setLayout (new FlowLayout())..add(b1).add(b2).parseInt(txt. Container c = getContentPane(). } } // tratare evenimente generate de model class TF extends JTextField implements ChangeListener { public TF (int n) { super(n).Florian Moraru – Programare Orientata pe Obiecte addActionListener(this). prin extinderea clasei AbstractListModel. }. care asigurã partea de generare a evenimentelor si de gestiune a listei de ascultãtori la evenimente. mod.getSource(). } } // Doua butoane care comanda un camp text class MFrame extends JFrame { JButton b1. b1= new IncBut (m.setElement(n+1).

Comunicarea dintre cele trei pãrti ale modelului MVC se face fie prin evenimente. fie prin apeluri de metode. Arhitectura MVC (Model-View-Controller) separã în cadrul unei componente vizuale cele trei functii esentiale ale unui program sau ale unui fragment de program: intrãri (Controller). Existã clase predefinite pentru obiecte model (DefaultComboBoxModel. Legãtura de la imagine la controler constã în aceea cã trebuie mai întâi pozitionat mouse-ul pe suprafata butonului si apoi apãsat butonul de la mouse.Florian Moraru – Programare Orientata pe Obiecte 13. un buton simplu este modelat printr-o variabilã booleanã cu douã stãri (apãsat sau ridicat). cu text sau cu imagine afisatã pe buton. Este posibil ca imaginea butonului sã fie modificatã direct de actiunea operatorului (de controler) si nu indirect prin intermediul modelului. In figura urmãtoare linia continuã reprezintã un apel de metodã iar linia întreruptã reprezintã un eveniment transmis între obiecte.Preia actiunile transmise prin mouse sau prin tastaturã obiectului respectiv. date si prelucrãri (Model). JList. prin crearea impresiei de buton în pozitie ridicatã sau coborâtã. Componente Swing cu model Arhitectura MVC Un obiect vizual folositã într-o interfatã graficã îndeplineste mai multe functii: . Model View Controller De exemplu. în afara legãturilor dintre model si celelalte douã pãrti (imagine si comandã). Clasele 154 . Imaginea butonului reflectã de obicei starea sa. printr-o tastã) si are pe ecran imaginea unei ferestre dreptunghiulare (rotunde). iar partea de comandã (“controller”) interpreteazã gesturile utilizatorului si actioneazã asupra modelului (defineste “comportarea” componentei). Partea de comandã este notificatã prin evenimente de actiunile (“gesturile”) operatorului uman si modificã starea modelului prin apeluri de metode ale obiectului cu rol de “model”. pe baza unor colectii. deci defineste starea si logica componentei. . Obiectul model poate fi creat automat în constructor. JTree. . în plus poate apela direct si metode ale obiectului de redare (pentru modificarea imaginii afisate). Imaginea (“view”) redã într-o formã vizualã modelul. JMenuBar includ întotdeauna un obiect model care poate fi extras printr-o metodã “getModel” pentru a se opera asupra lui. DefaultTreeModel) dupã cum se pot defini si alte clase model care sã respecte interfete impuse. Componentele JComboBox.Prezintã pe ecran date într-o imagine specificã (controlabilã prin program). iesiri (View).DefaultListModel. prin evenimente. deoarece comunicarea dintre controler si imagine poate fi destul de complexã. iar partea de imagine poate interoga modelul. Modelul semnaleazã pãrtii de prezentare orice modificare în starea sa. cu sau fãrã contur. Partea numitã “model” reprezintã datele si functionalitatea componentei. sau poate fi creat de programator si transmis clasei care asigurã prezentarea datelor din model. prin apeluri de metode. este comandat printr-un clic de mouse (si. Un model este o structurã de date care poate genera evenimente la modificarea datelor si contine metode pentru adãugarea si eliminarea de “ascultãtori” la evenimentele generate de model. Clasele JFC folosesc o variantã a modelului MVC cu numai doi participanti: o clasã model si o clasã “delegat” care reuneste functiile de redare si de control pentru a usura sarcina proiectantului. JTable.Permite modificarea datelor ca rãspuns la actiuni ale operatorului uman sau la cereri exprimate prin program si actualizeazã imaginea de pe ecran a acestor date. Poate exista si o legãturã directã între partea de control si partea de redare. eventual.

se afiseazã date din directoare sau din fisiere al cãror nume se introduce sau se modificã de cãtre operator dupã afisarea interfetei grafice (prin introducere în câmpuri text.”doi”. iar afisarea trebuie sã reflecte aceste modificãri. nu se construiesc alte obiecte vizuale (JList. Modificãrile asupra obiectului model sunt redate automat pe ecran deoarece obiectul vizual este ascultãtor la evenimente generate de model. // obiect de tip Vector JList list3 = new JList (new DefaultListModel()). In general. Utilizarea unui model de listã Componenta vizualã JList afiseazã pe ecran o listã de valori. Clasele model gata definite au un nume care începe cu “Default” (DefaultListModel. TableModel. dar în listã se memoreazã atât adresa cât si tipul obiectului ascultãtor. JList list2 = new JList(vec). Datele afisate pot fi de orice tip clasã si sunt memorate într-un obiect colectie (Vector) sau model. De exemplu.JTable s. JTree) cu noile date dupã afisarea interfetei grafice si nici nu se apeleazã metode de reafisare (“repaint” sau altele). Exemple: String v ={“unu”. Existã mai multi constructori. Clasele cu model au atât constructor cu argument model. Datele prezentate într-un obiect JList. se pot modifica în cursul executiei programului. DefaultTableModel. O altã posibilitate este sã se creeze un nou model (de exemplu dupã reordonarea unui vector sau unui tabel) si sã se retransmitã noul model la acelasi obiect vizual din interfata graficã. deci modificarea datelor din model va modifica automat si afisarea pe ecran (se vor afisa noile date din obiectul model). dar are metode de adãugare si de modificare a obiectelor din vectorul folosit de obiectul model. Exemplu de afisare într-un obiect JList a numelor fisierelor dintr-un director al cãrui nume se introduce (sau se modificã) într-un câmp text: 155 .a. ele au de obicei înregistrate în lista de ascultãtori si obiecte ascultãtor la selectie. de exemplu). cu parametri de tipuri diferite: vector intrinsec. sunt ascultãtori la evenimente generate de clasele model respective. clasele model contin si metode de modificare a datelor (interfetele claselor model nu impun astfel de metode). JTable.”trei”}. noduri din arbore). celule sau coloane din tabel. fie prin mouse. Ca tehnicã generalã. Lista de ascultãtori a unui obiect Swing este unicã. Clasa model de listã predefinitã DefaultListModel are un singur constructor. DefaultTreeModel) si constructori ce primesc ca argument un vector (model de listã). ComboBoxModel. dar aceste clase trebuie sã respecte anumite interfete (ListModel. fãrã argumente. // vector intrinsec Vector vec = new Vector ( Arrays. Utilizatorii au posibilitatea sã-si defineascã alte clase model. sau se apeleazã metode de modificare a obiectului model.Florian Moraru – Programare Orientata pe Obiecte JList.a. cu generarea de evenimente de selectie. sub forma unei coloane. o matrice (model de tabel) sau un nod rãdãcinã (model de arbore). obiect Vector sau obiect ListModel. o referintã la acest obiect este memoratã în obiectul JList de constructorul obiectului JList. JTable. Pentru facilitarea definirii de noi clase model (model de listã sau de tabel) existã clase abstracte care implementeazã partial interfetele mentionate: AbstractListModel. Obiectele vizuale cu model permit si selectarea unor elemente afisate (elemente din listã. AbstractTableModel. de aceea existã metode “getModel” în toate aceste clase. cât si constructori cu diverse structuri de date. Un obiect JList cu continut variabil se poate folosi si fãrã obiect model creat explicit. si permite selectarea uneia dintre valorile afisate. ci se apeleazã metode care transmit la obiectul vizual un alt model (“setModel”). prin retransmiterea unui vector cu date la obiectul JList (metoda “setListData”). TreeModel). JTree s. pe baza cãrora se construiesc automat obiecte model. JList list1 = new JList (v). indiferent dacã s-a transmis un model creat separat sau nu (model creat implicit).asList(v)). fie prin tastele cu sãgeti.

600).add (new JScrollPane(jlist)). show(). care apeleazã metoda “setModel” din JList. Exemplu: class FileList extends JFrame implements ActionListener{ DefaultListModel model = new DefaultListModel(). show()."North").exists()) return.i++) // adauga nume fisiere la model model.getText()).600). jlist.addActionListener (this). Trebuie observat cã orice constructor creeazã un obiect model simplu.add (tf. 156 . String files[] = d. cp. “setElementAt”. // vector cu nume de fisiere Vector v = new Vector (Arrays. JTextField tf = new JTextField(12).list().clear().add (new JScrollPane(jlist)). cp.isDirectory ()) return.asList(files)). if (! d. // transmite vector la JList } } Modificarea datelor din vector (vector intrinsec sau obiect Vector) nu are efect asupra datelor afisate în obiectul JList decât dacã se apeleazã la fiecare modificare si metoda “setListData”.length. } public void actionPerformed (ActionEvent ev) { File d=new File(tf.i<files. “getSize”) si nu permite modificarea datelor din acest model (date transmise printr-un vector). String files[] = d. // modelul modifica si obiectul JList ! } } Putem sã nu folosim obiect model separat atunci când lista este folositã doar pentru selectia unor elemente din listã iar continutul listei afisate nu se mai modificã. // pentru continut director JTextField tf = new JTextField(12). tf. iar metoda “getModel” poate fi apelatã indiferent cum a fost creatã lista. tf."North"). model. File cu nume director if (! d. public FileList() { Container cp = getContentPane(). cp. Modificarea datelor din model (prin metode ca “addElement”.getText()). Modelul creat implicit are numai metode de acces la date (“getElementAt”.add (tf. // pentru nume director public FileList() { Container cp = getContentPane().list(). // creare ob. cp.addActionListener (this).Florian Moraru – Programare Orientata pe Obiecte class FileList extends JFrame implements ActionListener{ JList jlist = new JList().addElement(files[i]). // sterge continut anterior model for (int i=0. setSize(300. JList jlist = new JList(model).setListData(v). setSize(300. deoarece obiectul JList este ascultãtor la evenimentele generate de model. } public void actionPerformed (ActionEvent ev) { File d=new File(tf. dacã nu primeste unul ca argument. “removeElementAt”) se reflectã automat pe ecran.

// lista de selectie public ListSel(Object a[]) { list = new JList(a). // afisare lista si cimp text } public void valueChanged(ListSelectionEvent e) { // lansata la selectie din lista String sel = (String) list. } } Programul anterior nu permite modificarea continutului listei de selectie deoarece interfata ListModel nu prevede metode de modificare a modelului.add(new JScrollPane(list). afisare element selectat JList list. Container cp = getContentPane(). private JTextField toAdd = new JTextField(10). cp.addActionListener(this). field =new JTextField(10). Selectarea unui element dintr-o listã produce un eveniment ListSelectionEvent. // ptr. Ca aspect. cp. Exemplul urmãtor aratã cum se pot adãuga elemente la sfârsitul unei liste de selectie. dar permite selectarea unuia sau mai multor elemente din listã.add(addBut). // adauga camp text cp. private JButton addBut=new JButton("Add"). // ascultator la camp text Container cp = getContentPane(). // acest obiect este si ascultator la ev. private DefaultListModel listModel. // adauga buton cp.add(field. // campul text sub lista pack().setLayout( new FlowLayout()). Putem obtine fie obiectul selectat (metoda “getSelectedValue”)."patru"}. setVisible(true).Florian Moraru – Programare Orientata pe Obiecte De obicei lista este afisatã într-un panou cu derulare (JScrollPanel) pentru a permite aducerea pe ecran a elementelor unei liste oricât de mari (care nu este limitatã la numãrul de linii din panoul de afisare). // valoare element selctat field. // ascultator la buton toAdd. addBut. Exemplul urmãtor aratã cum se poate programa o listã de selectie. // date afisate in lista ListSel ls = new ListSel(a). fie pozitia în vector (sau în model) a elementului selectat (metoda “getSelectedIndex”).add(toAdd). cu un buton pentru adãugare (“Add”) si un câmp text unde se introduce elementul ce va fi adãugat la listã. class ListAdd extends JFrame implements ActionListener { private JList list.setText(sel). // creare obiect lista list. tratat de metoda “valueChanged” (din interfata ListSelectionListener).getSelectedValue().addListSelectionListener(this).add(new JScrollPane(list)). Un obiect JList genereazã evenimente cauzate de selectarea unui element din listã si evenimente cauzate de modificarea continutului listei. // creare cimp text cp. // constructor public ListAdd() { listModel = new DefaultListModel(). elementul selectat este afisat într-un câmp text. class ListSel extends JFrame implements ListSelectionListener { JTextField field. // model de date pentru lista list = new JList(listModel)."trei". pentru verificarea selectiei corecte. cp. // afisare element selectat } public static void main(String s[ ]) { String[ ] a ={"unu". o listã JList seamãnã cu o zonã text JTextArea. // adauga panou cu derulare pentru lista } 157 ."doi".”Center”).”South”).addActionListener(this).

intervalAdded Un observator la modificãri ale listei trebuie sã implementeazã interfata ListDataListener. // comanda operatia de stergere JList list.addElement(a[i]).getDefaultToolkit().”North”). metoda “removeElement” apeleazã metoda “fireIntervalRemoved” si “setElementAt” apeleazã metoda “fireContentsChanged”.Florian Moraru – Programare Orientata pe Obiecte // receptor la buton si la campul text public void actionPerformed(ActionEvent e) { if (toAdd.getText().addElement → AbstractModel. Putem defini o clasã adaptor cu implementãri nule pentru toate cele trei metode : class ListDataAdapter implements ListDataListener { public void intervalRemoved (ListDataEvent ev) { } public void intervalAdded (ListDataEvent ev) { } public void contentsChanged (ListDataEvent ev) { } } Exemplul urmãtor aratã cum putem reactiona la stergerea unui element din listã prin afisarea dimensiunii listei dupã stergere: class LFrame extends JFrame implements ActionListener. // adauga element la model lista toAdd. return. // pozitia elementului sters // constructor public LFrame (Object a[]) { for (int i=0. toate cu argument ListDataEvent. 158 . un obiect al acestei clase se va înregistra ca ascultãtor la listã. c. Modificarea vectorului din modelul de listã genereazã evenimente pentru obiecte înregistrate ca ascultãtori la listã: metoda “addElement” din clasa DefaultListModel apeleazã metoda “fireIntervalAdded”.”South”).addElement(toAdd. model.i<a. cu o metodã “valueChanged” . urmãtorul: DefaultListModel. but. tipul evenimentului si pozitia din vector unde s-a produs modificarea. deci sã defineascã metodele “intervalAdded”. Container c = getContentPane().addListSelectionListener(this). Lantul de apeluri care produce activarea unei metode scrise de programator ca urmare a unei modificãri a colectiei de date din model este.addActionListener (this). // sterge camp text } } } Exemplul anterior poate fi extins cu un buton de stergere element selectat din listã si o clasã care implementeazã interfata ListSelectionListener.addListDataListener( new LDL()). c.setText (""). // daca nimic in campul text } listModel.add(new JScrollPane(list)).i++) model. list. // rezultat modificare JButton but = new JButton("Delete").getText()).add(but. care contine informatii despre sursa.fireIntervalAdded → → (ListDataEvent) → ListDataListener. în cazul listei. ListSelectionListener { DefaultListModel model = new DefaultListModel(). list = new JList(model). c.beep(). “intervalRemoved” si “contentsChanged”. int index.length.equals("")) { // verifica ce este in campul text Toolkit. // model de lista JTextField result = new JTextField(4).add(result.

Clasa JTable are si ea metodele “getValueAt” si “setValueAt”. setSize(300.”One”. // cu vectori intrinseci // cu obiecte Vector // cu obiect model Clasa DefaultTableModel are mai multi constructori cu argumente (inclusiv cu matrice de celule si vector de titluri) si are metode pentru retransmiterea datelor la model (setDataVector) si pentru modificarea directã a valorii din oricare celulã din tabel: setValueAt(Object value. 159 .”Eins”}. Crearea unui model explicit este necesarã în cazuri mai speciale: când nu vrem ca toate celulele sã fie editabile. }. “Englezã”. când vrem ca datele din celule sã fie prezentate prin imagini sau altfel decât prin metoda “toString” (folositã implicit la afisarea datelor).int row. JTable table1 = new JTable (cells. Un obiect JTable include un obiect model. dar pot fi modificate fie prin program (metoda “setPreferredWidth”).removeElementAt(index).getSelectedIndex(). // nume de coloane Object[ ][ ] cells = { {“Unu”. fie prin deplasarea (“tragerea”=“dragging”) marginilor coloanelor folosind un mouse. modelul de date creat implicit este public accesibil si permite modificarea datelor din model prin metoda “setValueAt”.200). Secventa urmãtoare ilustreazã folosirea unor constructori: // tabelul primeste direct datele String [ ] titles = { “Românã”. dar numai tipuri clasã. } } Utilizarea unui model de tabel Componenta vizualã JTable afiseazã pe ecran un tabel de celule structurat în linii si coloane. creat automat (implicit) sau primit de un constructor. Object[] numecol). } // ascultator la butonul de stergere public void actionPerformed (ActionEvent ev) { model. Dimensiunile coloanelor sunt implicit egale si calculate în functie de numãrul coloanelor. modificarea aspectului tabelului si obtinerea de informatii despre celulele selectate prin “mouse”.. care apeleazã metodele cu acelasi nume ale obiectului model continut de obiectul tabel.. JTable (Vector date. {“Doi”.”Two”. Datele din celule pot fi de tipuri diferite. Vector numecol).setText(" "+ model.”Zwei”}.”Germanã”}. // tabelul primeste un model al datelor DefaultTableModel model = new DefaultTableModel (cells. JTable (TableModel model). } // ascultator la selectie din lista public void valueChanged(ListSelectionEvent e) { index = list. Construirea unui obiect JTable se poate face folosind un vector de titluri si o matrice de celule (de obiecte Object)..setText(model. Fiecare coloanã poate avea un titlu.size()). } // ascultator la stergere din lista class LDL extends ListDataAdapter { // clasa inclusa in LFrame public void intervalRemoved (ListDataEvent ev) { result. JTable table2 = new JTabel (model).int col).Florian Moraru – Programare Orientata pe Obiecte result.titles). permitând selectarea si editarea de celule. sau folosind un obiect model TableModel : JTable (Object[][] date.titles).size()+" "). setVisible(true).

titles)." Date "}.400). add (tf.length. // daca e director model.getRowCount()."North"). setSize(400. if ( ! f.i. setSize(400. // nume fisier model.1)."North"). add(new JScrollPane(jtable).length][4]. // daca s-a introdus gresit TableModel model = jtable.setValueAt (new Date(fl[i].i<fl.0). // creare tabel provizoriu (fara date) add (tf.i.i<fl. creat initial: class FileTable extends JFrame implements ActionListener { JTextField tf= new JTextField (14).toLocaleString(). // pentru nume director JTable jtable.i.600).getName().j<4.Florian Moraru – Programare Orientata pe Obiecte In exemplele urmãtoare se afiseazã într-un obiect JTable atributele fisierelor dintr-un director al cãrui nume este introdus într-un câmp text (pe 4 coloane).listFiles(). add(new JScrollPane(jtable).length.length())." Dir ".i. // pune in model atribute fisiere din director File [] fl =f. tf.i++) for (int j=0. tf.setValueAt (new Long(fl[i]. // titluri de coloane Object[][] cells= new Object[100][4] .j++) model.isDirectory()) return. // atributele se obtin cu metode din File for (int i=0.2).i<model.addActionListener(this)."Center"). // titluri de coloane DefaultTableModel model= new DefaultTableModel() . // pentru nume director JTable jtable." Date "}.3). // daca s-a introdus gresit File [] fl =f. // obtinere model creat de constructor // sterge ce era inainte in tabel for (int i=0.isDirectory()). } public void actionPerformed (ActionEvent ev) { File f = new File (tf.i++) { // pentru fiecare linie i din modelul de tabel model." Dir ". // un obiect model public FileTable() { jtable = new JTable(model). // data } } } In varianta urmãtoare se creeazã un nou model de tabel pentru fiecare nou director si se transmite acest model la acelasi obiect JTable.listFiles().getText()).lastModified()).setValueAt (fl[i]. Prima solutie modificã datele din modelul creat implicit si extras cu “getModel”: class FileTable extends JFrame implements ActionListener { JTextField tf= new JTextField (14). // dimensiune fisier model.setValueAt ("". Object a[][] = new Object [fl. // numarul de linii depinde de nr de fisiere ! public FileTable() { jtable = new JTable(cells. } public void actionPerformed (ActionEvent ev) { File f = new File (tf." Size ".addActionListener(this). // pentru continut director String [] titles= {"File Name".j).i++) { // completare matrice cu atribute fisiere 160 ."Center").getModel(). // aloca memorie ptr matrice for (int i=0. // pentru continut director String [] titles= {"File Name".i.setValueAt (new Boolean (fl[i].isDirectory()) return.getText()). // obiect cu nume director if ( ! f." Size ".

getName(). // nici o celula editabila (modificabila din exterior) } } // tabel nemodificabil prin extindere AbstractTableModel class MyTableModel extends AbstractTableModel { Object [ ] titles. iar pentru a considera o coloanã ca o listã trebuie folosit un model de coloanã ColumnModel. } public boolean isCellEditable (int row.prin metoda “getSelectedColumn” din clasa JTable. } // numar de linii public Object getValueAt (int row. Numãrul coloanei selectate se poate afla în douã moduri: . // transmis la ob. } // numar de coloane public int getRowCount() { return cells. unei coloane sau unei celule. JTable } } Obiectul model separat transmis la crearea unui obiect JTable poate fi: din clasa DefaultTableModel. titles=t. Pentru evenimente de selectie se foloseste modelul de selectie de la lista JList : fiecare linie din tabel este consideratã ca o listã. Object t[]) { cells=c.length. .cnames).toLocaleString(). 161 . // continut celule public MyTableModel ( Object c[][].Evenimente produse de modificarea structurii tabelului In exemplul urmãtor se permite selectarea unei celule (prin “mouse” sau prin tasta Tab) si se afiseazã într-un câmp text coordonatele (linie-coloanã) celulei selectate.int col) { return false.Evenimente produse de modificarea datelor din tabel . Selectarea unei coloane din tabel se face prin clic pe mouse dupa pozitionarea pe coloana doritã (pe o celula cu date.length. // titluri coloane Object [ ][ ] cells .Evenimente produse la selectarea unei linii.} } Pentru ambele variante crearea tabelului este identicã : JTable table = new JTable(new MyTableModel(cells. } model = new DefaultTableModel(a. } public int getColumnCount() { return titles. Object[ ] cnames) { super (cells.lastModified()). Un tabel poate genera mai multe tipuri de evenimente : . a[i][3]= new Date(fl[i]. } public String getColumnName(int col) { return titles[col].titles)).prin tratarea evenimentului de selectie coloanã ListSelectionEvent.titles). a[i][1]= new Boolean (fl[i].length()).toString(). dintr-o subclasã a clasei DefaultTableModel sau AbstractTableModel. Exemplele urmãtoare aratã cum se pot defini alte clase model de tabel pentru un tabel cu celule nemodificabile de cãtre operator (dar modificabile prin program): // tabel nemodificabil prin extindere DefaultTableModel class MyTableModel extends DefaultTableModel { public MyTableModel (Object[ ][ ] cells. a[i][2]= new Long(fl[i]. .Florian Moraru – Programare Orientata pe Obiecte a[i][0]= fl[i].int col) { return cells[row][col].isDirectory()). dintr-o clasã nouã care sã implementeze interfata TableModel. nu pe titlul coloanei). // crearea unui nou model jtable.setModel(model).

"+c). cp. int i = sm.Pe baza unui vector de vectori (Object[] sau Vector). se poate face numai prin modificarea totalã (crearea altui obiect model) sau partialã a modelului folosit de obiectul JTree. colSM.addListSelectionListener(this).getSelectionModel().getSelectionModel(). care afiseazã un arbore cu date constante fãrã legãturã cu aplicatia (colors. // i este numar de linie else c=i. // model ptr selectie linii ListSelectionModel colSM . din acest motiv este important sã stim cum se creeazã si cum se modificã modelul datelor dintr-un arbore. de a comprima un subarbore la rãdãcina sa si de a selecta orice nod din arbore prin “mouse”.add(rc. show().getMinSelectionIndex(). // indice selectat din lista if (sm==rowSM) r=i. pentru a trata evenimente legate de selectia unor noduri este folosit un model TreeSelectionModel. rowSM = table."South"). de exemplu). Un obiect JTree se poate construi în mai multe feluri: . // model ptr selectie coloane public TableSel(Object a[][]. subtip al interfetei TreeModel. etc. obiect Vector.add(new JScrollPane(table)). sports.getSource(). setSize(300. cu posibilitatea de a extinde orice nod intern prin subarborele sãu. acesta poate fi un obiect din clasa DefaultMutableTreeNode sau obiecte din clase definite de utilizatori care implementeazã 162 . Modificarea datelor dintr-un obiect JTree. int r. Modelul de arbore poate fi creat explicit de utilizator înainte de a construi obiectul JTree sau este creat automat în interiorul clasei JTree pe baza unei alte colectii de date furnizate de utilizator (vector intrinsec.c. Existã si un constructor JTree fãrã argumente. } // actiune la selectie celula public void valueChanged(ListSelectionEvent e) { ListSelectionModel sm = (ListSelectionModel)e. Clasa model de arbore foloseste un obiect nod.addListSelectionListener (this). JTable table . table. Modelul de arbore este fie un obiect din clasa DefaultTreeModel.). dar care implementeazã interfata TreeModel. Container cp= getContentPane().Pe baza unui model de arbore.setText(r+". fie obiect al unei clase definite de utilizator.b). . subtip al interfetei TreeNode. Acest model genereazã numai evenimente de modificarea datelor. colSM = table. .Pe baza unui obiect nod de arbore (rãdãcinã).Object b[]) { table= new JTable(a. cp. dupã afisarea acestuia. rowSM. pentru un arbore de fisiere putem alege un director initial arbitrar (directorul curent.300).setCellSelectionEnabled(true). } } Utilizarea unui model de arbore Clasa JTree permite afisarea unor structuri arborescente (ierarhice).getColumnModel().Florian Moraru – Programare Orientata pe Obiecte class TableSel extends JFrame implements ListSelectionListener { JTextField rc = new JTextField(5). food) dar de obicei putem evita aceastã situatie. // i este numar de coloana rc. // numar linie si coloana celula curenta ListSelectionModel rowSM . O aplicatie poate trata evenimentele de selectare a unor noduri sau de modificare structurã arbore sau de extindere -contractie noduri corespunzãtor scopului aplicatiei. ca rãdãcinã a arborelui.

plus o legãturã cãtre nodul pãrinte. // enumerare noduri din subarbore public Enumeration postorderEnumeration() . // adauga acestui nod un fiu public Enumeration preorderEnumeration() . // nodul parinte al acestui nod boolean isLeaf(). // adaugare noduri la radacina tree.r).getAbsolutePath()). prin metode ale clasei DefaultTreeModel. // succesor cu indice cunoscut int getIndex(TreeNode node).getAbsolutePath()). // enumerare noduri din subarbore public Enumeration breadthFirstEnumeration() . // elimina succesor dat ca nod void setUserObject(Object object). Pentru a ilustra aceste posibilitãti de modificare dinamicã a unui obiect JTree vom relua problema afisãrii continutului oricãrui director (si a subdirectoarelor sale) într-un obiect JTree (numele directorului este preluat dintr-un câmp text).getText()). Se poate modifica direct modelul. // modifica datele din acest nod void removeFromParent(). // indicele unui succesor dat TreeNode getParent().r). class DirTree extends JFrame implements ActionListener { JTree tree. // elimina succesor cu indice dat void remove(MutableTreeNode node). Container cp = getContentPane(). // modifica parintele acestui nod Clasa DefaultMutableTreeNode implementeazã interfata MutableTreeNode si foloseste câte un vector de succesori la fiecare nod (obiect de tip Vector). // nume director TNode r . Exemple de metode în plus fatã de interfete: public Object getUserObject (). // creare si transmitere model nou } // constructor public DirTree () { File f = new File(". dar este mai simplu de lucrat cu metode ale clasei nod de arbore.").isDirectory()) return. 163 . fie prin crearea unui nou arbore (cu o altã rãdãcinã) si transmiterea adresei noii rãdãcini la modelul de arbore existent. // enumerator pentru succesorii acestui nod Exemple de metode din interfata MutableTreeNode: void remove(int index). // obtine date din acest nod (orice obiect) public void add(MutableTreeNode child) . // enumerare noduri din subarbore Modificarea dinamicã a continutului unui obiect JTree se face fie prin crearea unui nou model de arbore si transmiterea lui la obiectul JTree deja afisat. // numar de succesori ai acestui nod TreeNode getChildAt(int childIndex). JTextField dir= new JTextField (14). // eliminã acest nod void setParent(MutableTreeNode newParent). TNode r = new TNode(f. TNode r = new TNode(f. Exemple de metode din interfata TreeNode: int getChildCount(). // o noua radacina r dirtree(f. tree= new JTree(new DefaultTreeModel(r)). // ascultator la campul text (modificare director) public void actionPerformed (ActionEvent ev) { File f=new File (dir. dirtree( f.Florian Moraru – Programare Orientata pe Obiecte interfata TreeNode (noduri nemodificabile) sau interfata MutableTreeNode (cu operatii de modificare).setModel(new DefaultTreeModel(r)). // daca acest nod este o frunza Enumeration children(). if (! f.

tf. // continua pentru nodul “fiu” } } Urmeazã varianta cu modificarea rãdãcinii de arbore folosite de modelul existent: class DirTree extends JFrame { JTree tree.addActionListener( this). Exemplu de afisare valoare nod selectat: public void valueChanged (TreeSelectionEvent ev) { try { // TNode node= (TNode) tree.500).add(fiu).add (dir. // daca nu e director (nod frunza) File [] files=d.Florian Moraru – Programare Orientata pe Obiecte cp.} } 164 .getUserObject(). setSize(300. } // creare arbore de fisiere private void dirtree (File d.listFiles(). // nume director DefaultTreeModel model = new DefaultTreeModel(). TNode r) { if ( ! d.setRoot(r).i++){ // pentru fiecare fisier din director File file=files[i].i<files. cp. } . if (! f.printStackTrace().length.isDirectory() ) return. } Pentru ambele programe se afiseazã initial continutul directorului curent (atunci se creeazã modelul de arbore) deoarece constructorul fãrã argumente JTree afiseazã niste valori constante (care nu reprezintã nume de fisiere). La selectarea unui nod din arbore se genereazã evenimentul TreeSelectionEvent si se apeleazã metoda “valueChanged” din ascultãtorii de tip TreeSelectionListener care au fost înregistrati la obiectul JTree.getLastSelectedPathComponent().. dir.getSelectionPath(). // fisiere din directorul d for(int i=0."North").fiu). TNode node =(TNode) tree. TNode r = new TNode(f.toString()).r). model. // ascultator la campul text (modificare director) public void actionPerformed (ActionEvent ev) { File f=new File (dir. // creare nod pentru acest fisier r. // adauga ca fiu la nodul r dirtree (file.getText()).getAbsolutePath()).isDirectory()) return.. dirtree(f.getLastPathComponent().setText(node. if (node==null) return. JTextField dir= new JTextField (14).add (new JScrollPane(tree)). TNode fiu=new TNode(file). // afisare valoare nod selectat } catch(Exception ex) {ex.

xml” si “org. fiecare atribut are un nume si o valoare. StAX s.xml”.a. 165 . “org.w3c”. ca în HTML). fisiere de configurare. . deci semnificatia sau modul de interpretare a acestor date (si nu modul de prezentare. Java si XML Fisiere XML în aplicatii Java Prelucrarea si generarea de documente XML în programe Java sunt operatii tot mai necesare datoritã diverselor utilizãri ale fisierelor XML: serializare continut obiecte si apeluri de proceduri la distantã. fãrã nici o referire la o clasã concretã. Un fisier XML este un fisier text care contine marcaje. Marcajele au rolul de a descrie datele încadrate de marcaje. </ computer > <computer name=”SDS” price=”495” /> Asa cum se vede din ultimul exemplu. transmiterea de mesaje între calculatoare (mesaje SOAP). Standardele formale sau impuse de practicã (SAX. Un element poate contine alte elemente. proprietãtile unor entitãti (cum sunt numele si pretul unui produs) pot fi transmise fie ca atribute ale marcajului ce specificã entitatea. Mediile integrate Java (IDE) includ editoare de XML si permit verificãri asupra sintaxei XML. un marcaj singular (“empty element tag”) este o prescurtare pentru cazurile în care nu existã caractere între “start tag” si “end tag”. astfel <timestmp/> este o prescurtare pentru <timestmp> </timestmp> Marcajele de început sau singulare pot fi însotite de atribute.) constituie si exemple de programare la nivel de interfatã. Incepând cu versiunea 4 clasele Java pentru aplicatii XML au devenit standard si s-au extins. continutul poate include un text sau alte elemente sau elemente si text. Exemple: <computer type =”desktop”> . fie ca elemente incluse între marcajele ce definesc o entitate. . în pachete ca “javax.Florian Moraru – Programare Orientata pe Obiecte 14. fisiere “build” folosite de programul “ant” pentru construire si instalare de aplicatii. fisiere descriptor de componente Java (“deployment descriptor”). Un prim exemplu este un mic fragment dintr-un fisier XML listã de preturi: <priceList> <computer> <name> CDC </name> <price> 540 </price> </ computer > <computer> <name> SDS </name> <price> 495 </price> </ computer > </priceList> Un element este fragmentul cuprins între un marcaj de început si un marcaj de sfârsit: <price> 540 </price> Un element are un nume (acelasi cu marcajul de început) si poate avea mai multe atribute si un continut. DOM. pe oricâte niveluri de includere.a. arãtând cum un API poate fi definit numai din interfete Java. In afara marcajelor pereche pot exista si marcaje singulare de forma <tag/>. în sensul cã multimea de marcaje folosite poate fi definitã în functie de tipul aplicatiei si nu este fixatã dinainte (ca în HTML). un marcaj este un sir între paranteze unghiulare (‘<’ si ‘>’). Limbajul XML (“Extensible Markup Language”) este un limbaj cu marcaje (“tag” va fi tradus aici prin “marcaj”) extensibil. s.

Exemple de erori de utilizare a marcajelor semnalate de un parser: <a> … <b> … </a> … </b> <a> … <b> … </b> … </c> (corect este <a>…<b>…</b>.xmlsoap. price) > <!ELEMENT name (#PCDATA) > <!ELEMENT price (#PCDATA) > PCDATA (“Parsed Character Data”) este un alt nume pentru siruri de caractere care trebuie luate în considerare de parser (spre deosebire de tipul CDATA). Se pot defini clase corespunzãtoare elementelor XML. Un parser XML primeste ca intrare un fisier XML. utilizat si la validarea fisierelor XML care folosesc aceste marcaje. care poate permite si crearea de noi documente XML prin program.. dar locul unde este definit ‘m’ nu este precizat. Un document XML este o instantiere a unei scheme. cu relatiile. verificã utilizarea corectã a marcajelor si (optional) face o validare fatã de fisierul DTD sau XSD specificat. Validarea mãreste timpul de analizã si este inutilã atunci când fisierele XML sunt generate de cãtre programe si nu sunt editate manual. iar trecerea între fisiere XML si clase Java (în ambele sensuri) este asiguratã de clasele din interfata API numitã JAXB (Java XML Binding). Fisierul XSD este un fisier XML care foloseste marcaje predefinite si oferã mai multe posibilitãti decât fisierul DTD în descrierea multimii de marcaje.</a>) Validarea XML necesitã specificarea unui fisier schemã (DTD sau XSD) la începutul documentului XML si înseamnã certificarea faptului cã au fost folosite numai marcajele din schemã.Florian Moraru – Programare Orientata pe Obiecte Multimea marcajelor folosite într-un document XML si relatiile dintre ele sunt definite într-un fisier “schemã” XML. Un fisier XML bine format (“well formed”) are toate perechile de marcaje incluse corect. 166 .org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.org/soap/encoding/"> <SOAP-ENV:Body> <m:GetLastTradePrice xmlns:m="Some-URI"> <symbol>MOT</symbol> </m:GetLastTradePrice> </SOAP-ENV:Body> </SOAP-ENV:Envelope> In exemplul anterior se folosesc douã prefixe pentru spatii de nume: SOAP-ENV si ‘m’. Se folosesc douã tipuri de fisiere schemã: fisiere DTD (Document Type Definition) si fisiere XSD (XML Schema Definition). Un parser XML este un analizor de documente XML. asa cum un obiect este o instantiere a unei clase. atunci numele de marcaje trebuie prefixate de un simbol asociat spatiului de nume. la fel cum incluse perechile de paranteze dintro expresie sau perechile de acolade din Java. Exemplu de mesaj (cerere la un serviciu Web) în protocolul SOAP : <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas. Exemplu de fisier DTD pentru lista de preturi: <!ELEMENT priceList (computer)+> <!ELEMENT computer (name. atributele si tipurile de date descrise în schemã. Clasele Java sunt generate pe baza unei scheme XML (de obicei un fisier DTD). primul are precizatã adresa Web (URI) unde este definit. Pentru a permite utilizarea de marcaje cu acelasi nume în scheme diferite s-a introdus si în XML conceptul “spatiu de nume”: un spatiu de nume contine marcaje distincte si este declarat la începutul unui fisier schemã. Dacã într-un acelasi fisier XML se folosesc marcaje din mai multe spatii de nume.xmlsoap.

materializat într-un API Java. Standardul DOM (“Document Object Model”) defineste interfata API pentru accesul aplicatiilor la arborele creat de parser. Un parser SAX sau StAX este mai rapid. sir dintre marcaje etc. Problema principalã în proiectarea unui parser XML este aceea cã el trebuie sã informeze aplicatia despre marcajele. atributele si celelalte elemente de infoset întâlnite. atribute folosite în marcaje. Avantajul unui parser care creeazã un obiect arbore este acela cã permite operatii de cãutare. care poate fi reprezentat fie printr-un fisier text (XML). comentarii XML. In principiu existã douã categorii mari de programe parser. Evenimentele SAX sunt produse de întâlnirea unui atom XML (marcaj. fisierul listã de preturi este corect si sub forma urmãtoare: <!DOCTYPE priceList SYSTEM "priceList. care creeazã în memorie o structurã de date ierarhicã (un arbore) cu structura si continutul fisierului XML pe care o transmite aplicatiei. Este util atunci când o aplicatie este interesatã numai de anumite sectiuni dintr-un document XML si nu de întreg continutul. dar nu permite operatii repetate de cãutare în document întrun mod eficient. Un obiect convertor (“transformer”) primeste la creare un obiect sursã si un obiect rezultat. Un parser bazat pe modelul DOM are ca principal dezavantaj memoria necesarã pentru arborele corespunzãtor unui document XML mai mare. s. fie ca o secventã de evenimente XML. Abrevierile SAX (“Simple API for XML”) si StAX (“Streaming API for XML”) se referã la standardizarea comunicãrii dintre aplicatii si parser (prin interfete în Java).DTD"> <priceList> <computer><name>CDC</name><price>540</price></computer> <computer><name>SDS</name><price>495</price> </ computer ></priceList> O altã criticã adusã modelului DOM este aceea cã interfata API nu este total în spiritul orientãrii pe obiecte si nu foloseste clasele colectie si iterator din Java. instructiuni de prelucrare. Aceste parsere sunt si ele de douã tipuri: cu apelare de metode “callback” din aplicatie de cãtre parser (“push parsers”) sau cu extragere de informatii despre fisierul XML prin apelarea de cãtre aplicatie a unor metode din parser (“pull parsers”). de aceea ele sunt ignorate de aplicatii în majoritatea cazurilor. HTML). dupã modul cum prezintã rezultatul analizei: . fie printr-un arbore în memorie. 167 . De obicei spatiile albe sunt introduse (la editarea manualã) numai pentru aspect si nu sunt semnificative. .a. Toate spatiile albe consecutive dintre douã marcaje formeazã un singur atom XML. sub forma unor interfete Java. consumã mai putinã memorie si este mai usor de folosit (nu necesitã navigarea printr-un arbore). care informeazã aplicatia imediat ce se întâlneste un element semnificativ. In cazul unui parser “pull” (de exemplu StAX) aplicatia detine controlul si cere programului parser informatii despre urmãtorul element de limbaj din documentul analizat. caractere între marcaje (date si spatii albe).). iar metoda “transform” face transformãrile necesare de la sursã la rezultat. de acelasi tip cu alte siruri de caractere dintre marcaje succesive. Modelul SAX nu permite crearea sau modificarea de documente XML prin program. Un alt standard. de sfârsit si goale). dar modelul StAX permite aceste operatii. de modificare în arbore si de creare a unui alt document XML pe baza arborelui. dar actiunile declansate de aceste “evenimente” sunt specifice fiecãrei aplicatii si nu fac parte din programul parser. atribut. este XSLT (XML Stylesheet Language for Transformation). Un parser “push” (de exemplu SAX) detine controlul procesului de analizã XML si oferã aplicatiei informatii pe mãsurã ce parcurge documentul XML. care permite specificarea unor transformãri asupra unui document XML în vederea conversiei sale într-un alt format (de ex.Parsere orientate document. De exemplu. De aceea s-au propus si alte variante de modele arbore : JDOM si DOM4J (‘J’ de la Java). mai ales cã în acest arbore apar noduri si pentru spatiile albe folosite la indentare si la fiecare linie nouã. adicã o serie de interfete Java care trebuie implementate de programul parser si sunt folosite de aplicatii pentru acces la datele furnizate de parser.Parsere în flux continuu (“streaming parsers”).Florian Moraru – Programare Orientata pe Obiecte Datele dintr-un fisier XML si relatiile dintre ele constituie un “infoset”. Elementele unui infoset sunt atomi XML: marcaje (de început. Un parser XML respectã o interfatã cu aplicatiile (API).

Florian Moraru – Programare Orientata pe Obiecte Interfetele “Source” si “Result” sunt implementate de clase “DOMSource” si “DOMResult”, “SAXSource” si “SaxResult” sau “StreamSource”, “StreamResult” (fluxuri de intrare-iesire ca sursã si rezultat al transformãrii). In felul acesta se poate trece între diferite reprezentãri posibile ale documentelor XML, cu sau fãrã modificãri în continutul documentelor.

Utilizarea unui parser SAX
SAX (Simple API for XML) a fost primul API pentru XML si nu este un standard oficial, dar este utilizat si în prezent. De fapt, multe programe parser DOM au fost scrise pe baza unor parsere SAX . Un parser SAX citeste un fisier XML, detecteazã eventuale erori formale si aruncã exceptii, dar în principal apeleazã anumite metode cu nume si prototip ce depind de tipul elementului de infoset recunoscut în fisier. Metodele din aplicatie apelate de un parser SAX sunt grupate în câteva interfete (ContentHandler, DTDHandler, ErrorHandler), definite în pachetul org.xml.sax, dintre care numai ContentHandler este importantã atunci când nu se cere si validarea fatã de un DTD. Principalele metode “callback” din aceastã interfatã sunt:
// apelata la inceput de element (marcaj de inceput) public void startElement (String uri, String localName,String qName, Attributes atts) throws SAXException; // apelata la sfarsit de element (marcaj de terminare) public void endElement (String uri, String localName, String qName) throws SAXException; // apelata la detectarea unui sir de caractere public void characters (char ch[ ], int start, int length) throws SAXException;

Parametrul “uri” indicã locatia unde este definit spatiul de nume pentru acest tag, iar “qName” (“qualified name”) este numele complet, prefixat de identificatorul spatiului de nume; dacã nu este specificat un spatiu de nume (uri este un sir vid), atunci “qName” este numele de tag si nu “localName”(care este tot un sir vid). Interfata Attributes corespunde unei liste si are metode ca: getLength, getQName(int), getValue(int). Exemplu de extragere si afisare atribute:
public void startElement(String uri, String aName, String qName, Attributes atts) throws SAXException { for (int i = 0; i < atts.getLength(); i++) System.out.println (atts.getQName(i)+”=”+ atts.getValue(i)); ...

Utilizarea unui parser SAX implicã definirea unei clase care implementeazã cel putin interfata ContentHandler sau care extinde clasa DefaultHandler (clasã adaptor care implementeazã toate metodele interfetelor prin functii cu efect nul). Metodele activate la întâlnirea de marcaje primesc numele marcajului sub douã forme: numele local (în cadrul spatiului de nume implicit) si numele “qName”, calificat cu identificatorul spatiului de nume. Primul argument (uri) aratã unde este definit spatiul de nume al marcajului. In cazul unui singur spatiu de nume se foloseste numai argumentul “qName”, care contine numele marcajului . Obiectul parser SAX este creat prin apelul unei metode fabricã fie în constructorul clasei “handler”, fie în functia “main” sau într-o altã functie. Pentru cã existã mai multe programe parser SAX pentru Java si pentru a putea utiliza orice parser cu minim de modificãri, se practicã obtinerea unui obiect parser prin apelul unei metode fabricã si nu prin instantierea directã a unei singure clase.

168

Florian Moraru – Programare Orientata pe Obiecte Metoda “parse” a clasei “SAXParser” trebuie sã primeascã obiectul ce contine documentul XML (un flux de I/E) si obiectul “handler”, cu metode de tratare a evenimentelor. Exemplul urmãtor afiseazã “frumos” un document XML, cu indentare, deci un fel de “pretty printing” sau “beutifier” pentru fisiere XML:
import java.io.*; import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.*; // Pretty printing cu parser SAX class PPSax extends DefaultHandler { String sp=""; // spatii ptr indentare la fiecare linie public static void main(String argv[])throws Exception { DefaultHandler handler = new PPSax(); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); saxParser.parse( new File(argv[0]), handler); } // metode din DefaultHandler redefinite (metode callback) // un marcaj de inceput element public void startElement(String namespaceURI,String sName,String qName, Attributes attrs) throws SAXException { StringBuffer sb = new StringBuffer(sp+ "<"+ qName+" "); if (attrs != null) { for (int i = 0; i < attrs.getLength(); i++) { String aName = attrs.getLocalName(i); // Attr name if ("".equals(aName)) aName = attrs.getQName(i); sb.append (" " + aName + "="+ "\"" + attrs.getValue(i)+"\" "); } } System.out.println (sb.append (">")); sp+=" "; // creste indentare dupa un marcaj de inceput } // un marcaj de sfarsit element public void endElement(String namespaceURI, String sName, String qName ) throws SAXException { sp=sp.substring(2); // scade indentarea System.out.println (sp+"</"+qName+">"); } // un sir de caractere delimitat de marcaje public void characters(char buf[], int offset, int len)throws SAXException { String s = new String(buf, offset, len); s=s.trim(); // elimina spatii albe if (s.length()==0) return; // daca au fost doar spatii albe System.out.println (sp+ s); } }

Utilizarea unui parser DOM O aplicatie DOM creeazã mai întâi un obiect parser (printr-o fabricã de obiecte) si apoi apeleazã metoda “parse” pentru acest obiect, cu specificarea unui fisier XML. Metoda “parse” are ca rezultat un obiect de tip Document, care este arborele creat pe baza documentului XML, conform modelului DOM.

169

Florian Moraru – Programare Orientata pe Obiecte Pentru fisierul XML anterior cu lista de preturi, dar scris tot pe o singurã linie si fãrã spatii albe, arborele DOM aratã astfel:
priceList computer name price computer name price

CDC

540

SDS

495

De fapt, orice arbore DOM mai are un nivel rãdãcinã (nefigurat aici) care corespunde întregului document XML (nod cu numele “#document” si valoarea null). Fiecare nod dintr-un arbore DOM are un nume, o valoare si un tip. Tipurile sunt numere întregi, dar existã si nume mnemonice pentru aceste tipuri. Exemple de constante din interfata Node:
public static final short ELEMENT_NODE public static final short ATTRIBUTE_NODE public static final short TEXT_NODE public static final short DOCUMENT_NODE = 1; = 2; = 3; = 9;

Nodurile cu text au toate acelasi nume (“#text”), iar valoarea este sirul de caractere ce reprezintã textul. In exemplul anterior cele 4 noduri terminale sunt noduri text (cu valorile “CDC”,”540”,”SDS”,”495”). Parserul DOM creeazã noduri text si pentru grupuri de spatii albe (blancuri sau terminator de linie înainte sau dupã un marcaj). Pentru fisierul XML cu 10 linii si indentare numãrul de noduri din arborele DOM este 20 : 1 pe nivelul 1, 5 pe nivelul 2, 10 pe nivelul 3 si 4 pe nivelul 4. Dintre acestea sunt 9 noduri text cu spatii albe (3 pe nivelul 2 si 6 pe nivelul 3). Nodurile pentru elemente au numele marcajului si valoarea null. In arborele de mai sus apar numele nodurilor de pe primele 3 niveluri (de tip 1) si valorile nodurilor de pe ultimul nivel (de tip 3). Modelul DOM defineste mai multe interfete Java, implementate de parser si folosite de cãtre aplicatii : - Interfata Node contine metode de acces la un nod de arbore DOM si la succesorii sãi, dar si metode pentru crearea si modificarea de noduri de arbore DOM. Exemple:
public String getNodeName(); // numele acestui nod public String getNodeValue() throws DOMException; // valoarea acestui nod public void setNodeValue(String nodeValue) throws DOMException; public short getNodeType(); // tipul acestui nod public NodeList getChildNodes(); // lista succesorilor acestui nod public Node getFirstChild(); // primul succesor al acestui nod public Node getNextSibling(); // urmatorul succesor al acestui nod public Node removeChild(Node oldChild) throws DOMException; public Node appendChild(Node newChild) throws DOMException; public NamedNodeMap getAttributes(); // atributele acestui nod

- Interfata NodeList contine metode pentru acces la lista de succesori ai unui nod:
public Node item(int index); public int getLength(); // nodul cu numarul “index” // lungime lista de noduri

- Interfetele Document, Element, Attr, CharacterData, Text, Comment, extind direct sau indirect interfata Node cu metode specifice acestor tipuri de noduri.

170

Florian Moraru – Programare Orientata pe Obiecte Interfata NamedNodeMap corespunde unei liste de atribute (obiecte de tip Attr) si contine metode ca getLength() si item(i). Exemplu de extragere atribute:
Element elm=(Element) node; // Node node; NamedNodeMap attrs = elm.getAttributes(); for (int i=0;i <attrs.getLength(); i++) Attr a = (Attr) attrs.item(i);

Interfata Attr contine metode de acces la numele si valoarea unui atribut, precum si la elementul cãruia apartine acel atribut:
public interface Attr extends Node { public String getName(); public String getValue(); public void setValue(String value) throws DOMException; public Element getOwnerElement(); }

Din interfata Element vom mentiona câteva metode:
public String getAttribute(String name); // obtinerea valorii unui atribut cu nume dat public void setAttribute(String name,String value) throws DOMException; public NodeList getElementsByTagName(String name);

Ultima metodã este utilã pentru extragerea selectivã de noduri dintr-un arbore DOM, dar existã în standardul DOM si alte mecanisme de filtrare a nodurilor din arbore. Clasele care implementeazã interfetele DOM fac parte din parserul DOM, iar numele si implementarea lor nu sunt cunoscute utilizatorilor; ele constituie un bun exemplu de separare între interfatã si implementare si de programare la nivel de interfatã. Clasa care implementeazã interfata Node contine si o metodã “toString”, care produce un sir de forma nume[valoare], cu numele si valoarea nodului, indiferent de tipul nodului si dacã existã sau nu atribute asociate unui element. Afisarea/prelucrarea unui arbore DOM se face de obicei printr-o functie recursivã care prelucreazã nodul curent (primit ca argument), atributele sale si apoi se apeleazã pe ea însãsi pentru fiecare succesor. Parcurgerea listei de succesori se poate face în douã moduri: - Folosind metodele “getFirstChild” si “getNextSibling” ; - Folosind metoda “getChildNodes” si metode ale interfetei NodeList. Exemplu de afisare nume si valoare noduri DOM folosind prima variantã:
import javax.xml.parsers.*; import org.w3c.dom.*; import java.io.File; // afisare nume si valori noduri din arbore DOM (cu metoda Node.toString ) class DomEcho { public static void printNode(Node node) { System.out.println (node.toString() ); //afisare nod curent (fara atribute) Node child = node.getFirstChild(); if (child !=null) { printNode (child); // afisare subarbore al primului fiu while ( (child=child.getNextSibling())!=null) printNode(child); // afisare subarbori pentru ceilalti fii } } // varianta de obtinere noduri fii

171

Ca exemplu de creare a unui arbore DOM prin program (folosind metode ca “createElement”.item(k)). createTextNode”). } In general. if (type==1) { // nod Element s+= node.getNodeName()+" ".indent+” “).out. // valoare nod cu eliminare de spatii albe return s. cu indentare. // apel recursiv pentru fiecare fiu } Metoda “Node. // afisare nod curent. // sir cu nume si/sau valoare nod int type = node. k++) // enumerare fii printNode (kids.getDocumentElement()). k<kids.println ( indent + node. “createAttribute”.getNodeType(). // apel recursiv pentru fiecare fiu } // creare si afisare arbore DOM public static void main (String arg[]) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory. Pentru serializare trebuie adãugate marcaje de sfârsit si paranteze unghiulare la marcaje.Florian Moraru – Programare Orientata pe Obiecte public static void printNode(Node node) { System.item(i)+" ". // afisare nod curent (fara atribute) NodeList kids = node.getChildNodes(). DocumentBuilder builder = factory.getLength(). String indent) { System. arbore care poate fi creat prin program (nu pe baza unui fisier XML) si apoi folosit pentru crearea unui fisier XML. k++) // enumerare fii printNode (kids.out. rezultatul unui parser DOM.toString()).newInstance().toString()). Exemplul urmãtor afiseazã frumos.getAttributes().newDocumentBuilder().getChildNodes(). // foloseste “toString” din clasa Attribute } else if (type==3) // daca nod text s = node. // nume element (marcaj) NamedNodeMap attrs = node. // lista de atribute for (int i=0.getLength(). k<kids. dar numai pentru noduri cu elemente si noduri text: Functia anterioarã poate fi modificatã pentru serializarea unui arbore DOM. operatiile efectuate la fiecare nod depind de tipul nodului iar functia recursivã va contine o selectie dupã tipul nodului (un bloc switch).item(k).parse( new File(arg[0]) ). // lista de fii if (kids != null) // daca exista fii ai nodului curent for (int k=0.getLength(). static String toString (Node node){ String s="".trim().toString” predefinitã poate fi înlocuitã cu o metodã staticã cu argument “Node” care sã tinã seama de tipul nodului si sã adauge si atributele la sirul ce reprezintã continutul unui nod DOM (pentru noduri text nu se afiseazã numele. i<attrs. // lista de fii if (kids != null) // daca exista fii ai nodului curent for (int k=0. } } La functiile anterioare se poate adãuga un parametru suplimentar ce reprezintã nivelul sau indentarea necesarã la fiecare nivel: public static void printNode(Node node. printNode(doc. Document doc = builder.println ( node.getNodeValue(). cu indentare NodeList kids = node. i++) s+= attrs. urmeazã fragmente dintr-un program care creeazã un obiect DOM folosind un parser SAX: 172 . iar pentru nodurile element nu se afiseazã valoarea).

newSAXParser().trim())). crt=dom. dom = builder.createElement(qName). i++) { Attr a = dom. public String getText().Florian Moraru – Programare Orientata pe Obiecte class SAXDOM extends DefaultHandler { private Node crt. a. // radacina arbore SAXParserFactory saxfact = SAXParserFactory. // rezultat=tip eveniment public boolean hasNext() throws XMLStreamException.setValue(atts. } Metoda “next” are ca rezultat tipul atomului XML din dreptul cursorului si avansul cursorului pe urmãtorul atom.parse( new File(xmlFile). length).newInstance(). String lName. int length) throws SAXException { String s = new String(ch.. for (int i = 0. .length() > 0) crt.setAttributeNode (a). String lName. i < atts. if (s.createAttribute (atts. Exemplu: XMLStreamReader xmlr. Fragment din interfata pentru analiza unui document XML: public interface XMLStreamReader { public int next() throws XMLStreamException. int start. String qName) throws SAXException { crt = (Node)crt.newInstance(). SAXParser saxParser = saxfact. Acest tip (un întreg) poate fi comparat cu una din constantele simbolice definite în interfata XMLEvent sau se pot folosi metode din interfata XMLStreamReader pentru determinarea tipului de atom XML.newDocumentBuilder(). } public void characters(char[] ch. String qName.newDocument(). start. … 173 . } public void endElement(String nsURI. crt = element. iar o aplicatie trebuie sã aleagã între cele douã. Attributes atts) throws SAXException { Element element = dom. Modelul cursor are douã interfete Java principale: XMLStreamReader si XMLStreamWriter. DocumentBuilder builder = domfact.getValue(i)). element. public SAXDOM (String xmlFile) throws Exception { DocumentBuilderFactory domfact = DocumentBuilderFactory. numite API cursor si API iterator. } crt. saxParser.createTextNode(s.appendChild (dom. // nod curent din arbore DOM private Document dom. public String getNamespaceURI().getParentNode(). } // metode callback apelate de parserul SAX public void startElement(String uri.. public String getLocalName().appendChild(element).getQName(i)).trim(). this). } Utilizarea unui parser StAX StAX reuneste douã interfete API.getLength().

getLocalName().createXMLStreamReader(new FileReader(args[0])).getText()). si care contin toate informatiile despre urmãtorul element de limbaj XML. import javax. } } Pentru crearea de fisiere XML se foloseste interfata XMLStreamWriter: public interface XMLStreamWriter { public void writeStartElement(String localName) throws XMLStreamException. } } public static String toString (XMLStreamReader xr){ if (xr. } Modelul iterator lucreazã cu obiecte eveniment.hasNext()){ int et = xr..stream. return "".xml.. public void writeEndElement() throws XMLStreamException. while(xr.print(xmlr.println (s).isStartElement()|| xr.*.stream. .getText()).xml.*.newInstance().out. XMLStreamReader xr = xif.getText()).out. if (xr. Exemplu de afisare simplã. import javax.hasNext()){ int eventType = xmlr.getEventType()) System.io.length()>0) System. if (s. obtinute prin metoda “nextEvent”. a unui fisier XML care contine numai elemente cu atribute si caractere: import java.print(xmlr. 174 . fãrã indentare.CHARACTERS == eventType) // sau ==xmlr. public XMLEvent nextEvent() throws XMLStreamException.events.out. // Afisare in ecou cu StAX cursor class PPCursor { public static void main(String[] args) throws Exception { XMLInputFactory xif = XMLInputFactory. Interfetele pentru analiza si respectiv sinteza unui document XML din modelul iterator sunt XMLEventReader si XMLEventWriter: public interface XMLEventReader extends Iterator { public boolean hasNext(). … // cu determinare tip eveniment if (xmlr.next(). // tip eveniment String s = toString(xr).next().isEndElement()) return xr. Interfata XMLEvent contine constante si metode pentru analiza evenimentelor XML.hasText()) // daca este text in pozitia curenta System.Florian Moraru – Programare Orientata pe Obiecte while(xmlr.isCharacters() && !xr.*. public void writeCharacters(String text) throws XMLStreamException.isWhiteSpace()) return (xr. // cu metode ale clasei parser if (xmlr.

import javax. } Exemplu de afisare în ecou a continutului unui fisier XML folosind clase si metode din modelul StAX iterator.println(e. Exemplu pentru prima solutie: class PPEvent { // pretty printing cu parser StAX in modul iterator public static void main(String[] args) throws Exception { XMLInputFactory factory = XMLInputFactory. } } } Pentru afisare cu indentare trebuie determinat tipul evenimentului. class EventEcho { public static void main(String[] args) throws Exception { XMLInputFactory factory = XMLInputFactory. import javax. if ( !e. public void add(XMLEvent e) throws XMLStreamException.Florian Moraru – Programare Orientata pe Obiecte public Object next().stream.* . Avem câteva posibilitãti de a schimba sirul ce corespunde unui eveniment StAX: . definitã de noi.nextEvent(). public void add(Attribute attribute) \ throws XMLStreamException. while(r. if (txt.length()>0 && !e.hasNext()) { XMLEvent e = r.out.trim().xml.O metodã staticã cu argument XMLEvent..O altã clasã care contine o variabilã XMLEvent si o metodã toString (nestaticã).createXMLEventReader(new FileReader (args[0])).events. XMLEventReader r = factory.START_ELEMENT: 175 .. String txt= toString(e). dar nu cunoastem numele clasei care implementeazã interfata XMLEvent.println(txt). } } static String sp="".xml. .io.*.newInstance().stream.isEndDocument()) System. // spatii ptr indentare static String toString (XMLEvent ev) { // sir asociat unui eveniment (inclusiv text cu spatii albe) switch (ev. XMLEventReader r = factory. Putem redefini metoda “toString” a obiectului eveniment XML pentru a adãuga indentarea.nextEvent().*.createXMLEventReader( new FileReader(args[0])). } public interface XMLEventWriter { public void flush() throws XMLStreamException. while(r.isEndDocument()) System. inclusiv metoda “toString” a clasei care implementeazã interfata “XMLEvent”: import java.toString())..hasNext()) { XMLEvent e = r. . public void close() throws XMLStreamException. .out.getEventType()){ case XMLEvent..newInstance().

.toString(). for (int i=0.toString(). Vizualizarea arborelui XML Afisarea într-un obiect JTree a rezultatului unui parser XML pune câteva probleme interesante de programare cu obiecte si admite mai multe solutii: .getNodeName(). NamedNodeMap attrs = domNode. public NodeAdapter(Node node) { domNode = node. i<attrs. care depinde de tipul nodului int nt = domNode.CHARACTERS: default: return sp+ ev. if ( nt==Node.getNodeType().getChildNodes().getLength().Folosirea unei clase adaptor de la un tip de nod la un alt tip de nod. case XMLEvent. Crearea de obiecte XMLEvent pentru fiecare atom XML (inclusiv spatii albe) este un dezavantaj al modelului iterator fatã de modelul cursor (ca memorie si ca timp). return s. return new NodeAdapter (node).getNodeValue().Clasa adaptor implementeazã interfata TreeNode si primeste un obiect org.getLength(). } public String toString() { // sir cu continut nod.Node.getAttributes(). case XMLEvent. i++) { 176 . String s="".dom.Crearea unui obiect JTree cu datele din arborele DOM .Clasa adaptor extinde clasa DefaultMutableTreeNode si primeste un obiect de tip Node.toString(). Avem douã variante: . dar sub alte forme. return sp+ev. } public TreeNode getChildAt(int i) { Node node = domNode.Stabilirea unei corespondente nod la nod între arborii DOM si JTree .END_ELEMENT: sp=sp.w3c. } } Variabila “sp” trebuie sã fie staticã pentru a-si pãstra valoarea între apeluri ale functiei “toString”. Clasa adaptor primeste apeluri de metode din interfata TreeeNode si le transformã în apeluri de metode din interfata Node (DOM).ELEMENT_NODE){ s=domNode. sp+=" ". if ( nt==Node.substring(2).item(i). Pentru economie de spatiu vom folosi a doua variantã si vom redefini numai metodele folosite la vizualizarea arborelui (alte metode care nu sunt folosite la vizualizare sunt folosite în aplicatii care prelucreazã arborele afisat si trebuie de asemenea redefinite) : class NodeAdapter extends DefaultMutableTreeNode { Node domNode. deoarece metodele necesare pentru pentru operatii cu noduri sunt prezente în ambele tipuri de noduri.getChildNodes().TEXT_NODE) s=domNode. } public int getChildCount() { return domNode.Florian Moraru – Programare Orientata pe Obiecte String s= sp+ ev.

Florian Moraru – Programare Orientata pe Obiecte Node attr = attrs.newInstance(). In principiu se defineste o clasã care implementeazã metodele interfetei TreeModel prin apeluri de metode din interfata Node (DOM). s+=attr. String nodeName = domNode. // sau cu model de arbore frame. dar din nou apare problema redefinirii metodei “toString” dintr-o clasã cu nume necunoscut (clasa pentru nod DOM) pentru a tine seama de tipul nodului (ca si în cazul clasei pentru XMLEvent). 177 .getNodeValue()+'\"'. JTree tree = new JTree(new NodeAdapter(doc)). } } return s. public DomNode(Node node) { domNode = node. Document doc = builder. i++) { DomNode n = this. frame.newDocumentBuilder().getNodeName()+"="+'\"'+ attr. } public String toString() { String s= "". JFrame frame = new JFrame(argv[0]).domNode == n.setSize(500.domNode) return i. frame. } // Metode noi pentru indici si numar fii public int index( DomNode child) { int count = childCount(). Putem sã definim o clasã pentru o variantã de nod DOM cu metoda “toString” redefinitã si cu câteva metode noi. i<count.parse( new File(argv[0]) ). if ( ! nodeName. DocumentBuilder builder = factory.add ( new JScrollPane(tree) ). } if (domNode.getContentPane(). for (int i=0.getNodeName().setVisible(true). model care sã foloseascã ca noduri (inclusiv nodul rãdãcinã) chiar noduri DOM (rãdãcina poate fi de orice subtip al clasei Object).getNodeValue() != null) { // elimina spatii albe din noduri text String t = domNode.item(i). } return s.child(i). } O altã solutie de afisare a unui arbore DOM într-un obiect JTree se bazeazã pe definirea unei clase model de arbore.startsWith("#")) { s += nodeName. } } Utilizarea clasei adaptor se va face astfel: public static void main(String argv[ ]) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.trim(). utile în clasa model de arbore: class DomNode { Node domNode. if (child.getNodeValue(). } return -1.480). s += t.

root=root. } } Clasa model de arbore cu noduri DOM aratã astfel: class DomTreeModel implements TreeModel { Node root. return new DomNode(node). } public Object getChild(Object parent. } public boolean isLeaf(Object aNode) { DomNode node = (DomNode) aNode. return node.getChildNodes(). } public int childCount() { return domNode. // radacina arbore DOM public DomTreeModel (Node root) { this. public void addTreeModelListener(TreeModelListener listener) { listenerList. 178 . } public void valueForPathChanged(TreePath path.getChildNodes().item(searchIndex).childCount() == 0.index((DomNode) child).addElement( listener ). Object newValue) { // Nu se permit modificari in arborele afisat } private Vector listenerList = new Vector(). } public void removeTreeModelListener(TreeModelListener listener) { listenerList. add( new JScrollPane(tree)). } } Urmeazã clasa responsabilã cu afisarea arborelui: class domView extends JFrame { public domView(Node root) { // primeste radacina arbore DOM JTree tree = new JTree(new DomTreeModel(root)).removeElement( listener ). } public int getChildCount(Object parent) { DomNode node = (DomNode) parent. setSize(400.getLength(). Object child) { DomNode node = (DomNode) parent. } public int getIndexOfChild(Object parent.child(index). int index) { DomNode node = (DomNode) parent.Florian Moraru – Programare Orientata pe Obiecte } public DomNode child(int searchIndex) { Node node = domNode. return node. return node. return node. } // Metode din interfata TreeModel public Object getRoot() { return new DomNode(root).500).childCount().

Florian Moraru – Programare Orientata pe Obiecte
setVisible(true); } public static void main(String argv[]) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse( new File(argv[0]) ); new domView(document); // radacina arbore DOM } }

Solutiile anterioare folosesc eficient memoria (nu dubleazã arborele DOM într-un arbore JTree), dar nu pot elimina nodurile text cu spatii albe, pentru cã se preiau toate nodurile DOM în arborele afisat cu JTree (nu se pot exclude noduri DOM decât cu anumite artificii). O solutie mai simplã construieste un arbore JTree având în noduri referinte la noduri din arborele DOM; se pot elimina noduri text care contin numai spatii albe, dar în memorie se vor afla doi arbori:
class domJTree1 { public static void main (String argv[]) throws Exception { JFrame frame = new JFrame(argv[0]); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse( new File(argv[0]) ); TNode root = new TNode (doc.getDocumentElement()); // creare nod radacina add (root,new TNode(doc)); // functie recursiva de creare arbore JTree t= new JTree (root); frame.add(new JScrollPane(t)); frame.setSize(400,400); frame.setVisible(true); } // adaugare nod fiu la nod parinte din arbore DOM in arbore JTree private static void add ( TNode child, TNode parent) { Node domnode = (Node) (child.getUserObject()); // nodul DOM din nodul JFC if (domnode.getNodeName().charAt(0)=='#' && // nu se creeaza noduri text “albe” ((String)domnode.getNodeValue()).trim().equals("")) return; parent.add(child); NodeList nodes = domnode.getChildNodes(); // fiii nodului curent if (nodes != null) for (int i=0; i<nodes.getLength(); i++) add ( new TNode (nodes.item(i)), child); // creare nod JFC din nod DOM } }

O aplicatie cu fisiere XML Programul Ant (Apache) realizeazã o secventã de actiuni descrisã într-un fisier XML, cu numele implicit “build.xml” sau cu un alt nume dat ca argument în comanda “ant” (unde mai pot apare si o serie de optiuni). O actiune (“target”) constã din mai multe operatii, are un nume si o actiune anterioarã, de care depinde. O operatie (“task”) constã de obicei în executarea unui program sau unei comenzi de sistem. Existã mai multe operatii cu nume predefinit dar si operatii generice, cum ar fi executia oricãrei comenzi de sistem (nume de target “exec”). Exemple de operatii predefinite : exec, mkdir, copy, delete, javac, java, etc. Urmeazã un exemplu simplu de fisier “build.xml” cu 3 actiuni: stergerea unor fisiere (“clean”), crearea unui nou director (“mkdir”), si copierea unui fisier în noul director (“copy”):

179

Florian Moraru – Programare Orientata pe Obiecte
<project name="MyProject" default="copy" > <target name="clean" description="delete files " > <echo message="del dist"/> <delete dir="dist" /> </target> <target name="mkdir" depends="clean"> <mkdir dir="dist"/> </target> <target name="copy" depends="mkdir" description="copy files " > <copy file="build.xml" tofile="dist/build.xml" /> </target> </project>

Un element “target” descrie o actiune, iar fiecare element continut de “target” reprezintã un task (o operatie). Ordinea de executie va fi: “clean”, “mkdir”, “copy” deoarece actiunea implicitã din acest proiect este “copy”, iar “copy” depinde de “mkdir”, care depinde de “clean”. De mentionat cã actiunile pot fi descrise în orice ordine, chiar dacã actiunea de care depinde actiunea curentã nu a fost deja descrisã. Exemplu de fisier “build.xml”:
<project name="MyProject" default="cinci" > <target name="trei" depends="doi" > <echo message= "3" /> </target> <target name="cinci" depends="patru" > <echo message= "5" /> </target> <target name="unu" > <echo message= "1" /> </target> <target name="doi" depends="unu" > <echo message= "2" /> </target> <target name="patru" depends="trei" > <echo message= "4" /> </target> </project>

Rezultatul executiei fisierului anterior de cãtre “ant” va fi:
Buildfile: build.xml unu: [echo] 1 doi: [echo] 2 trei: [echo] 3 patru: [echo] 4 cinci: [echo] 5

Vom schita un mic program “ant”, mult simplificat în raport cu programul real, dar care redã esenta acestuia. Simplificarea rezidã în numele de task-uri si de atribute ce pot apare în fisierul “build.xml”, dar se pãstreazã functionalitatea programului “ant”. Si în aceastã aplicatie ierarhia de elemente XML este importantã: astfel un task poate avea orice nume si este executat numai dacã apare în cadrul unui target. Anumite elemente task (“exec”, de exemplu) pot contine alte elemente (argumente pentru executia comenzii). Vom prefera de aceea un parser DOM care retine si relatile de subordonare dintre elemente, spre deosebire de un parser secvential (SAX). Programul are trei pãrti distincte, dar diferite ca dimensiune: crearea unei liste de actiuni (targets), în ordinea aparitiei lor în proiect, stabilirea ordinii de executie pentru aceste actiuni, executia fiecãrui task dintr-o actiune, functie de tipul operatiei. Toate elementele de tip target vor fi introduse într-o listã (un vector) pe mãsurã ce sunt întâlnite în fisierul “build.xml”. Pentru stabilirea ordinii de executie vom începe cu targetul implicit (atributul “default” din elementul “project”) si vom parcurge lantul de dependente, punând elementele într-o

180

Florian Moraru – Programare Orientata pe Obiecte stivã (lantul de executie este parcurs în ordine inversã folosing atributul “depends” al fiecãrui target). Executia actiunilor din proiect se va face în ordinea extragerii din stivã. Programul începe prin obtinerea obiectului parser si executia acestuia pentru crearea obiectului de tip Document (arborele DOM):
class AntDOM { public static void main(String argv[]) throws Exception { String xmlfile = "build.xml"; // nume implicit de fisier XML DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new File(xmlfile)); // creare arbore DOM new DOMHandler().handleProject(doc); // utilizare arbore DOM } }

Metoda “handleProject” creeazã lista de actiuni si retine actiunea de start:
class DOMHandler { private String start=null; // start tag private List<Element> tlist = new ArrayList<Element>();

// target list

public void handleProject(Document doc) { Node pnode = doc.getDocumentElement(); // nodul <project> Element project = (Element) pnode; // ptr. acces la atribute start=project.getAttribute("default"); // prima actiune executata // prelucrare noduri "target" NodeList targets = pnode.getChildNodes(); // fiii nodului “project” for (int i=0; i< targets.getLength(); i++) { // ptr fiecare nod “target” Node knode=targets.item(i); if ( knode.getNodeType()==Node.ELEMENT_NODE) // ignora noduri text tlist.add((Element)knode); // adauga target la lista } execprj(); // executie actiuni din proiect }

Metoda “execprj” stabileste ordinea de executie pentru actiuni folosind o stivã si lanseazã secventa de operatii din fiecare actiune:
private void execprj () { // parcurgere lista in ordinea dependentelor Stack<Element> stack = new Stack<Element>(); String tn=start; // target name Element t, tt =null; // un target while ( ! tn.equals("")) { // repeta pana la un target fara “depends” // cauta in tlist un element cu numele tn for (Iterator it=tlist.iterator(); it.hasNext();) { t=(Element)it.next(); if ( t.getAttribute("name").equals(tn) ) tt=t; // tt este targetul cu numele tn } stack.push(tt); // pune target pe stiva tn= tt.getAttribute("depends"); // urmatorul target in lantul dependentelor } // executie targets in ordinea din stiva while ( ! stack.empty()) { t= stack.pop(); // un target

181

Florian Moraru – Programare Orientata pe Obiecte
System.out.println (t.getAttribute("name") + ":"); // scrie nume target NodeList tasks =t.getChildNodes(); // lista de taskuri for (int i=0; i<tasks.getLength();i++) { Node knode=tasks.item(i); if ( knode.getNodeType()==Node.ELEMENT_NODE) // elimina spatii albe exectask ( (Element) knode); // executie task } } }

Metoda “exectask” depinde de numele si de atributele elementului task si de aceea ne vom limita la câteva operatii simple:
private void exectask (Element task) { // executie task Runtime rt=Runtime.getRuntime(); // ptr lansare din Java de fisiere executabile String tname=task.getTagName(); // nume task System.out.print ("\t["+ tname +"] "); // scrie nume task if (tname.equals("echo")) // operatia “echo” System.out.print(task.getAttribute("message")); // are doar atributul “message” if (tname.equals("mkdir")) { // operatia “mkdir” String value=task.getAttribute("dir"); // are atributul “dir” cu nume director try { rt.exec ("cmd /c md "+ value);} // lansare comanda catch(Exception e) { e.printStackTrace();} } System.out.println(); // dupa orice task }

182

la solutii diferite pentru modelul obiect al unui infoset XML: DOM. Programarea orientatã obiect este o paradigmã de descompunere a codului folosind clase si obiecte în loc de proceduri (functii). Conceptele devin interfete.Analiza aplicatiei concrete si modelarea obiectelor si actiunilor din aplicatie. . In plus. Distribuirea de responsabilitãti între clase (interfete) se poate face în mai multe feluri (dupã diferite criterii).a. plus proiectarea la nivel abstract a structurii software. diagrame de obiecte. fiecare având responsabilitãti distincte.). Aceastã fazã cuprinde analiza cerintelor si a cazurilor de utilizare (“use cases”). DOM4J. precum si a secventei de evenimente importante din sistem (UML= Unified Modelling Language). Un proiect ce contine numai clasele rezultate din analiza aplicatiei poate fi mai compact. chiar dacã anumite clase care implementeazã aceste interfete se vor modifica în timp.Bibliotecile standard Java. iar conceptele instabile pot deveni clase algoritmice. cu relatii între aceste concepte (obiecte conceptuale). s. care nu se vor modifica odatã cu modificarea cerintelor. JDOM. Conceptele stabile pot deveni servicii reutilizabile. Un program si modulele sale componente trebuie proiectate de la început având în vedere posibilitatea de a fi extins si adaptat ulterior într-un mod cât mai simplu si mai sigur (proiectare în perspectiva schimbãrii = “design for change”). de instrumentele software folosite si de considerente de arhitecturã a sistemului. In general se poate afirma cã mãrirea flexibilitãtii în extinderea si adaptarea unei aplicatii se poate obtine prin mãrirea numãrului de clase si obiecte din aplicatie. ceea ce a condus. dar nu este scalabil si adaptabil la alte cerinte apãrute dupã realizarea prototipului aplicatiei. In plus.Florian Moraru – Programare Orientata pe Obiecte 15. care iau decizii sau tin seama de contextul aplicatiei.Responsability-Collaboration-Card).a. Din acest motiv clasele Java nu contin metode pentru scrierea datelor clasei în fisiere sau pentru conversia datelor în XML. orice modificare în procedurile de lucru cu suportul extern sau în modelul XML ar necesita modificarea acestor clase care ar reuni mai multe responsabilitãti. s. format din concepte specifice domeniului modelat. s. In acest scop relatiile de dependentã dintre clase trebuie formulate explicit prin interfete bine definite si stabile.a. este importantã o separare a pãrtilor susceptibile de schimbare de pãrtile care nu se mai modificã si evitarea cuplajelor "strânse" dintre clase. clase. (AXI)OM. Rezultatul analizei este un model conceptual. S-au propus si se folosesc în faza de proiectare diverse instrumente software care sã faciliteze identificarea si specificarea claselor (de exemplu. Mai exact. obiecte si relatii dintre ele. astfel cã faza care precede scrierea de cod devine tot mai importantã. Analiza orientatã obiect modeleazã aplicatia printr-un sistem de obiecte care colaboreazã între ele. CRC=Class. Rezultatele proiectãrii se exprimã de obicei în diverse diagrame UML (diagrame statice de clase. de exemplu. Proiectarea orientatã obiect completeazã modelul conceptual furnizat de analizã cu restrictii de implementare impuse de limbajul folosit . usor de mentinut si usor de refolosit (pãrti din el în alte aplicatii). O clasã trebuie sã aibã o singurã responsabilitate si deci sã existe un singur motiv pentru modificarea ei. diagrame de evenimente. 183 . Proiectare orientatã pe obiecte Analiza si proiectarea orientate pe obiecte Aplicatiile tind sã devinã tot mai complexe si mai distribuite. XOM.Folosirea unor solutii de proiectare deja folosite cu succes în alte aplicatii. este vorba de concepte si de relatii stabile. . Un sistem software bine proiectat este usor de înteles. Clasele si obiectele necesare într-o aplicatie pot rezulta din: . vizualizarea relatiilor dintre obiecte si clase. trebuie avutã în vedere si reutilizarea unor clase din aplicatie în alte aplicatii înrudite.

fragilitatea (modificarea unei pãrti de cod face sã nu mai lucreze corect alte pãrti de cod). De obicei sunt verificate dacã îndeplinesc conditiile si prelucrate numai fisierele normale. Altfel spus. “grep”. imobilitatea (dificultatea de a extrage din program pãrti care sã poatã fi utilizate si în alte aplicatii).Dezvoltarea iterativã. cãutarea fisierelor care satisfac anumite conditii (exprimate eventual prin expresii regulate). compact. “find”. nu si fisierele subdirector (toate directoarele trec de filtre). Solutia este fie împãrtirea în clase mai mici sau definirea de interfete pentru o parte din metodele clasei. 184 . Mai precis. Beneficiarii unei clase nu trebuie sã depindã de metode pe care nu le folosesc si care s-ar putea modifica. “pkzip” s. . de îmbunãtãtire treptatã a solutiilor. pornind de la o variantã initialã simplã la care se adaugã treptat alte functii.a. usor de testat si usor de întretinut. Un alt principiu este acela cã module de pe un nivel superior nu trebuie sã depindã de module de pe un nivel inferior. Aceste programe sunt fie comenzi sistem sau utilitare independente (“dir” sau “ls”. cu multe metode. disponibilã pe site-ul Apache). fãrã sã stie nimic în plus despre acea subclasã si fãrã a folosi operatorul instanceof pentru a diferentia între diferite subtipuri. repetarea inutilã a unor secvente de cod si complexitatea inutilã (facilitãti nefolosite în prezent dar care vizeazã dezvoltãri ulterioare). astfel de situatii pot apare în cazul unor clase mari. functie numitã uneori “aplicator” si care primeste ca argument fisierul curent. s. Dacã o anumitã metodã din clasa de bazã nu are sens pentru o subclasã. care diferã însã prin operatiile aplicate asupra fisierelor gãsite. modificarea contextului în care se foloseste o clasã nu trebuie sã conducã si la modificarea clasei.Florian Moraru – Programare Orientata pe Obiecte Principiul “open-close” sustine cã o clasã trebuie sã fie deschisã pentru extindere dar închisã pentru modificãri.Codificarea trebuie precedatã de o etapã de analizã si de proiectare. atunci se va arunca o exceptie de operatie nepermisã în subclasã. Printre atributele unui cod sursã prost proiectat sunt: rigiditatea (încercarea de a modifica ceva antreneazã multe alte modificãri si în alte pãrti). Parcurgerea este programatã explicit si pentru fiecare fisier este apelatã o functie diferitã de la aplicatie la aplicatie (functie “callback” încorporatã într-un obiect functional). . Proiectarea este de obicei un proces iterativ. necesare în aplicatii cum ar fi: afisarea selectivã a unora dintre aceste fisiere. usor de înteles. arhivarea fisierelor gãsite într-un singur fisier comprimat.) Toate aceste programe contin o functie recursivã de parcurgere a unui sistem ierarhic de fisiere. pentru cã întotdeauna existã mai multe solutii diferite pentru o problemã. Un alt principiu spune cã utilizatorul unui tip clasã trebuie sã poatã folosi o subclasã derivatã. indexarea grupului de fisiere în vederea unei cãutãri rapide (folosind un produs cum este biblioteca de clase Lucene. optiunea “Search” din aplicatia “MyComputer” din Windows XP.a. pentru diversi utilizatori ai clasei. opacitatea (intentiile nu sunt reflectate bine în cod). Proiectarea unei aplicatii Java de traversare directoare Exemplul ales urmãreste sã ilustreze câteva probleme tipice care apar la scrierea unei aplicatii (relativ simple) cu obiecte: . Un programator cu experientã poate “simti” erori sau inabilitãti de proiectare (“design smells”) privind codul sursã. chiar dacã nu vedem decât rezultatul final al acestui proces. este posibilã de multe ori în abordarea unor probleme mai complexe. dacã o clasã contine o referintã la o altã clasã. Au fost scrise si publicate o serie de programe care au în comun prelucrarea fisierelor dintr-un director si din subdirectoarele sale. atunci aceasta va fi o clasã abstractã sau o interfatã. deci a unor “scheme de proiectare” confirmate de practicã este utilã de multe ori. dacã definim o clasã derivatã atunci vom deriva dintr-o clasã abstractã.). fie pãrti din aplicatii cu interfatã graficã (“WinZip”. destinate diferitelor categorii de utilizatori ai clasei.Cunoasterea solutiilor folosite în alte probleme. nici o clasã nu trebuie sã depindã de o clasã concretã volatilã. ci ambele vor depinde de abstractii. Un cod sursã rezultat dintr-o bunã proiectare este simplu.

// cautare fara filtru intr-un director new Finder(".Florian Moraru – Programare Orientata pe Obiecte In principiu ar fi posibilã si utilizarea unui iterator pe arborele de fisiere.isDirectory()) findrec (file).} // singura metoda publica a clasei public void find () { findrec(dir). la care metoda “next” ar furniza urmãtorul fisier într-o parcurgere prefixatã a arborelui de fisiere. deci cade în sarcina aplicatiei.out.length()).find(). Un astfel de iterator existã în clasa DefaultMutableTreeNode sub forma metodei “depthFirstEnumeration” dar pentru un arbore cu legãturi explicite între noduri. Mai importantã însã este observatia cã evitarea aplicãrii filtrului asupra directoarelor se va face în clasa filtru si nu în metoda “find”.". } // functie recursiva de parcurgere directoare private void findrec (File dir) { File[] files. for (int i = 0. Este demn de remarcat cã la nivelul limbajului C (la nivelul sistemului de operare) se foloseste un astfel de iterator cu douã functii “findFirst” si “findNext”. // filtru aplicat fisierelor de metoda listFiles private File dir. atunci nu se intrã în subdirectoare doarece toate fisierele director au lungimea zero. i < files. FileFilter filter) { dir = new File (dirname). // director initial // constructori public Finder (String dirname. new SizeFilter(1000)).size=size. if (file. } } } // exemple de utilizare new Finder("C:/Sun"). Exemplu de filtru: class SizeFilter implements FileFilter { // filtrare dupa lungime fisiere long size. else files= dir.println(file + " "+ file.null).length()>size. } } 185 . Programarea acestui iterator este mai dificilã decât scrierea functiei recursive care foloseste metoda “list”. else System. i++) { File file = files[i]. // cautare cu filtru Numele directorului initial putea fi transmis ca argument metodei “find” si nu constructorului clasei Finder..listFiles(filter). } public Finder (String d) { this(d.filter = filter. Dacã se aplicã un filtru de forma “se acceptã numai fisierele cu dimensiune peste o valoare datã” asupra tuturor fisierelor.listFiles(). this. if ( filter == null) files= dir.isDirectory() || f. iterator folosit de metoda “list” din clasa Java File.length. Exemplu de clasã cu o functie de afisare a fisierele dintr-un director dat (si din subdirectoare) care satisfac o conditie implementatã ca un filtru: public class Finder { private FileFilter filter.find().} public boolean accept (File f) { return f. public SizeFilter (long size) { this.

length. In plus.accept(file)) // daca fisier acceptat de filtru foundFile(file). ele trebuie sã fie metode callback. Optiunile (conditiile) de cãutare se pot transmite sub forma unui dictionar cu nume de optiuni si valori ale acestora.println("iese din: "+file). dictionar completat fie pe baza argumentelor din linia de comandã. gãsirea unui nou fisier (normal) si iesirea din directorul curent. indexare.println(file). evenimentele care trebuie notificate observatorului sunt trei: intrarea într-un nou director (schimbarea directorului curent). private void findrec (File f) { File[] files. adicã la fiecare nou fisier gãsit. obtinerea unor optiuni de cãutare. if ( filter. etc. // iesire din director } } // prelucrare fisier gasit public void foundFile (File file) { System. de exemplu. fie pe baza datelor introduse de operator în componente vizuale (câmpuri text sau alte obiecte grafice). De aceea. s. } public void endDir (File file) { System. este izolatã partea din aplicatie care nu se mai modificã (procesul de cãutare în arborele de subdirectoare) de partea susceptibilã de modificare (modul de folosire a rezultatelor cãutãrii.out. un program de afisare cu indentare va modifica indentarea la aceste momente. if (f.isDirectory()) { // daca este un (sub)director files=f.println("intra in: " +file). File file. } endDir(f). arhivare. Problema seamãnã cu o schemã “observat-observator”.Florian Moraru – Programare Orientata pe Obiecte Anumite aplicatii pot fi interesate de momentele în care se intrã (si se iese) dintr-un subdirector. // fisier gasit findrec (file). Vom adãuga acum functiei recursive “findrec” evitarea filtrãrii directoarelor si semnalarea intrãrii si iesirii din subdirectoare. } // operatii la schimbare director public void startDir (File file) { System. startDir(f).listFiles().out. iar obiectul observator este specific aplicatiei si este notificat la fiecare schimbare de stare în obiectul observat. care pot fi folosite atât în modul text (linie de comandã) cât si cu interfatã graficã. } Ultimele trei metode nu trebuie sã facã parte din clasa Finder. i < files. Solutii posibile pentru clasa Finder Putem observa o asemãnare între fisierele gãsite de acest utilitar si elementele extrase dintr-un fisier XML de cãtre un parser XML (numãr posibil mare de elemente si structurã ierarhicã) si deci ne putem inspira din modul în care diverse programe parser XML transmit aplicatiilor informatiile 186 .). // intrare intr-un director for (int i = 0.a). parte din aplicatia care prelucreazã fisierele gãsite (prin afisare. i++) { file = files[i]. Separarea algoritmului de traversare (parcurgere) de operatia aplicatã asupra fisierelor gãsite permite reutilizarea acestui algoritm într-o diversitate de aplicatii. în care obiectul observat este cel care viziteazã sistemul de fisiere (cu metoda recursivã).out. Pentru evitarea unor cãutãri de duratã se poate specifica o adâncime maximã de subdirectoare în care se cautã sau posibilitea de întrerupere fortatã a cãutãrii de cãtre utilizator.

i++) System. care trebuie implementatã de o clasã a aplicatiei. .Florian Moraru – Programare Orientata pe Obiecte despre atomii XML gãsiti într-un fisier.step=0. .getName()). în care sã implementeze si metodele "callback". dupã modelul DOM sau dupã modelul XPath.step= step.Metodele "callback" fac parte din aceeasi clasã (abstractã) cu metoda "find" (ca metode abstracte sau cu efect nul). In cazul nostru metoda “find" si cele câteva mici metode auxiliare pot fi incluse fãrã costuri într-o aplicatie ce implicã cãutarea de fisiere.Metodele "callback" grupate într-o interfatã.println(file.Apelarea de metode “callback” din aplicatie.out. } // metode callback redefinite protected void foundFile(File file) { for (int i=0. inclusiv nume de directoare. Solutia clasei abstracte. Vom prefera o clasã neinstantiabilã. Generarea de evenimente si utilizarea de clase ascultãtor are ca avantaj posibilitatea existentei mai multor ascultãtori. de aceea semnaleazã aplicatiei evenimentele importante si transmite acesteia numele fisierelor sau subdirectoarelor gãsite. nu se foloseste pentru un parser XML (SAX) deoarece un program parser este compus din multe clase si metode si nu este eficient sã fie inclus în orice aplicatie care-l foloseste. cu metode "callback" apelate de "find" si definite de aplicatie. this. Putem distinge douã categorii mari de solutii: . // scrie “indent” spatii System. cu metode având efect nul si nu cu metode abstracte. care include si metodele ce apeleazã metodele "callback".print(" "). Pentru a reduce dimensiunea programului nostru si a-l face mai usor de înteles vom folosi modelul SAX.Crearea unui arbore cu fisierele gãsite sau unei liste de cãi la fisiere. Aceste solutii au avantajul cã evitã un consum mare de memorie în cazul unui numãr foarte mare de fisiere gãsite. dupã modelul evenimentelor generate de componentele grafice Swing (cu ascultãtori la evenimente). Asemãnarea constã în aceea cã functia de traversare a sistemului de fisiere (corespunde unei functii “parse”) stie sã gãseascã fisiere dar nu stie ce sã facã cu fisierele gãsite.int step) { // d este directorul initial super(d). // si apoi numele fisierului } protected void startDir(File dir) { // la intrarea intr-un director indent += step. Si cu aceasta alegere avem (cel putin) douã variante: .out. deoarece nu toate trebuie redefinite în clasa derivatã (aplicatia care foloseste metoda find este o clasã derivatã): abstract class Finder { … // la fel ca in clasa Finder anterioara // metode apelate de “find” protected void startDir(File dir) {} protected void foundFile(File file) {} protected void endDir(File dir) {} } Ca exemplu de utilizare vom realiza o afisare indentatã a numelor de fisiere. sau generarea de evenimente specifice la gãsirea fiecãrui fisier. iar un utilizator trebuie sã-si defineascã o clasã derivatã. dupã modelul SAX. // indentare curenta si pas indentare public FileLister (String d. // si creste indentarea 187 . deci realizarea mai multor operatii diferite la producerea unui eveniment (cum ar fi arhivarea fisierelor gãsite si afisarea lor într-un obiect JTree). intrarea si iesirea dintr-un director. iar o referintã la aceastã clasã este transmisã metodei "find" (sau la construirea unui obiect ce contine metoda "find"). fãrã filtrare: class FileLister extends Finder { private int indent=0.i<indent.

// scade indentarea } public static void main (String args[]) { FileLister lister = new FileLister(args[0].isDirectory()) { // daca este un (sub)director files=f. în care se va defini numai metoda “foundFile”.3). // pas de indentare de 3 spatii lister.a. iar conditia de acceptare a unui fisier este ca data (lungimea) sã fie mai micã sau mai mare ca aceastã limitã. s. findrec(dir).listFiles(). // apel metoda din interfata findrec (file). Aceste optiuni vor fi transformate în filtre individuale de tipul FileFilter si apoi într-un singur filtru combinat. // variabila a clasei Finder public void find (FileHandler h) { this. pentru a fi folosit de cãtre metoda “listFiles”.accept(file)) h. aceasta va contine numai trei metode callback: public interface FileHandler { void startDir (File dir) .startDir(f). dupã data ultimei modificãri a fisierului si dupã dimensiunea fisierului. In exemplu se creeazã dictionarul de optiuni în functia “main”. } // cautare recursiva private void findrec (File f) { File[] files. fie al constructorului clasei.h=h. } h. dar într-o aplicatie realã optiunile sunt extrase fie din linia de comandã.foundFile(file). Ne vom rezuma la trei criterii de cãutare uzuale: dupã nume (cu sabloane “wildcards”). Read-Only. Ca exemplu simplu de utilizare vom da un program de afisare a cãilor la fisierele care satisfac simultan anumite conditii. File file. // apel metoda din interfata } } Extinderea clasei Finder Vom extinde acum clasa Finder cu anumite optiuni de cãutare transmise printr-un dictionar fie ca argument al metodei "find". if ( filter == null || filter. 188 . // apel metoda din interfata for (int i = 0. i < files.endDir(f). de acelasi tip.find(). Pentru ultimele douã criterii se dã o limitã. fie din câmpuri text sau alte componente vizuale de interfatã graficã. fisiere care contin un anumit sir. } } In varianta cu interfatã. In practicã se folosesc mai multe criterii: fisiere cu anumite atribute: Hidden. } Clasa Finder se modificã pentru a primi un obiect care implementeazã interfata FileHandler: private FileHandler h. if (f. i++) { file = files[i]. void endDir (File dir) .Florian Moraru – Programare Orientata pe Obiecte } protected void endDir(File dir) { // la iesirea dintr-un director indent -= step.length. void foundFile (File file) . h.

put ("after".7. } } Rezultã cã a fost modificatã si clasa Finder astfel ca sã primeascã un argument de tip Map în locul unui argument de tipul FileFilter: abstract class Finder { private FileFilter filter. // lista de filtre public ComboFilter() { filters = new ArrayList<FileFilter>(). Map opt) { dir = new File (dirname). // obiect de listare fisiere cu optiuni lister.11)). this. } public boolean accept( File file) { // verificare succesiva filtre din lista if (file.new Date (107.opt). // nume si valoare optiune 1 opts. } protected void foundFile(File file) { System.add (filter).put ("less".find(). "*. // afisare cale completa la fisier } public static void main (String args[]) { Map opts = new HashMap().step= step.size()>0) // daca exista optiuni filter = new IOFilter(opt).opts). // director initial public Finder (String dirname.Florian Moraru – Programare Orientata pe Obiecte class FileLister extends Finder { public FileLister (String d.hasNext().println(file). // creare filtru combinat din optiuni } … // in rest la fel ca in clasa abstracta Finder data anterior Pentru satisfacerea simultanã a mai multor criterii vom defini o clasã auxiliarã pentru filtre combinate care foloseste o listã de filtre: public class ComboFilter implements FileFilter { private List<FileFilter> filters. // nume si valoare optiune 3 FileLister lister = new FileLister(args[0]. derivatã din ComboFilter.) { FileFilter filter = (FileFilter) it.out.put ("name".*av?"). if (opt !=null && opt. if (! filter. it. // nu se mai continua cu verificarea } } return true.iterator(). // orice nume de director trece de filtru ! for (Iterator it = filters. } public void addFilter( FileFilter filter) { // adauga un nou filtru la lista filters. // daca file a trecut de toate filtrele } } Clasa IOFilter.isDirectory()) return true.next(). // nume si valoare optiune 2 opts.new Long (1200)).accept(file)) { // daca un filtru nu accepta fisierul return false. opts. int step) { // opt= dictionar de optiuni super(d. Map opt. // filtru complex de cautare private File dir. creeazã lista de filtre: 189 .

equals("less")) // fisiere mai mici ca o valoare return new SizeFilter ((Long)val. Iterator itr = entries. return more ? !smaller : smaller. else return null. private boolean after.iterator().toString().equals("name") ) // fisiere cu nume dat (care se potrivesc) return new WildcardFilter((String)val). } this.final Object val) { if ( opt. this. } // implicit “after” public DateFilter(Date limit. if (options != null) { Collection entries = options.Entry entry = (Map. this. } // implicit la mai mare public SizeFilter(Long size.hasNext()) { // parcurge toata lista de optiuni Map. FileFilter filter = selectFilter(entry.after = after. if (filter != null) addFilter(filter).Florian Moraru – Programare Orientata pe Obiecte class IOFilter extends ComboFilter { public IOFilter (Map options) { super(). // verificare la mai mare sau la mai mic public SizeFilter(Long size) { this(size. true). if (opt. while(itr. } } // filtru dupa data ultimei modificari a fisierului public class DateFilter implements FileFilter { private Date limit. if (opt. // adauga la lista de filtre } } } // selectarea unui filtru dupa numele optiunii private FileFilter selectFilter(final String opt.length()). boolean after) { this. if (opt.equals("more")) // fisiere mai mari ca o valoare return new SizeFilter ((Long)val).more = more. // false = before public DateFilter(Date limit) { this(limit. entry. if (opt.equals("after")) // fisiere ulterioare unei date return new DateFilter ((Date)val). true).next(). } public boolean accept(File file) { boolean smaller = new Long(file. boolean more) { if (size < 0) { throw new IllegalArgumentException("The size is negative"). 190 . // optiune necunoscuta } } Urmeazã clasele pentru filtre individuale: // filtru dupa lungime fisier public class SizeFilter implements FileFilter { private Long size.getKey(). // o dimensiune data private boolean more.equals("before")) // fisiere anterioare unei date return new DateFilter ((Date)val.limit = limit.size = size.false).getValue()).Entry)itr.entrySet().false).compareTo(size) < 0.

str=str.wildcard = wildcard . return false.charAt(0) == '*') { do { pat=pat.after(limit) : filedate. } else if (pat. Vom defini mai întâi o clasã derivatã din clasa abstractã Finder pentru crearea unui arbore de fisiere.substring(1).charAt(0) == pat. } } // filtru dupa nume (potrivire cu un sablon ce poate contine ‘*’ si ‘?’) public class WildcardFilter implements FileFilter { private String wildcard.length()>0) if (wildMatch(pat.') return false.length()>0 && pat.length()>0 && pat. wildcard=wildcard. boolean caseSensi) { this. care va putea fi folosit fie în aplicatii cu interfatã graficã.substring(1).charAt(0) == '*') pat=pat. } } while (pat. return pat.before(limit).substring(1).caseSensi = caseSensi . fie în aplicatii cu interfatã text: 191 .substring(1).} public WildcardFilter(String wildcard. } else if (pat. } Alte exemple de utilizare a clasei Finder Pentru a verifica cât de versatilã este clasa Finder vom încerca sã o folosim în douã aplicatii mai complexe: o aplicatie cu interfatã graficã pentru afisarea unui arbore de fisiere si un arhivator.toUpperCase ().substring(1). } public boolean accept(File file) { String name = file. } // daca sirul “str” se potriveste cu sablonul “pat” (pattern) private boolean wildMatch ( String pat. } while (pat. String str) { while (str.toUpperCase().length() > 0 ) { if (pat. return after ? filedate. if (str. this.Florian Moraru – Programare Orientata pe Obiecte } public boolean accept(File file) { Date filedate = new Date(file.substring(1))) return true. pat=pat.charAt(0) == '?') { if (str. name).length()==0) return false.length()==0) return true.length()==0. true).getName(). str=str. private boolean caseSensi. str=str. while (str.lastModified()). if (pat.charAt(0) == '.substring(1). if (! caseSensi) { name= name. } else { return false. // true=conteaza daca litere mari sau mici public WildcardFilter(String wildcard) { this(wildcard.charAt(0)) { pat=pat.charAt(0) == '*'). } return wildMatch(wildcard.

câmpuri text pentru preluare nume director.TNode> nodes.root). // valoare nod parinte TNode par = nodes. show(). File d = new File(dir).getParentFile(). un obiect File contine calea completã la un fisier. // ptr.put(file. // radacina contine numele directorului nodes= new HashMap<File. } // constructor fara optiuni // redefinirea metodei apelate la fiecare fisier gasit protected void foundFile(File file) { TNode kid = new TNode(file). // dictionar cu nume si valori optiuni // initializari public FinderGUI() { super("File Finder"). Map opts ) { super(dir. // adresa nod parinte par. // creare nod ptr file nodes. nodes. Clasa pentru interfata graficã foloseste un obiect JTree pentru afisare. options= new HashMap(). // pentru introducere nume director private JTextField name. // adauga nod fiu la nod parinte } public TNode getRoot () { return root.TNode>(). // creare camp text director folder.find().EXIT_ON_CLOSE). // comanda afisarea intr-o fereastra minimala } // initializare componente private void initComponents() { String mmopt[]={"at least". ar fi fost posibil sã existe mai multe noduri cu acelasi continut (fisiere cu acelasi nume în directoare diferite). // utilizare limita: at least / at most private JButton start. // pentru selectare nume fisiere cautate private JTextField size. dar atunci era mai complicat sã aflãm nodul pãrinte. // nodurile contin obiecte File root=new TNode (d).kid). In plus. // initial afisare din directorul curent 192 .put(d. // afisate in JComboBox folder = new JTextField(10).setText(".get(parent). pack().opts)."). // ptr a evita apelul explicit al metodei find din afara clasei } public FileTree (String dir) { this(dir. // pentru afisare fisiere gasite private Map options. obiect JComboBox pentru alegere mod de utilizare limitã (ca limitã inferioarã sau ca limitã superioarã) si un buton care comandã citirea datelor din câmpuri text si afisarea arborelui cu fisierele selectate conform optiunilor: class FinderGUI extends JFrame implements ActionListener { // obiecte vizuale din GUI private JTextField folder. // dictionar cu continut si adrese noduri public FileTree (String dir. mascã de selectie fisiere si dimensiune limitã fisiere. // plasare componente vizuale pe ecran setDefaultCloseOperation (JFrame. } } Nodurile arborelui ar fi putut contine obiecte String cu numele fisierelor în loc de obiecte File. // pune adresa nod in dict File parent = file. care diferã chiar pentru fisiere cu acelasi nume din directoare diferite. // dictionar optiuni de cautare fisiere initComponents(). private Map<File.null).Florian Moraru – Programare Orientata pe Obiecte // clasa derivata pentru afisare cai la fisiere gasite (fara indentare) class FileTree extends Finder { private TNode root.add(kid). dimensiune limita a fisierelor cautate private JComboBox mm. // buton pornire cautare private JTree jtree. // valoare si adresa nod radacina super."at most"}. // initializare obiecte vizuale setLayout().

// creare arbore afisat initial jtree= new JTree(new DefaultTreeModel (ft. controls.add (controls.add (new JLabel("File Size")). // creare camp text ptr limita dimensiune size.controls.add (new JLabel("File Name")). } } Scrierea unui program de arhivare în format “zip” este facilitatã de existenta clasei de bibliotecã ZipOutputStream din pachetul java.getRoot(). // nume optiune asupra dimensiunii de fisiere options. // creare buton start.add ( folder). // initial limita inferioara zero mm=new JComboBox(mmopt).put ("name".controls. controls. display.getText().add( size).add(mm).setLayout (new BorderLayout()). Exemplu de metodã staticã pentru arhivarea fisierelor dintr-un director care nu contine si subdirectoare: public static void zipFolder(String dir. // controlalele pe o coloana setContentPane(mainpanel).util. JPanel display = new JPanel().2)). File folder = new File(dir). // radacina arbore creat jtree. mainpanel.getRoot()))."Center").getSelectedIndex().add(start). // sterge continut anterior dictionar options. // pune masca de selectie fisiere in dic.setText("*.setLayout (new BorderLayout()).add (new JScrollPane(jtree)). int ix=mm.put (how. // ascultator la actionare buton FileTree ft=new FileTree(".clear().controls.*"). Metoda “putNextEntry” scrie antetul care precede fiecare fisier în arhivã.setLayout( new GridLayout(0.setModel (new DefaultTreeModel(r)). // limita dimensiuni fisiere String how = ix==0? "more":"less".add (display.add( name). FileTree ft = new FileTree (folder. // creare camp text masca nume fisiere name.sz). display. // creare obiect selectie start = new JButton("Start"). // creare arbore cu fisierele selectate TNode r= ft. { 193 . // modificare date afisate in JTree } public static void main (String args[]) { FinderGUI gui = new FinderGUI().zip. controls.setText("0"). iar metoda “write” scrie datele din fisierul arhivat în arhivã (un bloc de octeti).addActionListener(this)."West").Florian Moraru – Programare Orientata pe Obiecte name = new JTextField(10). // pune limita dimensiunii fisiere in dic.getText()).add (new JLabel("Folder")). mainpanel. } // plasare obiecte vizuale pe ecran private void setLayout() { JPanel mainpanel = new JPanel(). JPanel controls = new JPanel(). controls. controls.name.trim()). controls. } // ascultator la eveniment produs de buton public void actionPerformed (ActionEvent ev) { options. // indice optiune selectata din ComboBox Long sz = new Long(size.getText(). Pentru ca arhiva sã aibã structura sistemului de fisiere arhivat (astfel ca la dezarhivare sã se refacã structura de directoare si fisiere) este suficient sã dãm metodei “putNextEntry” calea completã la fisier si nu doar numele fisierului arhivat. // initial toate fisierele size= new JTextField(10)."). String outFile) final int BUFFER=4096. mainpanel.options).

printStackTrace(). for (int i=0. out. if (file. // close each entry in. BUFFER).0. out. byte[] data = new byte[BUFFER]. while((count = in. // ptr reducerea timpului de citire din fisier private ZipOutputStream out. "C:/test.closeEntry(). File files[] = folder.write(data. 0. while((count = in. try {out. //close each entry } in.listFiles().5. count). 0.} catch(Exception e) {e.read(data.getName())). BUFFER).read(data. // write data header (name.putNextEntry(new ZipEntry(files[i]. out.closeEntry(). 194 .zip").printStackTrace().length. size. } catch(Exception e) {e. try { out = new ZipOutputStream ( new BufferedOutputStream( new FileOutputStream(outFile))). BufferedInputStream in = null.close().0. etc) int count. etc) int count.close(). de aceea vom testa dacã este sau nu director pentru a evita exceptiile” class FinderZip extends Finder { final int BUFFER=4096.4".printStackTrace(). out. byte[] data = new byte[BUFFER].write(data.Florian Moraru – Programare Orientata pe Obiecte try { ZipOutputStream out = new ZipOutputStream( new BufferedOutputStream( new FileOutputStream(outFile))). // obiect asociat cu fisierul arhiva public FinderZip(String dir.} } } Exemplu de utilizare: new FinderZip ("F:/groovy-1.BUFFER)) != -1) out.putNextEntry(new ZipEntry(file. count).} } protected void foundFile(File file) { BufferedInputStream in = null.} super.close().find().isDirectory()) return. try { in = new BufferedInputStream(new FileInputStream (file). size. Metoda “foundFile” din clasa “Finder” este apelatã atât pentru fisiere normale cât si pentru subdirectoare.close(). } catch(Exception e) {e.printStackTrace().getAbsolutePath())). i<files. } catch(Exception e) {e. String outFile) { super (dir). // write data header (name. out. i++) { in = new BufferedInputStream(new FileInputStream(files[i]).} } Dacã directorul “dir” contine si subdirectoare atunci apar exceptii la încercarea de citire date din aceste subdirectoare.BUFFER)) != -1) out.

O fabricã de obiecte (“Object Factory”) permite crearea de obiecte de tipuri diferite. . Clasele JDK (în special clase JFC) care urmeazã anumite scheme de proiectare au primit nume care sugereazã rolul clasei într-un grup de clase ce interactioneazã : clase iterator.a. In cadrul acestor scheme existã clase care au un anumit rol în raport cu alte clase si care au primit un nume ce descrie acest rol. Aceste obiective pot fi atinse în general prin clase (obiecte) “slab” cuplate.util”) si clasele observator si observat (“java. care stiu cât mai putin unele despre altele. obiectelor sau componentelor. O clasificare uzualã a schemelor de proiectare distinge trei categorii: . pentru cã permite separarea utilizãrii de implementare. Clase si metode “fabricã” de obiecte In cursul executiei un program creeazã noi obiecte. .Solutii optime pentru probleme comune de proiectare. clase “fabricã” de obiecte.Scheme structurale (Structural patterns).Este recomandatã crearea de clase si obiecte suplimentare. care furnizeazã aplicatiei datele si metodele necesare. cu rol de intermediari. Se mai foloseste schema pentru crearea de clase cu obiecte unice ("Singleton") în familia claselor colectie.Scheme de comunicare (de interactiune) între obiecte. care lasã mai multã libertate în detaliile de implementare a unor obiecte cu comportare predeterminatã.a. O fabricã de obiecte se poate realiza în douã forme: 195 . Definitii posibile pentru schemele de proiectare folosite în aplicatii cu clase: .Abstractii la un nivel superior claselor. pentru aceste obiecte. . care definesc comunicarea între clase.swing”. dar toate subtipuri ale unui tip comun (interfatã sau clasã abstractã). Schemele structurale folosite in JDK sunt clasele “decorator” din pachetul “java. Argumentul principal în favoarea studierii si aplicãrii schemelor de clase este acela cã aplicarea acestor scheme conduce la programe mai usor de modificat. pentru crearea de diverse obiecte.util”) extinse în JFC la schema cu trei clase MVC ( Model-View-Controller). Cea mai folositã schemã de creare obiecte în Java este metoda fabricã. O solutie mai flexibilã pentru crearea de obiecte este utilizarea unei fabrici de obiecte. pe baza ipotezei cã existã o singurã clasã.Proiectarea cu interfete si clase abstracte este preferatã fatã de proiectarea cu clase concrete.io” si clasele “adaptor” din “javax.Scheme “creationale“ (Creational patterns) prin care se genereazã obiectele necesare. Scheme de proiectare Scheme de proiectare Experienta acumulatã în realizarea unor aplicatii cu clase a condus la recunoasterea si inventarierea unor scheme (sabloane) de proiectare (“Design Patterns”). care grupeazã mai multe obiecte în structuri mai mari. clase observator. . Schemele de interactiune prezente în JDK sunt clasele iterator (“java. astfel existã clase iterator. . adicã a unor grupuri de clase si obiecte care coopereazã pentru realizarea unor functii. Majoritatea obiectelor sunt create folosind operatorul new. care nu se mai modificã. pentru decuplarea unor clase cu roluri diferite. Dincolo de detaliile de implementare se pot identifica clase cu aceleasi responsabilitãti în diferite aplicatii.Scheme de interactiune (Behavioral patterns). . clase adaptor.Compozitia (agregarea) este preferabilã în raport cu derivarea.Florian Moraru – Programare Orientata pe Obiecte 16. Trebuie remarcat cã si clasele predefinite JDK au evoluat de la o versiune la alta în sensul extinderii functionalitãtii si aplicãrii unor scheme de proiectare reutilizabile. prezentã în mai multe pachete. clase “model” s. Principalele recomandãri care rezultã din analiza schemelor de proiectare si aplicatiilor sunt: . clase adaptor s.

Metoda creeazã un obiect numai la primul apel. } // alte metode fabricã în clasã Clasa BorderFactory (pachetul “javax. Pentru acest fel de metode fabricã existã câteva variante: . Rezultatul metodei este de un tip interfatã sau clasã abstractã. Alegerea (sub)tipului de obiecte fabricate se poate face fie prin parametri transmisi metodei fabricã sau constructorului de obiecte “fabricã”. O metodã fabricã poate fi o metodã staticã sau nestaticã. Aceastã solutie se practicã atunci când obiectele fabricate nu diferã mult între ele. b2= new JButton(“Etched2”). atunci când existã diferente mai mari între obiectele fabricate. fie clase definite de utilizatori. // chenar “gravat” // chenar construit cu metoda fabricã Border eb = BorderFactory. . BevelBorder. Metoda fabricã poate fi apelatã direct de programatori sau poate fi apelatã dintr-un constructor. Toate obiectele chenar apartin unor clase care respectã interfata comunã Border si care sunt fie clase predefinite. metoda fabricã va furniza la fiecare apel o referintã la un obiect unic si nu va instantia clasa.. b2. public static Border createEtchedBorder() { return sharedEtchedBorder.Florian Moraru – Programare Orientata pe Obiecte .Ca metodã fabricã (de obicei metodã staticã) dintr-o clasã. . cu orice nume de clase si cu orice date initiale necesare fabricãrii obiectelor. Rezultatul metodei este de un tip clasã instantiabilã. ascunzând utilizatorilor efectul real al cererii pentru un nou obiect. O metodã fabricã de un tip clasã instantiabilã tine evidenta obiectelor fabricate si.Pentru cã tipul obiectelor ce trebuie create nu este cunoscut exact de programator. fie prin fisiere de proprietãti (fisiere de configurare). care poate fi clasa abstractã ce defineste tipul comun al obiectelor fabricate. la executie.Pentru a evita crearea de obiecte identice (economie de memorie si de timp). Metode si clase fabricã pot fi întâlnite în diverse pachete din JSE (Java Standard Edition ) si.createEtchedBorder(). Putem deosebi douã situatii în care este necesarã utilizarea unei metode “fabricã” în locul operatorului new: . Utilizarea fabricilor de obiecte poate fi privitã ca un pas înainte pe calea separatiei interfatãimplementare.Metoda are ca rezultat o referintã la un obiect creat la încãrcarea clasei: public class BorderFactory { private BorderFactory () { } // neinstantiabilã static final sharedEtchedBorder = new EtchedBorder(). 196 . care include toate subtipurile de obiecte fabricate de metodã (fabricã "polimorficã").setBorder (new EtchedBorder ()). Un exemplu este metoda “createEtchedBorder” din clasa BorderFactory care creeazã referinte la un obiect chenar unic. desi se pot crea si obiecte chenar diferite cu new : JButton b1 = new JButton (“Etched1”). dar poate fi dedus din alte informatii furnizate de utilizator. în loc sã producã la fiecare apel un nou obiect. dupã care nu mai instantiazã clasa. } .swing”) reuneste mai mai multe metode “fabricã” ce produc obiecte chenar de diferite forme pentru componentele vizuale Swing. LineBorder. în JEE (Java Enterprise Edition).setBorder (eb). // chenar construit cu “new” b1.Ca o clasã fabricã. . TitleBorder si CompundBorder (chenar compus). în sensul cã permite oricâte implementãri pentru o interfatã.. produce referinte la obiecte deja existente. Exemple de clase chenar predefinite: EmptyBorder. mai ales.

out. care depind de uzantele locale si de stilul dorit de utilizator (cu sau fãrã numele zilei din sãptãmânã.FRENCH). Tipul obiectelor fabricate este determinat de acesti parametri. indiferent de modul cum sunt implementate acele servicii. Portabilitatea doritã de Java cere ca operatiile sã fie programate uniform. Exemple: Date date = new Date(). 197 . // conversie din Calendar in Date System. Alte metode fabricã care produc obiecte adaptate particularitatilor locale (de tarã) se aflã în clase din pachetul “java. pentru comunicarea prin mesaje (JMS.getInstance(). //luna.luna.Florian Moraru – Programare Orientata pe Obiecte Un exemplu de metodã fabricã controlabilã prin argumente este metoda “getInstance” din clasa Calendar. Obiectele de tip datã calendaristicã au fost create initial în Java ca instante ale clasei Date. dar ulterior s-a optat pentru o solutie mai generalã. Afisarea unei date calendaristice se poate face în mai multe forme. folosind o singurã interfatã API între client (cel care solicitã operatia) si furnizorul de servicii (“provider”). Clasa abstractã Calendar (din “java. Clasa DateFormat contine metoda “getDateInstance” care poate avea 0. an. DateFormat df2 = DateFormat. sau de faptul cã existã mai multe implementãri. Adaptarea se face printr-un parametru al metodei fabricã care precizeazã tara si este o constantã simbolicã din clasa Locale. } Obiectele de tip Calendar se pot utiliza direct sau transformate în obiecte Date: Calendar azi = Calendar. // zi. Locale.println ( df2. fie nu existã o singurã clasã posibilã pentru aceste obiecte.1 sau 2 parametri si care poate produce diferite obiecte de tip DateFormat. se folosesc uneori fabrici de fabrici de obiecte. dar care sã nu afecteze prea mult aplicatiile existente. unul de tip TimeZone si altul de tip Local.format(date)). unele necunoscute la scrierea metodei dar adãugate ulterior.a.getDateInstance (2.println (now). System. pentru adaptarea orei curente la fusul orar (TimeZone) si a calendarului la pozitia geograficã (Local). Astfel de interfete API existã pentru servicii prin natura lor diversificate ca mod de realizare: pentru acces la orice bazã de date relationalã (JDBC).getDateInstance (). zi cu luna in engleza System. Metoda “getInstance” fãrã argumente produce implicit un obiect din cel mai folosit tip de calendar: public static Calendar getInstance() { return new GregorianCalendar().util”) contine câteva metode statice cu numele “getInstance” (cu si fãrã parametri). SAAJ).getTime(). DateFormat.format(date)).out. fie existau anterior furnizori diferiti pentru aceste clase si se încearcã unificarea lor.an cu luna in franceza Fabrici abstracte Clasele de obiecte fabricã pot fi la rândul lor subtipuri ale unui tip comun numit fabricã abstractã (“AbstractFactory”). pentru analizã de fisiere XML etc. care poate crea obiecte de diferite subtipuri ale tipului Calendar . care sã tinã seama de diferitele tipuri de calendare folosite pe glob. Altfel spus.text”: NumberFormat. Uneori se stie ce fel de obiecte sunt necesare. dar nu se stie exact cum trebuie realizate aceste obiecte (cum trebuie implementatã clasa). DateFormat df1 = DateFormat. Date now = azi. Metoda “getInstance” poate avea unul sau doi parametri. pentru tipul de calendar folosit în Europa si în America. fie sunt anticipate si alte implementãri ale unei clase în viitor. s.println ( df1. care fabricã obiecte de tip Calendar.out. Una din clasele instantiabile derivatã din Calendar este GregorianCalendar.). cu nume complet sau prescurtat pentru lunã etc.

SOAPConnection con = factory. fãrã a exclude posibilitatea altor fabrici de conexiuni SOAP.HashSet. livratã de obicei de cãtre furnizorul bazei de date. obiectul fabricã de conexiuni nu este obtinut prin instantierea unei singure clase ci este fabricat într-un fel sau altul.newInstance()."1"}. dar nu existã în pachetul “java. care ascund particularitãti de implementare ale acestor obiecte. In exemplul anterior am folosit o metodã fabricã de obiecte colectie. Fabricile de conexiuni pot fi uneori si ele foarte diferite si sã necesite parametri diferiti (ca tip si ca utilizare)."3". deci sã respecte un contract cu utilizatorii de conexiuni.jdbc.class). HashSet set = (HashSet) newCollection (a.} for (int i=0. care va putea fi folositã si pentru clase colectie încã nedefinite. Obiectele conexiune sunt create de fabrici de conexiuni.JdbcOdbcDriver”)."2". return c. care nu se poate face prin instantierea unei singure clase. Exemplu: public static Collection newCollection (Object a[]. Obiectul conexiune este fabricat de metoda “getConnection” pe baza informatiilor extrase din clasa driver. la fel este fabricat si obiectul “instructiune” de cãtre metoda “createStatement”.i<a. Class cls) { Collection c=null.sql” nici o clasã instantiabilã care sã implementeze aceste interfete. O solutie ar putea fi definirea mai multor clase fabricã de colectii."2". // utilizare obiect conexiune In exemplul anterior Connection si Statement sunt interfete. // clasa driver Connection con = DriverManager.println(e). toate compatibile cu un tip comun (tipul interfatã “CollectionFactory”). furnizatã de firma “Sun” pentru a permite exersarea de mesaje SOAP. de la diversi alti furnizori.length. Diversitatea apare în modul de creare a obiectului conexiune. Toate obiectele conexiune trebuie sã prevadã anumite metode.newInstance(). numitã “DriverManager” foloseste o clasã “driver”. In JDBC clasa fabricã de conexiuni.i++) c. Utilizarea acestei fabrici este limitatã deoarece nu permite transmiterea de parametri la crearea obiectelor colectie. Pentru a întelege mai bine ce se întâmplã într-o fabricã (de fabrici) de obiecte am imaginat urmãtorul exemplu: o fabricã pentru crearea de obiecte colectie (de orice subtip al tipului Collection) pe baza unui vector implicit de obiecte.forName (“sun.out. } catch (Exception e) { System.createConnection(). Exemplu: Class. Statement stm = con. try { c = (Collection) cls. } // utilizare (in “main”) String a[ ] = { "1". la crearea unei colectii ordonate este necesarã transmiterea unui obiect comparator (subtip al tipului Comparator). Exemplu: 198 . Exemplu de utilizare a unei fabrici de fabrici de conexiuni pentru mesaje SOAP: SOAPConnectionFactory factory = SOAPConnectionFactory.getConnection (dbURL. cu nume si implementare cunoscute la scrierea aplicatiei. sub forma unei clase cu nume cunoscut (încãrcatã înainte de utilizarea clasei fabricã) . de aceea. … In exemplul anterior obiectul fabricã “factory” este produs de o fabricã abstractã implicitã (numele este ascuns în metoda “newInstance”).createStatement().odbc.Florian Moraru – Programare Orientata pe Obiecte Pentru acces la servicii oferite de un server se creeazã un obiect “conexiune”.passwd). iar operatiile ulterioare se exprimã prin apeluri de metode ale obiectului “conexiune”.user.add (a[i]). dar care implementeazã interfata. de exemplu.

i++) col.length. 199 . } catch (Exception e) { System. col =(Collection) cls.util.newCollection (a).HashSet").i++) col.out.newInstance(args).getFactory ("java. CollectionFactory cf1 = FFactory.TreeSet".Comparator comp) { return new SortedCollectionFactory (clsName.} } public Collection newCollection (Object a[]) { for (int i=0.Florian Moraru – Programare Orientata pe Obiecte // interfata pentru fabrici de colectii interface CollectionFactory { public Collection newCollection (Object [] a). HashSet set1 = (HashSet) cf1.comp) .add (a[i]).newInstance(). return col."1"}. } } // exemple de utilizare public static void main (String arg[]) throws Exception { String a[] = { "1".length. col =(Collection) constr.i<a. public SimpleCollectionFactory (String clsName) { try { Class cls = Class.util.forName(clsName).add (a[i]). } catch (Exception e) { System. Comparator comp) { try { Class cls = Class. } } // fabrica de colectii ordonate class SortedCollectionFactory implements CollectionFactory { private Collection col. Object[] args = new Object[] { comp }.} } public Collection newCollection (Object a[]) { for (int i=0.println(e).out.class}."3". new MyComp()). } Instantierea directã a acestor clase fabricã ar obliga utilizatorul sã cunoascã numele lor si ar necesita modificãri în codul sursã pentru adãugarea unor noi clase fabricã. } public static CollectionFactory getFactory (String clsName.println(e). CollectionFactory cf2 = FFactory.getFactory ("java."2".forName(clsName). Constructor constr = cls.i<a."2". } // fabrica de colectii simple (neordonate) class SimpleCollectionFactory implements CollectionFactory { private Collection col. vom defini o metodã staticã (supradefinitã) cu rol de fabricã de obiecte fabricã: // metode fabrica de fabrici de colectii class FFactory { public static CollectionFactory getFactory (String clsName) { return new SimpleCollectionFactory (clsName). return col. Class[] clsargs = new Class[] {Comparator. public SortedCollectionFactory (String clsName.getConstructor(clsargs). De aceea.

println(set2). Clase cu rol de comandã (actiune) Schema numitã "Command" permite realizarea de actiuni (comenzi) multiple si realizeazã decuplarea alegerii operatiei executate de locul unde este emisã comanda.Florian Moraru – Programare Orientata pe Obiecte TreeSet set2 = (TreeSet) cf2. // creare bara meniu principal setJMenuBar (mbar). cu o singurã functie. în aplicatiile reale) se poate face într-o singurã metodã: public void actionPerformed (ActionEvent e) { Object source = e. prin producerea unui eveniment de tip ActionEvent.getSource(). // actiune asociata optiunii "Open" 200 . de felul urmãtor: public interface Command { public void execute(). sã examinãm pe scurt programarea unui meniu cu clase JFC. // adauga bara meniu la JFrame JMenu mFile = new JMenu ("File"). // adauga optiuni la meniu vertical mFile. Un meniu este format dintr-o barã meniu orizontalã (obiect JMenuBar). // sau un alt obiect ca argument Tratarea evenimentelor generate de diverse elemente de meniu (mai multe surse de evenimente.add (mFile). Pentru a compara solutia schemei "Command" cu alte solutii de selectare a unei actiuni (comenzi).println(set1). System. open.add (exit). care se extinde într-un meniu vertical cu douã elemente ("Open" si "Exit") sunt necesare instructiunile: JMenuBar mbar = new JMenuBar(). // adauga optiunea mFile la bara meniu JMenuItem open = new JMenuItem ("Open"). Ea foloseste o interfatã generalã.addActionListener (this). ceea ce permite modificarea lor separatã si chiar selectarea unei anumite actiuni în cursul executiei. // sau un alt obiect ca argument exit. mFile.out. El constituie singura legãturã dintre partea de interfatã graficã a aplicatiei si partea de logicã specificã aplicatiei (tratarea evenimentelor). In situatii reale pot fi diferente foarte mari între clasele fabricã pentru obiecte complexe. // optiune pe bara orizontala mbar. Exemplu: public interface ActionListener { public void actionPerformed( ActionEvent ev). // optiune meniu vertical JMenuItem exit = new JMenuItem ("Exit"). // sursa evenimentului if ( source == open) fileOpen( ).addSeparator( ).add (open). } Un obiect dintr-o clasã ascultãtor este un obiect "comandã" si realizeazã o actiune. // optiune meniu vertical mFile. } In acest caz particular se putea folosi o singurã clasã fabricã de colectii cu mai multi constructori. Un element de meniu este o optiune care declanseazã o actiune la selectarea ei. Se realizeazã astfel decuplarea celor douã pãrti.newCollection (a).out. } // executã o actiune nedefinitã încã In Swing existã mai multe interfete "ascultãtor" corespunzãtoare interfetei “Command”. deoarece sunt diferente mici între cele douã fabrici. care contine mai multe optiuni de meniu (obiecte JMenu) si fiecare optiune poate avea un submeniu vertical cu mai multe elemente de meniu (obiecte JMenuItem). System. Pentru programarea unui meniu cu o singurã optiune "File".addActionListener (this).

O clasã staticã inclusã si o metodã staticã (în aceeasi clasã exterioarã) care instantiazã clasa (pentru a-i transmite un parametru).addActionListener (cmd). Metoda "execute" este o metodã polimorficã. pentru acces la alte variabile din interfata graficã).exit(0). cu o metodã staticã care produce o referintã la unicul obiect posibil ( o variabilã staticã si finalã contine aceastã referintã). conduce la definirea mai multor clase care implementeazã aceastã interfatã (clase de nivel superior sau clase incluse în clasa JFrame. dar are acelasi rol cu o barã meniu: selectarea de actiuni de cãtre operator. necesare pentru stabilirea si obtinerea proprietãtilor unui obiect actiune: text afisat în optiune meniu. Schema "Command" mai este folositã în programarea meniurilor si barelor de instrumente ("toolbar"). } // actiune asociata optiunii "Exit" Aplicarea schemei "Command".getSource(). numitã clasã "Singleton". imagine afisatã pe buton din bara de instrumente. open.. deci trebuie interzisã instantierea repetatã a clasei respective. care implementeazã interfata Action. care contine o metodã "actionPerformed".exit(0). deci de tipul obiectului care este sursa evenimentului.Florian Moraru – Programare Orientata pe Obiecte else if (source == exit) System. O barã de instrumente contine mai multe butoane cu imagini (obiecte JButton). 201 . de tip Action. Clase cu un singur obiect Uneori este nevoie de un singur obiect de un anumit tip. In loc sã se adauge celor douã bare obiecte diferite (dar care produc aceeasi actiune) se adaugã un singur obiect. cu o interfatã "Command". exit.execute(). ActionListener cmd = new CmdListener(). prin utilizarea obiectelor "actiune". iar selectarea unei implementãri sau alta se face în functie de tipul variabilei “c”. Existã câteva solutii: .O clasã fãrã constructor public. Interfata Action corespunde interfetei "Command". câte un obiect pentru fiecare actiune selectatã. // nume optiune (afisat in meniu) } public void execute () { System. Interfata Action extinde interfata ActionPerformed cu douã metode "setValue" si "getValue". } } .addActionListener (cmd). o scurtã descriere ("tooltip") a "instrumentului" afisat. Exemplu: class FileExitCmd extends JMenuItem implements Command { public FileExitCmd (String optname) { super(optname). // actiune asociata optiunii } } In programarea anterioarã a meniului apar urmãtoarele modificãri: class CmdListener implements ActionListener { public void actionPerformed (ActionEvent e) { Commnad c = (Command) e.. . c. iar metoda "actionPerformed" corespunde metodei "execute".

addElement(o). Relatia dintre o clasã JFC generator de evenimente si o clasã ascultãtor (receptor) care reactioneazã la evenimente este similarã relatiei dintre o clasã observatã si o clasã observator. // metode find. Programatorul de aplicatie va defini una sau mai multe clase cu rol de observator.} public boolean contains(Object o) {return eq(o. un buton JButton are rolul de obiect observat. SingletonSet(Object o) {element = o. SingletonMap). pentru a realiza anumite actiuni.. iar o clasã care implementeazã interfata ActionListener si contine metoda “actionPerformed” are rolul de observator. Exemplu: public class Collections { public static Set singleton(Object o) { return new SingletonSet(o). Obiectul observat contine o listã de referinte la obiecte observator si. } .add (new TreeSet (Collections.. SingletonList.} .singleton ( new Integer(i) ) )).i<n. EmptyList. } private static class SingletonSet extends AbstractSet { private Object element. union } Schema claselor observat – observator In aceastã schemã existã un obiect care poate suferi diverse modificãri (subiectul observat) si unul sau mai multe obiecte observator. for (int i=0. } 202 . care ar trebui anuntate imediat de orice modificare în obiectul observat.. compatibile cu interfata Observer. la producerea unui eveniment. } } . . EmptyMap) si colectii cu un singur obiect (SingletonSet. } Exemplu de utilizare în problema claselor de echivalentã : public class Sets { // colectie de multimi disjuncte private List sets. Schema observat-observator a generat clasa Observable si interfata Observer din pachetul "java.contains(o)) // "observers" este vector din clasa Observable observers. apeleazã o anumitã metodã a obiectelor observator înregistrate la el.. // lista de multimi // constructor ( n= nr de elemente in colectie) public Sets (int n) { sets = new ArrayList (n). Exemplu: public void addObserver(Observer o) { // adauga un nou observator la lista if (!observers.Florian Moraru – Programare Orientata pe Obiecte Ambele solutii pot fi întâlnite în clasa Collections (neinstantiabilã) pentru a se crea obiecte din colectii "speciale" : colectii vide (EmptySet..i++) sets. Motivul existentei acestei interfete este acela cã în clasa Observable (pentru obiecte observate) existã metode cu argument de tip Observer pentru mentinerea unui vector de obiecte observator.util"..} public int size() {return 1. . De exemplu. element). // public Iterator iterator() { .

arg). programatorul de aplicatie va defini o subclasã care preia toate metodele clasei Observable. // lista de obiecte observator public void addObserver(Observer o) { // adauga un observator la lista if (!obs. dar adaugã o serie de metode specifice aplicatiei care apeleazã metodele superclasei “setChanged” si “notifyObservers”.addElement(o). 203 . model. // anunta observatorii ca s-a produs o schimbare } } Clasa observator poate arãta astfel. Exemplu de definire a unei subclase pentru obiecte observate: class MyObservable extends Observable { public void change() { // metoda adagata in subclasa setChanged().Florian Moraru – Programare Orientata pe Obiecte Interfata Observer contine o singurã metodã "update". i>=0. i--) ((Observer)obs.contains(o)) obs. } } Exemplu de utilizare a obiectelor observat-observator definite anterior: public static void main(String[ ] av) { ProgressBar bar= new ProgressBar(). // daca s-a produs o scimbare private Vector obs. class ProgressBar implements Observer { public void update( Observable obs. apelatã de un obiect observat la o schimbare în starea sa care poate interesa obiectele observator înregistrate anterior: public interface Observer { void update(Observable o. MyObservable model = new MyObservable().. for (int i = arrLocal. Metoda “notifyObservers” apeleazã metoda "update" pentru toti observatorii introdusi în vectorul "observers": public class Observable { private boolean changed = false..elementAt(I)). } // o este obiectul observat Clasa Observable nu este abstractã dar nici nu este direct utilizabilã.out.update(this. } public void notifyObservers(Object arg) { // notificare observatori de schimbare if (!changed) return. } . // din clasa Observable notifyObservers(). Object arg).print('#'). } protected void setChanged() { // comanda modificare stare ob. int n=1000000000. m=n/100.length-1. dacã metoda “update” adaugã câte un caracter la o barã afisatã pe ecran (la fiecare apelare de cãtre un obiect MyObservable). Object x ) { System.addObserver(bar). observat changed = true.

Astfel. addChangeListener fireActionEvent. Ulterior vom adãuga o a treia fereastrã în care se afiseazã dimensiunea totalã a fisierelor selectate.Clase cu rol de redare vizualã a modelului. putem modifica structurile de date folosite în memorarea foii de calcul (modelul) fãrã ca aceste modificãri sã afecteze partea de prezentare. Schema MVC a apãrut în legãturã cu programele de biroticã (pentru calcul tabelar si pentru editare de texte). o barã de instrumente “tool bar”). Pentru a avea mai multã libertate în plasarea ferestrelor pe ecran vom crea douã panouri. Secventa urmãtoare realizeazã crearea obiectelor necesare si gruparea lor si este comunã pentru toate variantele discutate pentru aceastã aplicatie. clase care implementeazã metodele de tratare a evenimentelor ("actionPerformed" s. stateChanged Clase “model” în schema MVC Schema MVC (Model-View-Controller) este o extindere a schemei "Observat-observator". prin usurinta de modificare separatã a fiecãrei pãrti (în raport cu un program monolit la care legãturile dintre pãrti nu sunt explicite). au un rol similar cu interfata Observer.i<n. Obiectele folosite de aplicatie pot fi : JTextField pentru introducerea unui nume de fisier. aplicatii de tip “client-server” s. corespund metodei "update". 204 .i++) if ( i%m==0) model. fireChangeEvent actionPerformed. sau putem adãuga noi forme de prezentare a datelor continute în foaia de calcul sau noi forme de interactiune cu operatorul (de exemplu. Pentru a ilustra folosirea schemei MVC în proiectarea unei aplicatii si afirmatia cã introducerea de obiecte (si clase) suplimentare face o aplicatie mai usor de modificat vom considera urmãtoarea problemã simplã: Operatorul introduce un nume de fisier. . Separarea de responsabilitãti oferitã de modelul MVC este utilã pentru realizarea de aplicatii sau pãrti de aplicatii: componente GUI. adicã de obiect observator al modelului. JTextField pentru afisare dimensiune totalã si JTextArea pentru lista de fisiere. dar evenimentele nu sunt produse ca urmare directã a unor cauze externe programului (nu sunt cauzate direct de actiuni ale operatorului uman). iar metodele "actionPerformed" s. aplicatia verificã existenta acelui fisier în directorul curent si adaugã numele fisierului la o listã de fisiere afisatã într-o altã fereastrã.Florian Moraru – Programare Orientata pe Obiecte for (int i=0. care sunt douã “imagini” diferite asupra listei de fisiere selectate. care genereazã evenimente pentru obiectele receptor înregistrate la model. Fereastra de introducere are rolul de “controler” deoarece comandã modificarea continutului celorlalte douã ferestre. .a).Clase cu rol de “model”. Ele vor avea atasate si etichete JLabel care explicã semnificatia ferestrei.a. Un obiect “model” este un obiect observat (ascultat). Separarea netã a celor trei componente (M. V si C) permite mai multã flexibilitate în adaptarea si extinderea programelor.change(). de unde si numele de “Document-View-Controller”.Clase cu rol de comandã a unor modificãri în model. Programatorul de aplicatie defineste clase ascultãtor compatibile cu interfetele "xxxListener". } // modifica periodic stare obiect observat Interfetele ActionListener. adicã de obiect observat care contine si date.a.a. ItemListener s. Corespondenta dintre metodele celor douã grupe de clase este astfel: addObserver notifyObservers update addActionListener. Arhitectura MVC foloseste clase având trei roluri principale: . Dacã nu existã fisier cu numele primit atunci se emite un semnal sonor si nu se modificã lista de fisiere (si nici dimensiunea totalã a fisierelor).

getDefaultToolkit(). panel1. // panou pentru controler si sizeView panel1.getText().exists()) listView.add( new JLabel("Size")). In secventa anterioarã s-a definit o clasã inclusã anonimã cu rol de “adaptor” între controler si imagine. // imagine dimensiune totala TextField controler = new TextField(10). panel1. c. Continutul clasei adaptor depinde si de tipul componentei de comandã (aici un câmp text) si de rolul componentei imagine în aplicatie. panel1.setText(""). In varianta aplicatiei cu numai douã ferestre (cu un singur obiect “imagine”) legarea obiectului “listView” la obiectul “controler” se face prin înregistrarea unui obiect ascultãtor la sursa de evenimente de tip ActionEvent si prin implementarea metodei “actionPerformed” cu actiunea declansatã în obiectul receptor: // Legare listView la controler controler. // Controler public MVC () { // constructor Container c = getContentPane().setVisible(true).setSize(300. // adauga nume la lista afisata else Toolkit.add(controler). // imagine lista TextField sizeView = new TextField(15). // Legare imagini la controler . frame. panel1.add(panel2). dar poate fi extins pentru “slefuirea” aspectului ferestrei principale cu chenare pe componente. panel2.add(new JLabel("File")).add (sizeView).add(new JScrollPane(listView)). cu intervale de separare (“strouts”) si între ferestre si cu o altã grupare a componentelor atomice Swing în panouri.setLayout( new FlowLayout()).append ( " "+ newElem +"\n").addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ev) { String newElem = controler.setLayout (new FlowLayout ()).Florian Moraru – Programare Orientata pe Obiecte class MVC extends JFrame { TextArea listView = new TextArea(10. c. // ptr a verifica existenta fisierului if ( f. Pentru extinderea acestei aplicatii cu afisarea dimensiunii totale a fisierelor introduse pânã la un moment dat avem mai multe solutii: 205 .add( new JLabel("List")). frame. panel2. // Afisare imagini JPanel panel2 = new JPanel()..setLayout(new FlowLayout()). // panou pentru listView panel2. } public static void main(String args[]) { MVC frame = new MVC(). // sterge cimp de intrare } }).add(panel1). // sirul introdus in “controler” File f = new File (newElem).beep().. // daca nu exista fisier controler. // pentru defilare in zona text c.200). // Afisare controler JPanel panel1 = new JPanel(). // afisare fereastra } } Programul este utilizabil.20).

De remarcat cã va trebui sã repetãm o serie de operatii din adaptorul deja existent.beep().exists()) { sum+= f.getText(). sizeView. } else Toolkit. sizeView. // sterge cimp de intrare } }).getDefaultToolkit(). // suma dimensiuni fisiere public void actionPerformed(ActionEvent ev) { String newElem = controler. if ( f. // adauga nume la lista afisata } else Toolkit.append ( " "+ newElem +"\n").Florian Moraru – Programare Orientata pe Obiecte a) Adãugarea unui nou receptor la “controler” printr-o altã clasã adaptor între controler si noul obiect “imagine”. controler. // ptr a verifica existenta fisierului if ( f. cum ar fi verificarea existentei fisierului cu numele introdus în câmpul text. // sirul introdus in “controler” File f = new File (newElem). } }). De fapt aplicatia functioneazã corect numai dacã este adãugat mai întâi adaptorul pentru câmpul text “sizeView” si apoi adaptorul pentru zona text “listView” (ordinea inversã modificã comportarea aplicatiei): // Legare controler la sizeView controler.getText().setText("").beep(). // Legare controler la listView controler.getText(). // sterge camp de intrare } }). if ( f.addActionListener( new ActionListener() { int sum=0. Mai neplãcut este faptul cã succesiunea în timp a executiei metodelor “actionPerformed” din cele douã obiecte adaptor (receptor) nu este previzibilã. modificarea sau extinderea ei necesitã modificãri într-o clasã existentã (clasa adaptor).length(). actualizate într-o singurã functie apelatã la declansarea evenimentului de modificare a continutului câmpului text “controler”: // Legare controler la sizeView si listView controler. File f = new File (newElem).append ( " "+ newElem +"\n").length().setText(" "+ sum).exists()) { listView.setText(" "+ sum). clasã care poate deveni foarte mare dacã numãrul 206 . controler. public void actionPerformed(ActionEvent ev) { String newElem = controler. // afisare dimensiune totala listView.addActionListener( new ActionListener() { int sum=0. b) Modificarea adaptorului astfel ca sã lege obiectul “controler” la ambele obiecte imagine.getDefaultToolkit(). File f = new File (newElem).addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ev) { String newElem = controler. Desi este forma cea mai compactã pentru aplicatia propusã.setText("").exists()) { sum+= f.

String file. dar fãrã nici un efect (“ListDataAdapter”).. Extragerea datelor direct dintr-o componentã vizualã Swing nu este totdeauna posibilã si oricum ar fi diferitã pentru fiecare tip de componentã.exists()) model. “intervalRemoved” si “contentsChanged”. sum += f. Sau. definitã explicit.lastElement(). controler. Introducerea unui nou obiect cu rol de “model” care sã continã datele aplicatiei (lista de fisiere validate) permite separarea celor trei pãrti din aplicatie si modificarea lor separatã. Din acest motiv clasa adaptor este mai bine sã fie o clasã cu nume. eliminare element si modificare continut. pentru cã un câmp text sau o zonã text nu genereazã evenimente la modificarea lor prin program ci numai la interventia operatorului (evenimentele sunt externe si asincrone programului în executie). desi lista de fisiere ar putea fi necesarã si pentru alte operatii decât afisarea sa (de exemplu. Un receptor al evenimentelor generate de un model ListModel trebuie sã implementeze interfata ListDataListener.setText(" "+ sum). In variantele prezentate aplicatia nu dispune de datele introduse într-un obiect accesibil celorlalte pãrti din aplicatie. dar va genera alte evenimente si va necesita alte metode de preluare a datelor).length(). pentru comprimare si adãugare la un fisier arhivã).addElement ( file ).Florian Moraru – Programare Orientata pe Obiecte obiectelor ce comunicã si/sau functiile acestora cresc. Acum sunt necesare trei clase adaptor care sã lege la model obiectul controler si cele douã obiecte imagine.setText(""). // sterge cimp de intrare } } // clase adaptor intre model si imaginile sale class MV1Adapter extends ListDataAdapter { long sum=0.getText(). File f= new File(file). . c) Cuplarea obiectului “sizeView” la obiectul “listView” sau invers nu este posibilã. Se poate defini o clasã adaptor cu toate metodele interfetei ListDataListener.beep(). Dintre modificãrile posibile mentionãm : adãugarea unor noi obiecte imagine (de exemplu o fereastrã cu numãrul de fisiere selectate si o barã care sã arate proportia de fisiere selectate din fisierul curent ) si înlocuirea câmpului text din obiectul controler cu o listã de selectie (ceea ce este preferabil. care contine trei metode : “intervalAdded”. Pentru problema datã se poate folosi modelul de listã DefaultListModel. public void intervalAdded ( ListDataEvent ev) { file = (String) model. dintre care douã extind clasa “ListDataAdapter”: DefaultListModel model = new DefaultListModel(). // adaptor intre controler si model class CMAdapter implements ActionListener { public void actionPerformed(ActionEvent ev) { String file = controler. care contine un vector (si suportã toate metodele clasei Vector). File f. putem defini o altã clasã care implementeazã interfata ListModel . if (f. chiar dacã rãmâne inclusã în clasa MVC. sumView. dar în plus poate genera trei tipuri de evenimente: adãugare element..getDefaultToolkit(). else Toolkit. } } class MV2Adapter extends ListDataAdapter { public void intervalAdded (ListDataEvent ev) { // obiect “model” 207 . f= new File(file).

addActionListener( new CMAdapter()). // model la controler model. partea de vizualizare corespunde interfetei cu utilizatorul (obiecte grafice afisate de aplicatie în browser. animatie si alte efecte vizuale). iar partea de comandã (“controller”) corespunde preluãrii cererilor HTTP si transmiterii rãspunsurilor corespunzãtoare (care include de obicei date din model ). validãri asupra datelor introduse de operator.addListDataListener( new MV1Adapter()). fãrã a modifica obiectele existente sau metodele acestor obiecte. Desi textul sursã al aplicatiei a crescut substantial fatã de varianta monolit. } } .. 208 . Schema MVC este mult folositã în realizarea aplicatiilor Web. extinderea sau modificarea aplicatiei a devenit mai simplã si mai sigurã pentru cã se face prin prin adãugarea de noi obiecte sau prin înlocuirea unor obiecte. // Cuplare obiecte MVC prin obiecte adaptor controler.. // sumView la model model. Aplicatia se poate îmbunãtãti prin modificarea modului de alegere a fisierelor de cãtre operator: în loc ca acesta sã introducã mai multe nume de fisiere (cu greselile inerente) este preferabil ca aplicatia sã afiseze continutul unui director specificat iar operatorul sã selecteze fisierele dorite.Florian Moraru – Programare Orientata pe Obiecte listView. unde partea de model contine clasele ce corespund tabelelor bazei de date (inclusiv logica de utilizare a acestor date).append(" "+ model. // listView la model } O clasã "adaptor" are rolul de adaptare a unui grup de metode (o "interfatã") la un alt grup de metode si reprezintã o altã schemã de proiectare.lastElement()+"\n").addListDataListener( new MV2Adapter()).

Programarea cu fire de executie Fire de executie concurente Paralelismul unor activitãti se poate realiza la nivelul sistemului de operare pentru activitãti ce rezultã din executarea unor programe independente (nu neapãrat distincte). Altfel spus. fie de evenimente externe procesului. dar care pot comunica prin intermediul sistemului de operare. Activitãtile paralele pot evolua complet independent unele de altele (asincron) sau pot utiliza anumite resurse comune (zone de memorie. care sã decidã ce proces primeste controlul procesorului. Cedarea controlului procesorului de cãtre procesul activ (în executie) se poate face voluntar (la cererea sa. la producerea anumitor evenimente cu efect asupra stãrii poceselor. In anumite aplicatii este necesar ca mai multe activitãti sã progreseze în paralel.In executie. sau blocat.Suspendat. . cele douã procese pot evolua în paralel.Un program de tip “server” care trebuie sã rãspundã cererilor adresate de mai multi “clienti” (programe client). un proces poate trece ciclic prin câteva stãri. cum ar fi o decizie a planificatorului de procese. La nivelul sistemului de operare activitãtile paralele se numesc procese (în sisteme de tip Unix) sau “taskuri” (în sisteme Microsoft Windows ). . Paralelismul proceselor nu implicã neapãrat mai multe procesoare care lucreazã simultan. pentru cã existã un alt proces mai important si gata de executie. astfel ca în cazul când mai multe procese sunt gata de executie. fiecare cu spatiul sãu de memorie (de nume). . In Java paralelismul (aparent) are loc în cadrul unui singur program (sau aplet). când procesul asteaptã producerea unui eveniment. Un proces poate contine mai multe fire (subprocese). Principalele stãri în care se poate afla un proces sunt: . Java este primul limbaj care a inclus facilitãti de programare cu subprocese concurente.Algoritmi paraleli pentru îmbunãtãtirea performantelor unor operatii de cãutare sau de alt tip. chiar pe un singur procesor care executã alternativ secvente de intructiuni din P1 si din P2. iar schimbarea stãrii sale este cauzatã fie de evenimente interne ( o actiune a procesului activ). în cadrul unei aplicatii. astfel încât se creeazã aparenta cã un acelasi program (calculator) poate efectua în paralel mai multe operatii.Gata de executie dar inactiv.P2) sunt concurente sau paralele atunci când executia lui P2 începe înainte de terminarea completã a lui P1. Se spune cã douã procese (fire) P1 si P2 (lansate în ordinea P1. De obicei fiecare proces are o prioritate. Planificatorul de procese este parte din masina virtualã Java (JVM). se alege procesul cel mai important pentru activare.Un joc cu imagini multiple animate simultan. fisiere etc) si atunci este necesarã sincronizarea lor în încercarea de acces la o resursã comunã. Dupã crearea sa si înainte de terminare. sub forma unor fire de executie (“threads”). sau în asteptare. fiecare cu o conexiune separatã la server. iar numele folosit pentru o astfel de activitate este acela de “thread” (fir de executie) sau “proces de categorie usoarã” (“ligthweight process”). Câteva exemple tipice de aplicatii cu paralelism între activitãtile din interiorul aplicatiei: . ci este o alternativã la executia strict secventialã a celor douã procese : se executã complet P1 si apoi se excutã complet P2 (fãrã întreruperi). . Un alt nivel de paralelism poate avea loc în cadrul fiecãrui program (aplicatie).Florian Moraru – Programare Orientata pe Obiecte 17. prin apelul unei metode) sau ca urmare a lansãrii unei operatii de intrare-iesire. Procesele paralele îsi disputã timpul procesorului si trebuie sã existe un planificator de procese (numit si dispecer). 209 . Este posibil ca în douã procese (fire) paralele sã se execute aceeasi secventã de instructiuni sau secvente de instructiuni complet diferite. paralelism suprapus peste concurenta proceselor la nivelul sistemului de operare. când procesul este activ (detine controlul procesorului). deoarece foloseste acelasi spatiu de memorie (si de nume) cu celelalte fire de executie din acelasi program.

Procesele paralele (concurente) îsi pot disputa anumite resurse comune (date din memorie.concurrent”. } } Crearea unui nou fir de executie înseamnã crearea unui nou obiect si se face într-un fir activ (care poate fi cel implicit. fir1. prin care procesele se adreseazã planificatorului. s. Firul pãrinte apeleazã de obicei o singurã metodã a firului “fiu” (metoda “start”). join. alãturi de cuvântul cheie “synchronized” au existat încã din prima versiune Java. s. atunci firul este activat imediat dupã apelul metodei “start”. creat automat si în care se executã functia “main” cu rol de program principal. fie pe baza unei clase care implementeazã interfata Runnable (interfatã cu o singurã metodã “run”). de aceea nici nu se mai creeazã uneori variabile de tipul subclasei. într-o pozitie care depinde de prioritatea firului. pentru lansarea sa în competitie cu celelalte fire. pentru functia “main”). Clasa Thread si interfata Runnable se aflã în pachetul “java.Florian Moraru – Programare Orientata pe Obiecte Un singur proces poate fi în executie. In Java existã întotdeauna un fir de executie implicit (cu numele “main”). Planificatorul activeazã un fir prin apelarea metodei “run”. Firele din clase ce extind clasa Thread pot avea un constructor cu parametru de tip String. Interactiunile dintre procese (sau subprocese) paralele nu se fac prin apeluri directe între procese ci prin intermediul dispecerului. notify). unele apelate de dispecer iar altele care apeleazã dispecerul. Exemplu: Thread fir1 = new T().. Metodele clasei Thread sunt de douã categorii: metode ale obiectelor si metode statice.a. fisiere de date.). Efectul metodei “run” (apelatã de dispecer) depinde de rolul pe care îl are firul de executie în cadrul unei aplicatii. Coordonarea si sincronizarea proceselor paralele necesitã existenta unor operatii specifice. prezentã în orice fir de executie (mostenitã de la clasa Thread). fie în lista proceselor suspendate sau blocate.) sau îsi pot transmite date între ele (procese numite “producãtor” si “consumator”).. 210 .start(). // obiect anonim de tipul T Apelul metodei “start” dintr-un fir nu are ca efect imediat apelul metodei “run” din acel fir. yield. Clasa Thread contine câteva metode.a. Dacã nu existã alte fire gata mai importante si dacã task-ul Java este activ. ci introducerea firului respectiv în coada proceselor gata de executie. Alte fire de executie pot fi create din firul “main” sau din alte fire create anterior. Operatiile prin care un fir interactioneazã cu planificatorul sunt fie metode din clasa Object (wait. In clasa Thread metoda “run” nu are nici un efect si de aceea trebuie definitã o subclasã. cu metoda “run” redefinitã: class T extends Thread { public void run() { .util. Clasele pentru fire de executie se definesc fie ca subclase ale clasei Thread (cu metoda “run” redefinitã). prin care se poate da un nume firului creat. Exemplu: new T( ). Fire de executie în Java In Java un fir de executie este un obiect dintr-o subclasã a clasei de bibliotecã Thread.lang” si. Incepând cu versiunea 5 au fost introduse noi clase si metode pentru executia si sincronizarea firelor concurente cu resurse comune în pachetul “java.start(). fie metode din clasa Thread (sleep. Dintre procesele gata planificatorul alege pe cel cu prioritate maximã si care este primul în coadã (dacã sunt mai multe procese gata cu aceeasi prioritate). celelalte se aflã fie în lista proceselor “gata”.

“wait” etc. } // creare si lansare procese paralele public static void main (String [] arg) { T p1 = new T("Unu").) si se apeleazã una din metodele prin care se cedeazã controlul celorlalte fire concurente (“yield”.println (i + " " + getName()). // la terminarea procesului } private int time (int i) {return (int) (100*Math.i<10. } // “doarme” un timp catch (InterruptedException e) { } } System.Florian Moraru – Programare Orientata pe Obiecte Metode care pot fi aplicate oricãrui obiect fir de executie: start : Cere lansarea unui fir dintr-un alt fir (pãrinte). cum este Microsoft Windows ). De obicei metoda “run” contine un ciclu în care se executã anumite operatii (afisare. desenare.start(). T p2 = new T("Doi").start(). notifyAll : Firul activ notificã (anuntã) firele în asteptare cã s-a produs evenimentul asteptat prin “wait”. p2. Exemplu: class T extends Thread { // clasa pentru obiecte fire de executie public T (String nume) { super(nume). Metodele “wait” si “notify” pot fi folosite numai în secvente sincronizate. Este posibil ca mai multe fire concurente sã execute aceleasi operatii. dar si de alte evenimente din sistem (pentru un sistem de operare multi-tasking care alocã intervale de timp fiecãrui task. Metoda “run”.println (getName()+ " terminat"). asa cum este definitã în interfata Runnable (pe care o implementeazã si clasa Thread). conditie care nu poate fi verificatã la compilare dar care produce la executie exceptia IllegalMonitorStateException. p1.). când este posibil.random()). operatii cu fisiere etc. isAlive : Verifica dacã firul pentru care se apeleazã s-a terminat definitiv sau nu.out. // apel constructor clasa Thread } public void run () { for (int i=1.i++) { System. “sleep”. interrupt: Cere întreruperea firului pentru care se apeleazã getName/setName : Obtine/modificã numele firului pentru care se apeleazã getPriority/setPriority : Obtine/modificã prioritatea firului pentru care se apeleazã join(): Asteaptã terminarea firului pentru care se apeleazã Metode statice care pot fi folosite doar de firul în executie (firul curent): Thread currentThread(): referintã cãtre firul curent. nu poate arunca mai departe exceptia InterruptedException si de aceea exceptia trebuie “prinsã” si tratatã în metoda “run”. 211 . Metoda “run” din Thread se redefineste în fiecare fir definit de utilizatori si determinã efectul executãrii acestui fir. în executie sleep(long millis): autosuspendare fir curent pentru un timp specificat în milisecunde boolean interrupted(): testeazã si anuleazã starea de întrerupere a firului curent Metodele urmãtoare sunt mostenite de la clasa Object dar se folosesc numai în obiecte de un tip derivat din Thread sau Runnable : wait : Este apelatã de firul activ pentru a se autosuspenda în asteaptarea unui semnal de la un alt fir paralel (care detine o resursã comunã) notify. } } Succesiunea de afisare depinde de secventa de numere aleatoare generate.out. // scrie numele procesului try { sleep (time(i)).

} catch (Exception e) { } } } } Un fir este terminat definitiv fie prin terminarea instructiunilor din metoda “run”.out.read() > 0 ). } // asteapta tastarea unei clape catch (IOException e) {} } public static void main (String [] arg) { Fir t = new Fir (). la initierea unei operatii de I/E. Metodele “wait”. Definirea unei subclase a clasei Thread nu mai este posibilã pentru procese paralele din cadrul unui aplet. atunci când planificatorul preia controlul ( la apelul unei metode sau la terminarea unei operatii de I/E). care nu este subclasã a clasei Thread. Metoda “main” este si ea parte dintr-un fir de executie creat automat la activarea masinii virtuale Java pentru interpretarea unui program.out.isAlive()) { System. care contine numai metoda “run”. fie în functia “main”.i<8. fie datoritã unei exceptii propagate în afara metodei “run”. try { Thread. de exemplu).sleep(100). } catch (InterruptedException e) {} 212 .currentThread si Thread.start().sleep(20). De aceea a fost creatã si o interfatã numitã Runnable.sleep ( milisec). se pot folosi metodele statice din clasa Thread : Thread.in. Un program cu mai multe fire paralele se terminã atunci când s-au terminat toate firele (mai pot rãmâne fire de tip “daemon” dupã terminarea unui program)."). In aceastã clasã. dar aceasã prioritate poate fi modificatã prin metoda “setPriority”. “notify” si “notifyAll” nu puteau face parte din aceatã interfatã deoarece ele nu pot fi implementate de orice utilizator si de aceea fac parte din clasa Object. Lansarea în executie a unui fir se poate face numai prin apelul metodei “start” din clasa Thread .println (Thread. Fiecare fir are o prioritate asociatã (un întreg între 1 si 10).currentThread() + ” “+ i). terminarea operatiei respective este un eveniment tratat de planificator. care aratã importanta relativã a acelui fir fatã de alte fire. este ales firul cu prioritate maximã pentru a fi activat. La creare un fir primeste prioritatea implicitã 5. Exemplu: class Proces implements Runnable { // fire cu interfata Runnable public void run () { for (int i=1. “sleep” sau “join”. dacã nu primeste explicit un nume la construirea obiectului de un subtip al tipului Thread . Fiecare fir are asociatã o variabilã (boolean) cu starea sa de întrerupere.Florian Moraru – Programare Orientata pe Obiecte Exceptia InterruptedException apare atunci când se încearcã întreruperea unui fir aflat într-o stare de asteptare ca urmare a unui apel “wait”.print(". deoarece un aplet este o subclasã a clasei Applet si în Java nu este permis ca o clasã sã extindã mai mult de o singurã clasã. // afisare punct la fiecare 100 ms try { Thread.Dacã mai multe fire sunt gata de executie. t. fie într-o altã metodã (dintr-un alt fir. Exemplul urmãtor pune în evidentã aceastã situatie: // proces de citire de la tastatura class Fir extends Thread { public void run () { try { while ( System. De aceea trebuie creat câte un obiect Thread pe baza fiecãrui obiect Runnable. ca metode native si finale. Fiecare fir de executie primeste automat un nume. folosind un constructor al clasei Thread care admite un parametru de tip Runnable. Cedarea controlului de cãtre un fir se face si implicit. în aceste cazuri nu se produce întreruperea solicitatã. // activare fir de citire de la tastatura while (t.i++) { System. Crearea unui fir de executie folosind interfata Runnable necesitã definirea unei clase care implementeazã aceastã interfatã si definirea metodei “run” din aceastã clasã.

new Thread (new Proces ()). etc. In general.start(). Exemplul clasic este cel al unui sistem de rezervare a locurilor (trenuri. hoteluri. class Contor { private int m. Pentru a ilustra efectele nesincronizãrii a douã fire cu o resursã comunã vom considera exemplul a douã fire care actualizeazã aceleasi date (incrementeazã un acelasi contor). cu douã metode publice: “get” care citeste valoarea curentã a variabilei contor (de tip private) si “incr” care mãreste cu 1 valoarea variabilei contor. care nu poate fi întreruptã si care poate fi “anulatã” în caz de eroare la una din aceste operatii. deoarece în caz contrar pot apare situatii de blocare definitivã a unor procese. deoarece rezultatele sale pot sã depindã si de alt factori care nu au legãturã cu algoritmii folositi dar tin de interactiunile voluntare sau involuntare dintre aceste activitãti. Verificarea corectitudinii unui program cu activitãti paralele este mai complicatã decât în cazul unui program secvential. Sistemul poate functiona corect cât timp cererile simultane nu se referã la un acelasi loc si cererile cãtre acelasi loc sunt secventiale în timp. iar altele rãmân a fi descoperite în lucrãrile destinate acestei problematici. sau situatii de actualizare eronatã a unor date comune sau situatii de transmitere incorectã a unor date între fire de executie. actualizarea bazelor de date din mai multe procese paralele poate conduce la astfel de erori si este prevãzutã gruparea operatiilor de actualizare a unui articol într-o “tranzactie”. public Contor () { m=0. comportarea unui program corect trebuie sã fie reproductibilã. de timpul necesar fiecãrui fir pentru operatiile cu resursa comunã sau de alti parametri care nu au legãturã cu algoritmul aplicatiei cu fire multiple. indiferent de conditiile în care se executã programul respectiv. Contorul este un obiect dintr-o clasã “Contor” definitã de noi. iar un alt proces reactivat poate dori sã execute aceeasi secventã de incrementare a aceluiasi contor (cazul a douã oficii din localitãti diferite de la care se cere simultan rezervarea unui loc la un zbor sau la un tren). rezultatele unor programe pot fi diferite dacã sunt rulate în conditii diferite si nu s-a tinut seama la scrierea lor de toate eventualitãtile.) în care existã o bazã de date comune folositã si actualizatã de la mai multe terminale dispersate ca locatii si care actioneazã independenet unele de altele (nu poate fi controlatã ordinea în care apar solicitãrile de rezervare si continutul acestora). Sincronizarea firelor concurente cu resurse comune trebuie sã asigure rezultate corecte indiferent de numãrul firelor. atunci poate avea loc o actualizare eronatã a datelor si o informare gresitã a clientilor. In ambele situatii trebuie reglementat accesul la resursele comune. Actualizarea se realizeazã prin mai multe operatii (instructiuni masinã si/sau operatii de I/E) si aceastã secventã de operatii poate fi întreruptã (de planificator). } } Fire de executie cu date comune Programarea cu fire concurente are o serie de particularitãti fatã de programarea secventialã. Dacã secventa de operatii pentru rezervarea unui loc poate fi întreruptã de cãtre o altã cerere cãtre acelasi loc. unele vor fi mentionate aici. Altfel spus.start(). } public void incr () { // pentru obiecte folosite de fire // incrementare m in trei pasi 213 . Altfel spus. de ordinea lansãrii lor. Firele paralele pot evolua un timp independent unele de altele. avioane. dar în anumite momente pot sã-si dispute resurse comune sau doresc sã-si transmitã date prin variabile comune.Florian Moraru – Programare Orientata pe Obiecte } } // creare si lansare procese paralele public static void main (String [] arg) { new Thread ( new Proces ()). Pentru un contor în memorie vom forta transferul controlului de la un fir la altul prin introducerea de apeluri la “sleep” sau “yield” între instructiunile care realizeazã actualizarea contorului.

// pasul 2 Thread.Florian Moraru – Programare Orientata pe Obiecte int aux. aux=aux+1. ca urmare a initierii unor operatii de citire/scriere pe disc si nu prin cedarea voluntarã a controlului: class Fir extends Thread { String file. // contor folosit de cele doua fire Fir f1 = new Fir ( c ).isAlive() ) // asteapta terminare f1 si f2 try {Thread. // pasul 1 Thread. } } // pentru obiecte fir de executie class Fir extends Thread { Contor c.random()*20)).i++){ try { sleep(10).i<250. Un exemplu asemãnãtor foloseste un contor memorat într-un fisier disc si incrementat din mai multe fire concurente.sleep((int) (Math.isAlive() && f2. while ( f1. fiecare proces incrementeazã apoi si scrie înapoi în “m” aceeasi valoare a variabilei sale “aux”.out. Explicatia este simplã: firul f1 citeste în “aux” o valoare “m” a contorului. try { aux=m. Din aceastã cauzã valoarea contorului creste cu 1 si nu cu 2 cum ar fi trebuit dacã cele douã fire erau sincronizate fatã de resursa comunã. Fir f2 = new Fir ( c ).random()*20)). } } } class ExFir { public static void main (String args[]) { Contor c= new Contor().c=c.println (c. Rezultatul final depinde în general de timpii de asteptare ai fiecãrui fir (între operatii).incr(). de ordinea lansãrii si de alte procese din sistem. // valoare finala contor } } // citire valoare contor Fiecare din cele douã fire face câte 5 incrementãri ale contorului comun. // pasul 3 } catch (InterruptedException e) {} } public int get () { return m.sleep((int) (Math. în acest caz pierderea controlului de cãtre fiecare fir concurent se face automat. m=aux.file=file. f2. } public void run () { for (int i=0. public Fir ( Contor c) { this. este întrerupt de firul f2 care citeste si el în variabila sa proprie “aux” aceeasi valoare “m”.start().i<5.} catch (Exception e) {} System. } public void run () { for (int i=0.out. public Fir ( String file) { this.get()).get()). System. } catch(InterruptedException e) {} c. ci este o valoare mai micã ( 5 pentru programul anterior).start(). dar valoarea finalã a contorului nu este 10.println (getName()+" "+c. f1.sleep(100).i++) 214 .

Este important ca instructiunile dintr-o sectiune criticã sã se execute neîntrerupt pentru un anumit proces si pentru un anumit obiect. trebuie serializat accesul la resursa comunã pentru procese (fire) concurente sau trebuie asiguratã excluderea mutualã a firelor în raport cu resursa comunã.sleep((int) (Math. Altfel spus.”mutex”. fãrã ca un alt proces sã poatã executa si el aceleasi operatii pentru un acelasi obiect.random()*20)).writeInt (n+1). acest cuvânt se adaugã metodei “incr” (eventual si metodei “get”): public synchronized void incr () { int aux.} } } In functia “main” au fost lansate 4 fire ca cel prezentat si s-a afisat valoarea din fisier dupã terminarea lor. aux=aux+1. // pasul 1 Thread. // scrie contor modificat f. fie un bloc de instructiuni precedate de cuvântul cheie synchronized. de numãrul firelor concurente si de alti parametrii care nu ar trebui sã influenteze rezultatele programului.Florian Moraru – Programare Orientata pe Obiecte try { RandomAccessFile f = new RandomAccessFile (file. In exemplul anterior cu incrementarea unui contor din memorie. Sectiunile de cod din fiecare proces care acceseazã resursa comunã se numesc sectiuni critice sau regiuni critice.”monitor”). când un proces începe sã modifice o resursã comunã trebuie interzis altor procese sã modifice aceeasi resursã înainte ca procesul care a obtinut primul resursa sã fi terminat operatiile de modificare a resursei."rw").readInt().sleep((int) (Math. astfel ca sã o putem aplica si la actualizarea unor date dintr-un fisier din mai multe fire concurente: 215 . // pasul 3 } catch (InterruptedException e) {} } Dupã ce firul f1 a intrat în executia metodei sincronizate “incr” pentru un obiect “c”. Sincronizarea se face la nivel de obiect si nu la nivel de functie.printStackTrace(). // repozitionare pentru scriere contor modificat f. } catch (IOException ex){ ex. Putem generaliza solutia de mai sus prin definirea unui obiect sincronizat. int n = f. rezultatul a fost mereu altul dar oricum mai mic decât 1000 (4*250). try { aux=m.close().random()*20)). iar la iesirea din blocul sincronizat zãvorul este deschis pentru a permite executarea secventei sincronizate din alte fire. m=aux. Sincronizarea firelor de executie concurente Prima solutie Java pentru sincronizarea firelor de executie a fost utilizarea cuvântului cheie synchronized în declararea unor metode de acces la resurse comune (înainte de tipul metodei) sau ca atribut al unui bloc de instrcutiuni. iar firul f2 care încearcã acest lucru este pus în asteptare pânã la terminarea metodei sincronizate. // pasul 2 Thread. In Java o sectiune criticã este fie o metodã. In general. nu se permite altui fir sã mai execute o metodã sincronizatã pentru acelasi obiect. // citire contor f.seek(0). Acest mod de comportare este tipic pentru programe cu fire de executie concurente nesincronizate: rezultate diferite în functie de numãrul de operatii. la intrarea într-un bloc sincronizat zãvorul se închide si nu permite accesul din alte fire la obiectul protejat. Fiecare obiect are asociat un “zãvor” (“lock”.

} } } In exemplul anterior este interzis accesul simultan din mai multe fire la orice operatie si la întregul fisier. nu este posibil ca douã fire sã adauge în paralel elemente la un acelasi vector (“addElement”). // nume fisier public SyncObject(String file){ this. int n = f. Toate metodele de acces la un vector (din clasa Vector) sunt sincronizate pentru a fi sigure la apelare din fire concurente. De exemplu. Deoarece sincronizarea se face la nivel de obiecte. este preferabil sã se sincronizeze numai operatiile de actualizare a unui aceluiasi articol din fire concurente.writeInt(0). fiind permisã citirea unui articol din diferite fire si actualizarea paralelã a unor articole diferite din acelasi fisier de cãtre fire diferite.} } synchronized void update() { // metoda sincronizata try { RandomAccessFile f = new RandomAccessFile (file.readInt(). nu este permisã apelarea din fire diferite a unor metode sincronizate diferite pentru acelasi obiect. f. f. Cuvântul synchronized ar trebui adãugat oricãrei metode care modificã un obiect ce poate fi folosit în comun de mai multe fire de executie paralele.i++){ obj. f. } catch(IOException ex){ ex."rw").file=file.i<250. Deblocarea obiectului are loc la terminarea executiei metodei sincronizate. // obiect cu metode sincronizate public Fir ( SyncObject obj) { this. O metodã sincronizatã poate fi întreruptã.close().obj=obj. ceea ce nu este bine ca timp pentru fisiere mari accesate din multe fire paralele. Dupã apelarea unei metode “sincronizate” pentru un obiect acest obiect este “blocat” cu un “zãvor” sau “lacãt” (“lock”) si nu mai poate fi apelatã o altã metodã sincronizatã pentru acelasi obiect (dintrun alt proces).seek(0). dar nu poate fi reapelatã pentru acelasi obiect dintr-un alt fir concurent. De asemenea.close(). datele comune proceselor trebuie încapsulate în obiecte 216 .Florian Moraru – Programare Orientata pe Obiecte class Fir extends Thread { SyncObject obj. } public void run () { for (int i=0."rw"). try { RandomAccessFile f = new RandomAccessFile (file. Multe din metodele claselor JDK sunt sincronizate (dar nu toate). } catch (IOException ex) { ex. f.printStackTrace(). f.writeInt (n+1).update(). // metoda sincronizata a obiectului } } } Pentru fisierul cu un singur articol obiectul sincronizat va fi: class SyncObject { String file.printStackTrace().

fisier) se transmite adresa obiectului ce constituie resursa comunã. Desi fiecare metodã din clasa Vector este sincronizatã. f2. La crearea unui proces care foloseste o astfel de resursã comunã (variabilã. Un monitor este asociat unui obiect (sau unei clase) si nu unei metode.start().start(). // modifica sir din pozitia i sleep(10). totusi o secventã de apeluri de metode nu este automat sincronizatã. din fire concurente. Vector c= new Vector(Arrays. f1.i<c. Douã fire pot executa în paralel secvente din douã metode sincronizate. // firul activ pierde controlul s= i+" "+ s. Fir f2 = new Fir ( c ).} catch (Exception e) {} System.isAlive() && f2.asList(s)).size(). // scoate sir din vector sleep(10). Fir f1 = new Fir ( c ). colectie.setElementAt(s. 217 . public void run () { for (int i=0.isAlive() ) try {Thread.c=c. firul respectiv devine proprietarul monitorului pânã la terminarea metodei. Prin monitoare se asigurã accesul strict secvential al firelor concurente la operatii critice asociate unui obiect. while ( f1.i). Un monitor este un zãvor initial deschis si care se închide dupã ce un fir a apelat o metodã synchronized pentru obiectul respectiv. // firul activ pierde controlul c. Sincronizarea unei metode se poate exprima în douã moduri: synchronized void f() { … } sau: void f() { synchronized (this) { … } } Exemplul urmãtor aratã când poate fi necesarã sincronizarea unor secvente de instructiuni care opereazã asupra unui obiect. prin adãugarea unui caracter la fiecare sir din vector."trei"}.out."doi".println (c). O altã denumire folositã este cea de “monitor” : fiecare obiect poate avea un monitor asociat (creat efectiv numai atunci când este necesar). iar firul activ poate pierde controlul între apeluri de metode sincronizate: class Fir extends Thread { Vector c.Florian Moraru – Programare Orientata pe Obiecte utilizate în comun. // pune in pozitia i sirul modificat } catch(InterruptedException e) {} } } } public Fir ( Vector c) {this. } } class ModifyVector { public static void main (String args[]) { String s[]={"unu".sleep(100).i++){ synchronized (c) { try { // pentru exceptii generate de “sleep” String s = (String) (c.elementAt(i)). Douã procese modificã un acelasi vector. De asemenea. // continut final vector } } O clasã este sigurã într-un context cu fire concurente (“thread-safe”) dacã rezultatele metodelor sale sunt aceleasi indiferent din câte fire paralele sunt apelate aceste metode pentru aceleasi obiecte. un fir poate executa o metodã sincronizatã si un alt fir o metodã nesincronizatã pentru un acelasi obiect. dar pentru obiecte diferite.

Acest tip de blocare (numit “starvation”) poate fi evitat prin programarea corectã a proceselor cu resurse comune. // asteapta semnal de la celalalt fir 218 .Procesul asteaptã eliberarea unei resurse de cãtre un alt proces (prin “wait”).start(). t1. System. fie nu transmite semnal de eliberare a resursei (prin “notify” sau “notifyAll”). // cedare procesor catre t1 si t2 System. t2.println ("Pornire fire"). Exemplu: List safelist = Collections. } // asteptare terminare cu metoda “join” public static void main (String [] arg) throws Exception { Fir t1=new Fir(). Sunt prevãzute însã metode de obtinere a unor clase colectie echivalente sincronizate. Exemplu: // main lanseaza doua fire de tip “Fir” class Fir extends Thread { public synchronized void run () { for (int i=1.out. Fir t2=new Fir(). Uneori este nevoie ca firul “main” care a lansat alte fire sã afiseze rezultatele ce reprezintã efectul lor.println (i + " " + getName()).out. iar procesul care detine resursa fie nu poate fi activat. Avem cel putin douã variante ca firul “main” sã astepte terminarea celorlalte fire active: .println ("Pornire fire").i<10. Metodele sincronizate au performante mai slabe decât metodele nesincronizate si de aceea nu toate clasele JDK sunt “thread-safe”. t1. aceastã calitate a unei clase se obtine prin adãugarea atributului synchronized metodelor care modificã date din clasã.Florian Moraru – Programare Orientata pe Obiecte Practic.start().println ("Terminare fire"). try { wait().isAlive()) Thread.i++) { System.start().out. System. dar clasele colectie din Java 2 nu sunt implicit “thread-safe” din motive de performantã.join(). while (t1. Metoda “start” pune un fir în coada firelor gata de executie (poate chiar în executie) si revine imediat în firul care a executat metoda “start”. vom începe cu câteva situatii mai usor de înteles si de tratat. } Un proces poate fi blocat definitiv în câteva situatii. System.out.println ("Terminare fire"). t1. Pentru familiarizare cu problematica programãrii cu fire concurente în Java. t2. Probleme specifice programãrii cu fire concurente In afara erorilor de actualizare pot apare chiar blocãri definitive ale unor procese care se asteaptã reciproc pentru cã fiecare detine o resursã de care are nevoie celãlalt proces. Hashtable) au avut metode sincronizate. t2. Primele clase colectie (Vector.Folosind metoda “join” Exemplele urmãtoare ilustreazã cele douã posibilitãti: // asteptare terminare cu metoda “isAlive” public static void main (String [] arg) throws Exception { Fir t1=new Fir().join(). dintre care mentionãm : .yield(). mentionate în toate lucrãrile introductive.start().out.isAlive() && t2. dupã terminarea lor definitivã.Folosind metoda “isAlive” .synchronizedList (new ArrayList()). Fir t2=new Fir().

Exemplu: String o1 = "Lock ". iar P2 asteaptã eliberarea unei alte resurse detinute de P1 (asteaptã semnal de la P1).out. ilustrate în douã exemple cu douã butoane Swing: un buton care porneste o activitate de lungã duratã (afisarea în mod text sau în mod grafic pentru a evidentia cum avanseazã firul) si un buton de oprire a activitãtii de afisare. Acest tip de blocare reciprocã (numit “deadlock”) poate fi evitat prin impunerea unei discipline de acces la resursele comune (o a numitã ordine de ocupare si eliberare a resurselor comune). // solutia 1: cu metoda “interrupt” // tratare evenimente de la butoane public void actionPerformed (ActionEvent ev) { Object source = ev. firul fiu testeazã periodic starea variabilei. O altã situatie este aceea în care un fir trebuie sã termine executia unui alt fir.Un proces P1 asteaptã eliberarea unei resurse (asteaptã un semnal) de la un alt proces P2. In general folosim o variabilã comunã.println (getName()+ " terminat"). 219 . // cedeaza controlul synchronized (o1) { // pune zavor pe o1 System. // cedeaza controlul synchronized (o2) { // pune zavor pe o2 System.out. } } . în caz contrar butonul de stop nu poate fi actionat si deci operatorul nu poate transmite semnalul de oprire.out.println(o1 + o2). String o2 = "Step ".Florian Moraru – Programare Orientata pe Obiecte } catch (InterruptedException e) { } notify(). Avem cel putin douã solutii.println("STOP"). } } } } }).out. Blocarea reciprocã poate apare în cazul proceselor cu resurse comune si nu are solutie dupã ce s-a produs. if (source==stop){ // buton de stop System. } } } } }). de aceea trebuie luate mãsuri pentru evitarea blocãrii mutuale. Thread t1 = (new Thread() { public void run() { while (true) { synchronized (o1) { // pune zavor pe o1 yield(). Thread t2 = (new Thread() { public void run() { while (true) { synchronized (o2) { // pune zavor pe o2 yield(). // anunta celalalt fir ca poate continua } System.getSource(). de obicei firul “pãrinte” terminã firul “fiu” pe care el l-a creat si lansat. la care au acces ambele fire. iar firul pãrinte modificã starea variabilei comune când doreste terminarea firului fiu.println(o2 + o1). De observat cã trebuie sã existe cel putin douã fire de executie.

// afiseaza puncte } System.exit(0). } // camp text cu nume fisiere if (source==start){ // buton de start t=new Thread() { // creare fir separat ptr activitatea de durata public void run(){ while (!mark){ // continua cat timp mark este “false” System. yield(). } } // solutia2 : firul de durata cedeaza periodic controlul firului in care se // trateaza evenimentele de la componentele Swing public void actionPerformed (ActionEvent ev) { Object source = ev.print(". pentru cã poate conduce la blocare.out. mark=true.").start(). t. .out.println("STOP"). dacã este nevoie ca alte fire sã termine fortat aceste operatii de I/E. iar în cazul firelor cu aceeasi prioritate nu se poate anticipa ordinea de lansare.getSource().").out. nu se poate preciza care dintre ele este scos din asteptare de executia metodei “notify”.exit(0).start().interrupt(). } System. metoda “notifyAll” scoate toate firele în asteptare din starea “wait” si le face gata de executie. dar operatiile de intrareiesire nu trebuie sã aparã în metode sincrinizate. if (source==stop){ // buton de stop System. } // camp text cu nume fisiere if (source==start){ // buton de start Thread t=new Thread() { public void run(){ while (!mark){ System. } }.Secventa de activare a mai multor fire nu este dictatã strict de prioritatea lor. Fire de tip producãtor-consumator Putem deosebi douã situatii de utilizare a unei resurse comune de cãtre douã fire concurente: 220 .Nici metoda “yield” nu trebuie folositã în metode sincronizate. } } // mark este initial “false” // cere intreruperea firului t Vom mentiona si alte aspecte legate de efectul metodelor Java folosite în programele cu fire si care pot condice la situatii greu de explicat: .print(".Florian Moraru – Programare Orientata pe Obiecte mark=true. t.Metodele “wait” si “notify” pot fi folosite numai în blocuri sincronizate . .Dacã sunt mai multe fire care asteaptã producerea unui eveniment cu metoda “wait”. t. // poate lipsi daca terminarea are loc la inchiderea ferestrei } }. .

i < 10. In exemplul urmãtor producãtorul “produce” numere întregi consecutive (la intervale de timp generate aleator) si le depune într-o variabilã (dintr-un obiect). public Consumer (Buffer c) { comun = c.Ordinea accesului la resursã nu este importantã. // folosire (consumare) date System.out.out.println("Producator " + " pune " + i).Ordinea în care firele acceseazã resursa este importantã. // producere de date System. } public void run() { for (int i = 0. } catch (InterruptedException e) { } contents = value.get(). full = true. try {sleep((int)(Math. Situatia transmiterii de date între douã procese paralele este schematizatã de obicei sub forma unui cuplu de procese numite “producãtor” si “consumator” (de mesaje). class Producer extends Thread { // proces producator private Buffer comun. i++) { int x= comun. i < 10. } } } // clasa pentru obiecte cu date comune class Buffer { private int contents.put(i). // indicator de stare buffer (plin/gol) // citeste continut variabila comuna public synchronized int get() { while ( ! full) try { wait().println("Consumator scoate " + x). dar în timp ce un fir foloseste resursa nu rebuie permis celuilalt fir accesul la resursã. 221 . return contents. Variabila “full” este necesarã pentru a arãta când poate fi modificat (full=false) si când poate fi folosit (full=true) continutul variabilei comune “contents”.Florian Moraru – Programare Orientata pe Obiecte . . notifyAll(). } catch (InterruptedException e) {} } } } class Consumer extends Thread { // proces consumator private Buffer comun. } catch (InterruptedException e) { } full = false. pentru cã ele îsi transmit date prin intermediul resursei (fire de tip producãtor-consumator sau emitãtor-receptor). care blocheazã si deblocheazã accesul altor procese la variabila comunã pe durata acestor operatii.random() * 100)). iar consumatorul preia aceste numere din variabila comunã si le afiseazã pentru a ne permite sã verificãm dacã toate numerele transmise au ajuns fãrã pierderi sau repetãri si în ordinea corectã la consumator. } public void run() { for (int i = 0. } // scrie o valoare in variabila comuna public synchronized void put(int value) { while ( full) try { wait(). public Producer (Buffer c) { comun = c. notifyAll(). // continut variabila comuna private boolean full = false. Sectiunile critice sunt metodele “get” si “put” din clasa “Buffer”. i++) { comun.

// si afisare pe ecran } catch (IOException e) {} } } // Program de test class Pipes { public static void main ( String args[])throws IOException { PipedWriter wr = new PipedWriter(). Clasele PipedWriter (PipedOutputStream) si PipedReader (PipedInputStream) din pachetul “java. try { Thread. // canal de scriere PipedReader rd = new PipedReader(wr). t2. public Consumer(PipedReader b) { buffer = b. pentru a permite altor fire sã modifice starea obiectului.} catch (Exception e) { } } } } // Consumator class Consumer implements Runnable { private PipedReader buffer. Operatiile de acces la date sunt sincronizate iar gestiunea zonei comune de date (o coadã) nu cade în sarcina programatorului.print(c +" ").sleep((int)(100*Math. c<='z'. } } Incepând cu Java 5 s-au introdus câteva interfete si clase pentru cozi sincronizate.io” permit comunicarea simplã de date între procese de tip producãtor si consumator printr-un “flux” de date. } public void run() { for (char c='0'.Florian Moraru – Programare Orientata pe Obiecte } } Metodele “wait”.write(c). în caz contrar apare o exceptie cauzatã de efectul acestor metode: “wait” elibereazã monitorul asociat obiectului respectiv.random())). } public void run() { char c.sleep((int)(100*Math. t1. // Producator class Producer implements Runnable { private PipedWriter buffer. De obicei comunicarea între procese (fire) se face printr-o coadã (un “buffer”). Thread t2 = new Thread ( new Consumer (rd)). “notify” si “notifyAll” pot fi apelate numai în metode sincronizate. public Producer(PipedWriter b) { buffer = b.start(). Cuplarea canalului de scriere (PipedWriter) cu canalul de citire (PipedReader) se poate face în douã feluri: la construirea obiectului PipedReader se transmite ca parametru o variabilã de tip PipedWriter. c++) { try {buffer. sau se foloseste metoda “connect”. // conectare citire cu scriere // sau rd= new PipedReader() si rd.out. dar ideea este cã operatiile de introducere în buffer si de extragere din buffer trebuie “sincronizate” pentru executarea lor strict secventialã în procese diferite. care permit comunicarea mai simplã între procese de tip producãtor-consumator: interfata BlockingQueue si clasele ArrayBlockingQueue si LinkedBlockingQueue.connect(wr) Thread t1 = new Thread ( new Producer (wr)).} catch (Exception e) { } try { while ( (c= (char)buffer.read()) > 0) // citire caracter din flux System. Exemplu de utilizare: 222 .} catch(IOException e){} // pune caracter in flux try { Thread.start().random())).

Thread. public Consumer(BlockingQueue q) { queue = q. Thread c = new Thread (new Consumer(q)). } catch (InterruptedException ex) { } } Object produce() { try { Thread. } } catch (InterruptedException ex) { } } void consume(Object x) { System.println(x+ " "+ Thread.out.sleep(10). p. Thread p = new Thread(new Producer(q)). } catch (InterruptedException e) { } } } } Cu programele anterioare se poate experimenta. } catch (InterruptedException e) { } System. c.out. try { Thread.isAlive() && c. // afisare continut coada try { Thread.i<9.put(produce()). while ( p.start(). } catch (InterruptedException e) { } } } class Setup { public static void main(String a[]) { BlockingQueue q = new ArrayBlockingQueue(100).out. Thread.concurrent.util. x++.getName()).currentThread().currentThread().currentThread(). } public void run() { try { while (true) { consume(x=(Integer)queue. private int x. x=0.sleep(100).sleep(150). return x. modificând timpii folositi în metoda “sleep” 223 . public Producer(BlockingQueue q) { queue = q.*.i++) queue.println(q).Florian Moraru – Programare Orientata pe Obiecte import java.yield().println( Thread. } } class Consumer implements Runnable { private final BlockingQueue queue.yield().currentThread(). private int x.start(). } public void run() { try { for (int i=0.getName()).take()). class Producer implements Runnable { private final BlockingQueue queue.isAlive()){ System.

Astfel de activitãti trebuie delegate altor fire de executie. Firul în care se trateazã evenimentul de la butonul de oprire cere terminarea firului creat si lansat de butonul “Start”. operatie care poate dura foarte mult pentru cã poate implica si citirea continutului unui numãr mare de fisiere în care se cautã un sir dat. stop = new JButton("Stop"). care poate fi observatã în toate exemplele Swing din Java Tutorial: obiectul GUI (dintr-o clasã derivatã din JFrame) nu este creat direct în “main” ci într-un alt fir de executie creat de “main”. Fire de executie în aplicatii Swing Aplicatiile Swing ridicã douã probleme din punct de vedere al firelor de executie: . text = new JTextField(10).addActionListener(this). // pentru oprire cautare private JTextArea area."North"). add(new JScrollPane(area). stop. } }). show(). . prin intermediul metodei “invokeLater”: public static void main (String args[]) { SwingUtilities. add (text.EXIT_ON_CLOSE). } Efectul metodei “invokeLater” este crearea unui obiect Thread si plasarea lui într-o coadã astfel ca firele pentru mai multe interfete grafice sã se execute succesiv si într-un fir separat de firul “main”. adicã ele trebuie tratate numai în ordinea în care s-au produs aceste evenimente. "Center")."South"). Operatorul are posibilitatea sã termine fortat procesul de cãutare prin actionarea unui buton “Stop”.50). Pentru a ilustra problema “înghetãrii” interfetei grafice vom considera cazul operatiei “Search” din Windows Explorer: dupã alegerea unor parametri de cãutare se lanseazã operatia de cãutare (printr-un buton “Search”). pack(). setDefaultCloseOperation (JFrame. // sir continut de fisierele cautate private JButton stop. // variabila ptr comunicare intre activitati // initializari public Search () { area = new JTextArea (30.Interfata graficã ar putea “îngheta” (nu mai rãspunde la gesturile operatorului) dacã tratarea unui eveniment este o actiune de duratã si se petrece tot în firul de executie cu tratarea evenimentelor (cu metode de tip “actionPerformed”). Pentru ca butonul “Stop” sã poatã fi actionat si sã producã terminarea cãutãrii este necesar ca actiunea de cãutare sã se producã într-un alt fir de executie decât firul evenimentelor Swing (“EventThread”). iar tratarea evenimentelor Swing trebuie sã se facã într-un acelasi fir de executie. 224 . Pentru prima problemã existã o solutie simplã. // pentru afisare nume fisiere gasite private boolean end=false. iar operatia de cãutare nu poate fi opritã doarece butonul de Stop nu poate fi actionat: class Search extends JFrame implements ActionListener { // obiecte vizuale din GUI private JTextField text.Metodele claselor Swing nu sunt sincronizate (nu sunt asigurate la apeluri din fire de executie concurente). add(stop. text.addActionListener(this). Exemplul urmãtor foloseste un singur fir de executie (în care se trateazã evenimentele Swing).invokeLater (new Runnable() { public void run() { new MyFrame ().Florian Moraru – Programare Orientata pe Obiecte din procesele producãtor si consumator si observând continutul cozii de mesaje.

v). // fisier gasit else area.invokeLater(new Runnable() { public void run() { new Search(). // cautare recursiva in subdirectoare else if ( file. area.Vector v) { if (end) return. } } Solutia problemei este crearea si lansarea unui alt fir ca urmare a evenimentului de câmp text (când se aflã numele sirului cãutat.getSource().txt. // afisare simpla continut vector } } // functie de cautare void findAll (File dir. } }). Object source = ev.length.add(file.toString()). if (source==stop) // buton de stop end=true. eventual si numele unui director unde se cautã).Florian Moraru – Programare Orientata pe Obiecte } // ascultatori la evenimente public void actionPerformed (ActionEvent ev) { Vector v= new Vector(). File[] files=dir.append(v.append(". i < files. // fisier care nu satisface conditia } } public static void main (String args[]) { SwingUtilities. // camp text cu nume fisiere if (source==text) { // nume fisiere cautate findAll (new File("C:/"). \n"). if (file.v).isDirectory()) findAll (file. 225 .getName()+"\n"). for (int i = 0.getText().text.getName().listFiles().contains(txt)) v. i++) { File file = files[i].String txt.

Sign up to vote on this title
UsefulNot useful