You are on page 1of 225

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 fr 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 Cuvntul 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

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 comunicrii 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

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 productor-consumator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Fire de executie n aplicatii Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224

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 aprut, n plus, comentarii care ncep prin perechea de caractere "//" si se termin la sfrsitul liniei n care apare acel comentariu. Java preia aceste dou feluri de comentarii, la care se adaug comentarii destinate generrii 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 implementrile 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 oricrui alfabet. Toate tipurile aritmetice din Java reprezint numere cu semn si nu mai exist cuvntul unsigned pentru declararea de variabile aritmetice fr 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 urmtoare 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

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. Exemplu:
byte b=65; char ch; ch =(char)b; ch='\n'; b =(byte)ch; // ch este 'A' // b este 10

Aceleasi reguli de conversie ntre tipuri numerice se aplic si ntre argumentele efective si argumentele formale, deoarece compilatorul face automat o atribuire a valorii argumentului efectiv la argumentul formal corespunztor. Exemplu:
double r = Math.sqrt(2); // promovare de la int la double ptr. 2

O alt conversie automat, de promovare se face pentru rezultatul unei functii, dac tipul expresiei din instructiunea return difer de tipul declarat al functiei. Exemplu:
static float rest (int a) { return a; // 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. Exemple :
f= (float)d; // cu pierdere de precizie n=(int)f; // cu trunchiere int r = (int) Math.sqrt(4); // conversie necesara de la double la int // functie de rotunjire din clasa Math public static int round (float a) { return (int)floor(a + 0.5f); // "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, instructiuni if fr else ). Exemplu:
public static int indexOf (int a[], int b) { // pozitia lui b in vectorul a for (int i=0;i<a.length;i++) if (a[i]==b) return i; // return 1; // eroare de compilare ! }

In Java nu exist operatorul sizeof din C, pentru determinarea memoriei ocupate de un tip sau de o variabil, pentru c nu este necesar acest operator (nici chiar pentru alocare dinamic de memorie). Cea mai important diferent dintre Java, pe de o parte, si limbajele C, C++ pe de alt parte, este absenta tipurilor pointer din Java. Deci nu exist posibilitatea de a declara explicit variabile pointer si nici operatorii unari & (pentru obtinerea adresei unei variabile) si * (indirectare printr-un pointer). Operatorul new din C++ pentru alocare dinamic are n Java un rezultat o referint si nu un pointer. Supradefinirea functiilor Supradefinirea sau suprancrcarea 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

Florian Moraru Programare Orientata pe Obiecte diferite ntr-o aceeasi clas. Pot exista functii cu acelasi nume (eventual si cu acelasi argumente si tip) n clase diferite, dar acesta nu este un caz de supradefinire, fiindc ele se afl n spatii de nume diferite. In Java, ca si n C++, o functie este deosebit de alte functii din aceeasi clas (de ctre compilator) prin "semntura" sa (prin "amprenta" functiei), care este format din numele, tipul si argumentele functiei. 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, pentru fiecare tip de date primitiv si pentru tipurile clas String si Object :
public class PrintStream ...{ public void print (int i) { write (String.valueOf(i)); } public void print (float f) { write (String.valueOf(f)); } public void print (boolean b) { write (b ? true : false); } public void print (String s) { if (s== null) s= null; write (s); } // din pachetul java.io // scrie un ntreg // scrie un numr real // scrie un boolean // scrie un sir de caractere

Functia String.valueOf este si ea supradefinit pentru diferite argumente. Declaratii de variabile O declaratie de variabil poate s apar fie ntr-o functie, fie n afara functiilor, dar ntr-o clas; nu exist variabile externe claselor. Locul declaratiei este important; o variabil dintr-o functie este local acelei functii, iar o variabil declarat la nivel de clas este utilizabil de orice functie din clas (si chiar de functii din alte clase).
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; } // verifica daca mai sunt cuvinte public boolean hasMoreTokens () { return crt < str.length(); } // extrage urmatorul cuvant dintr-un sir public String nextToken () { int start; // inceput de cuvant (variabila locala) while ( crt < str.length() && str.charAt(crt)==' ') ++crt; // ignora spatii start= crt; // unde incepe acest cuvant crt= str.indexOf (' ',start); // unde se termina acest cuvant if ( crt < 0) // ultimul cuvant nu trebuie urmat de spatiu crt= str.length(); return str.substring (start,crt);

Florian Moraru Programare Orientata pe Obiecte


} }

In C toate declaratiile dintr-un bloc trebuie s precead prima instructiune executabil din acel bloc. In C++ si n Java o declaratie poate apare oriunde ntr-un bloc, ntre alte instructiuni sau declaratii. Domeniul de valabilitate al unei variabile ncepe n momentul declarrii si se termin la sfrsitul blocului ce contine declaratia. Instructiunea for constituie un caz special: variabila contor se declar de obicei n instructiunea for, iar valabilitatea acestei declaratii este limitat la instructiunile repetate prin instructiunea for . Exemplu:
public static boolean este ( int x[ ], int y) { int n=x.length; // lungime vector x for (int k=0; k<n; k++) if ( x[k]==y) break; return k==n ? false : true; // eroare: k nedeclarat ! }

In Java nu exist cuvntul cheie const iar constantele sunt declarate ca variabile cu atributele static si final. Exemplu:
public static final double PI = 3.14159265358979323846; // 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. In Java nu exist operatorul sizeof , pentru c lungimea variabilelor este cunoscut, iar la alocarea de memorie (cu new) nu trebuie specificat dimensiunea alocat. Compilatorul Java nu are preprocesor, deci nu exist directivele preprocesor att de mult folosite n C si n C++ : #define, #include, etc. Structura programelor Java O aplicatie Java contine cel putin o clas, care contine cel putin o metod cu numele "main" de tip void si cu atributele static si public. Metoda "main" trebuie s aib ca unic argument un vector de obiecte String. Ca si n C, executia unui program ncepe cu functia main, doar c main trebuie s fie inclus, ca metod static, ntr-o clas si trebuie s aib un argument vector de siruri. Exemplul urmtor este un program minimal, care afiseaz un text constant:
public class Main { public static void main (String arg[) { System.out.println (" Main started "); } }

In Java nu conteaz ordinea n care sunt scrise functiile (metodele) unei clase, 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). Orice functie apartine unei clase si nu se pot defini functii n afara claselor. In exemplele urmtoare se vor folosi numai clase care reunesc cteva metode statice, functii care pot fi executate fr a crea obiecte de tipul clasei respective. Exemplul urmtor este un fisier surs cu o singur clas, care contine dou metode, ambele publice si statice:

Florian Moraru Programare Orientata pe Obiecte


class Main { public static void main (String arg[ ]) { writeln ("Hello world !"); } public static void writeln (String txt) { System.out.println (txt); } }

// 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), dac este apelat dintr-o metod a unei alte clase. Exemplu:
public class Main { public static void main (String arg[ ]) { Util.writeln ("Hello world !"); } } public class Util { public static void writeln (String txt) { System.out.println (txt); } } // cu "main" incepe executia

// afiseaza un text pe ecran

O metod ne-static trebuie apelat pentru un anumit obiect, iar numele ei trebuie precedat de numele obiectului (si un punct). Metoda "println" este apelat pentru obiectul adresat de variabila "out", variabil public din clasa System. Un fisier surs Java poate contine mai multe clase, dar numai una din ele poate avea atributul public. Numele fisierului surs (de tip java) trebuie s coincid cu numele clasei publice pe care o contine. O clas public este accesibil si unor clase din alte pachete de clase. Compilatorul Java creeaz pentru fiecare clas din fisierul surs cte un fisier cu extensia class si cu numele clasei. Dac este necesar, se compileaz si alte fisiere surs cu clase folosite de fisierul transmis spre compilare. Faza de executie a unui program Java const din ncrcarea si interpretarea tuturor claselor necesare executiei metodei main din clasa specificat n comanda java. Tipuri clas si tipuri referint O clas este o structur care poate contine att date ct si functii ca membri ai structurii. In Java nu mai exist cuvntul cheie struct , iar definirea unei clase foloseste cuvntul cheie class. Se pot defini clase ce contin numai date publice, echivalente structurilor din limbajele C si C++. Exemplu:
public class Point { // orice punct din plan public double x, y; // coordonate punct } // creare si utilizare obiect din clasa "Point" Point a = new Point(); // constructor implicit, generat automat a.x=2.; a.y =-3; // 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,y; // orice punct din plan // coordonate punct

Florian Moraru Programare Orientata pe Obiecte


public Point (double xi, double yi) { x=xi; y=yi; } } // functie constructor

Clasele (neabstracte) Java sunt de dou categorii: - Clase instantiabile, care pot genera obiecte, care contin date si metode (ne-statice). - Clase neinstantiabile , care contin doar metode statice (si eventual constante). O metod static corespunde unei functii din limbajul C, cu diferenta c numele functiei trebuie precedat de numele clasei din care face parte. Exemple:
double xabs = Math.abs(x); double y = Math.sqrt(x); int n = Integer.parseInt (str); // 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. Un obiect de tip T este o instantiere a clasei T si este referit printr-o variabil de tip T. Clasa Java cu numele String defineste un tip de date String, ce poate fi folosit n declararea de variabile, vectori sau functii de tip String. Exemple:
String msg = "Eroare"; String tcuv[] ={"unu","doi","trei"}; // o variabila sir // un vector de siruri

Un obiect Java corespunde unei variabile structur din C, iar o variabil de un tip clas corespunde unei variabile pointer la o structur din C. In Java toate obiectele sunt alocate dinamic, folosind operatorul new, iar variabila de tip clas trebuie initializat cu rezultatul operatorului new. Exemplu:
String mesaj; // String este o clas predefinit mesaj = new String ( Eroare ! ) ; // alocare memorie pentru sir

Pentru constantele de tip String se creeaz obiecte automat, de ctre compilator, ale cror adrese pot fi folosite n atribuiri sau initializri la declarare. Exemple:
System.out.println ("Eroare !"); String msg; msg = " Corect";

Clasa String contine mai multe metode (functii) publice, utilizabile n alte clase. De exemplu, metoda length, fr argumente, are ca rezultat (ntreg) lungimea sirului continut n obiectul de tip String pentru care se apeleaz metoda. Exemplu:
int len = mesaj.length();

Acest exemplu arat c membrii unei clase se folosesc la fel cu membrii unei structuri, indiferent c ei sunt variabile (cmpuri) sau functii (metode). Un alt exemplu este o constructie mult folosit n Java pentru afisarea la consol (n mod text) a unor siruri de caractere:
System.out.println (mesaj); System.out.println ( Eroare );

In aceste exemple System este numele unei clase predefinite, out este numele unei variabile publice (din clasa System) de un tip clas (PrintStream), iar println este numele unei metode din clasa PrintStream.

Florian Moraru Programare Orientata pe Obiecte Numele unei metode poate fi precedat de numele unei variabile clas sau de numele unei clase, dar ntotdeauna caracterul separator este un punct. Este uzual n Java s avem denumiri de variabile sau de metode care contin cteva puncte de separare a numelor folosite n precizarea contextului. Exemple:
if ( Character.isDigit ( str.charAt(0)) ) . . . // daca primul caracter e o cifra System.out.println (obj + obj.getClass().getName()); int maxdigits= (Integer.MAX_VALUE+"").length();

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. Indirectarea prin variabila referint este realizat automat de compilator, fr a folosi un operator special, ca n C . Tipul referint a fost introdus n C++ n principal pentru a declara parametri modificabili n functii, cu simplificarea scrierii si utilizrii acestor functii. In Java nu trebuie folosit o sintax special pentru declararea de variabile sau de parametri referint, deoarece toate variabilele de un tip clas sunt automat considerate ca variabile referint. Nu se pot defini referinte la tipuri primitive. O variabil referint Java nu este un obiect, dar contine adresa unui obiect alocat dinamic. O variabil referint apare de obicei n stnga unei atribuiri cu operatorul new sau cu constanta null n partea dreapt. Exemplu:
Vector a = new Vector( ); // a = variabila referinta la un obiect de tip Vector

Atunci cnd se apeleaz o metod pentru un obiect, se foloseste numele variabilei referint ca si cum acest nume ar reprezenta chiar obiectul respectiv si nu adresa sa. Exemplu:
System.out.println ( a.size() ); // afisare dimensiune vector a

Operatorul de concatenare '+', folosit ntre obiecte de tip String, poate crea impresia c variabilele de tip String contin chiar sirurile care se concateneaz si nu adresele lor. Exemple:
String s1="java.", s2="util.", s3="Rand"; System.out.println (s1+s2+s3); // scrie java.util.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 ). Exemplu:
int a=3, b=2 ; System.out.println ( a + + + b + = + (a+b)); // 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. Spatii ale numelor n Java Un spatiu de nume ("namespace") este un domeniu de valabilitate pentru un nume simbolic ales de programator. In cadrul unui spatiu nu pot exista dou sau mai multe nume identice (exceptie fac metodele supradefinite dintr-o aceeasi clas). Pot exista nume identice n spatii diferite. Fiecare clas creeaz un spatiu de nume pentru variabilele si metodele clasei; ca urmare numele metodelor sau datelor publice dintr-o clas trebuie precedate de numele clasei, atunci cnd se folosesc n alte clase. Exemple:
Main.writeln ("abc"); // clasa Main, metoda writeln

10

Florian Moraru Programare Orientata pe Obiecte


Math.sqrt(x); System.out // clasa Math, metoda sqrt // clasa System, variabila out

Clasele nrudite ca rol sau care se apeleaz ntre ele sunt grupate n "pachete" de clase ("package"). Instructiunea package se foloseste pentru a specifica numele pachetului din care vor face parte clasele definite n fisierul respectiv; ea trebuie s fie prima instructiune din fisierul surs Java. In lipsa unei instructiuni package se consider c este vorba de un pachet anonim implicit, situatia unor mici programe de test pentru depanarea unor clase. Un nume de pachet corespunde unui nume de director, cu fisierele de tip class corespunztoare claselor din pachet. Numele unui pachet poate avea mai multe componente, separate prin puncte. Numele de pachete cu clase predefinite, parte din JDK, ncep prin java sau javax. Exemple :
java.io , java.util.regex , java.awt, javax.swing.tree

Un pachet este un spatiu al numelor pentru numele claselor din acel pachet. In general numele unei clase (publice) trebuie precedat de numele pachetului din care face parte, atunci cnd este folosit ntr-un alt pachet. De observat c un fisier surs nu creeaz un spatiu de nume; este posibil si chiar uzual ca n componenta unui pachet s intre clase aflate n fisiere surs diferite, dar care au la nceput aceeasi instructiune "package". Exemplu:
// fisierul IOException.java package java.io; public class IOException extends Exception { . . . } // fisierul EOFException.java package java.io; public class EOFException extends IOException { . . . }

Pachetul cu numele "java.lang" ("language") este folosit de orice program Java si de aceea numele lui nu mai trebuie mentionat naintea numelui unei clase din java.lang. Clasele String,Integer,Object,System s.a. fac parte din pachetul java.lang. Exemplu care ilustreaz utilizarea unei clase dintr-un alt pachet dect java.lang :
public static void main (String arg[]) { java.util.Random rand =new java.util.Random(); for (int i=1;i<=10;i++) // scrie 10 numere aleatoare System.out.println ( rand.nextFloat()); }

Variabila cu numele "rand" este de tipul Random, iar clasa Random este definit n pachetul "java.util". Notatia "rand.nextFloat()" exprim apelul metodei "nextFloat" din clasa "Random" pentru obiectul adresat de variabila "rand". Instructiunea import permite simplificarea referirilor la clase din alte pachete si poate avea mai multe forme. Cea mai folosit form este:
import pachet.* ;

Instructiunea anterioar permite folosirea numelor tuturor claselor dintr-un pachet cu numele "pachet", fr a mai fi precedate de numele pachetului. Exemplul urmtor ilustreaz folosirea instructiunii "import":
import java.util.*; // sau import java.util.Random; class R { public static void main (String arg[]) {

11

Florian Moraru Programare Orientata pe Obiecte


Random rand =new Random(); for (int i=1;i<=10;i++) // scrie 10 numere aleatoare System.out.println ( rand.nextFloat()); } }

Uneori se prefer importul de clase individuale, att pentru documentare ct si pentru evitarea ambiguittilor create de clase cu acelasi nume din pachete diferite. Exemplu care arat riscurile importului tuturor claselor dintr-un pachet:
import java.util.*; import java.awt.*; class test { public static void main (String av[ ]) { List list; . . . // clasa java.awt.List sau interfata java.util.List ? } }

Definirea si utilizarea de vectori n Java Cuvntul vector este folosit aici ca echivalent pentru array din limba englez si se refer la un tip de date implicit limbajelor C, C++ si Java. 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. In Java, declararea unei variabile (sau unui parametru formal) de un tip vector se poate face n dou moduri, echivalente:
tip nume [ ]; tip [ ] nume; // la fel ca in C si C++ // specific Java

Declararea matricelor (vectori de vectori) poate avea si ea dou forme. Exemplu:


int a[ ][ ] ; int [ ][ ] b; // o matrice de ntregi // alt matrice de ntregi

In Java nu este permis specificarea unor dimensiuni la declararea unor vectori sau matrice, deoarece alocarea de memorie nu se face niciodat la compilare. Exemplu de eroare:
int a[100]; // corect: int a[] = new int[100];

O variabil vector este automat n Java o variabil referint iar memoria trebuie alocat dinamic pentru orice vector. 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; expresia determin numrul de componente (nu de octeti !) pe care le poate contine vectorul. Exemple:
float x[ ] = new float [10]; // aloca memorie ptr 10 reali int n=10; byte[ ][ ] graf = new byte [n][n];

Este posibil si o alocare automat, atunci cnd vectorul este initializat la declarare cu un sir de valori. Exemplu:
short prime[ ] = {1,2,3,5,7};

In lipsa unei initializri explicite, componentele unui vector sunt initializate automat, cu valori ce depind de tipul lor: zerouri pentru elemente numerice, null pentru variabile referint de orice tip.

12

Florian Moraru Programare Orientata pe Obiecte Un vector intrinsec cu componente de un anumit tip este considerat ca un obiect de un tip clas, tip recunoscut de compilator dar care nu este definit explicit n nici un pachet de clase. Numele acestor clase este format din caracterul [ urmat de o liter ce depinde de tipul componentelor vectorului: [I pentru int[], [B pentru byte[], [Z pentru boolean[], [C pentru char[], [F pentru float[] s.a.m.d. Variabila predefinit cu numele length poate fi folosit ( ca membru al claselor vector ) pentru a obtine dimensiunea alocat pentru un vector (capacitatea vectorului). Exemplu:
// functie de copiere a unui vector public static void copyVec ( int a [ ] ,int b[ ] ) { for (int i=0;i < a.length; i++) // a.length =dimensiune vector a b[i] = a[i]; }

De retinut c length este dimensiunea alocat si nu dimensiunea efectiv a unui vector, iar numrul de elemente din vector se transmite ca argument la functii, atunci cnd difer de capacitatea sa. In Java, se verific automat, la executie, ncadrarea indicilor ntre limitele declarate; iesirea din limite produce o exceptie si terminarea programului. Exemplu:
int [ ] a= new int [10]; for (int i=1;i<=10;i++) a[i]=i;

// exceptie la a[10]=10

Numerotarea componentelor unui vector este de la zero la (length-1), deci n exemplul anterior se produce exceptia de depsire a limitelor la valoarea i=10 . Variabila length nu trebuie confundat cu metoda "length()" din clasa String. Functia urmtoare determin sirul cel mai lung dintr-un vector de siruri:
static String maxLen ( String v[ ]) { String max =v[0]; for (int i=0;i<v.length;i++) if ( max.length() < v[i].length() ) // compara lungimile a doua siruri max=v[i]; // retine adresa sirului cel mai lung return max; }

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, atribuind succesiv lui ob valorile din vectorul a (valori ce pot fi de un tip primitiv sau de un tip clas). Functia care determin sirul de lungime maxim dintr-un vector de siruri se poate scrie astfel:
static String maxLen ( String v[ ]) { String max =0; // initializare maxim partial for (String s: v) // repeta pentru s cu valori in vectorul v if ( max.length() < s.length() ) // compara lungimile a doua siruri max=s; // retine in max sirul cel mai lung return max; }

O matrice este privit si n Java ca un vector de vectori, iar variabila "length" se poate folosi pentru fiecare linie din matrice, pentru a determina numrul de coloane.

13

Florian Moraru Programare Orientata pe Obiecte Deoarece orice matrice este alocat dinamic, nu exist probleme la transmiterea unei matrice ca argument la o functie. Nu este necesar transmiterea dimensiunilor matricei la o functie dac matricea este ocupat complet (la capacitatea ei). Exemplu:
class Matrice { public static void printmat (int a[ ][ ]) { for (int i=0;i<a.length;i++) { for (int j=0; j<a[i].length; j++) System.out.print (a[i][j]+ " "); System.out.println (); } } public static void main (String [ ] arg) { int mat[ ][ ]= { {1,2,3}, {4,5,6} }; printmat (mat); } } // functie de afisare matrice

// utilizare functie

O functie poate avea un rezultat de un tip vector (sau matrice). In Java, ca si n C, transmiterea parametrilor se face prin valoare, adic se copiaz valorile parametrilor efectivi n parametrii formali corespunztori, nainte de executia functiei. Deci o functie Java nu poate transmite rezultate prin argumente de un tip primitiv, dar poate modifica componentele unui vector primit ca argument. Exemplu:
// creare vector cu divizorii unui ntreg static int divizori (int n, int d[ ]) { int k=0; for (int i=1;i<n;i++) if ( n %i == 0) d[k++]=i; // pune divizorul i n vectorul d return k; // numar de divizori } // utilizare functie int div[ ]= new int [m]; // aloca memorie ptr vector divizori int nd = divizori (m, div); // completare vector divizori

Clasa Arrays din pachetul java.util reuneste functii pentru operatii uzuale cu vectori avnd elemente de orice tip (primitiv sau clas): afisare, sortare, cutare s.a. Exemplu:
import java.util.Arrays; class ExArrays { // utilizare functii din clasa Arrays public static void main ( String args[] ) { String t[ ] ={ "4","2","6","3","5","1" }; // un vector de siruri Arrays.sort (t); // ordonare vector t System.out.println ( Arrays.toString(t)); // afisare vector int k = Arrays.binarySearch (t, "3"); // cautare in vector ordonat float x[ ] = { 3.4, 2.8, 7.1, 5.6 }; // un vector de numere reale Arrays.sort (x); printVec(x); // ordonare vector x float y[ ] = (float[ ]) x.clone(); // y este o copie a vectorului x System.out.println ( Arrays.equals (x,y)); // scrie "true" } }

Din versiunea 5 s-au adugat clasei Arrays metode statice pentru transformarea n sir de caractere a continutului unui vector intrinsec unidimensional sau multidimensional cu elemente de orice tip

14

Florian Moraru Programare Orientata pe Obiecte primitiv sau de tip Object. Exemplu de folosire:
double [ ] a= {1.1, 2.2, 3.3, 4.4}; System.out.println ( Arrays.toString(a)); Double b[ ][ ]= { {0.0, 0.1, 0.2}, {1.0, 1.1, 1.2}, {2.0, 2.1, 2.2} }; System.out.println(Arrays.deepToString(b));

Exceptii program n Java O exceptie program este o situatie anormal aprut n executie si care poate avea cauze hardware sau software. Exceptiile pot fi privite ca evenimente previzibile, ce pot aprea n anumite puncte din program si care afecteaz continuarea programului, prin abatere de la cursul normal. Existenta exceptiilor este un mare avantaj al limbajului Java, pentru c permite semnalarea la executie a unor erori uzuale, prin mesaje clare asupra cauzei si locului unde s-a produs eroarea, evitnd efectele imprevizibile ale acestor erori (n C, de ex.). Exceptiile Java sunt de dou categorii: - Exceptii care nu necesit interventia programatorului (numite "Runtime Exceptions"), dar care pot fi interceptate si tratate de ctre programator. Dac nu sunt tratate, aceste exceptii produc afisarea unui mesaj referitor la tipul exceptiei si terminarea fortat a programului. Aceste exceptii corespund unor erori grave de programare, care nu permit continuarea executiei si care apar frecvent n programe, cum ar fi: erori de indexare a elementelor unui vector (indice n afara limitelor), utilizarea unei variabile referint ce contine null pentru referire la date sau la metode publice, mprtire prin zero, conversie prin "cast" ntre tipuri incompatibile, s.a. - Exceptii care trebuie fie tratate, fie "aruncate" mai departe, pentru c altfel compilatorul marcheaz cu eroare functia n care poate apare o astfel de eroare ("Checked Exceptions": exceptii a cror tratare este verificat de compilator).Aceste exceptii corespund unor situatii speciale care apar la utilizarea unui program (fisiere negasite, erori la operatii de citire-scriere, date incorecte), dar nu produc neaprat terminarea programului. Urmtorul program poate produce (cel putin) dou exceptii, dac este folosit gresit:
class Exc { public static void main (String arg[ ]) { System.out.println ( Integer.parseInt(arg[0])); } }

// afiseaza primul argument

O comand pentru executia programului de forma "java Exc" produce exceptia ArrayIndexOutOfBoundException, deoarece nu s-au transmis argumente prin linia de comand, vectorul "arg" are lungime zero si deci nu exist arg[0] (indice zero). O linie de comand de forma "java Exc 1,2" (argumentul arg[0] nu este un sir corect pentru un numr ntreg) produce o exceptie NumberFormatException, exceptie generat n functia "parseInt". Ambele exceptii mentionate erau exceptii "Runtime". Exceptiile generate de operatiile de intrare-iesire (inclusiv la consol) trebuie fie aruncate, fie tratate pentru c suntem obligati de ctre compilatorul Java. Compilatorul stie care metode pot genera exceptii si cere ca functiile care apeleaz aceste metode s arunce mai departe sau s trateze exceptiile posibile. Exemplu:
public static void main (String arg[ ]) throws Exception { int ch= System.in.read ( ); // citeste un caracter de la tastatura System.out.println ((char) ch); // afisare caracter citit }

15

Florian Moraru Programare Orientata pe Obiecte Absenta clauzei throws din functia "main" este semnalat ca eroare de compilator, pentru a obliga programatorul s ia n considerare exceptia ce poate apare la functia de citire "read", datorit citirii caracterului EOF (sfrsit de fisier) sau unei erori la citire. O metod care apeleaz o metod ce arunc exceptii verificate trebuie fie s semnaleze mai departe posibilitatea aparitiei acestei exceptii (prin clauza throws), fie s trateze exceptia printr-un bloc trycatch care s includ apelul metodei. Tratarea exceptiilor necesit folosirea unui bloc try-catch pentru delimitarea sectiunii de cod pentru care exceptiile posibile sunt redirectate ctre secvente scrise de utilizator pentru tratarea exceptiilor produse. Exemplu de verificare a unui sir dac reprezint un numr corect sau nu, prin tratarea exceptiei:
public static boolean isNumber (String s) { try { Float.parseFloat (s); } catch (NumberFormatException ex) { return false; // daca s-a produs exceptie } return true; // daca nu s-a produs exceptie }

Este preferabil aruncarea unei exceptii fat de tratarea prin ignorarea exceptiei, care mpiedic aparitia unui mesaj de avertizare la producerea exceptiei. Exemplu:
public static void main (String arg[]) { Object ref=null; try {int h = ref.hashCode(); } 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. Aruncarea exceptiei nu este posibil n definirea unor functii din interfete sau mostenite de la alte clase (pentru c ar modifica antetul functiei). In aceste cazuri vom face o tratare minimal a exceptiilor (afisarea cauzei cu metoda printStackTrace prezent n orice clas exceptie), cu acelasi efect la executie ca al exceptiilor Runtime:
int ch; try { while ( (ch = System.in.read()) >=0) // citeste un octet de la consola System.out.println ((char)ch); // afisare in ecou ca un caracter } catch(IOException e) {e.printStackTrace();}

Variabilele folosite n blocul try-catch vor fi declarate n afara acestui bloc, pentru a fi accesibile si n afara blocului. Exist si situatii cnd exceptia trebuie tratat si altfel dect prin afisarea unui mesaj; exemplul urmtor arat cum putem verifica dac un sir de caractere reprezint un numr corect (n orice format permis pentru numere nentregi), fr a examina fiecare caracter n parte:
public static boolean isNumber (String s){ try { Double.parseDouble(s); } catch(NumberFormatException ex) {return false;} return true; }

16

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, n care pot apare exceptii. Un bloc try se termin cu una sau mai multe clauze catch pentru diferite tipuri de exceptii care pot apare n bloc. In exemplul urmtor se foloseste un singur bloc try pentru diferite exceptii posibile:
try { f= new RandomAccessFile(arg[0],"r"); // deschide fisier pentru citire while ( true) System.out.println ( f.readInt()); // citeste si scrie un numar } catch (IOException e) { e.printStackTrace(); }

Varianta urmtoare foloseste dou blocuri try pentru a trata separat exceptiile:
try { f= new RandomAccessFile(arg[0],"r"); // deschide fisier pentru citire } catch (IOException e) { // exceptie de fisier negasit System.out.println("Eroare la citire fisier"); } try { while ( true) System.out.println ( f.readInt()); // citeste si scrie un numar } catch (IOException e) { } // exceptie de sfarsit de fisier la citire

Varianta urmtoare foloseste un singur bloc try, dar separ fiecare tip de exceptie:
try { f= new RandomAccessFile("numer","r"); while ( true) System.out.println ( f.readInt()); } catch (FileNotFoundException e) { System.out.println ("Fisier negasit"); } catch (EOFException e) { } catch (IOException e) { System.out.println("Eroare la citire fisier"); }

Este posibil si aruncarea unor exceptii putin probabile (exceptia de citire, de ex.) combinat cu tratarea altor exceptii (de exemplu exceptia de sfrsit de fisier). Producerea unor exceptii poate fi prevenit prin verificri efectuate de programator, ca n exemplele urmtoare:
void fun (Object obj) { if (obj==null) { System.out.println ("Null Reference"); System.exit(-1); } ... } public static void main (String arg[ ]) { if (arg.length == 0 ) { System.out.println ("No Argument"); System.exit(-1); } System.out.println ( arg[0]); // afiseaza primul argument }

Uneori este preferabil verificarea prin program (ca n cazul unor conversii de tip nepermise), dar alteori este preferabil tratarea exceptiei (ca n cazul detectrii existentei unui fisier nainte de a fi deschis, sau a utilizrii unor variabile referint ce pot fi nule).

17

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, Groovy, JRuby, Scala) care au n comun acelasi cod intermediar, aceeasi masin virtual Java si aceleasi biblioteci de clase (extinse si cu alte clase). Codul intermediar generat de compilatoarele Java (numit bytecode) este interpretat sau executat ntr-o masin virtual Java (JVM). Masina virtual mpreun cu bibliotecile standard de clase Java formeaz mediul de executie Java (JRE=Java Runtime Environment). 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). Acest mod de lucru face posibil utilizarea de clase cu nume cunoscut doar la executie sau nlocuirea unor clase, fr interventie n codul surs (cu conditia ca aceste clase s respecte anumite interfete). Impreun cu codul clasei (metodele clasei n format compilat) se mai ncarc si informatii despre clas, numite si metadate: tipul clasei (sau interfetei), numele superclasei, numele variabilelor din clas, numele si tipul metodelor clasei, formele de constructori pentru obiectele clasei s.a. Aceste metadate permit obtinerea de informatii despre clase la executie, instantierea de clase cunoscute numai la executie, apeluri de metode determinate la executie si alte operatii imposibile pentru un limbaj compilat. Mai nou, se pot aduga metadate (annotations=adnotri), utilizabile de ctre diverse programe nainte de executie sau n cursul executiei. In plus, utilizatorii au posibilitatea de a modifica anumite aspecte ale comportrii masinii virtuale Java. Semnalarea erorilor prin exceptii si informatiile furnizate despre exceptii se datoreaz tot executiei programelor Java sub controlul masinii virtuale. Codul intermediar Java este asistat (supravegheat) la executie de ctre masina virtual, ceea ce a dat nastere expresiei de cod controlat (managed code). Aceste facilitti, alturi de independenta fat de procesorul pe care se execut, explic de ce limbajele orientate pe obiecte mai noi (Java, C#, Ruby s.a.) sunt interpretate.

18

Florian Moraru Programare Orientata pe Obiecte 2. Introducere n programarea orientat pe obiecte Clase si obiecte Programarea cu clase si obiecte reprezint un alt mod de abordare a programrii dect programarea procedural (n limbaje ca C sau Pascal), cu avantaje n dezvoltarea programelor mari. Un program procedural (scris n C de exemplu) este o colectie de functii, iar datele prelucrate se transmit ntre functii prin argumente (sau prin variabile externe). In programarea procedural sarcina programatorului este de a specifica actiuni de prelucrare, sub form de proceduri (functii). Exemplul urmtor este o functie C de copiere a unui fisier, octet cu octet:
// copiere fisier in C void filecopy ( char * src, char * dst) { char ch; FILE * in =fopen(src,"r"); // deschide fisier sursa FILE * out =fopen(dst,"w"); // deschide fisier destinatie while ( (ch=fgetc (in)) != -1) // citeste un caracter fputc (ch, out); // scrie un caracter fclose(out); fclose(in); }

Actiunile necesare copierii se realizeaz prin functiile fopen, fgetc, fputc, fclose care primesc ca date variabilele in, out si ch. Un program orientat pe obiecte este o colectie de obiecte care interactioneaz prin apeluri de functii specifice fiecrui tip de obiect. 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. Exemplul urmtor este o functie Java de copiere octeti dintr-un fisier sursa src intr-un fisier destinatie dst:
public static void filecopy (String src, String dst) throws IOException { FileReader in = new FileReader (src); // un obiect FileWriter out = new FileWriter (dst); // alt obiect int ch; while ( (ch= in.read()) != -1) // cere obiectului in operatia read out.write(ch); // cere obiectului out operatia write in.close(); out.close(); // 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); prin metoda read se cere obiectului in s citeasc si s furnizeze un caracter, iar prin metoda write se cere obiectului out s scrie n fisier caracterul primit ca argument. Pentru obiectele in si out se pot apela si alte metode, din clasele respective. Obiectele in si out contin informatii despre fisierele src si dst necesare prelucrrii lor, cam aceleasi care se memoreaz ntr-o structur de tip FILE n limbajul C. La crearea obiectelor, folosind operatorul new se si deschid cele dou fisiere (actiunea functiei fopen din C). Definitia unei clase poate fi privit ca o extindere a definirii unui tip structur din C, care contine si functii pentru operatii cu datelor continute n clas. Aceste functii se numesc si metode ale clasei. In Java si n C++ functiile (metodele) sunt subordonate claselor; o metod poate fi aplicat numai obiectelor din clasa care contine si metoda. Un obiect corespunde unei variabile structur din C. In C++ o variabil de un tip clas este un nume pentru un obiect, dar n Java o variabil de un tip clas contine un pointer ctre un obiect (corespunde unei variabile referint din C++). De aceea, mai corect este exprimarea se apeleaz

19

Florian Moraru Programare Orientata pe Obiecte metoda read pentru obiectul adresat prin variabila in. Este posibil ca mai multe variabile de un tip clas s contin adresa aceluiasi obiect. Obiectele Java sunt create dinamic, folosind de obicei operatorul new, care are ca rezultat o referint la obiectul alocat si initializat printr-o functie constructor, apelat implicit de operatorul new. Eliberarea memoriei se face automat si nu cade n sarcina programatorului Java. O clas corespunde unei notiuni abstracte cum ar fi orice fisier disc, iar un obiect este un caz concret (o realizare a conceptului sau o instantiere a clasei). Un obiect de tip FileReader corespunde unui anumit fisier, cu nume dat la construirea obiectului. In general, obiecte diferite contin date diferite, dar toate obiectele suport aceleasi operatii, realizate prin metodele clasei din care fac parte. Relativ la exemplul Java, trebuie spus c utilizarea unei functii statice de copiere nu este n spiritul POO. Functiile statice Java corespund functiilor C si pot fi folosite fr ca s existe obiecte (ele fac parte totusi din anumite clase). Mai aproape de stilul propriu POO, ar trebui definit o clas copiator de fisiere, avnd si o metod (nestatic) de copiere, cu sau fr argumente. Exemplu:
public class FileCopier { // datele clasei private FileReader in; // sursa datelor private FileWriter out; // destinatia datelor // constructor public FileCopier (String src, String dst) throws IOException { in = new FileReader (src); out = new FileWriter (dst); } // o metoda de copiere public void copy () throws IOException { int c; while ( (c= in.read()) != -1) out.write(c); in.close(); out.close(); } } // clasa pentru verificarea clasei FileCopier class UseFileCopier { public static void main (String arg[]) throws IOException { FileCopier fc = new FileCopier (arg[0], arg[1]); // creare obiect fc fc.copy(); // cere obiectului fc operatia copy } }

Clasa FileCopier, definit anterior, trebuie privit doar ca un prim exemplu, ce trebuia s fie foarte simplu, si nu ca un model de clas real Java. 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. Numele de clase sunt substantive, uneori derivate din verbul ce defineste principala actiune asociat obiectelor respective. In Java exist obiecte comparator (de un subtip al tipului Comparator) folosite n compararea altor obiecte, clasa Enumerator folosit la enumerarea elementelor unei colectii, clasa StringTokenizer folosit la extragerea cuvintelor dintr-un sir s.a. Clasele sunt module de program reutilizabile Definirea si utilizarea de module functionale permite stpnirea complexittii programelor mari si reutilizarea de module prin crearea de biblioteci.

20

Florian Moraru Programare Orientata pe Obiecte In limbajul C un modul de program este o functie, dar n Java si C++ un modul este o clas, care reuneste n general mai multe functii n jurul unor date. Utilizarea de clase ca module componente ale programelor are o serie de avanaje fat de utilizarea de functii independente: - Metodele unei clase necesit mai putine argumente, iar aceste argumente nu sunt modificate n functie; efectul unei metode este fie de a face accesibile date din clas, fie de a modifica variabile din clas pe baza argumentelor primite. Variabilele unei clase sunt implicit accesibile metodelor clasei si nu mai trebuie transmise explicit, prin argumente (ca niste variabile externe metodelor, dar interne clasei). - Solutii mai simple pentru functii al cror efect depinde de stare (de context), cum ar fi de apeluri anterioare ale aceleeasi functii sau ale altor functii pregtitoare. - Sunt posibile verificri de utilizare corect a unor metode sau asupra succesiunii de apelare a unor functii; 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. - O clas poate ncapsula algoritmi de complexitate ridicat, realizati prin colaborarea mai multor functii, unele interne clasei; astfel de algoritmi fie nu sunt disponibili n C, fie sunt disponibili prin biblioteci de functii destul de greu de utilizat. Exemple sunt algoritmi pentru lucrul cu expresii regulate, pentru arhivare-dezarhivare, pentru operatii cu anumite structuri de date (arbori binari cu auto-echilibrare), s.a. - Se poate realiza un cuplaj mai slab ntre module, n sensul c modificarea anumitor module nu va afecta restul programului. Aceast decuplare sau separare ntre module se poate realiza prin mai multe metode, printre care folosirea de interfete Java, n spatele crora pot sta clase cu implementri diferite dar cu acelasi mod de utilizare. Existenta unui numr mare de clase predefinite n Java reduce mult timpul de dezvoltare a unor noi aplicatii, prin reutilizarea acestor clase (care trebuie cunoscute, ca posibilitti oferite). Pentru concretizare vom prezenta solutiile C si Java pentru cteva probleme. Primul exemplu se refer la utilizarea structurii de tip stiv (stack) n aplicatii. O stiv poate fi realizat fie printr-un vector, fie printr-o list nlntuit, dar operatiile cu stiva sunt aceleasi, indiferent de implementare: pune date pe stiv, scoate datele din vrful stivei si test de stiv goal. In limbajul C se pot defini functiile pentru operatii cu stiva astfel ca utilizarea lor s nu depind de implementarea stivei, prin folosirea unui pointer la o structur:
void initSt ( Stiva * sp); // initializare stiva int emptySt (Stiva * s); // test stiva goala int push (Stiva * sp, T x); // pune in stiva un element de tip T T pop (Stiva * sp ); // scoate din stiva un element de tip T

Definitia tipului Stiva si definitiile functiilor depind de implementare. Un exemplu de utilizare a unei stive:
#include "stiva.h" #define T int void main () { int x; Stiva s ; initSt (&s); // initializare stiva for (x=1; x<10; x++) // genereaza date ptr continut stiva push (&s,x); // pune x pe stiva while ( ! emptySt (&s) ) // cat timp stiva contine ceva printf("%d \n", pop (&s) ) ; // scoate din stiva si afiseaza }

Modificarea tipului de stiv necesit un alt fisier stiva.h si o alt bibliotec de functii (push, pop), care s fie folosit mpreun cu programul de utilizare a stivei. In Java acelasi program de exersare a operatiilor cu stiva arat astfel:

21

Florian Moraru Programare Orientata pe Obiecte


public static void main (String arg[ ]) { Stack s = new Stack(); // creare obiect stiva for (int x=1; x<10; x++) // pune 10 numere in stiva s.push ( new Integer(x)); // s.push(x) in Java 5 while ( ! s.empty()) // cat timp stiva nu e goala System.out.println ( s.pop()); // afiseaza obiectul scos din stiva }

Modificarea implementrii stivei, prin definirea unei alte clase Stack nu necesit modificri n functia anterioar, ci doar punerea noii clase n cile de cutare ale compilatorului si interpretorului Java. In plus, modificarea tipului datelor puse n stiv necesit modificri mai mici n Java dect n C. Un al doilea exemplu este cel al extragerii de cuvinte succesive dintr-un sir de caractere ce poate contine mai multe cuvinte, separate prin anumite caractere date. Problema este aceea c dup fiecare cuvnt extras se modific adresa curent n sirul analizat, deci starea sau contextul n care se execut functia ce d urmtorul cuvnt. In limbajul C se pot ntlni mai multe solutii ale acestei probleme n diferite functii de bibliotec: Functia strtok se foloseste relativ simplu, dar pretul pltit 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). In plus, primul apel difer de urmtoarele apeluri ale functiei. Exemplu:
cuv=strtok(str, sep); while (cuv !=NULL) { puts(cuv); cuv=strtok(0,sep); } // primul cuvnt din str, sep= sir de separatori // daca s-a gasit un cuvant // afisare cuvant // urmatorul cuvant din str

Functia strtod extrage urmtorul numr dintr-un sir si furnizeaz adresa imediat urmtoare numrului extras. Exemplu de utilizare:
char * p = str; double d; // str = sir analizat do { d= strtod (p,&p); // cauta de la adresa p si pune tot in p adresa urmatoare printf ("%lf\n", d); } while (d != 0); // d=0 cand nu mi exista un numar corect

In Java exist clasa de bibliotec StringTokenizer, folosit dup cum urmeaz:


String sep = new String (" ,.;\n\t"); // lista separatori de cuvinte StringTokenizer st = new StringTokenizer (sir,delim); // sir = sir analizat while (st.hasMoreTokens()) { // daca mai sunt cuvinte in sirul analizat String token = st.nextToken(); // extrage urmatorul cuvint din linie System.out.println (token); // afisare cuvint }

La crearea unui obiect StringTokenizer se specific sirul analizat, astfel c se pot analiza n paralel mai multe siruri, pentru fiecare folosind un alt obiect. Metodele nextToken si hasMoreTokens folosesc n comun o variabil a clasei care contine pozitia curent n sirul analizat (initializat cu adresa sirului, la construirea obiectului). In prelucrarea fisierelor apar situatii cnd executia cu succes a unei functii depinde de folosirea anterioar a altor functii (cu anumite argumente); de exemplu pentru a putea scrie ntr-un fisier, acesta trebuie anterior deschis pentru creare sau pentru adugare (extindere fisier existent). O situatie asemntoare apare la utilizarea unor functii care compun o interfat grafic si care trebuie folosite

22

Florian Moraru Programare Orientata pe Obiecte ntr-o anumit ordine. Astfel de conditionri reciproce nu se pot verifica automat n C, fiind vorba de functii independente. In Java operatiile sunt metode dintr-o aceeasi clas si se poate verifica printro variabil a clasei succesiunea corect de folosire a metodelor. Functionalitatea unei clase poate fi reutilizat n alte clase fie prin derivare, fie prin agregare (compunere). In acest fel, operatiile necesare ntr-o clas sunt fie mostenite de la o alt clas, fie delegate spre executie metodelor unei alte clase. De exemplu, extinderea automat a unui vector, necesar uneori dup adugarea la vector, este refolosit si ntr-o clas stiv vector, fie prin definirea clasei stiv ca o clas derivat din vector, fie prin folosirea unei variabile Vector n clasa stiv. In POO adaptarea unei clase la cerinte specifice unor aplicatii nu se face prin interventie n codul clasei ci prin derivare sau prin delegare, tehnici specifice POO. O interfat contine una sau mai multe operatii (metode abstracte) cu rol bine definit, dar a cror implementare nu poate fi precizat. Cel mai simplu exemplu din Java este interfata Comparator, cu o singur metod compare, pentru compararea a dou obiecte (dup modelul comparatiei de siruri din C). Pentru fiecare tip de obiecte (comparabile) va exista o alt definitie a functiei compare. O interfat cu o singur metod corespunde unui pointer la o functie din limbajul C, dar interfetele cu mai multe metode creeaz posibilitti inexistente n C. O interfat poate fi implementat de mai multe clase, toate cu acelasi rol dar cu mod de lucru diferit. Interfata Collection defineste cteva operatii ce trebuie s existe n orice colectie de obiecte, indiferent de structura colectiei: adugare obiect la colectie s.a. O clas creeaz un spatiu de nume pentru metodele clasei: pot exista metode cu acelasi nume (si aceleasi argumente) n clase diferite. Pachetul de clase (package) este o alt unitate Java care creeaz un spatiu de nume pentru clasele continute n el. O notiune proprie programrii cu obiecte este notiunea de component software. Ideea este de a obtine rapid un prototip al aplicatiei fr a scrie cod sau cu un minim de programare, prin asamblarea de componente prefabricate (n special pentru interfata grafic, dar nu numai). O component poate contine una sau mai multe clase si poate fi reutilizat si adaptat fr interventie n codul surs al componentei (care nici nu este disponibil). O component JavaBeans poate fi (re)utilizat fr a scrie cod, prin generarea automat a operatiilor de instantiere, de modificare a propriettilor si de conectare cu alte clase (prin apeluri de metode sau prin evenimente), n urma unor comenzi date de utilizator unui mediu vizual de dezvoltare a aplicatiilor. O component este de obicei o clas care respect anumite conditii. Clasele creeaz noi tipuri de date In limbajul C, prin definirea de tipuri structur, se pot defini noi tipuri de date ca grupuri de variabile de alte tipuri predefinite. De exemplu, vom defini o structur cu dou variabile (parte real, parte imaginar) pentru un numr complex:
typedef struct { float re; float im; } Complex ;

In Java, introducerea unui tip Complex se va face prin definirea unei clase, care va specifica si operatiile permise cu date de acest tip, precum si modul de construire a obiectelor de tip Complex (de initializare a prtii reale si prtii imaginare). Multe din clasele de bibliotec Java pot fi privite ca avnd drept scop extinderea limbajului cu noi tipuri de date, utile n mai multe aplicatii. Exemple: BigInteger, BigDecimal, String, Date etc. Pentru fiecare tip primitiv de date exist un tip clas asociat: Integer, Short, Long, Float, Double, Boolean, Character. Cel mai folosit tip de date definit printr-o clas este tipul String; un obiect de tip String contine ca date un vector de caractere si lungimea sa si suport un numr mare de metode ce corespund unor operatii uzuale cu siruri (realizate prin functii de bibliotec n limbajul C). Exemple de metode des folosite din clasa String:

23

Florian Moraru Programare Orientata pe Obiecte


int length (); // lungimea acestui sir (pentru care se apeleaz metoda) boolean equals (String s); // comparatie la egalitate de siruri int compareTo (String s); // comparatie de siruri cu acelasi rezultat ca strcmp (C) char charAt (int i); // caracterul din pozitia i a acestui sir int indexOf (char c); // indicele (pozitia) caracterului c in acest sir int indexOf (String s); // indicele (pozitia) sirului s in acest sir (prima aparitie) String substring(int i); // extrage din acest sir subsirul care incepe in pozitia i String substring(int i, int j); // 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. Vom citi un apel de metod de forma a.compareTo(b) astfel: compar acest obiect (a) cu obiectul b. Noile tipuri de date se pot folosi n declaratii de variabile, de functii, de argumente de functii. Exemplu de functie static care foloseste metodele indexOf, length si substring din clasa String
// inlocuire repetata in sirul s a subsirului s1 prin sirul s2 public static String replaceAll (String s, String s1, String s2) { int p = s.indexOf(s1); // p = pozitia primei aparitiii a lui s1 in s while ( p >=0 ) { // indexOf are rezultat 1 daca s nu contine pe s1 s = s.substring(0,p)+ s2 + s.substring(p+s1.length()); // concatenare subsiruri p=s.indexOf(s1, p+s2.length()); // urmatoarea aparitie a lui s1 in s } return s; // are ca rezultat sirul modificat prin inlocuire }

Utilizatorii si pot defini propriile clase, pentru tipuri de date necesare aplicatiilor; de exemplu, putem defini o clas BoolMatrix pentru o matrice cu elemente de tip boolean. In Java orice clas este automat derivat dintr-o clas generic Object si, ca urmare, trebuie s redefineasc anumite metode mostenite: toString, equals s.a. Exemplu de clas minimal pentru o matrice de biti:
// matrice cu elemente de tip boolean public class BoolMatrix { private boolean a[ ][ ]; // o matrice patratica private int n; // nr de linii si coloane // constructor de obiecte public BoolMatrix (int n) { this.n=n; a= new boolean[n][n]; // aloca memorie ptr matrice } // modifica valoare element public void setElement (int i,int j, boolean b) { a[i][j]=b; } // citire valoare element public boolean getElement (int i,int j) { return a[i][j]; } // sir cu elementele din matrice public String toString () { String s=""; for (int i=0;i<n;i++) { for (int j=0;j<n;j++) s=s + ( a[i][j]==true ? "1 ": "0 ") ; s=s+"\n"; }

24

Florian Moraru Programare Orientata pe Obiecte


return s; } // exemplu de utilizare public static void main (String arg[]) { BoolMatrix mat = new BoolMatrix(4); for (int i=0;i<4;i++) mat.setElement (i,i,true); System.out.println ( mat.toString()); } }

// sau System.out.println (mat);

Variabilele dintr-o clas sunt declarate de obicei cu atributul private, ceea ce le face inaccesibile pentru metode din alte clase. Se mai spune c datele sunt ascunse sau sunt ncapsulate n fiecare obiect. Metodele clasei sunt de obicei publice pentru a putea fi apelate din alte clase. Deoarece datele dintr-un obiect (variabile private) nu sunt direct accesibile din afara clasei si pot fi modificate numai prin intermediul metodelor clasei, utilizarea tipurilor de date definite prin clase este mai sigur dect a celor definite prin structuri. De exemplu, orice modificare a vectorului de caractere dintr-un obiect StringBuffer (StringBuilder) este nsotit de modificarea lungimii sirului (n metodele care pot modifica lungimea sirului), dar lungimea nu poate fi modificat direct de ctre functii din alte clase (si nici continutul vectorului de caractere). In Java nu se pot supradefini operatori, deci operatiile cu noile tipuri de date se pot exprima numai prin metode asociate obiectelor (metode nestatice). Tipurile de date definite prin clase pot forma ierarhii de tipuri compatibile (care se pot nlocui prin atribuire sau la transmitere de argumente). Clasele permit programarea generic Programarea generic ne permite s avem o singur clas pentru un vector (sau pentru o list), indiferent de tipul datelor care vor fi memorate n vector (n list). Tot programarea generic ne permite s folosim o singur functie (metod) pentru a parcurge elementele oricrei 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). Genericitatea colectiilor de obiecte poate fi realizat n limbajele cu clase prin dou metode: - prin colectii ce contin un supertip al tuturor tipurilor clas (tipul Object n Java); - prin clase ce au ca parametri tipurile de date folosite (numite templates n C++). Colectiile cu elemente de tip Object au existat de la nceput n Java, iar colectiile cu tipuri de date ca parametri au aprut din versiunea 5 si sunt numite generice (generics). 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, care este rdcina ierarhiei de clase Java. Tipul unei clase derivate este subtip al tipului clasei din care deriv, asa cum tipul int poate fi considerat ca un subtip al tipului long, iar tipul float ca un subtip al tipului double. La fel cum un argument formal de tip double poate fi nlocuit cu un argument efectiv de tip int, tot asa un argument formal de un tip clas B poate fi nlocuit cu un argument efectiv de un tip clas D; clasa D fiind derivat din clasa B. In felul acesta se pot scrie functii generice, 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). O colectie de variabile de tip Object poate contine referinte la obiecte de orice tip, pentru c acestor variabile li se pot atribui variabile de orice alt tip clas (care contin adresele unor obiecte). 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; adugarea la vector a unui pointer oarecare nu necesit o conversie, dar la extragerea din vector trebuie trecut de la tipul void* la tipul de pointer folosit la adugare (prin operatorul de conversie de tip cast).

25

Florian Moraru Programare Orientata pe Obiecte Exemplu de calcul a sumei valorilor unor obiecte numerice dintr-un vector:
Vector v = new Vector(); // creare obiect vector (extensibil) for (int k=1; k<11; k++) // genereaza numerele 1,2.3 10 v.add ( new Integer(k)); // adauga numarul k la vector ca obiect de tip Integer int sum=0; for (int k=0;k<v.size();k++) { Integer obj = (Integer) v.get(k); // extrage din vector si conversie din Object n Integer sum += obj.intValue(); // 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), 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). O colectie Java generic specific tipul obiectelor continute nc de la declarare (care poate fi orice subtip de Object sau de alt tip), iar la extragere nu mai trebuie fcut nici o conversie de tip. Acelasi exemplu dinainte cu colectii generice:
Vector <Integer> v = new Vector<Integer>(); // creare obiect vector de Integer for (int k=1; k<11; k++) // genereaza numerele 1,2.3 10 v.add ( new Integer(k)); // adauga numarul k la vector ca obiect de tip Integer int sum=0; for (int k=0;k<v.size();k++) sum += v.get(k).intValue();

O alt simplificare privind utilizarea colectiilor generice de tipuri ce corespund tipurilor primitive (Byte, Short, Integer, Float, Double, Boolean, Character) este trecerea automat de la tipul primitiv la tipul clas corespunztor (autoboxing) si trecerea invers (unboxing). Exemplu reluat:
Vector <Integer> v = new Vector<Integer>(); // creare obiect vector de Integer for (int k=1; k<11; k++) // genereaza numerele 1,2.3 10 v.add ( k); // conversie automata de la int la Integer int sum=0; for (int k=0;k<v.size();k++) sum += v.get(k); // 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, variabile, argumente de functii, etc.). Evolutia limbajelor de programare poate fi privit si ca un progres al abstractizrii, n sensul ndeprtrii progresive de masina fizic prin introducerea de notiuni tot mai abstracte. Fiecare limbaj de programare ofer programatorilor o masin virtual (sau abstract ) diferit de masina concret pe care se vor executa programele lor. 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. 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. Un program Java poate fi privit ca o descriere a unor obiecte si a interactiunilor dintre aceste obiecte. Intr-un program Java nu exist altceva dect obiecte si clase. Analiza orientat pe obiecte poate ncepe cu o descriere n limbaj natural a ceea ce trebuie s fac programul; substantivele din

26

Florian Moraru Programare Orientata pe Obiecte acest text corespund n general unor obiecte (clase), iar verbele din text corespund unor metode. O astfel de abordare este potrivit pentru aplicatii grafice, pentru jocuri, pentru aplicatii bancare s.a. Multe aplicatii folosesc o interfat grafic cu utilizatorii (operatorii) aplicatiei; obiectele vizuale afisate pe ecran si care pot fi selectate sau actionate de operator (ferestre, butoane, meniuri, casete cu text, s.a.) corespund direct unor obiecte din programele Java. Intr-o aplicatie bancar vor exista clase si obiecte de genul Account (cont bancar) si Customer (client al bncii). Un obiect Customer va contine date de identificare ale clientului si metode pentru obtinerea sau modificarea acestor date. Un obiect de tip Account va contine suma din cont (si alte date asupra operatiilor cu acel cont), precum si metode pentru depunerea de bani n cont, pentru retragerea de bani din cont si pentru vizualizarea sumei de bani din cont. Obiectele de tipul Account sau Customer se numesc si obiecte din domeniul aplicatiei (domain objects). Aplicatiile mai pot contine obiecte ajuttoare (helper) sau obiecte din clase predefinite pentru operatii cu anumite tipuri de date, cu colectii de obiecte, cu baze de date, cu conexiuni ntre calculatoare s.a. 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. 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. Orice clas colectie (deci si clasa Vector) contine o metod toArray care produce un vector intrinsec cu obiectele din colectie; metoda static Arrays.asList (Object[]) transform un vector intrinsec ntr-o colectie (de tip List). Un vector intrinsec ocup mai putin memorie, accesul la elementele vectorului este mai rapid si nu necesit conversie prin cast, ca n cazul obiectelor Vector. Intr-un astfel de vector se pot memora si date de un tip primitiv. Se recomand atunci cnd vectorul nu si modific dimensiunea initial sau dimensiunea se modific mai rar. Exemple: - Metoda split din clasa String produce un vector cu toate cuvintele dintr-un sir dat:
String[] split (String regexp);

- Metodele list si listFiles din clasa File produc un vector cu toate fisierele dintr-un director dat:
File[] listFiles();

Variabila public length are ca valoare dimensiunea alocat pentru un vector intrinsec (numit si capacitate). Redimensionarea unui vector intrinsec se face printr-o nou alocare de memorie si nu prin modificarea variabilei length (care are atributul final). Din versiunea 1.6 se poate folosi una din metodele statice copyOf din clasa Arrays pentru realocare vectori intrinseci. Exemplu:
class Array { private Object a[]; public Array (Object a[]){ this.a=a; } public void add (Object ob) { a=Arrays.copyOf(a,a.length+1); a[a.length-1]=ob; } public Iterator iterator() { return Arrays.asList(a).iterator(); } }

Obiectele din clasa Vector asigur extinderea automat a unui vector (realocarea dinamic pentru mrirea capacittii) la operatii de adugare si au o multime de metode pentru operatii uzuale cu

27

Florian Moraru Programare Orientata pe Obiecte vectori: obtinerea numrului de elemente, cutarea unui obiect dat, eliminarea de elemente, s.a. Exemple de metode utilizate frecvent:
int size( ); // dimensiunea acestui vector (nr de elemente) boolean contains (Object obj); // daca acest vector contine obiectul obj int indexOf (Object obj); // prima pozitie unde se afla obiectul obj sau 1 int indexOf (Object obj, int i); // prima pozitie de dupa i unde se afla obiectul obj int lastIndexOf (Object obj); // ultima pozitie din acest vector unde se afla obj Object elementAt (int i); // obiectul din pozitia i din acest vector Object get(int i); // obiectul din pozitia i din acest vector boolean add (Object obj); // adauga obj la sfarsitul acestui vector void addElement (Object obj); // adauga obj la sfarsitul acestui vector void insertElementAt (Object obj, int i); // introduce obj in pozitia i din vector boolean remove (Object obj); // elimina elementul cu valoarea obj din vector Object remove (int i); // elimina elementul din pozitia i din acest vector void setElementAt (Object obj, int i); // inlocuieste elementull din pozitia i cu obj Object[] toArray(); // produce un vector intrinsec cu aceleasi elemente String toString(); // produce un sir cu elementele din vector, 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 adugat clasa ArrayList, care are aceeasi functionalitate cu clasa Vector. Clasa Vector contine un vector intrinsec de elemente de tip Object, plus variabile pentru capacitatea alocat si pentru dimensiunea efectiv a vectorului. Se recomand pentru vectori de obiecte cu continut variabil (n decursul executiei programului). Exemplu de functie similar functiei split, dar fr expresii regulate, pentru extragere de cuvinte dintr-un sir (cuvintele sunt separate ntre ele printr-un numr oarecare de spatii albe):
class Unu { // cu rezultat vector intrinsec public static String[] split(String s) { StringTokenizer tok = new StringTokenizer(s); String[] a = new String [tok.countTokens()]; int i=0; while ( tok.hasMoreTokens()) a[i++]=tok.nextToken(); return a; } // utilizare functie public static void main (String arg[]) { String s = unu doi trei patru cinci ; String w[] = split(s); System.out.println (Arrays.toString (w)); } }

Afisarea continutului unui obiect Vector (ca si afisarea continutului oricrei colectii) este simplificat de existenta metodei toString. Metoda println cu argument Object foloseste automat metoda toString pentru obiectul respectiv:
System.out.println (w.toString()); // la fel cu: System.out.println(w);

Incepnd din versiunea 5 se poate preciza tipul elementelor din vector, pentru clasa Vector, ca si pentru orice alt clas colectie, folosind ceea ce se numeste un vector generic.

28

Florian Moraru Programare Orientata pe Obiecte 3. Utilizarea de clase si obiecte Clase fr obiecte. Metode statice. Metodele Java sunt de dou categorii: - Metode aplicabile obiectelor (Object Methods) - Metode statice, utilizabile independent de obiecte (Class Methods) O metod static Java corespunde unei functii din limbajul C, dar poate fi folosit numai precedat de numele clasei. In felul acesta putem avea functii cu acelasi nume n clase diferite si se reduce posibilitatea unui conflict de nume. Cteva clase Java nu sunt altceva dect grupri de functii statice relativ independente. Aceste clase nu sunt instantiabile, deci nu pot genera obiecte. Metodele statice sunt n general si publice, pentru a putea fi apelate din orice alt clas. 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.parseInt (args[0]); // functie statica din clasa Integer double r = Math.sqrt (x); // functie statica din clasa Math System.out.println (r); } }

In Java se citesc din fisiere sau se preiau din linia de comand siruri de caractere, iar analiza lor si conversia n format intern pentru numere se face explicit de ctre programator. Din versiunea 1.5 au fost introduse n Java metode similare functiilor "scanf" si "printf" din C. Exemple:
System.out.printf ("%s %5d\n", name, total); // afisare cu format Scanner sc=Scanner.create(System.in); // citire cu format String param= sc.next(); // metoda ptr citirea sirului urmator int value=sc.nextInt(); // metoda ptr citirea urmatorului numar intreg

Pentru functiile matematice nu exist alt posibilitate de definire dect ca metode statice, 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. Metodele statice au un parametru n plus fat de metodele nestatice cu acelasi efect. Exemple:
// o metoda statica pentru conversie numar din Integer in int public static int getInt (Integer a) { return a.intValue (); // apel metod nestatica } // exemplu de utilizare pentru conversie de la String la int Integer a = new Integer (str); int x = getint (a); // sau x = new Integer(str).intValue();

O metod static se poate referi numai la variabile statice din clase (variabile definite n afara functiilor si cu atributul static). Exemplu:
class A { static String msg = "O.K."; // "static" este necesar aici public static void main (String arg[ ]) { System.out.println (msg); } }

29

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). In Java metodele statice sunt putine, iar variabile statice sunt n general constantele simbolice. In practic functia main creeaz unul sau cteva obiecte si apeleaz una sau cteva metode pentru obiectele create (de obicei main contine putine instructiuni). Exemplu de verificare a clasei StrTok definite anterior:
public static void main (String[] arg) { String str = " unu doi trei "; // un sir cu mai multe cuvinte StrTok st = new StrTok(str); // creare obiect folosit la impartirea in cuvinte while ( st.hasMoreTokens()) // cat timp mai sunt cuvinte System.out.println(st.nextToken ()); // afiseaza urmatorul cuvant }

Metoda main de mai sus poate fi inclus n clasa verificat StrTok sau poate fi n alt clas. Metode statice pot fi ntlnite si n clase cu majoritatea metodelor nestatice, ca n exemplele urmtoare:
static Integer Integer.valueOf (String s); // conversie din String in Integer static int Integer.parseInt (String s); // conversie din String in int static String String.valueOf (int i); // conversie din int in String static boolean Character.isDigit (char ch); // daca ch este cifra static boolean Character.isLetter (char ch); // 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). Exemple:
String s=-123; Integer zero = new Integer(0); int x = Integer.parseInt(s); int y = zero.parseInt(s);

Multe functii recursive se scriu n Java ca metode statice, desi nu este imposibil s avem si metode nestatice recursive. Pentru exemplificare vom defini o functie care s afiseze fisierele dintr-un director dat si din subdirectoarele sale, care coboar recursiv n fiecare subdirector ntlnit (metoda listFiles din clasa File nu furnizeaz dect fisierele si subdirectoarele de pe primul nivel):
static void printFiles (File d, String sp) { // d= director, sp=spatii ptr indentare System.out.println (sp+d.getName()); // afiseaza nume director primit if ( ! d.isDirectory()) return; // daca nu e subdirector, se iese File [] files=d.listFiles(); // vector de fisiere din directorul d for (int i=0;i<files.length;i++) // pentru fiecare fisier din d printFiles (files[i], sp+" "); // apel recursiv, cu alta indentare }

Clase instantiabile. Metode aplicabile obiectelor Specific programrii orientate pe obiecte este utilizarea de clase care contin si date si care pot genera 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 este un caz particular concret al unei clase, deci o instantiere a unei clase. Intr-un program Java se creeaz obiecte si se apeleaz metode ale acestor obiecte. Toate obiectele sunt create dinamic, de obicei cu operatorul new. Una din cele mai folosite clase n Java este clasa String, care poate genera obiecte ce contin fiecare un sir de caractere. Un obiect String poate fi creat pe baza unui vector intrinsec de caractere sau de

30

Florian Moraru Programare Orientata pe Obiecte octeti sau pe baza altui obiect String sau StringBuffer( StringBuilder). Clasa String contine metode pentru cutarea ntr-un sir, pentru extragere de subsiruri, pentru comparatie de siruri si pentru anumite operatii de modificare a unui sir. In Java obiectele sunt create dinamic, la executie, folosind direct operatorul new (sau apelnd o metod fabric de obiecte). Adresa unui obiect se memoreaz ntr-o variabil referint de tipul clasei creia apartine obiectul. Exemple:
String fname = new String (test.java ); int p = fname.indexOf (.); String fext = fname.substring (p+1); // creare sir cu un nume de fisier // pozitia primului punct din nume // creare sir cu extensia numelui

In exemplul de mai sus, metoda "indexOf" se aplic obiectului cu adresa n variabila "fname" si are ca rezultat un ntreg, iar metoda "substring", aplicat aceluiasi obiect are ca rezultat un alt obiect, memorat la adresa din variabila "fext". Variabilele de un tip clas reprezint singura posibilitate de acces la obiectele Java si ele corespund variabilelor referint din C++. Pentru o scriere mai compact se practic uneori nlntuirea de metode (method chaining), adic aplicarea unei metode asupra rezultatului unei alte metode, ntr-o aceeasi expresie. Exemple:
String line = f.readLine().trim().toUpperCase(); // citeste linie, elimina spatii si trece in litere mari if ( fname.substring(fname.indexOf(.)+1).toLowerCase().equals(java) ) ...

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 adugarea altor caractere sirului initial); de aceea este posibil s nlntuim mai multe apeluri ale metodei append astfel:
StringBuilder sb = new StringBuilder(); sb.append (x=).append(x).append ( y=).append(y); System.out.println (sb);

Ordinea de evaluare pentru operatorul punct este de la stnga la dreapta (ca n C). In Java gestiunea memoriei dinamice este automat iar programatorul nu trebuie s aib grija eliberrii memoriei alocate pentru obiecte. Teoretic, memoria ocupat de un obiect este eliberat atunci cnd obiectul respectiv devine inaccesibil si inutilizabil, dar momentul exact al recuperrii memoriei nu poate fi precizat si depinde de modul de gestiune a memoriei dinamice. Este posibil si apelarea direct a colectorului de resturi de memorie (garbage collector), dar nu se practic dect foarte rar. Pentru utilizarea mai eficient a memoriei si pentru reducerea timpului de executie se recomand s nu se creeze obiecte inutile, atunci cnd exist alte posibilitti. De exemplu, o variabil de un tip clas care va primi ulterior rezultatul unei functii nu va fi initializat la declarare cu altceva dect cu constanta null. Exemplu:
RandomAccessFile f = new RandomAccessFile ("date.txt","r"); String line=null; // nu: line = new String(); ... line = f.readLine(); // 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); Integer count[ ]= new Integer [n]; for (int i=0; i<n;i++) count[i] = zero; // un obiect de tip Integer // un vector cu elem. de tip Integer // nu: count[i]= new Integer(0);

31

Florian Moraru Programare Orientata pe Obiecte

O a treia situatie frecvent este la construirea unui obiect pe baza altor obiecte; de obicei este suficient retinerea adresei obiectului primit ca parametru de constructor si nu trebuie creat un nou obiect. Exemplu:
class Elev { String nume; Float medie; // datele clasei public Elev ( String unnume, float med) { // constructor al clasei Elev nume = unnume; // nu: nume = new String(unnume); medie = new Float (med); } . . . // metode ale clasei Elev }

Variabile referint la un tip clas Variabilele Java pot fi: variabile de un tip primitiv (char, int, float, boolean etc) sau variabile de un tip clas (sau de un tip vector), care sunt variabile referint. Nu se pot defini referinte la tipuri primitive sau parametri referint de un tip primitiv. Variabila care primeste rezultatul operatorului new nu contine chiar obiectul ci este o referint la obiectul creat. 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. Indirectarea prin variabila referint este realizat automat de compilator, fr a se folosit un operator .
Referinta Obiect

Simpla declarare a unei variabile de un tip clas nu antreneaz automat crearea unui obiect. Compilatorul Java verific si anunt utilizarea unei variabile care nu a fost initializat. Secventa urmtoare va provoca o eroare la compilare :
String nume; System.out.println (nume); // utilizare variabil neinitializat

Variabilele declarate n functii nu sunt initializate de compilator, 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; public static void main (String arg[]) { System.out.println (v.size()); } }

// NullPointerException

Pentru elementele unui vector intrinsec compilatorul nu poate stabili dac au fost sau nu initializate si se produce exceptie la executie. Exemplu:
String tab[ ] = new String[10]; int n=tab[0].length(); // tab[i]=null in mod implicit // NullPointerException

32

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. Exemplu de eroare:
String linie=f.readLine(); ... if (linie == .) break; // citeste o linie din fisierul f // incorect, se compara adrese !

Comparatia la egalitate ntre obiecte Java se face prin metoda "equals" (de tip boolean).Exemplu :
if (linie.equals (.)) break; // linie de tip String

Metoda equals exist n orice clas Java iar metoda compareTo (cu rezultat int) exist numai n clasele cu obiecte comparabile, cum sunt clasele String, Integer, Float, Date etc. Operatorul de atribuire se poate folosi numai ntre variabile referint de acelasi tip sau pentru atribuirea constantei null la orice variabil referint. Efectul atribuirii ntre dou variabile referint este copierea unei adrese si nu copierea unui obiect. Atribuirea ntre variabile referint duce la multiplicarea referintelor ctre un acelasi obiect. Exemplu:
StringBuffer s2, s1=new StringBuffer (unu); s2=s1; // adresa sirului unu s2.append (doi); System.out.println (s1); // 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; String s2 = new String (s1);

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); Integer m2 = new Integer ( m1.intValue());

c) Folosind metoda general clone (mostenit de la clasa Object), dar numai pentru obiecte din clase care implementeaz interfata Clonable. Exemplu:
Vector a, b; b= (Vector) a.clone(); // 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). Copierea este superficial n sensul c se copiaz variabile referint (pointeri) si nu datele la care se refer acele variabile. 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. Incercarea a utiliza o variabil referint cu valoarea null pentru apelarea unei metode cauzeaz exceptia NullPointerException. Exemplu:
String s=null; int len = s.length(); // exceptie !

33

Florian Moraru Programare Orientata pe Obiecte Elementele unui vector de obiecte sunt initializate automat cu null la alocarea de memorie, iar prelucrarea unui vector completat partial poate produce exceptia de utilizare a unui pointer (referint) cu valoarea null. Exemplu:
Object a[] = new Object [10] ; // 10 valori null a[0]="unu"; a[1]="doi"; a[2]="trei"; Arrays.sort(a); // exceptie aruncata de functia sort

Metoda clone de copiere a unui vector intrinsec nu copiaz si valorile null, retinnd n vectorul clon numai elementele nenule. Exemplu:
Object [ ] b = new Object[10]; b = (Object[]) a.clone(); Arrays.sort (b); // nu produce exceptie

Clonarea de obiecte este o operatie folosit destul de rar n practica programrii. O situatie n care pare necesar clonarea este la transmiterea de obiecte pentru construirea altor obiecte, cum ar fi transmiterea unui sir la construirea unui obiect de tip StrTok (sau StringTokenizer). Constructorul StrTok retine o referint ctre sirul de analizat si nu face o copie a acestui sir, pentru c nu modific sirul primit. Intr-un vector sau ntr-o alt colectie se memoreaz referinte ctre obiecte si nu ctre clone ale obiectelor, pentru a permite modificarea obiectelor din colectie. Un exemplu este un vector de vectori, care s permit modificarea oricrui vector, ca si a obiectelor memorate n acesti vectori. Situatia este similar unui vector de pointeri din limbajul C (ctre siruri sau ctre diferite structuri alocate dinamic): nu se cloneaz structurile sau sirurile, iar adresele lor sunt memorate n vector). Argumente de functii de tip referint In Java, ca si n C, transmiterea unui parametru efectiv la apelarea unei functii se face prin valoare, adic se copiaz valoarea parametrului efectiv n parametrul formal corespunztor, nainte de executia instructiunilor din functie. Argumentele de un tip primitiv nu pot fi modificate de o metod Java, deci o metod nu poate transmite mai multe rezultate de un tip primitiv, dar acest lucru nici nu este necesar. Argumentele unei metode sunt de obicei date initiale, iar efectul metodei este modificarea variabilelor clasei si nu modificarea argumentelor. 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. Prin copierea adresei unui obiect n parametrul formal corespunztor 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. Exemplu de functie fr efect n afara ei:
// metoda statica pentru trecere sir in litere mari - gresit !!! static void toUpper (String t) { t = t.toUpperCase(); // se creeaza un nou obiect, cu alta adresa }

Aici se creeaz un obiect String prin metoda toUpperCase, iar adresa sa este memorat n variabila local t (care continea initial adresa sirului dat). Un obiect creat ntr-o functie trebuie transmis ca rezultat al functiei. Exemplu:
static String toUpper (String s) { return s.toUpperCase(); }

34

Florian Moraru Programare Orientata pe Obiecte O functie poate modifica un obiect a crui adres o primeste ca argument numai dac n clasa respectiv exist metode pentru modificarea obiectelor. Avantajele si riscurile obiectelor modificabile trasmise ca argumente pot fi ilustrate prin clasa BitSet, pentru multimi de ntregi realizate ca vectori de biti. Diferenta a dou multimi A-B poate fi realizat prin functia urmtoare:
public static void minus (BitSet a, BitSet b) { for (int i=0;i<b.size();i++) if (b.get(i)) // daca b[i] este 1 a.clear(i); // atunci se face a[i]=0 }

Dac vrem s pstrm multimea a atunci vom transmite o copie a ei


BitSet aux= (BitSet) a.clone(); minus (aux,b); // copiaza pe a in aux // aux = aux-b

In exemplul urmtor 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. Functia modific n mod nedorit multimile primite ca argumente din cauza multiplicrii referintelor.
public static BitSet minus (BitSet a, BitSet b) { BitSet c=a, d=a; // c , d si a se refera la acelasi obiect c.and(b); // c= a*b dar s-a modificat si a ! d.xor(c); // d= d / c dar s-a modificat si a ! return d; // d este multimea vid ! }

Urmeaz o variant corect pentru calculul diferentei a dou multimi de tip BitSet:
public static BitSet minus (BitSet a, BitSet b) { BitSet c=new BitSet(), d=new BitSet(); // c,d multimi initial vide c.or(a); c.and(b); // c=a*b d.or(a); d.xor(c); // d=b-a return d; }

Clase cu obiecte nemodificabile Clasele String, Integer, Float s.a. 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. In acest fel se protejeaz obiectul transmis ca argument fat de modificarea sa nedorit de ctre functia care l foloseste. Obiectele din clase fr metode de modificare a datelor (clase read-only) se numesc obiecte nemodificabile (immutable objects). Clasa String este o clas read-only si final, deci nu se poate extinde cu metode de modificare a vectorului de caractere continut n fiecare obiect. Exemplu de utilizare gresit a metodei replaceAll din clasa String pentru nlocuirea unui subsir s1 cu un alt subsir s2 n sirul s:
s.replaceAll (s1,s2);

Metodele replaceAll si replace au ca rezultat un nou sir obtinut dup substituire si nu modific obiectul pentru care se apeleaz; de aceea utilizarea corect este urmtoarea:
s = s.replaceAll (s1,s2);

35

Florian Moraru Programare Orientata pe Obiecte Clasele StringBuffer si StringBuilder (din 1.5) sunt variante ale clasei String care contin n plus si metode pentru modificarea obiectului (sirului). Exemple de metode care modific sirul continut ntrun obiect de tip StringBuffer: append, insert, delete, setCharAt, setLength. Un obiect de tipul StringBuffer transmis unei functii ca argument poate fi modificat de ctre functie. Variant pentru functia toUpper:
static void toUpper (StringBuffer s) { String str= new String (s); s.replace (0,str.length(),str.toUpperCase()); }

Clasa StringBuilder este mai performant dect StringBuffer pentru programele fr fire de executie multiple, dar exist numai din versiunea 1.5. Concatenarea de siruri este o operatie frecvent n Java. Metoda println folosit pentru afisarea pe ecran poate avea un singur argument de tip String. Pentru a scrie mai multe siruri acestea se concateneaz ntr-un singur sir cu operatorul +.Exemplu:
System.out.println ( x= + x); // x de orice tip

Intr-o expresie cu operatorul binar +, dac unul din operanzi este de tip 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). Aceast observatie poate fi folosit si pentru conversia unui numr n sir de caractere, ca alternativ a utilizrii metodei valueOf din clasa String. Exemplu:
float x = (float) Math.sqrt(2); String str = +x; // sau str = String.valueOf(x);

O instructiune de forma a=a+b; cu a si b de tip String este tratat de compilator astfel: se transform obiectele a si b n obiecte de tip StringBufer, se apeleaz metoda append si apoi creeaz un obiect String din obiectul StringBuffer rezultat din concatenare:
String a=unu, b=doi; StringBuffer am= new StringBuffer (a), am.append(bm); a= new String (am);

Dac trebuie s facem multe concatenri de siruri este preferabil ca timp s se foloseasc direct metoda append din clasa StringBuilder (StringBuffer). Exemplu:
public static String arrayToString ( int a[ ]) { // creare sir cu continutul unui vector StringBuilder aux = new StringBuilder ([); int n =a.length; for (int i=0;i<n-1;i++) aux.append (a[i] + ,); return new String (aux.append (a[n-1] +]) ) ; }

De observat c, mai nou, exist n clasa Arrays metoda static toString cu argument vector. Eliminarea unui subsir dintr-un sir se poate face folosind metoda delete din clasa StringBuffer sau cu metode ale clasei String. Exemplu:
static String delete1 (String s, int from, int to ) { // sterge din s intre pozitiile from si to if ( from > to || from < 0 || to > s.length() ) return s; // s nemodificat !

36

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 transformri pot fi necesare pentru c n clasa StringBuffer nu se regsesc 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 programrii procedurale n limbajele orientate obiect. Utilizarea de metode obiect (nestatice) permite utilizarea unor tehnici specifice programrii 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 cnd se urmreste reutilizarea metodelor unor clase n alte clase. Pentru a arta diferentele sintactice dintre definirea si utilizarea celor dou tipuri de metode vom folosi exemplul unei clase cu rolul clasei StringTokenizer, dar fr 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 (urmtorul 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 numr relativ mare de clase Java pentru operatii de intrare-iesire cu fisiere dar aici vom da cteva 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 oricrui 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 nti 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 adugat 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 att operatii de scriere ct 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 cnd se ajunge la sfrsitul fisierului si nu exist metode care s testeze sfrsitul 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

Florian Moraru Programare Orientata pe Obiecte 4. Definirea de noi clase Definirea unei clase n Java Definirea unei clase se face prin definirea variabilelor si functiilor clasei. In Java toate metodele trebuie definite (si nu doar declarate) n cadrul clasei. Ordinea n care sunt definiti membrii unei clase (date si functii) este indiferent. Este posibil ca o metod s apeleze o alt metod definit ulterior, n aceeasi clas. Datele clasei se declar n afara metodelor clasei si vor fi prezente n fiecare obiect al clasei. Ele sunt necesare mai multor metode si sunt de obicei putine. Variabilele clasei sunt accesibile tuturor functiilor clasei pentru citire sau modificare si sunt initializate de obicei n constructorul clasei. Nu se vor defini ca variabile ale clasei acele variabile care sunt folosite numai n una sau dou functii ale clasei. Dimensiunea unui obiect este determinat mai ales de numrul si tipul variabilelor clasei si nu de numrul metodelor clasei. Atunci cnd se creeaz multe obiecte dintr-o clas, cum ar fi obiecte de tip nod de arbore sau obiecte ce corespund unor elemente din fisiere XML, este important ca dimensiunea lor s fie mentinut la minimum, dar n cazul unor clase cu un singur obiect (cum sunt clasele derivate din JFrame pentru fereastra aplicatiei) nu conteaz numrul variabilelor clasei. Majoritatea claselor instantiabile grupeaz mai multe functii n jurul unor date comune. Ca exemplu vom schita o definitie posibil pentru o clas ale crei obiecte sunt numere complexe (nu exist o astfel de clas predefinit n bibliotecile JDK):
public class Complex { // datele clasei private double re,im; // metode ale clasei // adunare de numere complexe public void add ( Complex cpx) { re += cpx.re; im += cpx.im; } // scadere de numere complexe public void sub ( Complex cpx) { re = re - cpx.re; im = im - cpx.im; } }

// parte real si parte imaginar

Se observ c metodele unei clase au putine argumente, iar aceste argumente nu se modific, ceea ce este tipic pentru multe clase cu date: metodele clasei au ca efect modificarea datelor clasei, iar argumentele sunt de obicei date initiale necesare pentru operatiile cu variabilele clasei. Acesta este si un avantaj al programrii cu clase fat de programarea procedural: functii cu argumente putine si care nu sunt modificate n functie. Clasele de uz general se declar public pentru a fi accesibile din orice alt pachet. Toate variabilele numerice ale unei clase sunt initializate implicit cu zerouri si toate variabile referint sunt initializate cu null. Variabilele de interes numai pentru anumite metode vor fi definite ca variabile locale n functii si nu ca variabile ale clasei. Exemplu de variabil local unei metode:
public Complex conj ( ) { Complex c = new Complex(); c.re= re; c.im= -im; return c; } // c este o variabil local

41

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. In Java clasele cu date nu contin metode de afisare pe ecran a datelor din obiectele clasei, 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.out.println) sau introdus ntr-un alt flux de date sau folosit de alte metode. Functia urmtoare (metod a clasei "Complex" ) trebuie inclus n definitia clasei:
// conversie n sir, pentru afisare public String toString ( ) { return ( "(" + re+ "," + im+ ")"); }

Existenta metodei "toString" ne permite s afism direct continutul unui obiect din clasa "Complex" astfel:
Complex c = new Complex(); System.out.println (c); // scrie (0,0)

In plus, se poate afisa continutul unei colectii de obiecte "Complex", pentru c metoda "toString" a colectiei apeleaz metoda "toString" a obiectelor din colectie. Redefinirea metodei "equals" n clasa "Complex" permite cutarea unui obiect dat ntr-o colectie si alte operatii care necesit comparatia la egalitate. Exemplu:
public boolean equals (Object obj) { Complex cobj = (Complex) obj; return re==cobj.re && im==cobj.im; }

Functii constructor Orice clas instantiabil are cel putin un constructor public, definit implicit sau explicit si apelat de operatorul new la crearea de noi obiecte. Un constructor este o functie fr tip si care are obligatoriu numele clasei din care face parte. Constructorii nu sunt considerati metode, deoarece nu pot fi apelati explicit (prin nume). Variabilele oricrei clase sunt initializate automat la crearea de obiecte (cu valori zero pentru variabile numerice si null pentru variabile de orice tip clas). Aceste valori sunt preluate de fiecare obiect creat, dac nu se fac alte initializri prin constructori. 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. Exemplu:
public class Complex { private double re, im; // re=parte real ,im= parte imaginar public Complex ( double x, double y) { re=x; im=y; } . . . // metode ale clasei }

Exemple de creare a unor obiecte de tipul Complex :


Complex c1 = new Complex (2,-3) ; Complex c2= new Complex (0,1);

42

Florian Moraru Programare Orientata pe Obiecte Dac nu se defineste nici un constructor atunci compilatorul genereaz automat un constructor implicit, fr argumente si fr efect asupra variabilelor clasei (dar care apeleaz constructorul superclasei din care este derivat clasa respectiv). Este uzual s existe mai multi constructori ntr-o clas, care difer prin argumente, dar au acelasi nume (un caz de supradefinire a unor functii). Exemplu:
public Complex ( Complex c) { re=c.re; im=c.im; }

Este posibil si supradefinirea unor metode ale claselor. De exemplu, putem defini dou metode de adunare la un complex, cu acelasi nume dar cu argumente diferite:
// adunare cu un alt complex public void add ( Complex c) { re += c.re; im += c.im; } // adunare cu un numar real public void add ( double x) { re = re + x; }

In cazul cnd 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. Exemplu:
class Pair { private String key,value; public Pair (String key, String value) { this.key=key; this.value=value; // nu: this.key= new String(key); } // metode ale clasei }

Uneori metodele clasei chiar trebuie s modifice obiectele ale cror adrese le primeste, iar alteori nu exist posibilitatea modificrii acestor obiecte (din clase read-only). In cazul cnd variabilele clasei sunt vectori intrinseci sau clase colectie n constructor se aloc memorie pentru vectori sau colectii. Exemplu:
public class IntVector { // un vector de numere private float value[]; // adresa vector private int count; // nr de elemente in vector // constructori public IntVector( int n) { // n= capacitate initiala vector value= new int[n]; // aloca memorie ptr vector count= 0; // initial nici un element } ... // metode ale clasei }

Variabilele unei clase pot fi initializate la declararea lor, cu efect pentru toate obiectele clasei. Acest fel de initializare se practic pentru variabile membru de tip clas si pentru clase cu un singur obiect. Exemplu de initializare a unei variabile la declarare:
class TextFrame extends JFrame { JTextField t = new JTextField (10); public TextFrame () { getContentPane().add(t,"Center"); // initializare in afara constructorului // constructor clasa // adauga camp text la fereastra

43

Florian Moraru Programare Orientata pe Obiecte


} ... } // alte metode

Intr-o aplicatie se va crea un singur obiect de tip "TextFrame", iar obiectul de la adresa "t" va avea ntotdeauna mrimea 10. Instructiunea din constructor se va executa o singur dat, dar trebuie s fie inclus ntr-o functie. Crearea obiectului cmp text JTextField nu se face printr-o instructiune ci printr-o declaratie cu initializare. Initializri ale variabilelor clasei se mai pot face ntr-un bloc cuprins ntre acolade (inclus de compilator n orice constructor), ntr-un bloc static de initializare sau ntr-o metod static private. In Java nu sunt permise argumente formale cu valori implicite, dar un constructor cu mai putine argumente poate apela un constructor cu mai multe argumente, dintre care unele au valori implicite. Un constructor nu poate fi apelat explicit, ca si o metod, iar atunci cnd este necesar acest apel (din aceeasi clas) se foloseste cuvntul cheie this ca nume de functie. Exemple:
// constructor cu un parametru din clasa Complex public Complex (double re) { this (re,0); } // apel constructor cu dou argumente // constructor fara parametri din clasa Complex public Complex () { this (0); } // apel constructor cu un argument

Cuvntul cheie "this" Metodele nestatice dintr-o clas actioneaz asupra unor obiecte din acea clas. Atunci cnd se apeleaz o metod pentru un anumit obiect, metoda primeste ca argument implicit o referint la obiectul respectiv. Altfel spus, instructiunile urmtoare :
int n = a.length(); String b = a.substring (k); // lungimea sirului a // extrage subsir din pozitia k din sirul a

corespund instructiunilor urmtoare din limbajul C:


int n = length(a); // lungimea sirului a String b = substring (a,k); // extrage subsir din pozitia k din sirul a

Acest argument implicit poate fi folosit in interiorul unei metode nestatice prin intermediul variabilei predefinite this. Cuvntul cheie this este o variabil referint care desemneaz n Java adresa obiectului curent ("acest obiect"). Definitia metodei "length" ar putea fi rescris folosind variabila this astfel:
public int length (this) { return this.count; // "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, dar exist situatii cnd folosirea lui this este necesar. Un prim caz de folosire curent a variabilei this este la definirea unui constructor cu argumente formale avnd aceleasi nume cu variabile ale clasei. Exemplu:
public Complex ( float re, float im) { this.re=re; this.im=im; // ptr. a distinge arg. formale de var. clasei }

44

Florian Moraru Programare Orientata pe Obiecte Astfel de situatii pot fi evitate prin alegerea unor nume de argumente diferite de numele variabilelor clasei, dar clasele JDK folosesc aceleasi nume (si variabila this ) pentru c argumentele unui constructor contin valori initiale pentru variabilele clasei. Un alt caz de folosire a variabilei this este n instructiunea return, pentru metodele care modific datele unui obiect si au ca rezultat obiectul modificat. Exemplu:
public final class StringBuffer { private char value[ ]; // un vector de caractere private int count; // caractere efectiv folosite din vector // metode public StringBuffer deleteCharAt (int index) { // sterge caracterul din poz index System.arraycopy (value, index+1, value, index, count-index-1); count--; return this; // rezultatul este obiectul modificat de metod } // ... alte metode ale clasei StringBuffer }

Un caz particular este metoda "toString" din clasa "String":


public String toString () { return this; }

Uneori un obiect si trimite adresa sa metodei apelate (metod static sau dintr-un alt obiect). 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.addElement (obj); // apel metoda din clasa Vector Collections.sort(this); // ordonare obiect ptr care s-a apelat metoda add } }

Variabila this nu poate fi folosit n metode statice. Atribute ale membrilor claselor Membrii unei clase, variabile si metode, au n Java mai multe atribute: tipul variabilei sau metodei ( tip primitiv sau tip clas ), un atribut de accesibilitate (public, protected, private) plus atributele static, final, abstract (numai pentru metode). Variabilele locale pot avea doar atributul final. Cu exceptia tipului, fiecare din aceste atribute are o valoare implicit, folosit atunci cnd nu se declar explicit o alt valoare. Cuvintele cheie folosite pentru modificarea atributelor implicite se numesc "modificatori". De exemplu orice membru este nestatic si nefinal atunci cnd nu se folosesc modificatorii static sau final. O variabil static a clasei declarat final este de fapt o constant, nemodificabil prin operatii ulterioare primei initializri. Exemple de constante din clasa Byte:
public class Byte { public static final byte MIN_VALUE = -128; public static final byte MAX_VALUE = 127; public static final Class TYPE = Class.getPrimitiveClass(byte); ... // constructori si metode clasa Byte

45

Florian Moraru Programare Orientata pe Obiecte


}

Se vede din exemplul anterior c o variabil static final poate fi initializat si cu rezultatul unei functii, pentru c initializarea se face la ncrcarea clasei. Metodele unei clase instantiabile sunt de obicei nestatice, dar pot exista si cteva metode statice n fiecare clas cu date. Exemplu din clasa String:
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); } // sir echivalent unui obiect

O metod static poate fi apelat de o metod nestatic. Exemplu:


public String toString() { // metoda din clasa "Integer" return String.valueOf(value); // metoda statica din clasa String }

O metod static (inclusiv functia main) nu poate apela o metod nestatic, pentru c folosirea unei metode nesatice este conditionat de existenta unui obiect, dar metoda static se poate folosi si n absenta unor obiecte. O metod static poate apela un constructor. Exemplu:
public static Integer valueOf (String s) throws NumberFormatException { return new Integer(parseInt(s)); // static int parseInt(String,int); }

Parte din definitia unei metode este si clauza throws, 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. Exemplu de metod din clasa Integer, pentru conversia unui sir de caractere ntr-un ntreg :
public static int parseInt (String s) throws NumberFormatException { if (s == null) throw new NumberFormatException("null"); ... }

Anumite situatii speciale aprute n executia unei metode sunt uneori raportate prin rezultatul functiei (boolean) si nu prin exceptii. De exemplu, anumite functii de citire a unui octet dintr-un fisier au rezultat negativ la sfrsit de fisier, iar functia de citire a unei linii (ntr-un obiect String) are rezultat null la sfrsit de fisier. Uneori este posibil anticiparea si prevenirea unor exceptii. Exemplul urmtor arat cum se poate evita aparitia unei exceptii de iesire din limitele unui vector intrinsec, din cauza utilizrii gresite a unui program Java:
// program cu doua argumente n linia de comanda public static void main ( String arg[ ]) { if ( arg.length < 2) { System.out.println (usage: java copy input output); return; } . . . // prelucrare argumente primite }

46

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 ctre compilator. O metod abstract este o metod cu atributul abstract, declarat dar nedefinit n clasa unde apare pentru prima dat. O clas care contine cel putin o metod abstract este o clas abstract si nu poate fi instantiat, deci nu se pot crea obiecte pentru aceast clas (dar se pot defini variabile de un tip clas abstract). Metode declarate dar nedefinite n Java sunt si metodele "native" (cu atributul native), care se implementeaz ntr-un alt limbaj dect Java (de obicei limbajul C). Definitia lor depinde de particularitatile sistemului de calcul pe care se implementeaz metodele. Exemplu de metod nativ:
public static native void arraycopy (Object s, int si, Object d, int di, int length);

Incapsularea datelor n clase De obicei datele unei clase nu sunt direct accesibile utilizatorilor clasei, deci nu sunt accesibile functiilor din alte clase. Pe de o parte utilizatorii nu sunt interesati de aceste date (de exemplu, cel care foloseste o stiv nu are nevoie s vad vectorul stiv si alte detalii), iar pe de alt parte este mai sigur ca aceste date s fie modificate numai de metodele clasei si nu de ctre orice utilizator. O clas expune utilizatorilor si o interfat public, care const din totalitatea metodelor publice (accesibile) ale clasei si care arat ce se poate face cu obiecte ale clasei respective. Documentatia unei clase prezint numai interfata sa public, adic constructorii publici, metodele publice (tip, nume, argumente) si variabilele public accesibile. Este posibil modificarea implementrii unei clase, cu mentinerea interfetei sale. De exemplu, putem folosi o stiv realizat ca list nlntuit n locul unei stive vector, fr ca programele ce folosesc stiva s necesite vreo modificare. Atributul de accesibilitate se refer la drepturile unei clase asupra membrilor unei alte clase, si este legat de ceea ce se numeste "ncapsulare" sau "ascunderea datelor". Toate obiectele unei clase au aceleasi drepturi, stabilite la definitia clasei. Metodele unei clase pot folosi variabilele clasei, indiferent de atributul de accesibilitate al acestor variabile (private, protected, public). Metodele unei clase se pot apela unele pe altele, indiferent de ordinea definirii lor si de atributul de accesibilitate. Atributul public se foloseste pentru functiile si/sau variabilele clasei ce urmeaz a fi folosite din alte clase. In general, metodele si constructorii unei clase se declar public. Variabilele public accesibile se mai numesc si proprietti ale obiectelor. Datele unei clase au n general atributul private sau protected, iar accesul la aceste date este permis numai prin intermediul metodelor, publice, ale clasei respective. Exista si cazuri, mai rare, n care nu se respect principiul ncapsulrii iar variabilele unei clase sunt public accesibile. Este vorba de clase cu mai multe variabile, folosite relativ frecvent n alte clase si pentru care accesul prin intermediul metodelor ar fi incomod si ineficient. Exemplu din pachetul java.awt:
public class Rectangle { // dreptunghi de incadrare figura geometrica public int x, y, width, height; // coordonate: colt, ltime, nltime public Rectangle (int x0, int y0, int w, int h) { x0=x; y0=y; width=w; height=h; } ... // alte metode }

Un alt exemplu este clasa StreamTokenizer din pachetul java.io. Un obiect StreamTokenizer este un mic analizor lexical, care recunoaste tipul urmtorului simbol extras dintr-un fisier (flux de date): numr, cuvnt sau caracter special. Tipul urmtorului simbol este rezultatul unei metode, dar valoarea numrului sau sirului cuvnt se afl n variabile publice ale clasei. Exemplu:

47

Florian Moraru Programare Orientata pe Obiecte


public static void main (String arg[]) throws Exception { FileReader is = new FileReader(arg[0]); // un fisier cu numele in arg[0] StreamTokenizer st = new StreamTokenizer(is); // un obiect analizor lexical int type; // tip atom while ( (type=st.nextToken()) != st.TT_EOF) { // repeta pana la sfarsit de fis if (type == st.TT_WORD) // daca e un cuvant System.out.println (type+" "+ st.sval); // afisare cuvant if (type == st.TT_NUMBER) // daca e un numar System.out.println (type+" "+ st.nval); // afisare numar else System.out.println((char)type); // afisare caractere speciale } }

Variabilele sval (de tip String) si nval (de tip double) sunt variabile publice, iar TT_EOF, TT_WORD si TT_NUMBER sunt constante (variabile publice statice si finale) din clasa StreamTokenizer. O solutie alternativ ar fi fost obtinerea valorii cu o metod public avnd un rezultat de tipul generic Object, dar utilizarea acestei valori ar fi necesitat o conversie la tipul indicat de metoda nextToken. 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. 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, numite uneori clase prietene. De exemplu, clasele VectorEnumerator si Vector sunt definite (n Java 1.0) n acelasi pachet si n acelasi fisier surs, iar variabile cu atributul protected din clasa Vector sunt folosite direct n clasa VectorEnumerator (n Java 1.2 clasa enumerator este inclus n clasa vector). In continuare vom schita definitia unei clase pentru o list nlntuit de numere ntregi. Mai nti 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; // date din nod Node leg; // legatura la nodul urmator public Node (int v) { val=v; leg=null;} // constructor }

Metodele clasei list trebuie s aib acces direct la variabilele clasei Node, ceea ce se poate realiza n dou moduri: - Clasele Node si MyList se afl n acelasi pachet. - Clasa Node este interioar clasei MyList. In ambele cazuri variabilele clasei Node pot s nu aib nici un atribut explicit de accesibilitate. Clasa MyList contine o variabil de tip Node (adresa primului element din list), care poate fi private sau protected, dac anticipm eventuale subclase derivate.
public class MyList { // lista inlantuita simpla (cu santinela) protected Node prim; // adresa primului element public MyList ( ) { prim = new Node(0); // 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); // creare nod nou cu valoarea v Node p=prim; // inaintare spre sfarsit de lista

48

Florian Moraru Programare Orientata pe Obiecte


while (p.leg != null) p=p.leg; p.leg=nou; } // sir ptr. continut lista public String toString () { String aux=""; Node p=prim.leg; while ( p != null) { aux=aux + p.val + " "; p=p.leg; } return aux; } } // rezultatul functiei toString // se pleaca de la primul nod din lista // cat timp mai exista un element urmator // intregul p.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, cu un argument suplimentar, 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,x); // apel functie recursiva } // fct recursiva de cautare (din clasa MyList) private boolean find (Node crt, int x) { if (crt==null) // daca sfarsit de lista return false; // atunci x negasit if ( x==crt.val) return true; // x gasit return find (crt.leg, x); // continua cautarea in sublista }

Functia find putea fi si static ( private static boolean find ...). Structura unei clase Java O clas care poate genera obiecte contine n mod normal si date, deci variabile ale clasei, definite n afara metodelor clasei (de obicei naintea functiilor). Majoritatea claselor instantiabile au unul sau mai multi constructori publici folositi pentru initializarea obiectelor si apelati de operatorul new. Metodele unei clase pot fi clasificate n cteva grupe: - Metode care permit citirea datelor clasei ( metode accesor). - Metode care permit modificarea datelor clasei ( si care lipsesc din anumite clase ). - Metode pentru operatii specifice clasei respective. - Metode mostenite de la clasa Object si redefinite, pentru operatii generale, aplicabile oricrui obiect Java ( equals, toString, hashCode). Conform unor cerinte mai noi ale standardului Java Beans, metodele de acces la variabile private au un nume care ncepe cu "get" si continu cu numele variabilei, iar metodele de modificare a variabilelor au un nume care ncepe cu "set". Exemplu:
public class Complex { private double re, im; // parte reala si imaginara // metode de citire a datelor public float getReal ( ) { return re; } // extrage parte reale

49

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 ctre un operator prin intermediul unui mediu vizual de dezvoltare (tipic sunt componente de interfat grafic: butoane, csute cu text, s.a.), dup care se pot modifica propriettile lor (tot interactiv, n faza de proiectare a interfetei grafice). De aceea clasele JavaBeans au si un constructor fr argumente si, obligatoriu, metode pentru citirea si modificarea propriettilor (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 propriettile 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: cutarea 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 cror 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, ctre 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 cuvnt ("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. Cuvntul 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 negsit }

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 negsit

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 prerea 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 oricrui mesaj la aparitia exceptiei). Clase si obiecte Java n faza de executie Pentru fiecare clas ncrcat n masina virtual Java este creat automat cte 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 cuvntul 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 cmpurilor (variabilelor clasei), numele metodelor clasei, formele functiilor constructor s.a. Metoda getName are ca rezultat un sir ce reprezint numele clasei al crui tip este continut ntrun obiect Class. Exemplul urmtor 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 fr 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, cunoscnd tipul argumentelor, se pot fabrica obiecte si apelnd 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 propriettile si metodele proprii unor obiecte, s modifice propriettile 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 neaprat 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 dect 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 fr 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 creia 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. Derivare, mostenire, polimorfism Clase derivate Derivarea nseamn definirea unei clase D ca o subclas a unei clase A, de la care mosteneste toate variabilele si metodele publice si protected. Subclasa D poate redefini metode mostenite de la clasa printe (superclasa) A si poate aduga noi metode si variabile clasei A. Tipul D este un subtip al tipului A. La definirea unei clase derivate se foloseste cuvntul cheie extends urmat de numele clasei de baz. 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); if (i < 0) i=-i-1; insertElementAt(obj,i); } // metoda redefinita: cautare binara in vector ordonat public int indexOf (Object obj) { // cauta in vector un obiect return Collections.binarySearch(this,obj); } }

In Java se spune c o subclas extinde functionalitatea superclasei, n sensul c ea poate contine metode si date suplimentare. In general o subclas este o specializare, o particularizare a superclasei si nu extinde domeniul de utilizare al superclasei. Cuvntul super, ca nume de functie, poate fi folosit numai ntr-un constructor si trebuie s fie prima instructiune (executabil) din constructorul subclasei. Exemplu:
public class IOException extends Exception { public IOException() { super(); } public IOException(String s) { super(s); } }

Principala modalitate de specializare a unei clase este redefinirea unor metode din superclas. Prin redefinire ("override") se modific operatiile dintr-o metod, dar nu si modul de utilizare al metodei. De multe ori, subclasele nu fac altceva dect s redefineasc una sau cteva metode ale superclasei din care sunt derivate. Redefinirea unei metode trebuie s pstreze "amprenta" functiei, deci numele si tipul metodei, numrul si tipul argumentelor . Majoritatea claselor Java care contin date redefinesc metodele equals si toString (mostenite de la clasa Object), pentru a permite compararea de obiecte si conversia datelor din clas ntr-un sir de caractere. Nu se poate extinde o clas final (cu atributul final) si nu pot fi redefinite metodele din superclas care au unul din modificatorii final, static, private. O metod redefinit ntr-o subclas "ascunde" metoda corespunztoare din superclas, iar o variabil dintr-o subclas poate "ascunde" o variabil cu acelasi nume din superclas. Apelarea metodei originale din superclas nu este n general necesar pentru un obiect de tipul subclasei, dar se poate face folosind cuvntul cheie "super" n locul numelui superclasei. Exemplu:
// vector ordonat cu adaugare la sfarsit si reordonare public class SortedVector extends Vector { public void addElement (Object obj) { // adaugare element si ordonare super.addElement (obj); // apel metoda addElement din clasa Vector

54

Florian Moraru Programare Orientata pe Obiecte


Collections.sort(this); } // metoda mostenita automat dar interzisa in subclasa public void insertElementAt (Object obj, int pos) { // ptr a nu modifica ordinea throw new UnsupportedOperationException(); } }

Dac metoda din subclas nu are exact acelasi prototip (nume,tip,argumente) cu metoda din superclas atunci nu are loc o redefinire si doar se adaug alt metod la cele mostenite. Pentru a permite compilatorului s verifice c are loc o redefinire de metod mostenit s-a introdus din 1.5 adnotarea @Override ca prefix al metodei:
import java.util.* ; // vector ordonat cu adaugare la sfirsit si reordonare class SortVec extends Vector { public SortVec () { super();} public SortVec ( Vector v) { super(); // apelat implicit ! for (int i=0;i<v.size();i++) addElement(v.elementAt(i)); } @Override public void addElement ( String obj) { // adaugare element si ordonare super.addElement (obj); // apel metoda din superclasa Collections.sort(this); }

Lipsa adnotrii @Override n exemplul anterior nu permite detectarea erorii c metoda addElement cu argument String nu redefineste metoda addElement din superclas, cu argument Object. O subclas se poate referi la variabilele superclasei fie direct prin numele lor, dac variabilele din superclas sunt de tip public sau protected, fie indirect, prin metode publice ale superclasei. Exemplu:
public class Stack extends Vector { ... public boolean empty() { // test de stiva goala return size()==0; // sau return elementCount==0; } }

Exemplul urmtor arat cum putem scrie o metod recursiv ntr-o clas derivat:
class FileExt extends File { public FileExt (String name) { super (name); } // afisare recursiva fisiere din subdirectoare // varianta 1: cu metoda listFiles public void printFiles (String sp) { System.out.println (sp + getName()); if ( ! isDirectory()) return; File [] files= listFiles(); for(int i=0;i<files.length;i++) {

55

Florian Moraru Programare Orientata pe Obiecte


FileExt f = new FileExt (files[i].getAbsolutePath()); f.printFiles (sp+" "); } } // varianta 2: cu metoda list public void printFiles (String sp) { // afisare recursiva cu indentare System.out.println (sp + getName()); if ( ! isDirectory()) return; String [] files= list(); for(int i=0;i<files.length;i++) { FileExt f = new FileExt (this+"/"+ files[i]); f.printFiles (sp+" "); } } }

Derivarea ca metod de reutilizare Derivarea este motivat uneori de necesitatea unor clase care folosesc n comun anumite functii dar au si operatii specifice, diferite n clasele nrudite. O solutie de preluare a unor functii de la o clas la alta este derivarea. O clasa derivat "mosteneste" de la superclasa sa datele si metodele nestatice cu atributele public si protected . Clasa SortedVector, definit anterior, mosteneste metodele clasei Vector: elementAt, indexOf, toString s.a. Exemplu:
SortedVector a = new SortedVector(); System.out.println ( a.toString()); // afisare continut vector

Chiar si metoda addElement redefinit n clasa derivat, refoloseste extinderea automat a vectorului, prin apelul metodei cu acelasi nume din superclas. In Java atributele membrilor mosteniti nu pot fi modificate n clasa derivat: o variabil public din superclas nu poate fi fcut private n subclas. Metodele si variabilele mostenite ca atare de la superclas nu mai trebuie declarate n subclas si pot fi folosite fr prefixul super. In schimb, trebuie definite functiile constructor, metodele si datele suplimentare (dac exist) si trebuie redefinite metodele care se modific n subclas. O subclas nu mosteneste constructorii superclasei, deci fiecare clas are propriile sale functii constructor (definite explicit de programator sau generate de compilator). Nu se genereaz automat dect constructori fr argumente. Din acest motiv nu putem crea un obiect vector ordonat prin instructiunea urmtoare:
SortedVector a = new SortedVector(n); // nu exista constructor cu argument !

In Java constructorul implicit (fr argumente) generat pentru o subclas apeleaz automat constructorul fr argumente al superclasei. Compilatorul Java genereaz automat o instructiune "super(); " nainte de prima instructiune din constructorul subclasei, dac nu exist un apel explicit si dac nu se apeleaz un alt constructor al subclasei prin "this(...)". Initializarea variabilelor mostenite este realizat de constructorul superclasei, desi valorile folosite la initializare sunt primite ca argumente de constructorul subclasei. 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 cuvntul cheie super ca nume de functie si nu este permis apelarea direct, prin numele su, a acestui constructor. Exemplu:
public class SortedVector extends Vector {

56

Florian Moraru Programare Orientata pe Obiecte


public SortedVector () { // vector cu dimensiune implicita super(); // initializari in superclasa } public SortedVector (int max) { // vector cu dimensiune specificata la instantiere super(max); // max folosit la alocarea de memorie in superclasa } ...

In exemplul anterior este necesar definirea constructorului fr argumente, care nu se mai genereaz automat dac se defineste (cel putin) un constructor cu argumente. Dac subclasa are variabile n plus fat de superclas, atunci ele vor fi initializate n constructorul subclasei. Exemplu:
public class SortedVector extends Vector { private Comparator comp; // variabila a subclasei // constructori public SortedVector ( int max, Comparator c) { super(max); comp=c; } public SortedVector ( Comparator c) { super(); comp=c; // super() poate lipsi, e generat automat } public void addElement (Object obj) { // adaugare element si ordonare super.addElement (obj); // apel metoda addElement din clasa Vector Collections.sort(this,comp); // ordonare dupa comparatorul primit } }

Apelul constructorului superclasei (super) trebuie s fie prima instructiune din constructorul subclasei. Dac trebuie efectuate anterior anumite operatii, atunci ele vor fi grupate ntr-o metod static (metode nestatice nu pot fi apelate naintea unui constructor, 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)); // argument de tip String } // citire continut fisier text intr-un sir ( functie statica !) static String readFile( String fname) { String text ="",line; // text va contine tot fisierul try { BufferedReader f= new BufferedReader( new FileReader(fname)); while ( (line=f.readLine()) !=null) text=text+line+"\n"; // text += line+\n; } catch (IOException ex) { ex.printStackTrace();} return text; } // verificare metode clasa FileTokenizer public static void main (String arg[]) { FileTokenizer t = new FileTokenizer (arg[0]); while ( t.hasMoreTokens()) System.out.print(" | "+ t.nextToken()); } }

57

Florian Moraru Programare Orientata pe Obiecte In exemplul urmtor clasa MyQueue (pentru liste de tip coad) este derivat din clasa MyList pentru liste simplu nlntuite. Cele dou clase folosesc n comun variabila prim (adresa de nceput a listei) si metoda toString. Metoda add este redefinit din motive de performant, dar are acelasi efect: adugarea unui nou element la sfrsitul listei.
public class MyQueue extends MyList { // Lista coada private Node ultim; // variabila prezenta numai in subclasa public MyQueue () { super(); // initializare variabila prim ultim=prim; // adresa ultimului element din lista } // adaugare la sfirsit coada (mai rapid ca in MyList) public void add (Object v) { Node nou= new Node(); nou.val=v; ultim.leg=nou; ultim=nou; } // eliminare obiect de la inceputul cozii public Object del () { if ( empty()) return null; Nod p=prim.leg; // primul nod cu date prim.leg=p.leg; if (empty()) // daca coada este goala ultim=prim; return p.val; } // test daca coada goala public boolean empty() { return prim.leg==null; } }

Din clasa MyList se poate deriva si o clas stiv realizat ca list nlntuit, cu redefinirea metodei de adugare add, pentru adugare la nceput de list, si cu o metod de extragere (si stergere) obiect de la nceputul listei. Ideea este c att coada ct si stiva sunt cazuri particulare de liste. Avantajele reutilizrii prin extinderea unei clase sunt cu att mai importante cu ct numrul metodelor mostenite si folosite ca atare este mai mare. Derivare pentru creare de tipuri compatibile Definirea unei noi clase este echivalent cu definirea unui nou tip de date, care poate fi folosit n declararea unor variabile, argumente sau functii de tipul respectiv. Prin derivare se creeaz subtipuri de date compatibile cu tipul din care provin. Tipurile superclas si subclas sunt compatibile, n sensul c o variabil (sau un parametru) de un tip A poate fi nlocuit fr conversie explicit cu o variabil (cu un parametru) de un subtip D, iar trecerea de la o subclas D la o superclas A se poate face prin conversie explicit (cast). Conversia de tip ntre clase incompatibile produce exceptia ClassCastException. Exemple de conversii ntre tipuri compatibile:
MyList a ; MyQueue q= new MyQueue(); a = q; // upcasting q = (MyQueue) a ; // downcasting

58

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. Conversia explicit de la tipul de baz la un subtip este necesar pentru a putea folosi metodele noi ale subclasei pentru obiectele subclasei.
MyList

downcasting
MyQueue

upcasting

De remarcat c dou subtipuri ale unui tip comun A nu sunt compatibile ( de ex. tipurile Integer si Float nu sunt compatibile, desi sunt derivate din tipul Number). Trecerea ntre cele dou tipuri nu se poate face prin operatorul de conversie, dar se poate face prin construirea altui obiect.
Number

Float

Integer

Procesul de extindere sau de specializare a unei clase poate continua pe mai multe niveluri, deci fiecare subclas poate deveni superclas pentru alte clase, si mai specializate. Posibilitatea de creare a unor tipuri compatibile si de utilizare a unor functii polimorfice constituie unul din motivele importante pentru care se foloseste derivarea. Aplicarea repetat a derivrii produce ierarhii de tipuri si familii de clase nrudite. Tipul superclasei este mai general dect tipurile subclaselor si de aceea se recomand ca acest tip s fie folosit la declararea unor variabile, unor argumente de functii sau componente ale unor colectii. In felul acesta programele au un caracter mai general si sunt mai usor de modificat. De exemplu clasa Scanner (java.util), care recunoaste si converteste automat numere din format extern n format intern, poate prelua textul de scanat si dintr-un flux de intrare-iesire. In acest scop exist urmtorii constructori cu argumente de tip interfat:
public Scanner (InputStream source); public Scanner (Readable source);

Tipurile InputStream si Readable sunt tipuri foarte generale, care include ca niste cazuri particulare fisiere text, siruri sau vectori de octeti s.a. In felul acesta se reduce numrul de constructori care ar fi fost necesar pentru fiecare clas instantiabil derivat din clasa abstract InputStream, de exemplu. Ierarhii de tipuri (primitive) exist si n limbajul C, permitnd s existe cte o singur functie matematic (sqrt, exp, log s.a.) pentru toate tipurile de numere (considerate ca subtipuri ale tipului double), n loc s avem cte o functie pentru fiecare tip numeric (int, float, s.a.). Putem constata n practica programrii cu obiecte c unele superclase transmit subclaselor lor putin functionalitate (putine metode mostenite ntocmai), deci motivul extinderii clasei nu este reutilizarea unor metode ci relatia special care apare ntre tipurile subclasei si superclasei. In general se definesc familii de clase, unele derivate din altele, care sunt toate compatibile ca tip cu clasa de la baza ierarhiei. Un exemplu este familia claselor exceptie, derivate direct din clasa Exception sau din subclasa RuntimeException. Instructiunea throw trebuie s contin un obiect de un tip compatibil cu tipul Exception; de obicei un subtip ( IOException, NullPointerException s.a.). O subclas a clasei Exception nu adaug metode noi si contine doar constructori:
public class RuntimeException extends Exception {

59

Florian Moraru Programare Orientata pe Obiecte


public RuntimeException() { super(); } public RuntimeException(String s) { super(s); } } // s= un mesaj suplimentar

Uneori superclasa este o clas abstract (neinstantiabil), care nu transmite metode subclaselor sale, dar creeaz clase compatibile cu clasa abstract. Un exemplu din Java 1.1 este clasa abstract Dictionary, care contine metode utilizabile pentru orice clas dictionar, indiferent de implementarea dictionarului: get, put, remove, keys, s.a. Clasa de bibliotec Hashtable extinde clasa Dictionary, fiind un caz particular de dictionar, realizat printr-un tabel de dispersie. Putem s ne definim si alte clase care s extind clasa Dictionary, cum ar fi un dictionar vector. Atunci cnd scriem un program sau o functie vom folosi variabile sau argumente de tipul Dictionary si nu de tipul Hashtable sau de alt subtip. Exemplu de creare a unui dictionar cu numrul de aparitii al fiecrui cuvnt dintr-un vector:
public static Dictionary wordfreq ( String v[]) { Dictionary dic = new Hashtable(); // tipul Hashtable se poate inlocui cu alt tip int cnt; // contor de aparitii for (int i=0; i< v.length; i++) { Object key = v[i]; // cuvintele sunt chei in dictionar Integer nr = (Integer) dic.get(key); // valoarea asociata cheii key in dictionar if ( nr != null) // daca exista cheia in dictionar cnt = nr.intValue()+1; // atunci se mareste contorul de aparitii else // daca cheia nu exista in dictionar cnt =1; // atunci este prima ei aparitie dic.put (key, new Integer(cnt)); // pune in dictionar cheia si valoarea asociata } return dic; }

Rezultatul functiei wordfreq poate fi prelucrat cu metodele generale ale clasei Dictionary, fr s ne intereseze ce implementare de dictionar s-a folosit n functie. Alegerea unei alte implementri se face prin modificarea primei linii din functie. Exemplu de folosire a functiei:
public static void main (String arg[]) { String lista[ ] = {"a","b","a","c","b","a"}; Dictionary d= wordfreq(lista); System.out.println (d); // scrie {c=1, b=2, a=3} }

Utilizarea unui tip mai general pentru crearea de functii si colectii cu caracter general este o tehnic specific programrii cu obiecte. In Java (si n alte limbaje) aceast tehnic a condus la crearea unui tip generic Object, 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, HashTable). Clasa Object ca baz a ierarhiei de clase Java In Java, clasa Object (java.lang.Object) este superclasa tuturor claselor JDK si a claselor definite de utilizatori. Orice clas Java care nu are clauza extends n definitie este implicit o subclas derivat direct din clasa Object. De asemenea, clasele abstracte si interfetele Java sunt subtipuri implicite ale tipului Object. Clasa Object transmite foarte putine operatii utile subclaselor sale; de aceea n alte ierarhii de clase (din alte limbaje) rdcina ierarhiei de clase este o clas abstract. Deoarece clasa Object nu contine

60

Florian Moraru Programare Orientata pe Obiecte nici o metod abstract, nu se impune cu necesitate redefinirea vreunei metode, dar n practic se redefinesc cteva metode. Metoda toString din clasa Object transform n sir adresa obiectului si deci trebuie s fie redefinit n fiecare clas cu date, pentru a produce un sir cu continutul obiectului. Metoda toString permite obtinerea unui sir echivalent cu orice obiect, deci trecerea de la orice tip la tipul String (dar nu prin operatorul de conversie ):
Date d = new Date(); String s = d.toString(); // dar nu si : String s = (String) d;

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); }

Pe de alt parte, 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. De aceea, metoda "equals" este rescris n clasele unde se poate defini o relatie de egalitate ntre obiecte. Exemplu din clasa Complex:
public boolean equals (Object obj) { Complex cpx =(Complex) obj; if ( obj != null && obj instanceof Complex) if (this.re == cpx.re && this.im == cpx.im) return true; return false; }

Pentru a evita erori de programare de tipul folosirii metodei "String.equals" cu argument de tip "Complex", sau a metodei "Complex.equals" cu argument de tip String se foloseste n Java operatorul instanceof, care are ca operanzi o variabil de un tip clas si tipul unei (sub)clase si un rezultat boolean. Redefinirea unei metode ntr-o subclas trebuie s pstreze aceeasi semntur cu metoda din superclas, deci nu se pot schimba tipul functiei sau tipul argumentelor. Din acest motiv, argumentul metodei "Complex.equals" este de tip Object si nu este de tip "Complex", cum ar prea mai natural. Dac am fi definit metoda urmtoare:
public boolean equals (Complex cpx) { ... }

atunci ea ar fi fost considerat ca o nou metod, diferit de metoda "equals" mostenit de la clasa Object. In acest caz am fi avut o supradefinire ("overloading") si nu o redefinire de functii. La apelarea metodei "Complex.equals" poate fi folosit un argument efectiv de tipul "Complex", deoarece un argument formal de tip superclas poate fi nlocuit (fr conversie explicit de tip) printrun argument efectiv de un tip subclas. Exist deci mai multe metode "equals" n clase diferite, toate cu argument de tip Object si care pot fi apelate cu argument de orice tip clas. Metoda "equals" nu trebuie redefinit n clasele fr date sau n clasele cu date pentru care nu are sens comparatia la egalitate, cum ar fi clasele flux de intrare-iesire. O variabil sau un argument de tip Object (o referint la tipul Object) poate fi nlocuit cu o variabil de orice alt tip clas, 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).

61

Florian Moraru Programare Orientata pe Obiecte Polimorfism si legare dinamic O functie polimorfic este o functie care are acelasi prototip, dar implementri (definitii) diferite n clase diferite dintr-o ierarhie de clase. Asocierea unui apel de metod cu functia ce trebuie apelat se numeste "legare" ("Binding"). Legarea se poate face la compilare (legare timpurie) sau la executie ("legare trzie" sau "legare dinamic"). Pentru metodele finale si statice legarea se face la compilare, adic un apel de metod este tradus ntr-o instructiune de salt care contine adresa functiei apelate. Legarea dinamic are loc n Java pentru orice metod care nu are atributul final sau static, metod numit polimorfic. In Java majoritatea metodelor sunt polimorfice. Metodele polimorfice Java corespund functiilor virtuale din C++. Metodele equals, toString sunt exemple tipice de functii polimorfice, al cror efect depinde de tipul obiectului pentru care sunt apelate (altul dect tipul variabilei referint). Exemple:
Object d = new Date(); Object f = new Float (3.14); String ds = d.toString(); // apel Date.toString() String fs = f.toString(); // apel Float.toString()

Apelul functiilor polimorfice este mai putin eficient ca apelul functiilor cu o singura form. Fiecare clas are asociat un tabel de pointeri la metodele (virtuale) ale clasei. Clasa MyQueue va avea o alt adres pentru functia "add" dect clasa MyList. Figura urmtoare arat cum se rezolv, la executie, apeluri de forma a.add(), q.add(), q.toString(), q.equals() s.a.
Object equals toString

equals toString

toString ...

toString

prim

aprim apri

MyList equals toString toString add

toString add

totoStrin

totoStri ... q
prim

aprim
ultim

MyQueu uee equals

aprim apri

toStrin
add

add del

totoStri del ...

Variabile

Obiecte

Clase

62

Florian Moraru Programare Orientata pe Obiecte Fiecare obiect contine, pe lng datele nestatice din clas, un pointer la tabela de metode virtuale (polimorfice). 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. Apelul q.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". Apelul q.toString conduce la metoda toString din superclas (MyList) deoarece este o metod mostenit si care nu a fost redefinit. Apelul q.equals merge n sus pe ierahia de clase si ajunge la definitia din clasa Object, deoarece nici o subclas a clasei Object nu redefineste metoda equals. Legtura dinamic se face de la tipul variabilei la adresa metodei apelate, n functie de tipul variabilei pentru care se apeleaz metoda. Fie urmtoarele variabile: MyList a, q; a = new MyList(); q = new MyQueue(); Solutia functiilor polimorfice este specific programrii orientate pe obiecte si se bazeaz pe existenta unei ierarhii de clase, care contin toate metode (nestatice) cu aceeasi semnatur, dar cu efecte diferite n clase diferite. Numai metodele obiectelor pot fi polimorfice, n sensul c selectarea metodei apelate se face n functie de tipul obiectului pentru care se apeleaz metoda. O metod static, chiar dac este redefinit n subclase, nu poate fi selectat astfel, datorit modului de apelare. Alegerea automat a variantei potrivite a unei metode polimorfice se poate face succesiv, pe mai multe niveluri. De exemplu, apelul metodei toString pentru un obiect colectie alege functia ce corespunde tipului de colectie (vector, lista nlntuit, etc); la rndul ei metoda toString dintr-o clas vector apeleaz metoda toString a obiectelor din colectie, n functie de tipul acestor obiecte (String, Integer, Date, etc.). O metod final (atributul final) dintr-o clas A, care este membr a unei familii de clase, este o metod care efectueaz aceleasi operatii n toate subclasele clasei A; ea nu mai poate fi redefinit. Exemplu:
public final boolean contains (Object elem) { return indexOf (elem) >=0 ; }

O metod static poate apela o metod polimorfic; de exemplu Arrays.sort apeleaz functia polimorfic compareTo (sau compare), al crei efect depinde de tipul obiectelor comparate, pentru a ordona un vector cu orice fel de obiecte. Exemplu de functie pentru cutare secvential ntr-un vector de obiecte:
public static int indexOf (Object[] array, Object obj) { for (int i=0;i<array.length;i++) if ( obj.equals (array[i])) // equals este polimorfica return i; // indice unde a fost gasit return -1; // negasit }

Pentru functia anterioar este esential c metoda equals este polimorfic si c se va selecta metoda de comparare din clasa creia apartine obiectul referit de variabila obj (aceeasi cu clasa elementelor din vectorul array). Structuri de date generice n POO O colectie generic este o colectie de date (sau de referinte la date) de orice tip, iar un algoritm generic este un algoritm aplicabil unei colectii generice. 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. Pentru a memora

63

Florian Moraru Programare Orientata pe Obiecte ntr-o colectie de obiecte si date de un tip primitiv (int, double, boolean s.a.) se pot folosi clasele anvelop ale tipurilor primitive: Integer, Short, Byte, Float, Double,Boolean, Char. Primele colectii generice din Java au fost clasele Vector, Hashtable si Stack dar din Java 1.2 sunt mai multe clase si sunt mai bine sistematizate. Cea mai simpl colectie generic este un vector cu componente de tip Object:
Object [ ] a = new Object[10]; for (int i=0;i<10;i++) a[i]= new Integer(i);

Vectorul intrinsec de obiecte generice se foloseste atunci cnd continutul su nu se modific sau se modific foarte rar, dar timpul de acces la elemente este important. Clasa Vector ofer extinderea automat a vectorului si metode pentru diferite operatii uzuale: cutare n vector, afisare n vector, insertie ntr-o pozitie dat s.a. In plus, numrul de elemente din vector se obtine prin metoda size si nu trebuie transmis separat ca argument functiilor, ca n cazul unui vector intrinsec (variabila length nu spune cte elemente sunt n vector ci doar capacitatea vectorului). Exemplu de functie care creaz un sir cu valorile dintr-un vector, pe linii separate:
public static String arrayToString ( Object a[], int n) { // vector intrinsec StringBuffer sb=new StringBuffer(); // sau StringBuilder n Java 5 for (int i=0;i<n;i++) sb.append ( a[i].toString()+\n); return sb.toString(); } public static String vectorToString ( Vector v) { // obiect vector StringBuffer sb=new StringBuffer(); // sau StringBuilder n Java 5 for (int i=0;i<v.size();i++) sb.append ( v.elementAt(i).toString()+"\n"); return sb.toString(); }

Ceea ce numim o colectie de obiecte este de fapt o colectie de pointeri (referinte) la obiecte alocate dinamic, dar aceast realitate este ascuns de sintaxa Java. Exemplu de adugare a unor siruri (referinte la siruri) la o colectie de tip Vector:
Vector v = new Vector (10); for (int k=1;k<11;k++) v.add ( k+); // aloca memorie pentru vector // sau v.add (String.valueOf(k));

Exist posibilitatea de a trece de la un vector intrinsec la o clas vector (metoda Arrays.asList) si invers (metoda toArray din orice clas colectie):
String a[]={1, 2, 3, 4}; Vector v = new Vector (Arrays.asList(a)); Object[] b = v.toArray();

Dac exist valori null n vectorul intrinsec, ele vor fi preluate de metoda asList n obiectul vector si pot produce exceptii la operatii asupra lor (si la ordonare). Un alt avantaj al utilizrii de clase colectie fat de vectori intrinseci este acela c se pot creea colectii de colectii, cu prelungirea actiunii unor functii polimorfice la subcolectii. De exemplu, metoda toString din orice colectie apeleaz repetat metoda toString pentru fiecare obiect din colectie (care, la rndul lui, poate fi o colectie).

64

Florian Moraru Programare Orientata pe Obiecte La extragerea unui element dintr-o colectie generic trebuie efectuat explicit conversia la tipul obiectelor introduse n colectie, pentru a putea aplica metode specifice tipului respectiv. Exemplu de creare a unui vector de siruri, modificarea sirurilor si afisarea vectorului :
public static void main (String args[]) { Vector v = new Vector() ; for (int i=0;i<args.length;i++) v.add (args[i]); // adauga sir la vector // modificare vector for (int i=0;i<v.size();i++) { String str = (String)v.elementAt(i); // sir din pozitia i v.setElementAt( str.toLowerCase(),i); // trece sir in litere mici } System.out.println(v); // afisare vector }

In Java, ca si n C, sunt posibile expresii ce contin apeluri succesive de metode, fr a mai utiliza variabile intermediare. Exemplu:
for (int i=0;i<v.size();i++) v.setElementAt ( ( (String)v.elementAt(i)).toLowerCase(), i);

Operatorul de conversie (cast) are prioritate mai mic dect operatorul punct si de aceea trebuie folosite paranteze pentru elementul cu tip schimbat, nainte de a folosi acest element pentru apelul unei metode. Expresiile cu metode nlntuite aplicate unor elemente din colectii Java necesit astfel de paranteze, dar notatiile se simplific n Java 5 dac se folosesc clase generice (cu tipuri declarate). S considerm o multime de obiecte realizat ca tabel de dispersie (hashtable). Tabelul de dispersie va fi realizat ca un vector de vectori, deci ca un obiect de tip Vector, avnd drept elemente obiecte de tip Vector (clasa Vector este si ea subclas a clasei Object). O solutie mai bun este cea din clasa JDK Hashtable, n care se foloseste un vector de liste nlntuite. Fiecare vector (sau list) contine un numr de sinonime, adic elemente cu valori diferite pentru care metoda de dispersie a produs un acelasi indice n vectorul principal. Metoda de dispersie are ca rezultat restul mprtirii codului de dispersie (produs de metoda hashCode) prin dimensiunea vectorului principal. Numrul de sinonime din fiecare vector nu poate fi cunoscut si nici estimat, dar clasa Vector asigur extinderea automat a unui vector atunci cnd este necesar.
public class HSet extends Vector { // tabel de dispersie ca vector de vectori private int n; // poate lipsi, este super.size() ! public HSet (int n) { // un constructor super(n); // alocare memorie ptr vector principal this.n=n; for (int i=0;i<n;i++) addElement (new Vector()); // aloca mem. ptr un vector de sinonime } public HSet () { // alt onstructor this(11); // dimensiune implicita vector principal } // apartenenta obiect la multime public boolean contains (Object el) { int k= (el.hashCode() & 0x7FFFFFFF) % n; // poz. n vector principal (nr pozitiv) return ((Vector)elementAt(k)).contains(el); // apel Vector.contains } // adaugare obiect la multime (daca nu exista deja) public boolean add (Object el) { // redefineste metoda add din Vector

65

Florian Moraru Programare Orientata pe Obiecte


int k = (el.hashCode() & 0x7FFFFFFF) % n; // poz. n vector principal (nr pozitiv) Vector sin = (Vector)elementAt(k); // vector de sinonime if ( sin.contains (el)) return false; sin.add (el); // adauga la vector din pozitia k return true; } // numar de obiecte din colectie public int size() { // redefineste metoda size din Vector int m=0; for (int k=0;k<n;k++) // ptr fiecare din cei n vectori m=m+((Vector)elementAt(k)).size(); // aduna dimensiune vector return m; } // conversie continut colectie n sir public String toString () { // redefineste metoda din Vector String s=""; for (int k=0;k<n;k++) { Vector v=(Vector)elementAt(k); if (v !=null) s=s + v.toString()+"\n"; } return s; }

Metodele polimorfice toString, contains si size din clasa HSet apeleaz metodele cu acelasi nume din clasa Vector. 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, care parcurge secvential toate elementele vectorului, dar o parte din ele pot avea valoarea null si genereaz exceptii la folosirea lor n expresii de forma v.toString() . Subliniem c variabilele unei colectii au tipul Object, chiar dac ele se refer la variabile de un alt tip (Integer, String, etc.); copilatorul Java nu vede dect tipul declarat (Object) si nu permite utilizarea de metode ale clasei din care fac parte obiectele reunite n colectie. Aceast situatie este destul de frecvent n Java si face necesar utilizarea operatorului de conversie (tip) ori de cte ori o variabil de un tip mai general (tip clas sau interfat) se refer la un obiect de un subtip al tipului variabilei. Exemplu:
Object obj = v.elementAt(i); // obiectul din vector poate avea orice tip Float f = (Float) obj; // sau: Float f = (Float) v.elementAt(i);

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). Incercarea de conversie ntre tipuri incompatibile poate fi semnalat la compilare sau la executie, printr-o exceptie. Exemple:
Float f = new Float(2.5); String s = (String) f; Object obj = f; String s = (String) obj; // 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). Pentru a evita astfel de erori la executie s-au introdus din Java 1.5 colectii generice care permit specificarea tipului obiectelor din colectie la definirea colectiei; 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>;

66

Florian Moraru Programare Orientata pe Obiecte 6. Interfete si clase abstracte Clase abstracte n Java Generalizarea si abstractizarea n programarea cu obiecte sunt realizate si prin clase abstracte. O superclas defineste un tip mai general dect tipurile definite prin subclasele sale; de exemplu, tipul Number este o generalizare a tipurilor clas numerice (Double, Float, Integer, Short, Byte). Uneori superclasa este att de general nct nu poate preciza nici variabile si nici implementri de metode, dar poate specifica ce operatii (metode) ar fi necesare pentru toate subclasele sale. In astfel de cazuri superclasa Java este fie o clas abstract, fie o interfat. O metod abstract este declarat cu atributul abstract si nu are implementare. Ea urmeaz a fi definit ntr-o subclas a clasei (interfetei) care o contine. Metodele abstracte pot apare numai n interfete si n clasele declarate abstracte. O clas care contine cel putin o metod abstract trebuie declarat ca abstract, dar nu este obligatoriu ca o clas abstract s contin si metode abstracte. O clas abstract nu este instantiabil, dar ar putea contine metode statice sau nestatice utilizabile n subclase. In Java avem dou posibilitti de a defini clase care contin doar metode statice si, ca urmare, nu pot fi instantiate (nu pot genera obiecte): - Clase ne-abstracte, cu constructor private, care mpiedic instantierea; - Clase abstracte, care nici nu au nevoie de un constructor Spre deosebire de interfete, clasele abstracte pot contine variabile si metode neabstracte. O clas abstract este de multe ori o implementare partial a unei interfete, care contine att metode abstracte ct si metode definite. Exemplu:
public interface Collection { // declara metode prezente in orice colectie int size(); // dimensiune colectie boolean isEmpty(); // verifica daca colectia este goala // alte metode } public abstract class AbstractCollection implements Collection { public abstract int size(); // metoda abstracta public boolean isEmpty ( return size()==0;} // metoda implementata }

Scopul unei clase abstracte nu este acela de a genera obiecte utilizabile, ci de a transmite anumite metode comune pentru toate subclasele derivate din clasa abstract (metode implementate sau neimplementate). O clas abstract constituie o baz de plecare pentru definirea altor clase. Derivarea dintr-o clas abstract se face la fel ca derivarea dintr-o clas neabstract folosind cuvntul extends. Exemplu:
public class MyCollection extends AbstractCollection { private Object [ ] array; // o colectie bazata pe un vector private int csize; // nr de elemente in colectie public MyCollection (int maxsize) { array = new Object[maxsize]; csize=0; // nr de obiecte in colectie } public int size() { return csize; } // alte metode }

67

Florian Moraru Programare Orientata pe Obiecte O clas abstract poate face o implementare partial, deci poate contine si metode neabstracte si/sau variabile n afara metodelor abstracte. Pentru a obtine clase instantiabile dintr-o clas abstract trebuie implementate toate metodele abstracte mostenite, respectnd declaratiiile acestora ( ca tip si ca argumente). In bibliotecile de clase Java exist cteva clase adaptor, care implementeaz o interfat prin metode cu definitie nul, unele redefinite n subclase. Ele sunt clase abstracte pentru a nu fi instantiate direct, dar metodele lor nu sunt abstracte, pentru c nu se stie care din ele sunt efectiv necesare n subclase. Exemplu de clas adaptor util n definirea de clase asculttor 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, fr s se preocupe de celalte metode nefolosite de subclas:
class KListener extends KeyAdapter { public void keyPressed(KeyEvent e) { char ch = e.getKeyChar(); // caracter generat de tasta apasata ... // folosire sau afisare caracter ch } }

Interfete Java O interfat Java ar putea fi considerat ca o clas abstract care nu contine dect metode abstracte si, eventual, constante simbolice. Totusi, exist deosebiri importante ntre interfete si clase abstracte. Metodele declarate ntr-o interfat sunt implicit publice si abstracte. De exemplu, interfata mai veche Enumeration contine dou metode comune oricrei clase cu rol de enumerare a elementelor unei colectii :
public interface Enumeration { boolean hasMoreElements(); Object nextElement(); } // 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, n vederea prelucrrii succesive a unui grup de obiecte. O clas (abstract sau instantiabil) poate implementa una sau mai multe interfete, prin definirea metodelor declarate n interfetele respective. Dintre clasele care implementeaz aceast interfat, sunt clasele enumerator pe vector si enumerator pe un tabel de dispersie Hashtable. Ulterior s-au adugat metode din aceast interfat si clasei StringTokenizer, care face o enumerare a cuvintelor dintr-un text:
public class StringTokenizer implements Enumeration { ... public boolean hasMoreElements() { return hasMoreTokens(); } public Object nextElement() { return nextToken(); } }

68

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, dar si scrierea unor metode general aplicabile oricrei clase care respect interfata comun. Exemplu de metod care calculeaz de cte ori apare un obiect ntr-o colectie de obiecte care are un enumerator:
public static int count (Enumeration enum, Object obj) { int m=0; while (enum.hasMoreElements() ) if (enum.nextElement().equals(obj) ) m++; return m; } // utilizare public static void main(String arg[]) { String a[]={2,1,3,1,1,2,4,1} ; Vector v = new Vector (Arrays.asList(a)); System.out.println (count (v.elements(),1)); }

Nu pot fi create obiecte de un tip interfat sau clas abstract, dar se pot declara variabile, argumente formale si functii de un tip interfat sau clas abstract. 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 fr conversie explicit cu o variabil de orice subtip, deci cu referinte la obiecte care implementeaz interfata sau care extind clasa abstract. Ca si n cazul claselor abstracte, se pot defini variabile, functii sau argumente de functii de un tip interfat. Astfel de variabile vor fi nlocuite (prin atribuire sau prin argumente efective) cu variabile de un tip clas care implementeaz interfata respectiv. Exemplu:
// variant pentru metoda Vector.toString public final String toString() { StringBuffer buf = new StringBuffer(); Enumeration e = elements(); // variabila de tip interfata ! buf.append("["); for (int i = 0 ; i <= size() ; i++) { String s = e.nextElement().toString(); // utilizarea variabilei interfata buf.append(s); buf.append(","); } buf.append("\b]"); return buf.toString(); } }

In exemplul anterior, metoda elements din clasa Vector are ca rezultat un obiect enumerator pe vector, de tipul Enumeration. Exemplu de posibil implementare a metodei elements din clasa Vector :
public Enumeration elements() { return new VectorEnumerator (this); } // definirea clasei iterator pe vector class VectorEnumerator implements Enumeration { private Vector v; private int crt = 0; // indice element curent din enumerare public VectorEnumerator (Vector v) {

69

Florian Moraru Programare Orientata pe Obiecte


this.v=v; } public boolean hasMoreElements() { return crt < v.size(); } public Object nextElement() { if (crt < v.size()) return v.elementAt (crt++); } }

O interfat poate extinde o alt interfat (extends) cu noi metode abstracte. Ca exemplu, interfata TreeNode reuneste metode de acces la un nod de arbore, 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, int index); // adauga un fiu acestui nod void remove(int index); // 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). Clasele care implementeaz o aceeasi interfat pot fi foarte diverse si nu formeaz o ierarhie (o familie). Un exemplu sunt clasele cu obiecte comparabile, care toate implementeaz interfata Comparable: String, Date, Integer, BigDecimal, s.a. O interfat este considerat ca un contract pe care toate clasele care implementeaz acea interfat se oblig s l respecte, deci o clas si asum obligatia de a defini toate metodele din interfetele mentionate n antetul ei, putnd contine si alte metode. O clas poate simultan s extind o clas (alta dect clasa Object, implicit extins) si s implementeze una sau mai multe interfete. Altfel spus, 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). De exemplu, mai multe clase predefinite SDK, cu date, implementeaz simultan interfetele Comparable (obiectele lor pot fi comparate si la mai mic mai mare), Clonable (obiectele lor pot fi copiate), Serializable (obiectele lor pot fi salvate sau serializate n fisiere disc sau pe alt mediu extern). Exemple:
public class String implements Comparable, Serializable { ...} public class Date implements Serializable, Clonable, Comparable { ...}

Este posibil ca n momentul definirii unei interfete s nu existe nici o singur clas compatibil cu interfata, cum este cazul interfetei Comparator. O interfat fr nici o metod poate fi folosit pentru a permite verificarea utilizrii unor metode numai n anumite clase, n faza de executie. Un exemplu tipic este interfata Clonable, definit astfel:
public interface Clonable { }

Clasa Object contine metoda "clone", folosit numai de clasele care declar c implementeaz interfata Clonable. Metoda neabstract "clone" este mostenit automat de toate clasele Java, dar este aplicabil numai pentru o parte din clase.

70

Florian Moraru Programare Orientata pe Obiecte Pentru a semnala utilizarea gresit a metodei "clone" pentru obiecte ne-clonabile, se produce o exceptie de tip CloneNotSupportedException atunci cnd ea este apelat pentru obiecte din clase care nu ader la interfata Clonable. O utilizare asemntoare o are interfata Serializable, pentru a distinge clasele ale cror obiecte sunt serializabile (care contin metode de salvare si de restaurare n / din fisiere) de clasele ale cror obiecte nu pot fi serializate ( fr obiecte "persistente"). Practic, toate clasele cu date sunt serializabile. Interfete ca Serializable si Clonable se numesc interfete de "marcare" a unui grup de clase ("tagging interfaces"), pentru a permite anumite verificri. Clasa Object nu contine o metod de comparare pentru stabilirea unei relatii de precedent ntre obiecte, dar dac s-ar fi decis ca metoda "compareTo" s fac parte din clasa Object, atunci ar fi fost necesar o interfat de "marcare" pentru a distinge clasele cu obiecte comparabile de clasele cu obiecte necomparabile. O interfat care stabileste un tip comun poate fi att de general nct s nu contin nici o metod. Un exemplu este interfata EventListener (pachetul "java.util"), care stabileste tipul asculttor la evenimente, dar metodele de tratare a evenimentului nu pot fi precizate nici ca prototip, deoarece depind de tipul evenimentului. Interfata este extins de alte interfete, specifice anumitor asculttori (pentru anumite evenimente):
public interface ActionListener extends EventListener { public void actionPerformed(ActionEvent e); } public interface ItemListener extends EventListener { public void itemStateChanged(ItemEvent e); }

Interfetele sunt preferabile n general claselor abstracte, pentru c ofer mai mult libertate subclaselor. Un exemplu simplu este cel al metodelor considerate esentiale pentru orice clas dictionar: pune pereche cheie-valoare in dictionar, obtine valoarea asociat unei chei date s.a. Aceste metode au fost reunite nainte de Java 1.2 ntr-o clas abstract (Dictionary) care continea numai metode abstracte. Incepnd cu Java 2 aceleasi metode (plus nc cteva) au fost grupate n interfata Map. Avantajul solutiei interfat n raport cu o clas abstract este evident atunci cnd 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, dar nu ar putea extinde si clasa abstract Dictionary . Interfetele si clasele abstracte nu trebuie opuse, 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, iar clasa abstract este o implementare partial a interfetei, pentru a facilita definirea de noi clase prin extinderea clasei abstracte. In felul acesta se obtine o familie de clase deschis pentru extinderi ulterioare cu clase compatibile, care nu trebuie definite ns de la zero. Un exemplu este interfata Set (sau Collection) si clasa AbstractSet (AbstractCollection) care permit definirea rapid de noi clase pentru multimi (altele dect HashSet si TreeSet), compatibile cu interfata dar care beneficiaz de metode mostenite de la clasa abstract. Compararea de obiecte n Java In unele operatii cu vectori de obiecte (sortare, determinare maxim sau minim s.a.) este necesar compararea de obiecte (de acelasi tip). Functia de comparare depinde de tipul obiectelor comparate, dar poate fi o functie polimorfic, cu acelasi nume, tip si argumente dar cu definitii diferite. In plus, dou obiecte pot fi comparate dup mai multe criterii (criteriile sunt variabile sau combinatii de variabile din aceste obiecte). In Java, functia compareTo este destinat comparatiei dupa criteriul cel mai natural (cel mai frecvent iar uneori si unicul criteriu). Pot fi comparate cu metoda compareTo numai clasele care implementeaz interfata Comparable si deci care definesc metoda abstract compareTo:

71

Florian Moraru Programare Orientata pe Obiecte


public interface Comparable { int compareTo (Object obj); }

// rezultat <0 sau = 0 sau >0

Clasele care declar implementarea acestei interfete trebuie s contin o definitie pentru metoda "compareTo", cu argument de tip Object. Exemple de clase Java cu obiecte comparabile : Integer, Float, String, Date, BigDecimal s.a. Exemplu:
public class Byte extends Number implements Comparable { private byte value; . . . // constructori, metode specifice public int compareTo( Object obj) { // compara doua obiecte Byte return this.value- ((Byte) obj).value ; } }

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. Exemplu:
// determinare maxim dintr-un vector de obiecte oarecare public static Object max (Object [ ] a) { Comparable maxim = (Comparable) a[0]; for (int i=0;i<a.length;i++) if ( maxim.compareTo(a[i]) < 0) // daca maxim < a[i] maxim=(Comparable)a[i]; return maxim; }

Functia Arrays.sort cu un argument foloseste implicit metoda compareTo. Putem considera c interfata Comparable adaug metoda compareTo clasei Object si astfel creeaz o subclas de obiecte comparabile. Este posibil ca egalitatea de obiecte definit de metoda compareTo s fie diferit de egalitatea definit de metoda equals. De exemplu, clasa Arc produce obiecte de tip arc de graf cu costuri:
class Arc implements Comparable { int v,w; // noduri capete arc float cost; // cost arc public Arc (int x,int y) { v=x; w=y; } public boolean equals (Object obj) { Arc a= (Arc)obj; return (v==a.v && w==a.w); // arce egale dac au aceleasi extremitati } public String toString () {return v+"-"+w; } public int compareTo (Object obj) { Arc a = (Arc) obj; return cost a.cost; // arce egale dac au costuri egale ! } }

Metoda equals este folosit la cutarea ntr-o colectie neordonat (contains, add pentru multimi, s.a.), iar metoda compareTo este folosit la cutarea ntr-o colectie ordonat (Collections.binarySearch), ceea ce poate conduce la rezultate diferite pentru cele dou metode de cutare ntr-o list de obiecte Arc.

72

Florian Moraru Programare Orientata pe Obiecte Pentru comparare de obiecte dup alte criterii dect cel natural s-a introdus o alt functie cu numele compare, parte din interfata Comparator:
public interface Comparator { int compare (Object t1, Object t2); } // implicit abstracta, ne-statica !

Interfata Comparator putea fi si o clas abstract cu o singur metod, pentru c este improbabil ca o clas comparator s mai mosteneasc si de la o alt clas. Functia Arrays.sort are si o form cu dou argumente; al doilea argument este de tipul Comparator si precizeaz functia de comparare (alta dect compareTo). Functia care determin maximul dintr-un vector de obiecte poate fi scris si astfel:
public static Object max (Object a[ ], Comparator c) { Object maxim = a[0]; for (int k=1; k<a.length;k++) if ( c.compare (maxim, a[k]) < 0) // c este un obiect comparator maxim = a[k]; return maxim; }

Functia max cu dou argumente este mai general si poate fi folosit pentru a ordona un acelasi vector dup diferite criterii (dup diverse proprietti). De exemplu, pentru ordonarea unui vector de siruri dup lungimea sirurilor, vom defini urmtoarea clas comparator:
class LengthComparator implements Comparator { public int compare (Object t1, Object t2) { return ((String)t1).length() - ((String)t2).length(); } } . . . // ordonare dupa lungime String [ ] a = {patru,trei,unu}; Arrays.sort( a, new LengthComparator());

Pentru ordonarea unei matrice de obiecte dup valorile dintr-o coloan dat putem defini o clas comparator cu un constructor care primeste numrul coloanei:
class CompCol implements Comparator { int c; // numar coloana ce determina ordinea public CompCol (int col) { c=col; } // constructor cu un argument public int compare(Object o1, Object o2) { Comparable c1 =(Comparable) ((Object[ ])o1)[c]; // linia o1, coloana c Comparable c2 =(Comparable) ((Object[ ])o2)[c]; // linia o2, coloana c return c1.compareTo(c2); // compara valorile din coloana c (liniile o1 si o2) } }

Exemplu de utilizare a acestui comparator n functia de sortare :


Object a[ ][ ] = { {"3","1","6"}, {"1","5","2"}, {"7","3","4"} }; for (int i=0;i<3;i++) { // pentru fiecare coloana i Arrays.sort(a,new CompCol(i)); // ordonare dupa coloana i printMat (a); // afisare matrice a }

73

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. Un filtru poate contine o singur metod cu rezultat boolean, care s spun dac obiectul primit ca argument este acceptat sau respins de filtru. In bibliotecile Java filtrele se folosesc n clasa File pentru listare selectiv de fisiere dintr-un director, dar pot fi folosite si n alte situatii. Clasa File din pachetul java.io poate genera obiecte ce corespund unor fisiere sau directoare. Ea este destinat operatiilor de listare sau prelucrare a fisierelor dintr-un director si nu contine functii de citire-scriere din/n fisiere. Cel mai folosit constructor din clasa File primeste un argument de tip String ce reprezint numele unui fisier de date sau unui fisier director. Cea mai folosit metod a clasei File este metoda list care produce un vector de obiecte String, cu numele fisierelor din directorul specificat la construirea obiectului File. Exemplu de folosire a unui obiect File pentru afisarea continutului directorului curent:
import java.io.*; class Dir { public static void main (String arg[]) throws IOException { String dir ="."; // "numele" directorului curent File d =new File(dir); // java.io.File d = new java.io.File(dir); String [ ] files = d.list(); // vector cu fisierele din directorul curent System.out.println ("Directory of "+ d.getAbsolutePath()); for (int i=0; i< files.length;i++) System.out.println (files[i]); } }

Metoda listFiles produce un vector de obiecte File ce corespund fisierelor din directorul pentru care se apeleaz metoda. Metodele list si listFiles au si o variant cu un argument de un tip interfat ce specific filtrul aplicat fisierelor, pentru extragere selectiv de fisiere din director: - metoda list cu argument de tip FilenameFilter - metoda listFiles cu argument de tip FileFilter sau FilenameFilter. Interfetele FileFilter si FilenameFilter contin o singur metod, numit accept:
public interface FilenameFilter { // prezenta din jdk 1.0 boolean accept (File path, String filename); // path=director(cale) } public interface FileFilter { // prezenta din jdk 1.2 boolean accept (File path); // 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. 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. Exemplu de listare selectiv cu masc a directorului curent:
// clasa pentru obiecte filtru class FileTypeFilter implements FileFilter { String ext; // extensie nume fisier public FileTypeFilter (String ext) { this.ext = ext; } public boolean accept (File f) {

74

Florian Moraru Programare Orientata pe Obiecte


String fname = f.getName(); int pp = fname.indexOf('.'); if (pp==0) return false; return ext.equals(fname.substring(pp+1)) ; } } // Listare fisiere de un anumit tip class Dir { public static void main (String arg[ ]) throws IOException { File d =new File(.); // din directorul curent File [ ] files = d.listFiles(new FileTypeFilter("java")); for (int i=0; i< files.length;i++) System.out.println (files[i]); } }

Acelasi filtru (dup extensie fisier) n varianta cu interfata FilenameFilter:


class DirFilter implements FilenameFilter { String ftype; public DirFilter (String ft) { ftype=ft;} public boolean accept (File dir, String name) { return name.indexOf(ftype) != -1; } }

// name= nume fisier // daca numele contine tipul ca subsir

Functii callback Functia "compare" poart numele de functie "callback", deoarece este apelat de metode existente (Collections.sort, Collections.max, s.a) dar trebuie furnizat de aplicatia care foloseste una din aceste metode. 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. Practic, se transmite un pointer printr-un obiect ce contine o singur functie; adresa obiectului comparator este transmis sort, iar sort foloseste adresa continut n obiectul comparator pentru a se referi napoi la functia "compare". In limbajul C se transmite un pointer explicit la o functie. Situatia mai poate fi schematizat si astfel: o functie A ("main", de ex.) apeleaz o functie B ("sort", de ex.), iar B apeleaz o functie X ("compare") dintr-un grup de functii posibile. Adresa functiei X este primit de B de la functia A. De fiecare dat cnd A apeleaz pe B i transmite si adresa functiei X, care va fi apelat napoi de B. La scrierea functiei de sortare (Collections.sort) nu se cunostea exact definitia functiei de comparare (pentru c pot fi folosite diverse functii de comparare), dar s-a putut preciza prototipul functiei de comparare, ca metod abstract inclus ntr-o interfat. Putem deci s scriem o functie care apeleaz functii nc nedefinite, dar care respect toate un prototip (tip rezultat, tip si numr de argumente). Exemplu:
// determinare maxim dintr-o enumerare de obiecte necunoscute public static Object max (Enumeration enum, Comparator c) { Object maxim = enum.nextElement(); // primul element din enumerare while (enum.hasMoreElements()) { Object next = enum.nextElement(); if ( c.compare (maxim, next) < 0) // apel functie de comparare maxim=next; }

75

Florian Moraru Programare Orientata pe Obiecte


return maxim; }

Tehnica callback este folosit la scrierea unor clase de bibliotec, ale cror functii apeleaz functii din programele de aplicatii, scrise ulterior de utilizatorii pachetului. Ideea general este c o parte din algoritmul implementat de clasa de bibliotec depinde de specificul aplicatiei care foloseste acest algoritm. Un algoritm de sortare foloseste o functie de comparare, dar functia de comparare face parte din aplicatie, pentru c ea compar date din aplicatie. Functia accept din interfetele filtru este tot o functie callback, apelat de metodele list si listFiles din clasa de bibliotec File, dar definit ca parte din aplicatie. Un alt exemplu este un analizor lexical SAX, 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). Analizorul SAX impune aplicatiei s foloseasc functii care respect interfetele definite de analizor si apeleaz aceste functii callback. In acest caz parserul SAX stie s recunoasc atomi XML dar nu stie ce s fac cu acesti atomi; n schimb aplicatia stie cum s prelucreze atomii XML dar este scutit de analiza documentelor XML (realizat de parser). Functiile callback sunt definite de cei care dezvolt aplicatii dar sunt apelate de ctre functii din clase predefinite (de bibliotec). 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), cu suport neprecizat, vzut ca sir de octeti. Operatiile elementare de citire si de scriere octet sunt aplicabile oricrui 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), care contin metodele abstracte read si write. 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). Ca suport concret al unui flux de date Java se consider urmtoarele variante: fisier disc, vector de octeti (n memorie), sir de caractere (n memorie), canal de comunicare ntre fire de executie (n memorie). Pentru fiecare tip de flux exist implementri diferite ale metodelor abstracte read si write pentru citire/scriere de octeti. Clasele abstracte InputStream si OutputStream contin si metode neabstracte pentru citire si scriere blocuri de octeti, care apeleaz metodele de citire/scriere octet, precum si alte metode care nu sunt definite dar nici nu sunt abstracte, transmise tuturor subclaselor. Exemplu:
public abstract class InputStream { public abstract int read ( ) throws IOException; public int read ( byte b[ ]) throws IOException { return read (b,0,b.length); } public int available( ) throws IOException { return 0; } ... // alte metode } // citeste un octet (-1 la sfarsit de fisier) // citeste un bloc de octeti // nr de octeti rmasi de citit

Clasele concrete de tip flux de citire sunt toate derivate din clasa InputStream, ceea ce se reflect si n numele lor: FileInputStream, ByteInputStream, etc. 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), pentru un flux vector de octeti metoda "read" preia urmtorul octet din vector. Clasa Reader difer de InputStream prin utilizarea tipului char n loc de byte:
public abstract class Reader {

76

Florian Moraru Programare Orientata pe Obiecte


protected Reader() { ... } // ptr a interzice instantierea clasei public int read() throws IOException { // citire caracter urmator (-1 la sfarsit de fisier) char cb[ ] = new char[1]; if (read(cb, 0, 1) == -1) return -1; else return cb[0]; } public int read(char cbuf[ ]) throws IOException { // citire bloc de octeti return read(cbuf, 0, cbuf.length); } abstract public int read(char cbuf[ ], int off, int len) throws IOException; abstract public void close() throws IOException; . . . // alte metode }

Interfata DataInput reuneste metode pentru citire de date de tipuri primitive:


public interface DataInput { char readChar () throws IOException; int readInt () throws IOException; long readLong () throws IOException; float readFloat () throws IOException; String readLine () throws IOException; ... }

Clasa RandomAccessFile implementeaz ambele interfete si deci asigur un fisier n care se poate scrie sau din care se poate citi orice tip primitiv. 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. Operatiile cu fisiere depind de sistemul de operare gazd, deci o parte din metodele claselor de I/E sunt metode "native", dar numrul lor este mentinut la minim. Un fragment din clasa RandomAccessFile arat astfel:
public class RandomAccessFile implements DataOutput, DataInput { private native void open (String name, boolean write) throws IOException; public native int read () throws IOException; public native void write (int b) throws IOException; public native long length () throws IOException; public native void close () throws IOException; ... // alte metode }

Clasele DataInputStream si DataOutputStream implementeaz interfetele DataInput si respectiv DataOutput. Aceste clase simplific crearea si exploatarea de fisiere text si de fisiere binare, ce contin date de un tip primitiv ( numere de diferite tipuri s.a. ). Exemplu de variant modificat a clasei DataInputStream :
public class DataInputStream extends InputStream implements DataInput { InputStream in; // in poate fi orice fel de flux (ca suport fizic) public DataInputStream (InputStream in) { this.in=in; } // constructor public final byte readByte() throws IOException { int ch = in.read(); if (ch < 0) throw new EOFException(); return (byte)(ch); } public final short readShort() throws IOException {

77

Florian Moraru Programare Orientata pe Obiecte


InputStream in = this.in; int ch1 = in.read(); int ch2 = in.read(); // citeste doi octeti if ((ch1 | ch2) < 0) throw new EOFException(); return (short)((ch1 << 8) + (ch2 << 0)); // asamblare octeti pe 16 biti } public String readLine ( ) { ... } // citeste o linie public int readInt ( ) { ... } // citeste un numar intreg public float readFloat ( ) { ... } // citeste un numar real . . . // alte metode }

Obiecte de tip DataInputStream nu pot fi create dect pe baza altor obiecte, care precizeaz suportul fizic al datelor. Clasa DataInputStream nu are constructor fr argumente si nici variabile care s specifice suportul datelor. Variabila public System.in este de un subtip (anonim) al tipului InputStream si corespunde tastaturii ca flux de date. Pentru obiectul adresat de variabila System.in se poate folosi metoda read pentru citirea unui octet (caracter) dar nu se poate folosi o metod de citire a unei linii. Pentru a citi linii de la consol (terminate cu Enter), putem crea un obiect de tip DataInputStream , care suport metoda readLine. Exemplu:
// citire linie de la tastatura public static void main (String arg[]) throws Exception { DataInputStream f = new DataInputStream(System.in); String line= f.readLine(); System.out.println ( S-a citit: "+ line);

} Varianta pentru citire de linii de la tastatur :


public static void main (String arg[ ]) throws IOException { BufferedReader stdin = new BufferedReader(new InputStreamReader (System.in)); String line; while ( (line=stdin.readLine()) != null) { // dac nu s-a introdus ^Z (EOF) System.out.println (line); } }

78

Florian Moraru Programare Orientata pe Obiecte 7. Colectii de obiecte Object Infrastructura claselor colectie O colectie este un obiect ce contine un numr oarecare, variabil de obiecte. Colectiile se folosesc pentru memorarea si regsirea unor date sau pentru transmiterea unui grup de date de la o metod la alta. Colectiile Java sunt structuri de date generice, realizate fie cu elemente de tip Object, fie cu clase sablon (cu tipuri parametrizate) . Infrastructura colectiilor (Collection Framework) ofer clase direct utilizabile si suport pentru definirea de noi clase (sub form de clase abstracte), toate conforme cu anumite interfete ce reprezint tipuri abstracte de date (liste, multimi, dictionare). Un utilizator si poate defini propriile clase colectie, care respect aceste interfete impuse si sunt compatibile cu cele existente (pot fi nlocuite unele prin altele). 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 adugare obiect la colectie si de calcul numr de elemente din colectie, care depind de structura fizic a colectiei). Familia claselor colectie din Java este compus din dou ierarhii de clase : - Ierarhia care are la baz interfata Collection, - Ierarhia care are la baz interfata Map. O colectie, n sensul Java, este un tip de date abstract care reuneste un grup de obiecte de tip Object, numite si elemente ale colectiei. Un dictionar (o asociere ) este o colectie de perechi cheivaloare (ambele de tip Object); fiecare pereche trebuie s aib o cheie unic, deci nu pot fi dou perechi identice. Interfata Collection contine metode aplicabile pentru orice colectie de obiecte. Nu toate aceste operatii trebuie implementate obligatoriu de clasele care implementeaz interfata Collection; o clas care nu defineste o metod optional poate semnala o exceptie la ncercarea de apelare a unei metode neimplementate. Urmeaz descrierea complet a interfetei Collection :
public interface Collection { // operatii generale ptr orice colectie int size(); // dimensiune colectie (nr de elemente) boolean isEmpty(); // verifica daca colectie vida boolean contains(Object element); // daca colectia contine un obiect dat boolean add(Object element); // adauga un element la colectie boolean remove(Object element); // elimina un element din colectie Iterator iterator(); // produce un iterator pentru colectie boolean containsAll(Collection c); // daca colectia contine elem. din colectia c boolean addAll(Collection c); // adauga elem. din c la colectie boolean removeAll(Collection c); //elimina din colectie elem. colectiei c boolean retainAll(Collection c); // retine in colectie numai elem. din c void clear(); // sterge continut colectie Object[ ] toArray(); // copiere colectie intr-un vector }

Din interfata Collection sunt derivate direct dou interfete pentru tipurile abstracte: - Set pentru multimi de elemente distincte. - List pentru secvente de elemente, n care fiecare element are un succesor si un predecesor si este localizabil prin pozitia sa n list (un indice ntreg). Pentru fiecare din cele 3 structuri de date abstracte (list, multime,dictionar) sunt prevzute cte dou clase concrete care implementeaz interfetele respective. Structurile de date concrete folosite pentru implementarea tipurilor de date abstracte sunt : vector extensibil, list dublu nlntuit, tabel de dispersie si arbore binar.

79

Florian Moraru Programare Orientata pe Obiecte Toate clasele colectie instantiabile redefinesc metoda toString, care produce un sir cu toate elementele colectiei, separate prin virgule si ncadrate de paranteze drepte. Afisarea continutului unei colectii se poate face printr-o singur instructiune. De asemenea sunt prevzute metode de trecere de la vectori intrinseci de obiecte (Object [] ) la colectii de obiecte si invers: functia Arrays.asList, cu un argument vector intrinsec de obiecte si rezultat de tip List si functia toArray din clasa AbstractCollection, de tip Object[]. Exemplu:
String sv[] = {unu,doi,trei}; List list = Arrays.asList(sv); // nu este nici ArrayList, nici LinkedList ! System.out.println (list); // System.out.println (list.toString()); String aux[] = (String[ ]) list.toArray(); // aux identic cu sv

O a treia ierarhie are la baz interfata Iterator, pentru metodele specifice oricrui iterator asociat unei colectii sau unui dictionar. Toate colectiile au iteratori dar nu si clasele dictionar (se poate ns itera pe multimea cheilor sau pe colectia de valori). Clasa Collections contine metode statice pentru mai multi algoritmi "generici" aplicabili oricrei colectii ("min", "max") sau numai listelor (sort, binarySearch, reverse, shuffle). Ei foloseau initial tipul generic Object si metodele polimorfice equals, compareTo s.a., dar acum folosesc tipuri parametrizate (din versiunea 5). Exemple n forma mai veche dar mai simpl:
public static Object min (Collection col ); // obiect minim din colectia col public static Object min (Collection col , Comparator cmp); public static void sort (List lst); // ordonare lista lst public static void sort (List lst, Comparator cmp);

Multimi de obiecte O multime este o colectie de elemente distincte pentru care operatia de cutare a unui obiect n multime este frecvent si trebuie s aib un timp ct mai scurt. Interfata Set contine exact aceleasi metode ca si interfata Collection, dar implementrile acestei interfete asigur unicitatea elementelor unei multimi. Metoda de adugare a unui obiect la o multime "add" verific dac nu exist deja un element identic, pentru a nu se introduce duplicate n multime. De aceea obiectele introduse n multime trebuie s aib metoda equals redefinit, pentru ca obiecte diferite s nu apar ca fiind egale la compararea cu equals. Clasele multime predefinite si care implementeaz interfata Set sunt: HashSet : pentru o multime neordonat realizat ca tabel de dispersie TreeSet : pentru o multime ordonat, realizat ca arbore binar echilibrat automat (Red-Black Tree). Tabelul de dispersie asigur cel mai bun timp de cutare, iar arborele echilibrat permite pstrarea unei relatii de ordine ntre elemente si un timp de cutare bun. Exemplul urmtor foloseste o multime de tipul HashSet pentru un graf reprezentat prin multimea arcelor (muchiilor) :
class Graph { // graf reprezentat prin multimea arcelor private int n; // numar de noduri in graf private Set arc; // lista de arce public Graph ( int n) { this.n =n; arc = new HashSet(); } public void addArc (int v,int w) { // adauga arcul (v,w) la graf arc.add (new Arc (v,w)); } public boolean isArc (int v,int w) { // daca exista arcul (v,w)

80

Florian Moraru Programare Orientata pe Obiecte


return arc.contains (new Arc(v,w)); } public String toString () { return arc.toString(); } }

In general se recomand programarea la nivel de interfat si nu la nivel de clas concret. Asadar, se vor folosi pe ct posibil variabile de tip Collection sau Set si nu variabile de tip HashSet sau TreeSet (numai la construirea obiectului colectie se specific implementarea sa). Operatiile cu dou colectii sunt utile mai ales pentru operatii cu multimi:
s1.containsAll (s2) s1.addAll (s2) s1.retainAll (s2) 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 urmtoare:


Set sdif = new HashSet(s1); sdif.addAll(s2); Set aux = new HashSet(s1); aux.retainAll (s2); simdif.removeAll(aux); // reuniune s1+s2 // intersectie s1*s2 in aux // reuniune minus intersectie

Exemplul urmtor 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. S-a folosit o multime ordonat, pentru a permite afisarea cuvintelor n ordine lexicografic.
public static void main (String arg[ ]) throws IOException { Set toate = new HashSet (); // toate cuvintele distincte din text Set dupl =new TreeSet (); // cuvintele cu aparitii multiple Scanner sc = new Scanner (new File(arg[0])); String word=""; // un cuvant din fisier while ( sc.hasNext() ) { // repeta cat mai sunt cuvinte in fisier word= sc.next(); // scoate urmatorul cuvant if ( ! toate.add (word) ) // daca a mai fost in toate dupl.add (word); // este o aparitie multipla } System.out.println ("multiple: "+ dupl); // afisare cuvinte repetate Set unice = new TreeSet (toate); unice.removeAll (dupl); // elimina cuvinte multiple System.out.println ("unice: " + unice); // afiseaza cuvinte cu o singura aparitie }

Nici una din multimile HashSet sau TreeSet nu pstreaz ordinea de adugare a elementelor la multime, dar clasa LinkedHashSet contine o metod toString care produce un sir cu elementele multimii n ordinea adugrii lor la multime, dar are aceleasi performante la cutare ca HashSet. Interfata SortedSet,implementat de clasa TreeSet, extinde interfata Set cu cteva metode aplicabile numai pentru colectii ordonate:
Object first(), Object last(), // primul si ultimul element din multime SortedSet subSet(Object from, Object to), // submultimea definita prin 2 valori SortedSet headSet(Object to), // subSet (first(),to) SortedSet tailSet (Object from) // subSet (from, last())

81

Florian Moraru Programare Orientata pe Obiecte Liste secventiale Interfata List contine cteva metode suplimentare fat de interfata Collection : - Metode pentru acces pozitional, pe baza unui indice ntreg care reprezint pozitia : get (index), set (index,object), add (index, object), remove (index) - Metode pentru determinarea pozitiei unde se afl un element dat, deci cutarea primei si ultimei aparitii a unui obiect ntr-o list:
indexOf (object), lastIndexOf (object)

- Metode pentru crearea de iteratori specifici listelor:


listIterator (), listIterator (index)

- Metod de extragere sublist dintr-o list:


List subList(int from, int to);

Exist dou implementri pentru interfata List: ArrayList list vector, preferabil pentru acces aleator frecvent la elementele listei. LinkedList list dublu nlntuit, preferat cnd sunt multe inserri sau stergeri de elemente n list. In plus, clasei Vector i-au fost adugate noi metode pentru a o face compatibila cu interfata List. 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, int) void insertElementAt (Object, int)

Forma nou (1.2)


Object get(int) Object set (i, Object) void add (i,Object)

Exemplu de functie care schimb ntre ele valorile a dou elemente i si j:


static void swap (List a, int i, int j) { // din clasa Collections Object aux = a.get(i); // a.elementAt(i) a.set (i,a.get(j)); // a.setElementAt (a.elementAt(j) , i) a.set (j,aux); // a.setElementAt (aux , j) }

De observat c metodele "set" si "remove" au ca rezultat vechiul obiect din list, care a fost modificat sau eliminat. Metoda set se poate folosi numai pentru modificarea unor elemente existente n list, nu si pentru adugare de noi elemente. Algoritmii de ordonare si de cutare binar sunt exemple de algoritmi care necesit accesul direct, prin indici, la elementele listei; de aceea metodele sort si binarySearch din clasa Collections au argument de tip List si nu Collection. Urmeaz o variant a metodei binarySearch; n caz de obiect negsit, rezultatul este pozitia unde ar trebui inserat obiectul cutat astfel ca lista s rmn ordonat ( decalat cu 1 si cu minus).
public static int binSearch (List list, Object key) { int low = 0, high = list.size()-1; while (low <= high) { int mid =(low + high)/2; Object midVal = list.get(mid); // acces prin indice int cmp = ((Comparable)midVal).compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // gasit } return -(low + 1); // negasit }

82

Florian Moraru Programare Orientata pe Obiecte Exemplu de utilizare a functiei de cutare ntr-o secvent care adaug un obiect la o list ordonat, astfel ca lista s rmn ordonat:
int pos = Collections.binarySearch (alist,key); if (pos < 0) // daca key nu exista in lista alist.add (-pos-1, key); // se adauga key inainte de primul element mai mare ca el

Accesul pozitional este un acces direct, rapid la vectori dar mai putin eficient n cazul listelor nlntuite. De aceea s-a definit o clas abstract AbstractSequentialList, care este extins de clasa LinkedList dar nu si de clasa ArrayList. Metoda static Collections.binarySearch, cu parametru de tipul general List, recunoaste tipul de list si face o cutare binar n vectori, dar o cutare secvential n liste secventiale. Clasa LinkedList contine, n plus fat de clasa abstract AbstractList, urmtoarele metode, utile pentru cazuri particulare de liste (stive, cozi etc.): getFirst(), getLast(), removeFirst(), removeLast(), addFirst(Object), addLast(Object). Din versiunea 5 au mai fost adugate interfetele Queue si Deque cu cteva metode noi, interfete implementate de mai multe clase, printre care AbstractQueue, PriorityQueue, LinkedList, ArrayDeque Interfata Queue extinde interfata Collection cu o metod de adugare offer care nu produce exceptie dac nu mai este loc n colectie si, mai important, cu metode de acces la primul element din colectie (din coad) cu si fr exceptie n caz c nu exist (element si peek fr eliminare din coad, poll si remove cu eliminare din coad). Interfata Deque extinde interfata Queue cu metode de acces si la primul si la ultimul element din list: addFirst, removeFirst, getFirst, addLast, removeLast, getLast si variantele care nu produc exceptii: offer, poll, peek ( offerFirst, offerLast,). Cele mai multe clase de tip coad (ca BlockingQueue si SynchronousQueue) au fost introduse, alturi de alte clase, n pachetul java.util.concurrent pentru programare cu fire de executie concurente. Pentru aplicatiile cu structuri de date sunt interesante clasele ArrayDeque si PriorityQueue; clasa LinkedList implementeaz acum si interfata Deque si de aceea nu exist o clas LinkedDeque. Coada cu prioritti este implementat ca un vector heap extensibil (fr limite de capacitate). Indiferent de ordinea de adugare la coad, elementul din fat (obtinut prin una din metodele peek, poll sau remove) este cel cu prioritatea minim. Prioritatea este determinat de ctre metoda compareTo a obiectelor introduse n coad (constructor fr argumente) sau de ctre metoda compare a obiectului comparator transmis ca argument la construirea unei cozi. In exemplul urmtor se adaug si se scoate dintr-o coad de arce ordonat dup costuri, n ipoteza c metoda compareTo din clasa Arc compar costuri:
PriorityQueue<Arc> pq = new PriorityQueue<Arc>(); pq.add (new Arc(v,w,cost)); // adauga arc la coada pq // afisare coada in ordinea prioritatilor (costului arcelor) while ( ! pq.isEmpty()) System.out.println( pq.remove()); // scoate si sterge din coada

Clase dictionar Interfata Map contine metode specifice operatiilor cu un dictionar de perechi cheie valoare, n care cheile sunt unice . Exist trei implementri pentru interfata Map: HashMap dictionar realizat ca tabel de dispersie, cu cel mai bun timp de cutare. TreeMap dictionar realizat ca arbore echilibrat, care garanteaz ordinea de enumerare. LinkedHashMap tabel de dispersie cu mentinere ordine de introducere (din vers. 1.4) Definitia simplificat a interfetei Map este urmtoarea:

83

Florian Moraru Programare Orientata pe Obiecte


public interface Map { Object put(Object key, Object value); // pune o pereche cheie-valoare Object get(Object key); // extrage valoare asociat unei chei date Object remove(Object key); // elimin pereche cu cheie dat boolean containsKey(Object key); // verifica daca exista o cheie data boolean containsValue(Object value); // verifica daca exista o valoare data int size(); // dimensiune dictionar (nr de perechi) boolean isEmpty(); void clear(); // elimina toate perechile din dictionar Set keySet(); // multimea cheilor Collection values(); // colectia valorilor din dictionar Set entrySet(); // multimea perechilor cheie-valoare }

Metoda get are ca rezultat valoarea asociat unei chei date sau null dac cheia dat nu se afl n dictionar. 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. Efectul metodei put (k,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). Testul de apartenent a unei chei date la un dictionar se poate face fie direct prin metoda containsKey, fie indirect prin verificarea rezultatului operatiei get. Clasele care genereaz obiecte memorate ntr-un obiect HashMap sau HashSet trebuie s redefineasc metodele "equals" si "hashCode", astfel nct s se poat face cutarea la egalitate dup codul de dispersie. In exemplul urmtor se afiseaz numrul de aparitii al fiecrui cuvnt distinct dintr-un text, folosind un dictionar-arbore pentru afisarea cuvintelor n ordine.
class FrecApar { // frecventa de aparitie a cuvintelor intr-un text private static final Integer ONE= new Integer(1); // o constanta public static void main (String arg[]) { Map dic = new TreeMap (); String text = trei unu doi trei doi trei ; StringTokenizer st = new StringTokenizer (new String (text)); String word; while ( st.hasMoreTokens()) { word = st.nextToken(); Integer nrap = (Integer) dic.get(word); if (nrap == null) dic.put (word,ONE); // prima aparitie else dic.put (word, new Integer (nrap.intValue()+1)); // alta aparitie } System.out.println (dic); // afisare dictionar } }

Cheile dintr-un dictionar pot fi extrase ntr-o multime cu metoda keySet, iar valorile din dictionar pot fi extrase ntr-o colectie (o list) cu metoda values.Metoda entrySet produce o multime echivalent de perechi "cheie-valoare", unde clasa pereche are tipul Entry. Entry este o interfat inclus n interfata Map si care are trei metode: getKey, getValue si setValue. De observat c metodele entrySet,keySet si values (definite n AbstractMap) creeaz doar imagini noi asupra unui dictionar si nu alte colectii de obiecte; orice modificare n dictionar se va

84

Florian Moraru Programare Orientata pe Obiecte reflecta automat n aceste imagini, fr ca s apelm din nou metodele respective. O imagine este creat printr-o clas care defineste un iterator pe datele clasei dictionar si nu are propriile sale date. Metodele clasei imagine sunt definite apoi pe baza iteratorului, care d acces la datele din dictionar. 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, n timp ce la HashMap ordinea este aparent ntmpltoare (ea depinde de capacitatea tabelei de dispersie, de functia hashCode si de ordinea de introducere a cheilor). Pentru pstrarea ordinii de adugare se foloseste o list nlntuit, iar tabelul de dispersie asigur un timp bun de regsire dup cheie. Colectii ordonate Problema ordonrii este rezolvat diferit pentru liste fat de multimi si dictionare. Listele sunt implicit neordonate (se adaug numai la sfrsit de list) si pot fi ordonate numai la cerere, prin apelul unei metode statice (Collections.sort). Multimile, ca si dictionarele, au o variant de implementare (printr-un arbore binar ordonat) care asigur mentinerea lor n ordine dup orice adugare sau eliminare de obiecte. Exemplu de ordonare a unei liste:
public static void main (String arg[ ]) { String tab[ ] = {"unu","doi","trei",patru,cinci}; List lista = Arrays.asList (tab); Collections.sort (lista); System.out.println (lista); // [cinci,doi,patru,trei,unu] }

Putem s ne definim vectori sau liste ordonate automat, sau alte alte structuri compatibile cu interfata List si care asigur ordinea (un arbore binar, de exemplu). 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; // adresa obiectului comparator public SortedArray () { super();} public SortedArray (Comparator comp) { super(); cmp=comp; // retine adresa obiectului comparator } public boolean add (Object obj) { // adaugare la vector ordonat boolean modif= super.add(obj); // daca s-a modificat colectia dupa adaugare if ( ! modif) return modif; // colectia nemodificata ramane ordonata if ( cmp==null) Collections.sort (this); // ordonare dupa criteriul natural else Collections.sort (this,cmp); // ordonare cu comparator primit din afara return modif; } }

Problema clasei anterioare este c ar trebui redefinit si metoda set care modific elemente din list, pentru a mentine lista ordonat. Acest lucru nu este posibil prin folosirea metodei Collections.sort deoarece aceasta apeleaz metoda set si apare o recursivitate indirect infinit de forma set -> sort -> set -> sort -> set ->

85

Florian Moraru Programare Orientata pe Obiecte O multime ordonat este de tipul TreeSet iar un dictionar ordonat este de tipul TreeMap. Se pot defini si alte tipuri de colectii sau dictionare ordonate, care implementeaz (optional) interfata SortedSet, respectiv interfata SortedMap. Adugarea sau modificarea valorilor dintr-un arbore se face cu mentinerea ordinii si nu necesit reordonarea multimii sau dictionarului. Obiectele introduse ntr-o colectie TreeSet sau TreeMap trebuie s apartin unei clase care implementeaz interfata Comparable si contine o definitie pentru metoda compareTo. Exemplu de ordonare a unei liste de nume (distincte) prin crearea si afisarea unei multimi ordonate:
SortedSet lst = new TreeSet (lista); // sau se adauga cu metoda addAll

Iteratorul unei colectii ordonate parcurge elementele n ordinea dictat de obiectul comparator folosit mentinerea colectiei n ordine. O problem comun colectiilor ordonate este criteriul dup care se face ordonarea, deci functia de comparatie, care depinde de tipul obiectelor comparate. Sunt prevzute dou solutii pentru aceast problem, realizate ca dou interfete diferite si care au aplicabilitate distinct. Anumite metode statice (sort, min, max s.a.) si unele metode din clase pentru multimi ordonate apeleaz n mod implicit metoda "compareTo", parte a interfetei Comparable. Clasele JDK cu date (String, Integer, Date s.a.) implementeaz interfata Comparable si deci contin o metoda "compareTo" pentru o ordonare "natural" ( exceptie face clasa Boolean, ale crei obiecte nu sunt comparabile). Ordinea natural este ordinea valorilor algebrice (cu semn) pentru toate clasele numerice, este ordinea numeric fr semn pentru caractere si este ordinea lexicografic pentru obiecte de tip String. Pentru alte clase, definite de utilizatori, trebuie implementat interfata Comparable prin definirea metodei "compareTo", dac obiectele clasei pot fi comparate (si sortate). Pentru ordonarea dup un alt criteriu dect cel natural si pentru ordonarea dup mai mlte criterii se va folosi o clas compatibil cu interfata Comparator. Rezultatul metodei "compare" este acelasi cu al metodei "compareTo", deci un numr negativ dac ob1<ob2, zero dac ob1==ob2 si un numr pozitiv dac ob1>ob2. Un argument de tip Comparator apare n constructorii unor clase si n cteva metode din clasa Collections (sort, min, max) ce necesit compararea de obiecte. Pentru a utiliza o functie compare trebuie definit o clas care contine numai metoda "compare", iar un obiect al acestei clase se transmite ca argument functiei. Exemplu de ordonare a dictionarului de cuvinte-frecvent creat anterior, n ordinea invers a numrului de aparitii:
Set entset = dic.entrySet(); ArrayList entries = new ArrayList(entset); Collections.sort (entries, new Comp()); ... // clasa comparator obiecte Integer in ordine inversa celei naturale class Comp implements Comparator { public int compare (Object o1, Object o2) { Map.Entry e1= (Map.Entry)o1, e2= (Map.Entry)o2; // e1,e2=prechi cheie-val return ((Integer)e2.getValue()).compareTo ((Integer) e1.getValue()); } }

Din versiunea 5 s-a introdus n clasa Collections si un comparator pentru ordinea inversa:
Comparator reverseOrder(Comparator cmp); // 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 aplicrii unei prelucrri fiecrui element obtinut prin enumerare. Realizarea concret a enumerrii depinde de tipul colectiei si foloseste un cursor care nainteaz de la un element la altul, ntr-o anumit ordine (pentru colectii neliniare). Cursorul este un indice ntreg n

86

Florian Moraru Programare Orientata pe Obiecte cazul unui vector sau un pointer (o referint) pentru o list nlntuit sau pentru un arbore binar. Pentru colectii mai complexe, cum ar fi vectori de liste, enumerarea poate folosi indici (pentru vectori) si pointeri (pentru noduri legate prin pointeri). 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. Orice clas colectie Java 2 poate avea o clas iterator asociat. Pentru un acelasi obiect colectie (de ex. un vector) pot exista mai multi iteratori, care progreseaz n mod diferit n cadrul colectiei, pentru c fiecare obiect iterator are o variabil cursor proprie. Toate clasele iterator rebuie s includ urmtoarele operatii: - Pozitionarea pe primul element din colectie - Pozitionarea pe urmtorul element din colectie - Obtinerea elementului curent din colectie - Detectarea sfrsitului colectiei (test de terminare a enumerrii). Interfata Iterator contine metode generale pentru orice iterator:
public interface Iterator { boolean hasNext(); Object next(); void remove(); } // daca exista un element urmator in colectie // extrage element curent si avans la urmtorul // elimina element curent din colectie (optional)

De observat c modificarea continutului unei colectii se poate face fie prin metode ale clasei colectie, fie prin metoda remove a clasei iterator, dar nu ar trebui folosite simultan ambele moduri de modificare. In exemplul urmtor apare o exceptie de tip ConcurrentModificationException:
ArrayList a = new ArrayList(); Iterator it = a.iterator(); while (it.hasNext()) { a.add( "x"); it.next(); }

Pentru fiecare clas concret de tip colectie exist o clas iterator. Un obiect iterator este singura posibilitate de enumerare a elementelor unei multimi si o alternativ pentru adresarea prin indici a elementelor unei liste. Un dictionar nu poate avea un iterator, dar multimea perechilor si multimea cheilor din dictionar au iteratori. Clasele iterator nu sunt direct instantiabile (nu au constructor public), iar obiectele iterator se obtin prin apelarea unei metode a clasei colectie (metoda iterator). In felul acesta, programatorul este obligat s creeze nti obiectul colectie si numai dup aceea obiectul iterator. 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. Exemplu:
public static void sort (List list) { Object a[] = list.toArray(); // transforma lista in vector intrinsec Arrays.sort(a); // ordonare vector intrinsec (mai eficienta) ListIterator i = list.listIterator(); for (int j=0; j<a.length; j++) { // modificare elemente din lista i.next(); i.set(a[j]); } }

Pentru enumerarea unei colectii se va folosi o variabil de tip Iterator si nu metoda iterator(), deoarece fiecare apel al metodei reinitializeaz iteratorul prin pozitionare pe primul element.

87

Florian Moraru Programare Orientata pe Obiecte Fragmentul urmtor 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(); while (e.hasNext()) if (o.equals(e.next())) return true; return false; } public Object[] toArray() { Object[] result = new Object[size()]; Iterator e = iterator(); for (int i=0; e.hasNext(); i++) result[i] = e.next(); return result; } public boolean addAll (Collection c) { boolean modified = false; Iterator e = c.iterator(); while (e.hasNext()) { if(add(e.next())) modified = true; } return modified; } }

Interfata ListIterator contine metode pentru traversarea unei liste n ambele sensuri si pentru modificarea elementelor enumerate : hasNext(), next(), hasPrevious(), previous(), nextIndex(), previousIndex(), remove(), set (Object o), add(Object o). Exemplu de parcurgere a unei liste de la coad la capt:
List a = new LinkedList(); for (ListIterator i= a.listIterator (a.size()); i.hasPrevious(); ) System.out.println (i.previous());

Metoda listIterator() pozitioneaz cursorul pe nceputul listei, iar metoda listIterator(index) pozitioneaz initial cursorul pe indicele specificat. O clas dictionar (Map) nu are un iterator asociat, dar este posibil extragerea unei multimi de chei sau unei multimi de perechi cheie-valoare dintr-un dictionar, iar aceste multimi pot fi enumerate. Secventa urmtoare face o enumerare a perechilor cheie-valoare dintr-un dictionar map, folosind interfata public Entry, definit n interiorul interfetei Map:
for (Iterator it= map.entrySet().iterator(); it.hasNext();) { Map.Entry e = (Map.Entry) it.next(); System.out.println ( e.getKey()+:+e.getValue()); }

Definirea de noi clase colectie Din versiunea 1.5 s-a introdus tipul de date definit prin enumerare (si cuvntul cheie enum), precum si dou colectii performante pentru elemente de un tip enumerat: EnumSet si EnumMap

88

Florian Moraru Programare Orientata pe Obiecte (implementate prin vectori de biti). Biblioteca de clase Java mai contine si clase pentru crearea si prelucrarea de arbori multici, n pachetul javax.swing.tree: clasa instantiabil DefaultMutableTreeNode pentru un nod de arbore, clasa DefaultTreeModel pentru un (model de) arbore si interfete pentru alte eventuale implementri. Desi ele au fost create special pentru clasa JTree care asigur vizualizarea acestor arbori, cu facilitti de expandare/contractie noduri, selectie de noduri, s.a., pot fi folosite si separat de clasa JTree. Definirea unor noi clase colectie poate fi necesar din mai multe motive: - Cerinte de performant (de memorie ocupat sau timp de acces) impun alte implementri pentru tipurile abstracte de date existente; de exemplu o multime de obiecte realizat ca vector sau ca list simplu nlntuit. - Sunt necesare alte tipuri abstracte de date neimplementate n SDK: graf, colectie de multimi disjuncte, arbore binar s.a. Aceste noi clase colectie ar trebui s se conformeze cadrului creat de interfetele Collection, Set, List, Map, Iterator s.a. si pot s refoloseasc metode definite n clasele abstracte si n clasele instantiabile deja existente. In Java, elementele unei colectii pot fi de orice tip derivat din Object, deci pot fi la rndul lor colectii, ceea ce simplific definirea si utilizarea colectiilor de colectii. Exemplul urmtor este o clas pentru grafuri memorate prin liste de adiacente. Se foloseste un vector de liste nlntuite:
public class Graph { // graf reprezentat prin liste de adiacente private int n; // numar de noduri in graf private List a; // vector sau lista de noduri public Graph ( int n) { this.n =n; a = new ArrayList (n); // aloca memorie pentru vector for (int v=0;v<n;v++) a.add (new LinkedList()); // adauga o lista vida de vecini } public void addArc (int v,int w) { // adauga arcul (v,w) la graf List vlist = (List)a.get(v); // lista de vecini ai nodului v vlist.add(new Integer(w)); // adauga w la vecinii lui v } public boolean isArc (int v,int w) { // verifica daca arc de la v la w List vlist = (List)a.get(v); // lista vecinilor lui v return vlist.contains (new Integer(w)); // daca contine nodul w } public String toString () { // un sir cu toate listele de adiac. return a.toString(); } }

Clasa abstract AbstractCollection, compatibil cu interfata Collection, contine implementri pentru o serie de operatii care pot fi realizate indiferent de tipul colectiei, folosind iteratori : isEmpty, contains, toArray, remove, containsAll, addAll, removeAll, retainAll, clear, toString. Clasele AbstractSet si AbstractList extind clasa AbstractCollection, iar clasele instantiabile pentru multimi si liste extind aceste clase abstracte. Astfel se mostenesc toate functiile mentionate, desi unele din ele sunt redefinite din motive de performant. Prin extinderea claselor abstracte se pot defini noi clase colectie cu un efort de programare minim si cu pstrarea compatibilittii cu interfetele comune (List, Set, Map). Exemplu de clas pentru obiecte dictionar realizate ca vectori de perechi:
public class ArrayMap extends AbstractMap { // dictionar vector de perechi private ArraySet entries ; // multime-vector de perechi cheie-valoare public ArrayMap (int n) { entries = new ArraySet(n); } public Object put ( Object key, Object value) {

89

Florian Moraru Programare Orientata pe Obiecte


int i = entries.indexOf( new MEntry(key,value)); if (i<0) { // daca cheia key nu exista anterior in dictionar entries.add (new MEntry(key,value)); // se adauga o noua pereche return null; } else { // daca cheia key exista deja in dictionar Object v = ((MEntry) entries.get(i)).getValue(); // valoare asociata anterior cheii entries.set (i, new MEntry(key,value)); // modifica perechea return v; } } public Set entrySet ( ) { return entries; } public String toString( ) { return entries.toString(); } } // multime-vector de obiecte class ArraySet extends AbstractSet { public int size() { return entries.size(); } public Iterator iterator () { return new ASIterator();} } // clasa iterator (inclusa in clasa ArrayMap) class ASIterator implements Iterator { int i; ASIterator () { i=0; } public boolean hasNext() { return i < entries.size();} public Object next() { Object e= (Entry)entries.get(i); i++; return e; } public void remove () { entries.remove(i-1); } } // multimea cheilor // mostenita din AbstractMap dar redefinita

Clasa Entry implementeaz interfata Map.Entry si redefineste metoda equals (eventual si metoda toString):
class Entry implements Map.Entry { private Object key,val; public Entry (Object k, Object v) { key=k; val=v; } public Object getKey() { return key; } public Object getValue() { return val;} public Object setValue (Object v) { val=v; return v;} public boolean equals (Object obj) { return ((Entry)obj).getKey().equals(key); } public String toString() { return key+":"+val;} }

90

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, adic o alt imagine (perspectiv) asupra unei colectii de date din memoria RAM sau de pe un suport extern. Prima utilizare a acestui termen a aprut n domeniul bazelor de date unde exist posibilitatea prezentrii datelor din mai multe tabele fizice sub forma unui nou tabel ( cu o parte din coloanele tabelelor fizice), 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 cutare. In Java problema se pune de a privi un vector intrinsec sau o colectie ca fiind de un alt tip, dar fr a copia datele ntr-un alt tip de colectie. Exemplul cel mai simplu este metoda static Arrays.asList() care ne permite s vedem un vector intrinsec ca o colectie nemodificabil de un tip (anonim) compatibil cu interfata List. Clasa nou extinde o clas abstract (AbstractList) si defineste metodele abstracte (get, size) pe baza datelor existente n vectorul initial. Un alt exemplu l ofer metodele keySet si values din clasele dictionar, care trebuie s furnizeze o multime a cheilor si respectiv o colectie a valorilor din dictionar, dar fr s creeze noi colectii (fr s copieze referintele din dictionar n aceste noi colectii). Pe lng 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). Solutia acestei probleme foloseste ideea urmtoare: o nou colectie (multime) se poate defini prin extinderea unei clase abstracte cu definirea unei clase iterator, iar clasa iterator se poate defini prin parcurgerea multimii de perechi cheie-valoare (entrySet) si extragerea cmpului dorit din fiecare pereche (fie cheia, fie valoarea). Asadar, se vor defini noi clase pentru multimea cheilor si colectia valorilor, dar aceste clase nu vor contine date, pentru c metoda next a iteratorului clasei va furniza urmtoarea cheie sau valoare din dictionarul existent (ntr-o ordine care depinde de tipul clasei dictionar). Implementarea acestei idei n clasa AbstractMap foloseste clase incluse anonime, dar aici vom folosi clase exterioare, pentru a facilita ntelegerea ideii de colectie virtual. 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 cte un nou obiect la fiecare apel; o variabil a clasei AbstractMap retine o referint la obiectul creat numai la primul apel, iar la un apel ulterior se verific doar valoarea acestei variabile (initial valoarea null):
private transient Set keySet = null; // adresa obiectului creat la primul apel keySet public Set keySet() { if (keySet == null) // daca este primul apel keySet = new KSet(entrySet()); // atunci se creeaz obiectul unic de tip KSet return keySet; }

Clasa KSet implementeaz o multime virtual a cheilor dintr-un dictionar, dictionar definit complet prin multimea perechilor cheie-valoare, multime generat de metoda entrySet (specific fiecrui tip de dictionar):
class KSet extends AbstractSet { Set eset; // referinta la multimea de perechi cheie-valoare public KSet (Set entrySet) { eset=entrySet; } public Iterator iterator () { // iterator pe multimea cheilor return new KIter(eset); } public int size() { return eset.size(); }

91

Florian Moraru Programare Orientata pe Obiecte


public boolean contains(Object k) { return eset.contains(k); } }

Metoda de adugare la multime (add) nu poate fi definit pentru multimea KSet dar nici nu este necesar (pentru c nu este metod abstract n AbstractSet); multimea cheilor nu este modificat direct ci numai indirect, prin metode ale clasei dictionar (put, n principal). Clasa iterator parcurge multimea de perechi si extrage cheia din fiecare pereche:
class KIter implements Iterator { private Iterator i; public KIter (Set eset) { i=eset.iterator(); // iterator pe multimea de perechi cheie-valoare } public boolean hasNext() { return i.hasNext(); } public Object next() { return ((Map.Entry) i.next()).getKey(); // scoate cheia dintr-o pereche Map.Entry } public void remove() { i.remove(); } } // 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. Colectii arborescente Bibliotecile de clase Java contin o singur clas care poate fi utilizat ca arbore general, multici, sub numele de DefaultMutableTreeNode n pachetul javax.swing.tree, folosit pentru pstrarea datelor care vor fi afisate ntr-o component grafic JTree. Este o clas cu multe metode si posibilitti de utilizare si n aplicatii fr interfat grafic. Un arbore este complet definit prin nodul rdcin, deci un obiect arbore este de fapt un obiect nod de arbore. Vom folosi n continuare un nume mai scurt pentru clasa DefaultMutableTreeNode astfel:
class TNode extends DefaultMutableTreeNode { public TNode() { super();} public TNode (Object info) { super(info);}

} 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), o referint la nodul printe si un obiect cu date (de tipul generic Object). Se pot defini si alte variante dar care ar trebui s implementeze interfata MutableTreeNode pentru a fi folosite de un obiect JTree. O idee ar fi nlocuirea vectorului extensibil de succesori cu o list nlntuit de succesori. Dintre metodele publice mai des folosite mostenite de clasa TNode mentionm:
void add (TNode child); int getChildCount(); TNode getParent(); // adauga acestui nod fiul child // numarul fiilor acestui nod // parintele acestui nod

92

Florian Moraru Programare Orientata pe Obiecte


Enumeration children(); // fiii directi ai acestui nod Object getUserObject(); // datele din acest nod void remove (TNode child); // elimina fiul child din lista fiilor acestui nod Enumeration preorderEnumeration(); // noduri din subarborele acestui nod, in adancime Enumeration breadthFirstEnumeration(); // 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();} public TNode (Object info) { super(info);} } // cauta in arborele cu radacina root nodul ce contine un obiect dat obj public static TNode find (TNode root, Object obj) { Enumeration e = root.preorderEnumeration(); while ( e.hasMoreElements()){ TNode n = (TNode) e.nextElement(); if (n.getUserObject().equals(obj)) return n; } return null; }

Crearea unui arbore depinde de modul cum sunt furnizate datele ce vor fi introduse n arbore. Dac aceste date vin sub forma unor perechi parinte-fiu atunci fie utilizm metoda find de mai nainte pentru cutarea adresei nodului printe, fie folosim un dictionar n care cheile sunt valori din noduri si au asociate adresele nodurilor n arbore (dac valorile din noduri sunt distincte). Exemplu:
private TNode buildTree() { TNode p, c, r; // r este nodul radacina DataInputStream cin= new DataInputStream(System.in); String child, parent, line; Hashtable<String,TNode> h = new Hashtable<String,TNode>(); try { while ( (line=cin.readLine()) != null) { StringTokenizer tok = new StringTokenizer(line); parent = tok.nextToken(); // valoare nod parinte child =tok.nextToken(); // valoare nod fiu p = h.get(parent); // p = adresa nod parinte in arbore if (p==null) { // daca nu exista nod parinte r=p=new TNode(parent); // atunci creeaza nod parinte h.put(parent,p); // si pune adresa lui in dictionar } p.add(c=new TNode(child)); // creare si adaugare nod fiu h.put(child,c); // pune adresa nod fiu in dictionar } } catch (IOException ex) { ex.printStackTrace();} return r; }

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

Florian Moraru Programare Orientata pe Obiecte


public FTree() { super();} public FTree (String info) { super(info);} public FTree (File dir) { // arbore cu fisierele din directorul dir this (dir.getName()); // radacina arbore, cu nume fisier director dirtree (this, dir); // adaugare recursiva noduri la acest arbore } // creare arbore cu fisierele dintr-un director d si din subdirectoarele sale void dirtree ( FTree r, File d) { if ( ! d.isDirectory( ) ) return; // iesire daca d nu e fisier director String [ ] files=d.list(); // nume fisiere din directorul d for(int i=0;i<files.length;i++){ // ptr fiecare fisier File f = new File(d+"\\"+files[i]); // creare obiect File FTree kid = new FTree (f.getName()); // creare nod ptr nume fisier r.add ( kid); kid.setParent(r); // leaga nod la arbore dirtree (kid, f); // adauga continut subdirector f } } }

Se poate folosi si un constructor recursiv de nod, care construieste ntreg arborele:


class FileNode extends DefaultMutableTreeNode { public FileNode (File d) { super (d); // creare nod radacina if ( ! d.isDirectory() ) return; File [] files=d.listFiles(); // fisiere din directorul d for (int i=0;i<files.length;i++) { FileNode s=new FileNode(files[i]); // noduri fii ai radacinii add(s); s.setParent(this); // adauga fiecare fiu la radacina } } }

Exemplu de afisare n mod text, folosind metode ale clasei DefaultMutableTreeNode, cu indentare diferit la fiecare nivel din arbore:
public void print () { // metoda a clasei FTree printTree ( (FTree) getRoot(), ""); } static void printTree (FTree r, String ind) { // ind = indentare la fiecare apel final String inc =" "; // cu cat creste indentarea la un nou nivel System.out.println (ind+ r.toString()); // afisare nod radacina Enumeration e = r.children(); // fiii nodului r ind=ind+inc; // creste indentarea la un nou nivel while ( e.hasMoreElements()) // afisare recursiva fii printTree ((FTree) (e.nextElement()),ind); }

94

Florian Moraru Programare Orientata pe Obiecte 8. Colectii generice Clase generice n Java 5 Clasele colectie cu tipuri parametrizate au fost introduse n versiunea 1.5, ca solutii alternative pentru colectiile de obiecte Object. 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, dar n Java exist o singur clas generic (surs si compilat) n care se nlocuiesc parametri formali cu parametrii efectivi, ca si la apelul de functii. Exemple de declarare a unor vectori cu elemente de diferite tipuri :
ArrayList<Integer> a = new ArrayList<Integer>(); ArrayList<String> b = ArrayList<String> (100); ArrayList<TNode> c = ArrayList<TNode> (n); ArrayList<LinkedList<Integer>> graf; // initializata ulterior

Avantajele sunt acelea c se poate verifica la compilare dac se introduc n colectie numai obiecte de tipul declarat pentru elementele colectiei, iar la extragerea din colectie nu mai este necesar o conversie de la tipul generic la tipul specific aplicatiei. Exemplu cu un vector de obiecte Integer:
ArrayList<Integer> list = new ArrayList<Integer>(); for (int i=1;i<10;i++) list.add( new Integer(i) ); // nu se poate adauga alt tip decat Integer int sum=0; for (int i = 0; i< list.size();i++) { Integer val= list.get(i); // fara conversie de tip ! sum += val.intValue(); }

Exemplu de utilizare dictionar cu valori multiple pentru problema listei de referinte ncrucisate - n ce linii dintr-un fisier text apare fiecare cuvnt distinct :
public static void main (String[ ] arg) throws IOException { Map<String,List<Integer>> cref = new TreeMap<String,List<Integer>>( ); BufferedReader in = new BufferedReader (new FileReader (arg[0])); String line, word; int nl=0; while ((line=in.readLine()) != null) { ++nl; StringTokenizer st = new StringTokenizer (line); while ( st.hasMoreTokens() ) { word=st.nextToken(); List<Integer> lst = cref.get (word); if (lst==null) cref.put (word, lst=new LinkedList<Integer>()); lst.add (new Integer (nl)); } } System.out.println (cref); }

Trecerea de la un tip primitiv la tipul clas corespunztor si invers se face automat n versiunea 1.5, procese numite autoboxing si unboxing. Exemplu:

95

Florian Moraru Programare Orientata pe Obiecte


ArrayList<Integer> list = new ArrayList<Integer>(); for (int i=1;i<10;i++) list.add(10*i); // trecere automata de la int la Integer int sum=0; // suma valorilor din vector for (Iterator i = list.iterator(); i.hasNext();) sum += i.next(); // trecere automata de la Integer la int

Tot din versiunea 1.5 se poate utiliza o form mai simpl pentru enumerarea elementelor unei colectii care implementeaz interfata Iterable. Exemplu:
for (Integer it : list) sum += it; // it este iterator pe colectia list de obiecte Integer // sau sum += (int) it; sau sum += it.intValue();

Un alt exemplu, cu o list de siruri :


LinkedList<String> list = new LinkedList<String>(); for (int x=1;x<9;x++) list.add(x+""); String sb=""; for (String s : list) sb += s; // concatenare de siruri extrase din vector

In versiunea 1.5 toate interfetele si clasele colectie au fost redefinite pentru a permite specificarea tipului elementelor colectiei. Pentru exemplificare redm un fragment din clasa ArrayList, unde E este tipul neprecizat al elementelor colectiei :
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private transient E[] elementData; private int size; public E get(int index) { RangeCheck(index); return elementData[index]; } public boolean add(E o) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = o; return true; } public boolean contains(Object elem) { return indexOf(elem) >= 0; } ... }

Nu numai clasele pot avea parametri tip dar si metodele (numite metode generice). Exemplu:
// transforma un vector intr-o colectie (gresit) static void arrayToCol (Object[] a, Collection<?> c) { for (Object o : a) c.add(o); // eroare la compilare } // transforma un vector intr-o colectie (corect) static <T> void arrayToCol (T[] a, Collection<T> c) { for (T o : a) c.add(o); // correct

96

Florian Moraru Programare Orientata pe Obiecte


} // utilizare (fara argumente efective de tip la apel) String[] sa = new String[100]; Collection<String> cs = new LinkedList<String>(); arrayToCol (sa, cs); // T presupus a fi String

La utilizarea unui cod anterior versiunii Java 5 (cod fr generice) de ctre programe Java cu generice pot aprea mesaje la compilare inevitabile (care pot fi suprimate numai prin adnotarea @SuppressWarning ). Exemplu:
class OldClass { // metoda statica fara generice public static void addToMap (Map m, Object k, Object v) { m.put(k,v); } } class Generic2 { public static void main (String arg[]){ Map<String,Integer> m = new HashMap<String,Integer> (); OldClass.addToMap (m,"unu",1); System.out.println(m); } }

Avertismente la compilare apar si la redefinirea unor metode din clasa Object n alte clase. Exemplu:
class MEntry<K,V> extends Object implements Map.Entry<K,V> { ... public boolean equals (Object obj) { MEntry<K,V> e = (MEntry<K,V>) obj; }

Functia urmtoare nu produce avertisment la compilare:


public boolean equals (Object obj) { MEntry e = (MEntry) obj; }

Compilatorul Java poate verifica numai utilizarea corect de tipuri statice, pe baza numelor de tip. 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. Exemplu:
List<String> ls = new ArrayList<String>(); List<Object> lo = ls; // eroare la compilare !

Exemplul urmtor de utilizare arat de ce nu este corect atribuirea anterioar:


lo.add(new Object()); String s = ls.get(0); // adauga la lista lo // incercare de atribuire a unui Object la un String

Exemplul urmtor arat de ce se foloseste caracterul ? (wildcard) ca parametru de tip:

97

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.println(e); } // utilizare incorecta HashSet<String> m = new HashSet<String> (); printCol (m);

Functia "printC" poate fi folosit numai cu colectii de Object si nu cu colectii de un subtip al lui Object. 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.out.println(e); }

In concluzie Collection<?> este supertipul oricrei colectii de obiecte, iar caracterul '?' are sensul de "tip necunoscut". Declaratiile urmtoare nu sunt echivalente: Collection<Object> Collection<?> In linia 2 din exemplul urmtor apare eroare la compilare:
Collection<?> c = new ArrayList<String>(); c.add(new Object()); // eroare !

Compilatorul nu stie daca "Object" este un subtip al tipului 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. Fie functia urmtoare pentru calculul sumei numerelor dintr-o colectie:
static double sum (Collection<Number> c){ double s=0.; for (Number x: c) s+= x.doubleValue(); return s; } // utilizare incorecta ArrayList<Long> a = new ArrayList<Long> (); a.add(2L); a.add(3L); System.out.println( (long)sum(a));

Dac argumentul functiei "sum" ar fi ?' atunci nu am putea folosi metoda "doubleValue" dect dup conversie de la Object la Number:
static double sum (Collection<?> c){ double s=0.; for (Object x: c) s+= ((Number)x).doubleValue(); return s; }

98

Florian Moraru Programare Orientata pe Obiecte Desi nu apar mesaje la compilare pot apare exceptii la executie, ca n exemplul urmtor:
ArrayList<Date> d = new ArrayList<Date> (); ... System.out.println(sum(a)); // exceptie la conversia din Date in Number

Solutia sigur pentru definirea functiei "sum" este:


static double sum (Collection<? extends Number> c) { ...}

deci functia "sum" poate opera cu o colectie de obiecte de orice subtip al lui Number (Byte, Short, Integer, Long, Float, Double). Se spune ca Number este limita superioara a tipului necunoscut '?'. De observat c urmtoarea functie se compileaz cu erori:
static void addLong (Collection<? extends Number> c, long x) { c.add (new Long(x)); }

deoarece compilatorul nu poate verifica dac tipul necunoscut '?' este un supertip al tipului Long. Tipurile limitate superior se folosesc n general pentru argumente de functii. Exemplul urmtor arat necesitatea unor tipuri necunoscute limitate inferior (? super T). O colectie ordonat poate primi un comparator folosit la compararea elementelor din colectie:
class TreeSet<E> ... { ... public TreeSet (Comparator<E>) { ...} } // utilizare class MyComp<String> implements Comparator<String> { . . . } TreeSet<String> a = new TreeSet<String> ( new MyComp<String>());

Pentru a permite utilizarea unui comparator mai general se va scrie:


class TreeSet<E> ... { ... public TreeSet (Comparator<? super E>) { ...} class MyComp<? super E> implements Comparator<? super E> { . . . } TreeSet<String> a = new TreeSet<String> ( new MyComp<Object>());

Tipurile limitate superior se folosesc n general pentru rezultatele functiilor. Necesitatea unor limite multiple (Multiple bounds) este ilustrat de urmtoarea posibil definire a functiei "Collections.max" :
public static <T extends Comparable<T>> T max(Collection<T> coll) {...}

Definitia este prea restrictiv deoarece permite numai compararea de obiecte de tip T ntre ele. Exemplu de situatie care produce eroare la compilare desi nu ar trebui:
class A implements Comparable<Object> {...} Collection<A> a = ... A am = Collections.max(a);

99

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);

Definitia urmtoare 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 dect s definim clase generice noi aplicabile n diverse situatii si care s nu produc erori la compilare. Exemplele care urmeaz ilustreaz beneficiile claselor generice comparativ cu colectiile mai vechi, de variabile Object. Exemplu de clas pentru un graf reprezentat printr-un vector de liste (de noduri vecine):
class Graph { private int n; // numar de noduri in graf private ArrayList<LinkedList<Integer>> a; // vector de liste de intregi public Graph ( int nn) { n =nn+1; a = new ArrayList<LinkedList<Integer>> (n); for (int v=1;v<=n;v++) a.add (new LinkedList<Integer>()); } public void addArc (int v,int w) { LinkedList<Integer> vlist = a.get(v); vlist.add(new Integer(w)); } public boolean isArc (int v,int w) { LinkedList<Integer> vlist = a.get(v); return vlist.contains (new Integer(w)); } public int arcs (int v) { // nr de vecini (de arce) ptr nodul v return a.get(v)).size(); } }

Exemplul urmtor 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; // constructori public MyTable(int nl, int nc) { data= new Vector<Vector<Object>>(nl); for (int i=0;i<nl;i++) data.add (new Vector<Object>(nc)); } public MyTable (Object a[][]){

100

Florian Moraru Programare Orientata pe Obiecte


data = new Vector<Vector<Object>>(a.length); for (int i=0;i<a.length;i++){ Vector<Object> col = new Vector<Object> (a[0].length); for (int j=0;j<a[i].length;j++) col.add(a[i][j]); data.add(col); } } // metode public int size() { return data.size();} public Iterator iterator(){ return new TIterator (); } class TIterator implements Iterator { int i=-1; public boolean hasNext () { return i<size(); } public Object next() { int c= getColumns(); ++i; return getValueAt (i/c, i%c); } public void remove(){} } public int getColumns() { return data.get(0).size(); // ((Vector)data.get(0)).size() } public Object getValueAt(int i, int j) { return data.get(i).get(j); // ((Vector)data.get(i)).get(j) } public void setValueAt (Object v,int i, int j) { Vector<Object> lin = data.get(i); if (j<lin.size()) lin.set (j,v); else lin.add (j,v); } }

Fragmente dintr-o alt variant care nu produce avertismente unchecked or unsafe operation:
class MyTable<T> extends AbstractCollection<T> { private Vector<Vector<T>> data; // metode public T getValueAt(int i, int j) { return data.get(i).get(j); } public Iterator<T> iterator(){ return new TIterator<T> (this); } public void setValueAt (T v, int i, int j) { Vector<T> lin = data.get(i); if (j<lin.size()) lin.set (j,v); else lin.add (j,v); } }

101

Florian Moraru Programare Orientata pe Obiecte


class TIterator<T> implements Iterator<T> { int i=-1; MyTable<T> t; public TIterator (MyTable<T> t) { this.t=t; } public boolean hasNext () { return i< t.size(); public T next() { int c= t.getColumns(); ++i; return t.getValueAt(i/c,i%c); } public void remove(){} }

Colectii Google In 2007 firma Google a publicat un pachet de clase colectie care vin s completeze clasele Java de bibliotec. Dintre ele mentionm clase MultiSet pentru multimi cu valori multiple, MultiMap pentru dictionare cu chei multiple, BiMap pentru dictionare bidirectionale, n diverse variante de implementare, dar toate cu generice. O multime cu valori multiple este o colectie n care elementele nu sunt neaprat distincte, care nu permite accesul direct, prin indice (ca la liste), dar permite realizarea eficient a unor operatii cum sunt obtinerea numrului de elemente cu o valoare dat, eliminarea tuturor elementelor cu aceeasi valoare si compararea la egalitate a dou colectii cu elemente multiple. Interfata MultiSet este:
public interface Multiset<E> extends Collection<E> { int count (Object element); // numar de aparitii al unui element dat boolean add (E element, int n); // adauga de n ori un element int remove( Object element, int n); // elimina n aparitii ale unui element, daca count(elem)>n int removeAllOccurrences(Object element); Set<E> elementSet(); // multime virtuala a elementelor distincte (view) Set<Entry<E>> entrySet(); // multime virtuala de perechi element-contor de aparitii boolean equals(Object object); // daca doua obiecte MultiSet sunt egale int hashCode(); String toString(); // metode din Collection redefinite interface Entry<E> { // o pereche element si contor de aparitii E getElement(); // valoare element int getCount(); // numar de aparitii boolean equals(Object o); int hashCode(); String toString(); } }

Toate implementrile interfetei MultiSet se bazeaz pe un dictionar n care cheia este elementul din multime iar valoarea asociat cheii este numrul de repetri ale elementului respectiv. Astfel multimea de elemente {d,b,a,c,d,c,d,b,c,d} se va memora sub forma : { [d,4], [b,2], [a,1], [c,3] } Clasa AbstractMultiSet este definit dup exemplul clasei AbstractMap, 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(); // multime de perechi Entry @Override public int size() { // numar de elemente din multime long sum = 0L; for (Entry<E> entry : entrySet()) {

102

Florian Moraru Programare Orientata pe Obiecte


sum += entry.getCount(); } return (int) Math.min(sum, Integer.MAX_VALUE); } // 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, AtomicInteger> backingMap; // dictionarul folosit de acest multiset private long size; // pentru eficienta // constructor protected AbstractMapBasedMultiset(Map<E, AtomicInteger> backingMap) { this.backingMap = backingMap; this.size = super.size(); } // entrySet creeaz obiect numai la primul apel private transient volatile EntrySet entrySet; @Override public Set<Multiset.Entry<E>> entrySet() { if (entrySet == null) { entrySet = new EntrySet(); } return entrySet; } // clasa inclusa private class EntrySet extends AbstractSet<Multiset.Entry<E>> { @Override public Iterator<Multiset.Entry<E>> iterator() { final Iterator<Map.Entry<E, Integer>> backingEntries = backingMap.entrySet().iterator(); return new Iterator<Multiset.Entry<E>>() { Map.Entry<E, Atomic> toRemove; public boolean hasNext() { return backingEntries.hasNext(); } public Multiset.Entry<E> next() { final Map.Entry<E, Integer> mapEntry = backingEntries.next(); toRemove = mapEntry; return new AbstractMultisetEntry<E>() { public E getElement() { return mapEntry.getKey(); } public int getCount() { int count = mapEntry.getValue().get(); if (count == 0) { Integer frequency = backingMap.get(getElement()); if (frequency != null) { count = frequency.get(); } } return count; } }; } public void remove() { checkState(toRemove != null, "no calls to next() since the last call to remove()");

103

Florian Moraru Programare Orientata pe Obiecte


size -= toRemove.getValue().getAndSet(0); backingEntries.remove(); toRemove = null; } }; } @Override public int size() { return backingMap.size(); } @Override public void clear() { for (AtomicInteger frequency : backingMap.values()) { frequency.set(0); } backingMap.clear(); size = 0L; } } // alte metode mostenite si redefinite ptr eficienta }

Clasele HashMultiSet, TreeMultiSet apeleaz la implementri existente pentru dictionarul folosit:


public final class TreeMultiset<E> extends AbstractMapBasedMultiset<E> { // constructori public TreeMultiset() { super(new TreeMap<E, Integer>()); } public TreeMultiset(Comparator<? super E> comparator) { super(new TreeMap<E, Integer>(comparator)); } // metode redefinite pentru multimi ordonate @Override public SortedSet<E> elementSet() { return (SortedSet<E>) super.elementSet(); } @Override protected Set<E> createElementSet() { return new SortedMapBasedElementSet((SortedMap<E, Integer>) backingMap()); } // clase incluse si alte metode }

104

Florian Moraru Programare Orientata pe Obiecte 9. Reutilizarea codului n POO Reutilizarea codului prin delegare Unul din avantajele programrii orientate pe obiecte este posibilitatea de reutilizare simpl si sigur a unor clase existente n definirea altor clase, fr a modifica clasele initiale. Altfel spus, o tehnic specific POO este adugarea de noi metode unor clase existente, simultan cu reutilizarea unor metode mostenite. Metodele de reutilizare a codului sunt delegarea si derivarea, iar schema de proiectare ce realizeaz extinderea functionalittii unei clase se numeste decorator, de la imaginea unor decoratiuni adugate unui obiect existent pentru a-i spori utilitatea sau estetica. In cazul delegrii o clas A contine o variabil de un tip clas B, pentru reutilizarea functionalittii clasei B n noua clas A, prin delegarea ctre metodele clasei B a metodelor clasei A. Clasa urmtoare din colectiile Google ilustreaz esenta delegrii pentru decorarea unei clase:
public abstract class ForwardingObject implements Serializable { private final Object delegate; protected ForwardingObject(Object delegate) { this.delegate = checkNotNull(delegate); } protected Object delegate() { return delegate; } @Override public String toString() { return delegate().toString(); } }

De remarcat c nu sunt delegate si operatiile equals sau hashCode, care vor fi definite n subclasele acestei clase abstracte. In exemplul urmtor se defineste o clas pentru stive prin delegarea operatiilor cu stiva ctre metodele listei nlntuite de obiecte LinkedList:
public class LinkedStack { private LinkedList stack; public LinkedStack () { stack= new LinkedList(); } public Object push (Object obj) { stack.addFirst (obj); return obj; } public Object pop () { return stack.removeFirst(); } public boolean isEmpty () { return stack.isEmpty(); } public String toString () { return stack.toString(); } } // 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, desi continutul claselor este att de asemntor. Varianta definirii clasei "LinkedStack" ca o

105

Florian Moraru Programare Orientata pe Obiecte subclas a clasei LinkedList este preferabil aici, mai ales c dou dintre metode pot fi mostenite ca atare ("isEmpty" si "toString"). Clasa LinkedStack este numit si clas adaptor pentru c face trecerea de la un set de metode publice (addFirst,removeFirst ale clasei LinkedList) la un alt set de metode publice (push, pop etc.). Delegarea de metode ntre obiecte poate fi o relatie dinamic, modificabil la executie, spre deosebire de relatia static de derivare, stabilit la scrierea programului si care nu mai poate fi modificat. Clasa compus poate contine o variabil de un tip interfat sau clas abstract, care poate fi nlocuit la executie (la construirea unui obiect, de exemplu) printr-o referint la un obiect de un alt tip, compatibil cu tipul variabilei din clas. Vom relua exemplul cu stiva adugnd un grad de generalitate prin posibilitatea utilizatorului de asi alege tipul de list folosit pentru stiv (vector sau lista nlntuit):
public class StackList { private List stack; public StackList (List list) { stack=list; } public Object push (Object obj) { stack.add (0,obj); return obj; } ... // 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()); StackList st2 = new StackList (new LinkedList()); // vector // lista inlantuita

In exemplul urmtor se defineste o clasa dictionar cu valori multiple, n care fiecare cheie are asociat o lista de valori. Clasa preia o mare parte din functionalitatea clasei HashMap, prin delegarea unor operatii ctre metode din clasa HashMap.
public class MultiMap { HashMap m = new HashMap(); public void put (Object key, List val) { m.put (key, val); // apel HashMap.put } public List get (Object key) { return (List) m.get(key); // apel HashMap.get } public String toString () { String str=""; Iterator ik=m.keySet().iterator(); // apel HashMap.keySet while (ik.hasNext() ) { List lst = get(ik.next()); // lista de valori a unei chei str = str+ key + " : " + lst.toString() + "\n"; } return str; } public Set keySet () { return m.keySet(); } }

106

Florian Moraru Programare Orientata pe Obiecte Delegarea se mai numeste agregare sau compozitie: clasa agregat (compus) contine unul sau mai multe obiecte ctre care deleag anumite operatii. 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). In Java nu se face aceast diferent doarece obiectul compus contine referinte la obiectele componente, care pot exista si dup disparitia obiectului compus (desi s-ar putea pierde referintele la ele). Reutilizarea codului prin derivare Prin derivare se face o adaptare sau o specializare a unei clase mai generale la anumite cerinte particulare fr a opera modificri n clasa initial. Extinderea unei clase permite reutilizarea unor metode din superclas, fie direct, fie dup "ajustri" si "adaptri" cerute de rolul subclasei. Superclasa transmite subclasei o mare parte din functiile sale, nefiind necesar rescrierea sau apelarea metodelor mostenite. Vom relua exemplul clasei pentru stive realizate ca liste nlntuite. Operatiile cu o stiv se numesc traditional push si pop, dar clasa LinkedList foloseste alte nume pentru operatiile respective. De aceea, vom defini dou metode noi:
public class LinkedStack extends LinkedList { public Object push (Object obj) { addFirst (obj); return obj; } public Object pop () { return removeFirst(); } }

De observat c subclasa LinkedStack mosteneste metodele "toString", "isEmpty" si altele din clasa LinkedList. De asemenea, exist un constructor implicit care apeleaz constructorul superclasei si care initializeaz lista stiv. O problem n acest caz ar putea fi posibilitatea utilizatorilor de a folosi pentru obiecte "LinkedStack" metode mostenite de la superclas, dar interzise pentru stive: citire si modificare orice element din stiv, cutare n stiv s.a. Solutia este de a redefini aceste metode n subclas, cu efectul de aruncare a unor exceptii. Exemplu:
public Object remove (int index) { throw new UnsupportedOperationException(); }

Iat si o alt solutie de definire clasei "MultiMap" (dictionar cu valori multiple), 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 pstra interfata public a clasei HashMap:
public class MultiMap extends HashMap { public Object put (Object key, Object val) { List lst = (List) get(key); // extrage lista de valori asociata cheii key List result=lst; // rezultatul va fi lista anterioara if (lst==null) // daca cheia key nu exista in dictionar super.put (key, lst=new ArrayList()); // se introduce o pereche cheie-lista lst.add (val); // adauga prima valoare la lista return result; // lista anterioara de valori } }

107

Florian Moraru Programare Orientata pe Obiecte Comparatie ntre compozitie si derivare Delegarea (un B contine un A) se recomand atunci cnd vrem folosim (s reutilizm) functionalitatea unei clase A n cadrul unei clase B, dar interfetele celor dou clase sunt diferite. Derivarea (un B este un fel de A) se recomand atunci cnd vrem s reutilizm o mare parte din (sau toat) interfata clasei A si pentru clasa B. In plus, derivarea creeaz tipuri compatibile: putem s nlocuim o variabil sau un argument de tipul A printr-o variabil (sau argument) de tipul B . Att derivarea ct si compozitia permit reutilizarea metodelor unei clase, fie prin mostenire, fie prin delegare (prin apelarea metodelor obiectului continut). 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, clase de intrare-iesire s.a. In felul acesta clasele definite de programatori pot mosteni un numr mare de metode (si date) cu un efort minim de programare. Exemple: - O nou clas colectie poate mosteni de la AbstractCollection (AbstractSet, AbstractList, AbstractMap) o mare parte din metode, la care adaug cteva metode specifice noii clase; - O clas pentru o interfat grafic Swing extinde de obicei clasa JFrame de la care mosteneste metode de adaugare a unor componente Swing, de stabilire asezare (Layout) si de vizualizare; - O clas aplet (sau servlet) este definit de programator prin extinderea unei clase de bibliotec (JApplet, de exemplu) de la care mosteneste metode absolut necesare functionrii sale ca aplet. - O clas ce defineste un fir de executie Java nu se poate defini dect prin extinderea clasei de bibliotec Thread sau prin implementarea interfetei Runnable. 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), iar o alt parte prin metode ale unor clase definite de programatori si care este specific fiecrei aplicatii (sau prti de aplicatie). In plus, 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, metoda paint dintr-o subclas a clasei JApplet, s.a.). O situatie care apare la utilizarea unor API standard, cum ar fi DOM (XML), este aceea c nu se cunosc dect nume de interfete si nu se cunosc nume de clase care implementeaz aceste interfete (pot fi mai multe astfel de implementri si chiar unele care urmeaz s apar n viitor). Dac vrem s redefinim o metod dintr-o astfel de clas anonim (cum ar fi metoda toString asociat unui nod de arbore DOM, definit prin interfata Node) nu putem folosi derivarea si rmne doar delegarea. Delegarea se foloseste la definirea unor clase adaptor de la o interfat la o alt interfat dar si atunci cnd tipul obiectului delegat se poate modifica la executie (adresa obiectului este primit de constructorul fiecrui obiect). Un exemplu este cel al claselor flux de I/E care deleag operatiile elementare de citire/scriere de caractere individuale ctre obiecte ce depind de suportul fizic al fluxului de date (fisier disc, o zon de memorie, etc.). Un alt exemplu este cel al claselor flux de date cu sum de control, care contin o variabil interfat (Checksum), care va primi la instantiere adresa obiectului ce contine metoda de calcul a sumei de control. Legtura dintre un obiect flux si obiectul de calcul a sumei este stabilit la executie si asigur o flexibilitate sporit. Exemplu:
public class CheckedOutputStream extends FilterOutputStream { private Checksum cksum; // adresa obiect cu metoda de calcul suma control public CheckedOutputStream(OutputStream out, Checksum cksum) { super(out); this.cksum = cksum; } public void write(int b) throws IOException { out.write(b); cksum.update(b); // metoda din interfata Checksum }

108

Florian Moraru Programare Orientata pe Obiecte


... } // Exemplu de utilizare CRC32 Checker = new CRC32(); // o clasa care implementeaza interfata Checksum CheckedOutputStream out; out = new CheckedOutputStream (new FileOutputStream("date"), Checker); while (in.available() > 0) { int c = in.read(); out.write(c); }

Colectiile Google folosesc mai mult delegarea ca metod de reutilizare, comparativ cu clasele colectie din Java care se bazeaz mai mult pe derivare. De cele mai multe ori metoda de reutilizare a unor clase se impune de la sine, dar uneori alegerea ntre compozitie si derivare nu este evident si chiar a condus la solutii diferite n biblioteci de clase diferite. Un exemplu este cel al claselor pentru vectori si respectiv pentru stive. 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, desi un obiect stiv nu foloseste metodele clasei Vector ( cu exceptia metodei "toString"). Mai mult, nici nu se recomand accesul direct la orice element dintr-o stiv (prin metodele clasei Vector). In Java o subclas nu poate avea dect o singur superclas, dar uneori este necesar ca o clas s preia functii de la dou sau mai multe clase diferite. Implementarea mai multor interfete de ctre o clas nu este o solutie pentru mostenirea multipl de functii. 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; metodele din M fie apeleaz metode din clasa B, fie sunt mostenite de la clasa A. Clasa A este de multe ori o clas abstract, iar clasa B este instantiabil sau abstract. Exemplu de mostenire functii de la 3 clase:
class A { public void f1 () { System.out.println ("A.f1"); } } class B { public void f2 () { System.out.println ("B.f2"); } } class C { public void f3 () { System.out.println ("C.f3"); } } class M extends C { private A a = new A (); // initializarea var. a si b se poate face intr-un constructor private B b = new B (); public void f1 () { a.f1();} // delegare obiect a pentru operatia f1 public void f2 () { b.f2();} // delegare obiect b pentru operatia f2 } class X { public static void main (String arg[]) { M m = new M(); m.f1(); m.f2(); m.f3(); } }

Un exemplu real de mostenire multipl poate fi o clas pentru o multime realizat ca vector, care extinde clasa AbstractSet si contine o variabil de tip ArrayList :
public class ArraySet extends AbstractSet { private ArrayList set;

109

Florian Moraru Programare Orientata pe Obiecte


public ArraySet() { set = new ArrayList(); } public boolean add (Object obj) { if (! set.contains(obj) ) return set.add(obj); // delegare pentru operatia de adaugare return false; } public Iterator iterator() { return set.iterator(); // delegare pentru creare obiect iterator } public int size() { return set.size(); } }

Pentru multimi de tipul "ArraySet" se pot folosi toate metodele mostenite de la clasa AbstractSet: toString, contains, containsAll, addAll, removeAll, retainAll s.a. Aceeasi solutie de mostenire multipl este folosit n cteva clase JFC (Swing) de tip model; de exemplu, clasa DefaultListModel preia metode de la superclasa AbstractListModel si deleag unei variabile interne de tip Vector operatii cu un vector de obiecte. Un model de list este un vector cu posibilitti de generare evenimente (de apelare receptori) la modificri operate n vector. Combinarea compozitiei cu derivarea Derivarea pstreaz interfata clasei de baz, iar delegarea permite mai mult flexibilitate la executie. Combinarea delegrii cu derivarea mbin avantajele ambelor metode de reutilizare: mostenirea interfetei si posibilitatea modificrii obiectului delegat. Uneori variabila din subclas este chiar de tipul superclasei, tip clas abstract sau interfat. Totusi nu se justific o constructie de forma urmtoare, unde A este o clas instantiabil:
class B extends A { private A a ; public B (A a) { this.a=a; } ... // 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), deoarece adaug functii obiectului continut, ca un ambalaj pentru acel obiect. O clasa anvelop este numit si clas decorator, deoarece "decoreaz" cu noi functii o clas existent. De exemplu, putem defini o clas stiv mai general, care s poat folosi fie un vector, fie o list nlntuit, dup cum doreste programatorul. Clasa anvelop care urmeaz este compatibil cu tipul List si, n acelasi timp, foloseste metode definite n clasa AbstractList :
class StackList extends AbstractList { private AbstractList stack; // adresa obiect stiva public StackList (List list) { // constructor stack=(AbstractList)list; // retine adresa obiect stiva } public Object push (Object obj) { stack.add (0,obj);

110

Florian Moraru Programare Orientata pe Obiecte


return obj; } public Object pop () { Object obj= get(0); stack.remove(obj); return obj; } public int size() { return stack.size(); } }

Exemple de combinare a derivrii si compozitiei se gsesc n clasa Collections, pentru definirea de clase colectie cu functionalitate putin modificat fat de colectiile uzuale, dar compatibile cu acestea ca tip. Primul grup de clase este cel al colectiilor nemodificabile, care difer de colectiile generale prin interzicerea operatiilor ce pot modifica continutul colectiei. Exemplu:
class UnmodifiableCollection implements Collection, Serializable { Collection c; // metode care nu modifica continutul colectiei public int size() {return c.size();} public boolean isEmpty() {return c.isEmpty();} public boolean contains(Object o) {return c.contains(o);} public String toString() {return c.toString();} ... // metode care ar putea modifica continutul colectiei public boolean add (Object o){ throw new UnsupportedOperationException(); } public boolean remove (Object o) { throw new UnsupportedOperationException(); } ... }

Incercarea de adugare a unui nou obiect la o colectie nemodificabil produce o exceptie la executie; dac nu se defineau metodele "add", "remove" s.a. n clasa derivat, atunci apelarea metodei "add" era semnalat ca eroare la compilare. Exceptia poate fi tratat fr a mpiedica executia programului. Al doilea grup de clase sunt clasele pentru colectii sincronizate. Exemplu:
class SynchronizedCollection implements Collection, Serializable { Collection c; // colectia de baza public int size() { synchronized(this) {return c.size();} } public boolean add(Object o) { synchronized(this) {return c.add(o);} } . . . // alte metode }

In realitate, 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;

111

Florian Moraru Programare Orientata pe Obiecte


UnmodifiableList(List list) { super(list); this.list = list; } . . . // alte metode } public static List unmodifiableList (List list) { return new UnmodifiableList(list); }

Exemple de utilizare a claselor colectie "speciale" : String kw[] ={"if", "else", "do","while", "for"};
List list = Collections.unmodifiableList (Arrays.asList(kw)); Set s = Collections.synchronizedSet (new HashSet());

Clase de intrare-iesire cu derivare si delegare Combinarea derivrii cu delegarea a fost folosit la proiectarea claselor din pachetul java.io. Exist dou familii de clase paralele : familia claselor flux (Stream) cu citire-scriere de octeti si familia claselor Reader-Writer, cu citire-scriere de caractere. Numrul de clase instantiabile de I/E este relativ mare deoarece sunt posibile diverse combinatii ntre suportul fizic al fluxului de date si facilittile oferite de fluxul respectiv. Toate clasele flux de intrare sunt subtipuri ale tipului InputStream (Reader) si toate clasele flux de iesire sunt subtipuri ale tipului OutputStream (Writer). Clasele Reader, Writer si celelalte sunt clase abstracte. Dup suportul fizic al fluxului de date se poate alege ntre: - Fisiere disc : FileInputStream, FileOutputStream, FileReader, FileWriter s.a. - Vector de octeti sau de caractere : ByteArrayInputStream, ByteArrayOutputStream CharArrayReader, CharArrayWriter. - Buffer de siruri (n memorie): StringBufferInputStream, StringBufferOutputStream. - Canal pentru comunicarea sincronizat ntre fire de executie : PipedInputStream, PipedOutputStream, PipedReader, PipedWriter. Dup facilittile oferite avem de ales ntre: - Citire-scriere la nivel de octet sau bloc de octeti (metode read, write). - Citire-scriere pe octeti dar cu zon buffer: BufferedInputStream, BufferedReader, BufferedOutputStream, BufferedWriter. - Citire-scriere la nivel de linie si pentru numere de diferite tipuri (fr conversie): DataInputStream, DataOutputStream.. - Citire cu punere napoi n flux a ultimului octet citit (PushBackInputStream). - Citire nsotit de numerotare automat a liniilor citite (LineNumberInputStream). Combinarea celor 8 clase surs/destinatie cu optiunile de prelucrare asociate transferului de date se face prin intermediul claselor anvelop , care sunt numite si clase filtru de intrare-iesire. Dac s-ar fi utilizat derivarea pentru obtinerea claselor direct utilizabile atunci ar fi trebuit generate, prin derivare, combinatii ale celor 8 clase cu cele 4 optiuni, deci 32 de clase (practic, mai putine, deoarece unele optiuni nu au sens pentru orice flux). Pentru a folosi mai multe optiuni cu acelasi flux ar fi trebuit mai multe niveluri de derivare si deci ar fi rezultat un numr si mai mare de clase. Solutia claselor anvelop permite s se adauge unor clase de baz diverse functii n diferite combinatii. Principalele clase filtru de intrare-iesire sunt: FilterInputStream, FilterOutputStream si FilterReader, FilterWriter. Clasele de tip filtru sunt clase intermediare, din care sunt derivate clase care adaug operatii specifice (de prelucrare): citire de linii de text de lungime variabil, citire-scriere de numere n format intern, scriere numere cu conversie de format s.a. O clas anvelop de I/E contine o variabil

112

Florian Moraru Programare Orientata pe Obiecte de tipul abstract OutputStream sau InputStream, care va fi nlocuit cu o variabil de un tip flux concret (FileOutputStream, ...), la construirea unui obiect de un tip flux direct utilizabil. Clasele filtru de I/E fac parte din categoria clasele decorator, care aplic diverse decoratiuni (functionalitti) unor clase existente. Un decorator nu adaug functii (metode) noi clasei decorate dar modific actiunea unor metode existente prin delegare ctre 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). Clasa FilterInputStream este derivat din InputStream si contine o variabil de tip InputStream:
public class FilterInputStream extends InputStream { protected InputStream in; protected FilterInputStream (InputStream in) { // constructor (in clasa abstracta) this.in=in; // adresa obiectului "flux" } // citirea unui octet public int read () throws IOException { return in.read (); // citirea depinde de tipul fluxului } ... // alte metode }

Metoda "read" este o metod polimorfic, iar selectarea metodei necesare se face n functie de tipul concret al variabilei "in" (transmis ca argument constructorului). Metoda read din FilterInputStream preia functionalitatea metodei read din clasa delegat, sau deleag operatia de citire ctre clasa folosit la construirea obiectului filtru. Nu se pot crea obiecte de tipul FilterInputStream deoarece constructorul clasei este de tip protected, dar se pot crea obiecte din subclase ale clasei FilterInputStream. Clasele DataInputStream, BufferedInputStream, PushbackInputStream, LineNumberInputStream sunt derivate din clasa FilterInputStream si contin metode de prelucrare a datelor citite. 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", "readBoolean", readFloat", "readLine", etc. 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 ctre metoda read din FileInputStream:
FileInputStream fis= new FileInputStream (t.txt); DataInputStream dis = new DataInputStream (fis);

Decorarea unei clase flux de I/E se poate face repetat; astfel pentru a citi linii dintr-un fisier folosind o zon tampon si cu numerotare de linii vom folosi urmtoarea secvent de instructiuni:
public static void main (String arg[]) throws IOException { FileInputStream fis= new FileInputStream (arg[0]); BufferedInputStream bis = new BufferedInputStream (fis); LineNumberInputStream lnis= new LineNumberInputStream (bis); DataInputStream dis = new DataInputStream (lnis); String linie; while ( (linie=dis.readLine()) != null) System.out.println (lnis.getLineNumber()+" "+linie); }

De obicei nu se mai folosesc variabile intermediare la construirea unui obiect flux. Exemplu de citire linii , cu buffer, dintr-un fisier disc:

113

Florian Moraru Programare Orientata pe Obiecte


public static void main (String arg[ ]) throws IOException { DataInputStream dis = new DataInputStream ( new BufferedInputStream (new FileInputStream (arg[0]))); String linie; while ( (linie=dis.readLine()) != null) System.out.println ( linie); }

Ordinea n care sunt create obiectele de tip InputStream este important : ultimul obiect trebuie s fie de tipul DataInputStream, pentru a putea folosi metode ca "readLine" si altele. Si familia claselor Reader-Writer foloseste clase decorator:
public abstract class FilterReader extends Reader { protected Reader in; protected FilterReader(Reader in) { super(in); this.in = in; } public int read() throws IOException { return in.read(); } public int read(char cbuf[ ], int off, int len) throws IOException { return in.read(cbuf, off, len); } public void close() throws IOException { in.close(); } }

Clasele PrintStream si PrintWriter adaug claselor filtru metode pentru scriere cu format (cu conversie) ntr-un flux de iesire, metode cu numele print sau println. Constanta System.out este de tipul PrintStream si corespunde ecranului, iar constanta System.in este de un subtip al tipului InputStream si corespunde tastaturii. 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. Clasele adaptor extind o clas Reader (Writer) si contin o variabil de tip InputStream (OutputStream); o parte din operatiile impuse de superclas sunt realizate prin apelarea operatiilor pentru variabila flux (prin delegare). Este deci un alt caz de combinare ntre extindere si agregare. Un obiect InputStreamReader poate fi folosit la fel ca un obiect Reader pentru citirea de caractere, dar n interior se citesc octeti si se convertesc octeti la caractere. Codul urmtor ilustreaz esenta clasei adaptor:
public class InputStreamReader extends Reader { private ByteToCharConverter btc; private InputStream in; private InputStreamReader(InputStream in, ByteToCharConverter btc) { super(in); this.in = in; this.btc = btc; } public InputStreamReader(InputStream in) { this(in, ByteToCharConverter.getDefault()); } public int read() throws IOException { int byte = in.read(); // citire octet char ch = btc.convert(byte); // conversie din byte in char return ch; // caracter coresp. octetului citit }

114

Florian Moraru Programare Orientata pe Obiecte 10. Clase incluse Clase incluse O clas Java poate contine, ca membri ai clasei, alte clase numite clase incluse (nested classes). In cazul unei singure clase incluse, structura va arta astfel:
public class Outer { ... // date si/sau metode ale clasei Outer public class Inner { ... // date si/sau metode ale clasei Inner } ... // alti membri ai clasei Outer }

Clasele incluse cu nume primesc de la compilator un nume compus din numele clasei exterioare, caracterul $ si numele clasei interioare (Outer$Inner). Clasele care nu sunt incluse n alte clase se numesc clase de nivel superior (top-level classes). Clasa inclus poate fi si o clas static, sau o clas abstract sau o interfat. Un exemplu de interes pentru definirea de noi clase dictionar este interfata Entry, inclus n interfata Map, cu metode asociate unei perechi cheie-valoare, ambele de tip Object:
public interface Map { Object get (Object key); Object put (Object key, Object value); ... // alte metode abstracte din interfata Map public interface Entry { // acces la cheia si valoarea dintr-o pereche Object getKey(); // cheia Object getValue(); // valoarea Object setValue(Object value); // modifica valoarea boolean equals(Object o); // daca doua perechi sunt egale } }

In exemplul urmtor clasa inclus Entry este folosit numai n definitia clasei ArrayMap. Clasa inclus Entryimplementeaz interfata inclus Map.Entry:
public class ArrayMap extends AbstractMap { // clasa interioara static class Entry implements Map.Entry { private Object key,val; public Entry (Object k, Object v) { key=k; val=v; } public String toString() { return key+"="+val;} public Object getKey() { return key; } public Object getValue() { return val;} public Object setValue (Object v) { val=v; return v;} public boolean equals (Object obj) { return ((Entry)obj).getKey().equals(key); } } // alti membri ai clasei ArrayMap private ArraySet entries ; // multime de perechi cheie-valoare public ArrayMap (int n) { // constructor

115

Florian Moraru Programare Orientata pe Obiecte


entries = new ArraySet(n); } public Object put ( Object key, Object value) { entries.add (new Entry(key,value)); return key; } public Set entrySet () { return entries; } }

Tipul Entry este folosit n mai multe metode ale clasei AbstractMap. Exemplu:
public Object get(Object key) { Iterator i = entrySet().iterator(); while (i.hasNext()) { Entry e = (Entry) i.next(); if (key.equals(e.getKey())) return e.getValue(); } return null; }

Clasa interioar static ReverseComparator din clasa Collections, este folosit de metoda static reverseOrder prin intermediul unei variabilei statice:
public class Collections { public static Comparator reverseOrder() { // din clasa Collections return REVERSE_ORDER; } private static final Comparator REVERSE_ORDER = new ReverseComparator(); private static class ReverseComparator implements Comparator,Serializable { public int compare(Object o1, Object o2) { Comparable c1 = (Comparable)o1; return -c1.compareTo(o2); } } ... // alte metode si clase incluse din clasa Collections }

Clase interioare O clas inclus nestatic este numit si clas interioar (inner class), pentru c fiecare obiect din clasa exterioar va contine un obiect din clasa interioar. Cuvntul nested se refer la relatia sintactic dintre clase: clasa inclus este definit n cadrul definitiei clasei exterioare; cuvntul inner se refer la relatia dintre obiectele claselor incluse: un obiect al clasei exterioare contine n el un obiect al clasei incluse. Un exemplu este o clas iterator inclus n clasa colectie pe care actioneaz. In acest fel clasa iterator are acces la variabile private ale colectiei, nu poate fi instantiat direct ci numai prin intermediul colectiei (nu poate exista obiect iterator fr o colectie), fiind ascuns altor clase. Exemplu de iterator pe vector, clas interioar:
public class Vector extends AbstractList implements List, Cloneable { protected int count; // nr de elemente in vector protected Object elementData[]; // vector de obiecte

116

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; // indice element curent din enumerare public boolean hasMoreElements() { return count < elementCount; } public Object nextElement() { if (count < elementCount) return elementData[count++]; } } // . . . alte metode din clasa Vector }

Clasa iterator se poate defini si ca o clas top-level, 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; // indice element curent din enumerare private Vector v; public VectorEnum (Vector v) { this.v=v; } public boolean hasMoreElements() { return count < v.size(); } public Object nextElement() { if (count < v.size()) return v.elementAt(count++); else return null; } }

Un exemplu din Java Tutorial arat c uneori iteratorul trebuie s aib acces la datele colectiei (private), nefiind suficiente metodele publice de acces la acestea. Este cazul unui iterator pe o stiv vector, care face o enumerare a elementelor stivei de la vrf spre baza stivei, fr a scoate obiecte din stiv (fr a folosi o alt stiv). In exemplul urmtor o clas pentru un vector ordonat ("SortedArray") contine o variabil comparator ("cmp"), care poate fi initializat de un constructor al clasei. Dac se foloseste constructorul fr argumente, atunci variabila comparator primeste o valoare implicit, ca referint la un obiect comparator dintr-o clas interioar. Clasa "DefaultComp" nu mai trebuie definit de utilizatori si transmis din afar, 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, Object e2) { Comparable c1=(Comparable)e1; Comparable c2=(Comparable)e2; return c1.compareTo(c2);

117

Florian Moraru Programare Orientata pe Obiecte


} } // alti membri ai clasei SortedArray Comparator cmp=new DefaultComp(); // comparator implicit public SortedArray () { super();} public SortedArray (Comparator comp) { super(); cmp=comp; } public boolean add (Object obj) { int k=indexOf(obj); if (k < 0) k= -k-1; super.add(k, obj); return true; } public int indexOf (Object obj) { return Collections.binarySearch (this,obj,cmp); }

O alt form de clas interioar este o clas definit ntr-o metod a clasei externe. 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, Object o2) { Map.Entry e1= (Map.Entry)o1; // o pereche cheie-valoare Map.Entry e2= (Map.Entry)o2; // alta pereche cheie-valoare return ((Integer) e1.getValue()).compareTo ((Integer) e2.getValue()); } } Set eset = m.entrySet(); // multime de perechi cheie-valoare ArrayList entries = new ArrayList(eset); // vector de perechi cheie-valoare Collections.sort (entries, new VComp()); // ordonare vector System.out.println (entries); // afisare perechi ordonate dupa valori }

Motivele definirii de clase interioare pot fi diverse: - Pentru clase de interes local : clasa interioar este necesar numai clasei exterioare. - Pentru reducerea numrului 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). - Pentru a permite clasei exterioare accesul la membri private ai clasei interioare. - Pentru a permite claselor interioare accesul la variabile ale clasei exterioare si deci o comunicare mai simpl ntre clasele incluse (prin variabile externe lor). - Pentru ca o clas s poat mosteni functii de la cteva clase (mostenire multipl). 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. Aceste mosteniri se pot combina n obiecte ale clasei exterioare, pentru care se pot folosi metode mostenite de toate clasele incluse. Exemplu:
class A { void f1 () { System.out.println ("A.f1"); } } class B { void f2 () { System.out.println ("B.f2"); } } class C { void f3 () { System.out.println ("C.f3"); } } class M extends C { // M preia metode de la clasele A, B si C

118

Florian Moraru Programare Orientata pe Obiecte


class AltA extends A { } class AltB extends B { } void f1 () { new AltA().f1(); } void f2 () { new AltB().f2(); } // 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(); m.f1(); m.f2(); m.f3(); } }

Pentru clasa M se pot apela functii mostenite de la clasele A, B si C la care se pot aduga si alte functii suplimentare. Simplificarea comunicrii ntre clase In exemplul urmtor metodele clasei exterioare SimpleList se pot referi direct la variabile din clasa inclus Node (si nu prin intermediul unor metode publice).
public class SimpleList extends AbstractList { // clasa pentru liste simplu inlantuite // clasa inclusa in clasa SimpleList class Node { private Object val; private Node next; public Node (Object ob) { val=ob; next=null; } } // variabile ale clasei SimpleList private Node head; // inceput lista private int n; // nr de noduri in lista // functii membre ale clasei SimpleList public SimpleList () { head= new Node(null); // santinela n=0; } public int size() { return n; } public Object get (int k) { if ( k > n) return null; Node p=head.next; // var. din clasa inclusa for (int i=0;i<k;i++) p=p.next; // var. din clasa inclusa return p.val; // var. din clasa inclusa } public boolean add (Object el) { // adauga la sfarsit daca nu exista deja Node nou = new Node(el); Node p =head; // var. din clasa inclusa while (p.next != null) // var. din clasa inclusa p=p.next; // var. din clasa inclusa p.next=nou; // var. din clasa inclusa n=n+1; return true; } }

119

Florian Moraru Programare Orientata pe Obiecte O clas iterator poate lucra cu variabile ale clasei colectie pe care face enumerarea; accesul direct la aceste variabile este simplificat dac se include clasa iterator n clasa colectie. Instantierea clasei interioare se face prin metoda iterator din clasa exterioar:
public class SimpleList extends AbstractList { private Node head; // inceput lista // clasa iterator inclusa class SListIterator implements Iterator { private Node pos; // cursor= adresa nod curent SListIterator () { // constructor pos=head.next; // var head din clasa SimpleList } public boolean hasNext () { return pos != null; } public Object next() { Object obj =pos.val; // var. din clasa Node pos=pos.next; // var. din clasa Node return obj; } public void remove () { throw new UnsupportedOperationException(); } } // metoda a clasei SimpleList public Iterator iterator () { return new SListIterator(); } ... // alti membri ai clasei exterioare: clase, variabile, 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), variabilele fiind componente ale instantei curente. 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. O astfel de situatie apare n cazul a dou clase cu rol de productor si respectiv de consumator care-si transmit date printr-o coad de obiecte, pentru acoperirea diferentei dintre ritmul de producere si ritmul de consumare a mesajelor. Obiectul coad este folosit att de obiectul productor ct si de obiectul consumator. Avem cel putin dou solutii pentru a permite accesul la obiectul comun coad: - Transmiterea unei referinte la acest obiect la construirea obiectelor productor si consumator . - Includerea claselor productor si consumator ntr-o clas gazd, alturi de obiectul coad. Urmeaz mai nti solutia cu clase de acelasi nivel:
class Producer { // proces producator Queue q; public Producer (Queue q) { this.q=q; } public void put(Object x) { // pune un obiect in coada q.add(x); } } class Consumer { // proces consumator Queue q; public Consumer (Queue q) { this.q=q; }

120

Florian Moraru Programare Orientata pe Obiecte


public Object get() { if ( q.isEmpty()) return null; return q.del(); } } // simulare procese producator-consumator class Simulare { public static void main(String[] args) throws Exception { int qm=0,ql, r, k=0; Integer x; Queue q= new Queue(); Producer p=new Producer(q); // obiectul p se refera la ob. q Consumer c=new Consumer(q); // obiectul c se refera la ob. q while ( k < 21) { r= ((int)(Math.random() * 2)); switch (r) { case 0: p.put(new Integer(k)); k++; break; // activare producator case 1: System.out.println(c.get()); break; // activare consumator } } // daca coada goala

Pentru comparatie urmeaz solutia cu clase interioare. Clasele incluse au fost declarate statice pentru a putea fi instantiate din metoda static main.
class Simulare { static class Producer { // clasa inclusa public void run() { q.add(new Byte(k)); k++; } } static class Consumer { // clasa inclusa public void run() { if ( ! q.isEmpty()) System.out.println("Consumator " + " scoate " + q.del()); } } static Queue q ; // obiectul coada static Producer p ; // proces producator static Consumer c ; // proces consumator static byte k=1; // simulare procese producator-consumator public static void main(String[] args) { q= new Queue(); p=new Producer(); c=new Consumer(); while ( k <=20) switch ((int)(Math.random()*2)) { case 0: p.run(); break; case 1: c.run(); break; } } }

Clase interioare anonime In exemplele anterioare clasele incluse au fost definite explicit, folosind cuvntul class, dar se pot defini ad-hoc clase incluse anonime, pe baza unei alte clase sau a unei interfete (prin extindere sau prin implementare implicit, deoarece nu se folosesc cuvintele extends sau implements).

121

Florian Moraru Programare Orientata pe Obiecte Uneori numele unei clase incluse apare o singur dat, pentru a crea un singur obiect de acest tip. In plus, clasa inclus implementeaz o interfat sau extinde o alt clas si contine numai cteva metode scurte. Pentru astfel de situatii se admite definirea ad-hoc de clase anonime, printr-un bloc care urmeaz operatorului new cu un nume de interfat sau de clas abstract. Sintaxa definirii unei clase anonime este urmtoarea:
new Interf ( ) { ... // 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. 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). Exemplu de sortare a unei liste de obiecte n ordine descresctoare
Collections.sort (list, new Comparator( ) { // ordonare descrescatoare public int compare (Object t1, Object t2) { // incepe definitia clasei anonime Comparable c1=(Comparable)t1, c2=(Comparable)t2; return - c1.compareTo(c2); // rezultat invers metodei compareTo } }); // 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, Object e2) { Comparable c1=(Comparable)e1; Comparable c2=(Comparable)e2; return c1.compareTo(c2); } }; public SortedArray () { super();} public SortedArray (Comparator comp) { super(); cmp=comp; } public int indexOf (Object obj) { return Collections.binarySearch (this,obj,cmp); } ... // alte metode }

Iat si o alt definitie a metodei iterator din clasa SimpleList, n care clasa iterator pentru liste este anonim si este definit n expresia care urmeaz lui new.
public Iterator iterator () { return new Iterator() { // definirea clasei anonime iterator ca o clasa inclusa private Node pos=head.next; public boolean hasNext () { return pos != null; } public Object next() { Object obj =pos.val; pos=pos.next; return obj; }

122

Florian Moraru Programare Orientata pe Obiecte


public void remove () { } }; // sfrsit 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, dar pot exista dificultti la ntelegerea codului si erori de utilizare a acoladelor si parantezelor. Intre clasa interioar si clasa exterioar exist un "cuplaj" foarte strns; acest cuplaj poate fi un dezavantaj la restructurarea (refactorizarea) unei aplicatii, dar poate fi exploatat n definirea unor clase de bibliotec (care nu se mai modific). 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().iterator(); public boolean hasNext() { return i.hasNext(); } public Object next() { return ((Entry)i.next()).getValue(); } public void remove() { i.remove(); } }; // aici se termina instr. return new Iterator } public int size() { // din subclasa lui AbstractCollection return AbstractMap.this.size(); // this = obiect din clasa anonima } public boolean contains(Object v) { return AbstractMap.this.containsValue(v); } }; // aici se termina instr. values= new } return values; } }

Probleme asociate claselor incluse O clas inclus ntr-un bloc poate folosi variabile locale din acel bloc, inclusiv argumente formale ale functiei definite prin blocul respectiv. Orice variabil sau parametru formal folosit ntr-o clas inclus trebuie declarat cu atributul final, deoarece aceast variabil este copiat n fiecare instant a clasei incluse si toate aceste copii trebuie s aib aceeasi valoare (nemodificat n cursul executiei). Exemplul urmtor este o functie similar functiei iterator dintr-o clas colectie, dar itereaz pe un vector intrinsec de obiecte. 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.
public static Iterator arrayIterator (final Object a[] ) { // clasa interioara functiei class AI implements Iterator { int i=0; // iterator pentru vectori intrinseci

123

Florian Moraru Programare Orientata pe Obiecte


public boolean hasNext() { return i < a.length && a[i] != null ; } public Object next() { return a[i++]; } public void remove() { } // neimplementata } return new AI(); }

Clasa AI poate s fie definit ca o clas inclus anonim deoarece acest nume nu este folosit n afara functiei arrayIterator. O clas definit ntr-un bloc nu este membr a clasei ce contine acel bloc si nu este vizibil din afara blocului. 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 boolean hasNext() { return i < a.length && a[i] != null ; } public Object next() { return a[i++]; } public void remove() { throw new UnsupportedOperationException();} }; // aici se termina instr. return }

O clas anonim nu poate avea un constructor explicit si poate fie s extind o alt clas, fie s implementeze o interfat (ca n exemplul anterior) cu aceeasi sintax. Un nume de interfat poate urma cuvntului cheie new numai la definirea unei clase anonime care implementeaz acea interfat, iar lista de argumente trebuie s fie vid. O clas anonim nu poate simultan s extind o alt clas si s implementeze o interfat, sau s implementeze mai multe interfete. Numele claselor incluse anonime sunt generate de compilator prin adugarea la numele clasei exterioare a caracterului $si a unui numr (a cta clas inclus este). Variabilele locale din clasa exterioar sau din blocul exterior sunt copiate de compilator n cmpuri private ale clasei interioare, cu nume generate automat de compilator si transmise ca argumente constructorului clasei interioare. Pentru functia anterioar compilatorul Java genereaz un cod echivalent de forma urmtoare:
public static Iterator arrayIterator (final Object a[]) { return new C$1(a); // C este numele clasei ce contine metoda } class C$1 implements Iterator { private Object val$a[]; int i; OuterClass$1 (Object val$a[]) { // constructor this.val$a = val$a; i=0; } public boolean hasNext () { return i < val$a.length; } public Object next() { return val$a[i++]; } public void remove() { throw new UnsupportedOperationException();} }

124

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

125

Florian Moraru Programare Orientata pe Obiecte elementele necesare proiectrii de interfete grafice complexe, atrgtoare si personalizate dup cerintele aplicatiei si ale beneficiarilor, reducnd substantial efortul de programare a unor astfel de aplicatii (inclusiv editoare de texte, navigatoare pe retea si alte utilitare folosite frecvent). O parte din clasele JFC sunt folosite ca atare (prin instantiere) iar altele asigur doar o baz pentru definirea de clase derivate. Clase JFC pentru interfat grafic O alt clasificare posibil a claselor Swing este urmtoarea: - Clase JFC care au corespondent n clasele AWT, avnd aproape acelasi nume (cu prefixul 'J' la clasele JFC) si acelasi mod de utilizare: JComponent, JButton, JCheckBox, JRadioButton, JMenu, JComboBox, JLabel, JList, JMenuBar, JPanel, JPopUpMenu, JScrollBar, JScrollPane, JTextField, JTextArea. - Clase JFC noi sau extinse: JSlider, JSplitPanel, JTabbedPane, JTable, JToolBar, JTree, JProgressBar, JInternalFrame, JFileChooser, JColorChooser etc. - Clase de tip model, concepute conform arhitecturii MVC (Model-View-Controller): DefaultButtonModel, DefaultListSelectionModel, DefaultTreeModel, AbstractTableModel etc. Clasele JFC container de nivel superior sunt numai trei: JFrame, JDialog si JApplet. Primele dou sunt subclase (indirecte) ale clasei Window din AWT. Toate celelalte clase JFC sunt subclase directe sau indirecte ale clasei JComponent, inclusiv clasa container intermediar JPanel. Controalele JFC pot fi inscriptionate cu text si/sau cu imagini (ncrcate din fisiere GIF sau definite ca siruri de constante n program). In jurul componentelor pot fi desenate borduri , fie pentru delimitarea lor, fie pentru crearea de spatii controlabile ntre componente vecine. Un program minimal cu clase JFC, creaz si afiseaz componentele vizuale pe ecran, fr s trateze evenimentele asociate acestor componente. 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), care constituie fundalul pentru celelalte componente; se creaz componente atomice si se adaug la panou obiectele grafice create de programator (cu metoda add). In final, se stabilesc dimensiunile ferestrei principale (metoda setSize sau pack) si se comand afisarea ferestrei principale (metoda setVisible sau show). 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), mrire (maximizare) si nchidere fereastr . Exemplul urmtor afiseaz o fereastr cu titlu, dar fr alte componente vizuale :
import javax.swing.*; class EmptyFrame { public static void main ( String args[]) { JFrame frm = new JFrame(EmptyFrame); frm.pack(); // sau frm.setSize(500,300) frm.setVisible (true); // sau frm.show(); } }

Fereastra principal se afiseaz initial ntr-o form redus la bara de titlu cu cele 3 butoane generale, dup care poate fi mrit. Pentru afisarea continutului ferestrei chiar de la nceput se poate folosi metoda pack (din clasa JFrame) sau metoda setSize (din clasa Container). Adugarea de componente vizuale la fereastra principal JFrame se poate face n dou moduri. In exemplul urmtor se extrage "panoul" ferestrei cu "getContentPane" si se adaug o component JLabel (o etichet) la acest panou:

126

Florian Moraru Programare Orientata pe Obiecte


import javax.swing.*; class LabelFrame { public static void main (String args[ ]){ JFrame frame = new JFrame(); JLabel label = new JLabel ("Eticheta"); frame.add(label); frame.setSize(200,200); frame.setVisible(true); } }

// fereastra cu o eticheta in ea // fereastra aplicatiei // creare eticheta // adauga eticheta la fereastr // dimensiuni fereastra // afisare continut fereastr

Inainte de versiunea 6, nu era posibil adugarea de componente atomice (JLabel, de ex.) direct la un obiect JFrame, ci numai la un obiect Container, extras din JFrame cu metoda getContentPane:
frame.getContentPane().add(label); // adauga eticheta la fereastra principala

In exemplul urmtor se creeaz un panou JPanel, pe care se plaseaz eticheta, iar panoul este adugat ulterior la fereastra JFrame:
// adaugare la un panou introdus apoi in obiectul JFrame import javax.swing.*; class LabelFrame { // fereastra cu o eticheta in ea public static void main (String args[]){ JFrame frame = new JFrame(); // fereastra aplicatiei JPanel panel = new JPanel(); // un obiect panou panel.add (new JLabel ("Eticheta")) ; // adaug eticheta la panou frame.setContentPane (panel); // sau: frame.add (panel); frame.show (); // afisare continut fereastr } }

Dup apelul metodei setVisible sau show nu mai trebuie create si adugate alte componente vizuale ferestrei JFrame, chiar dac se modific date prezentate n unele din aceste componente (cum ar fi JList, JTable s.a.). Efectuarea unui clic pe butonul de nchidere al ferestrei principale (X) are ca efect nchiderea ferestrei, dar nu se termin aplicatia dac nu estre tratat evenimentul produs de acest clic. De aceea este necesar tratarea acestui eveniment, sau terminarea programului de ctre operator, prin Ctrl-C. Incepnd cu versiunea 1.3 mai exist o posibilitate de terminare a aplicatiei la nchiderea ferestrei principale:
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);

Solutii de programare a interfetelor grafice In general, crearea si afisarea unei interfete grafice necesit urmtoarele operatii din partea programatorului aplicatiei: - Crearea unui obiect fereastr principal, de tip JFrame sau de un subtip al tipului JFrame, si stabilirea propriettilor ferestrei (titlu, culoare, dimensiuni etc.) - Crearea componentelor atomice si stabilirea propriettilor acestora (dimensiuni, text afisat, culoare, tip chenar etc.) - Gruparea componentelor atomice n containere intermediare, care sunt obiecte de tip JPanel sau de un subtip al acestei clase. - Adugarea containerelor intermediare la fereastra aplicatiei si stabilirea modului de asezare a acestora, dac nu se prefer modul implicit de dispunere n fereastr.

127

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

Varianta a doua foloseste delegarea sarcinilor legate de afisare ctre un obiect JFrame, continut de clasa GUI: import javax.swing.*; import java.awt.*;
class GUI2 { private JFrame frame; private JLabel lbl1 = new JLabel ("Directory"); private JTextField txt1 = new JTextField (16); // constructor public GUI2 ( String title) { frame = new JFrame(title); init(); frame.show(); } // initializare componente private void init() {

128

Florian Moraru Programare Orientata pe Obiecte


frame.setLayout(new FlowLayout()); frame.add (lbl1); frame.add(txt1); frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); // txt1.addActionListener (new TxtListener()); frame.setSize (300,100); } // activare interfata grafica public static void main (String arg[]) { new GUI2("GUI solution 2"); } }

Varianta 3 defineste clasa GUI ca o subclas a clasei JPanel:


import javax.swing.*; class GUI3 extends JPanel { private JLabel lbl1 = new JLabel ("Directory"); private JTextField txt1 = new JTextField (16); // constructor public GUI3 () { init(); } // initializare componente private void init() { add (lbl1); add(txt1); // txt1.addActionListener (new TxtListener()); } public static void main (String arg[]) { JFrame frame = new JFrame ("GUI solution 3"); frame.add (new GUI3()); // frame.setContentPane(new GUI3()); frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); frame.setSize(300,100); frame.show(); } }

Clasele asculttor la evenimente ( TxtListener si altele) sunt de obicei clase incluse n clasa GUI pentru a avea acces la variabilele ce definesc obiecte JFC. Dac sunt putini asculttori, atunci clasa GUI poate implementa una sau cteva interfete de tip ascultator la evenimente si s contin metodele de tratare a evenimentelor (dispare necesitatea unor clase asculttor separate). Variabilele de tipuri JFC ( JLabel, JTextField, s.a) pot fi initializate la declarare sau n constructorul clasei GUI, deoarece va exista un singur obiect GUI. Metoda init de initializare a componentelor JFC poate lipsi dac are numai cteva linii. De observat c pentru clasele GUI constructorul este cea mai important functie si uneori singura functie din clas. Dup executia metodei setVisible nu se vor mai crea alte obiecte JFC (de exemplu, ca rspuns la evenimente generate de operatorul uman), deoarece ele nu vor mai fi vizibile pe ecran. In schimb, se practic modificarea continutului afisat n componentele deja existente; de exemplu, metoda setText din clasele JTextField si JLabel poate modifica textul afisat n astfel de componente JFC. Dispunerea componentelor ntr-un panou Plasarea componentelor grafice pe un panou se poate face si prin pozitionare n coordonate absolute de ctre programator, dar este mult mai simplu s apelm la un obiect de control al asezrii n panou (Layout Manager), obiect selectat prin metoda setLayout si care stabileste automat dimensiunile

129

Florian Moraru Programare Orientata pe Obiecte si pozitia fiecrei componente ntr-un panou. Pentru panoul de continut al ferestrei JFrame este activ implicit BorderLayout, mod care foloseste un al doilea parametru n metoda add. Dac nu se specific pozitia la adugare, atunci componenta este centrat n fereastr, iar dac sunt mai multe componente, atunci ele sunt suprapuse pe centrul ferestrei. Exemplu de plasare a trei butoane:
class DefaultLayout { // exemplu de asezare butoane in fereastra public static void main (String args[ ]) { JFrame frame = new JFrame(); Container cp = frame.getContentPane(); cp.add(new JButton( 1 ),BorderLayout.EAST); // cp.add (...,East) cp.add(new JButton( 2 ),BorderLayout.CENTER); // cp.add (...,Center) cp.add(new JButton( 3 ), BorderLayout.WEST); // cp.add (..., West) frame.setVisible(true); } }

De observat c obiectul extras cu "getContentPane" are tipul Container. O alt solutie este alegerea modului FlowLayout, care aseaz componentele una dup alta de la stnga la dreapta si de sus n jos n functie de dimensiunile lor si ale ferestrei principale. Exemplu:
class FlowLayoutDemo { // Asezare butoane in fereastra public static void main (String args[ ]) { JFrame frame = new JFrame(); Container cp = frame.getContentPane(); cp.setLayout(new FlowLayout()); // sau new GridLayout() for (int i=1;i<7;i++) // 6 butoane cu cifre cp.add (new JButton (String.valueOf(i)) ); frame.setSize(400,200); frame.setVisible(true); } }

De observat c pentru un panou JPanel asezarea implicit este FlowLayout. Alte modalitti de dispunere a componentelor ntr-un panou sunt GridLayout (o matrice de componente egale ca dimensiune), GridBagLayout, BoxLayout ( asezare compact pe vertical sau pe orizontal, la alegere) si CardLayout ( componente / panouri care ocup alternativ acelasi spatiu pe ecran). Alegerea modului de dispunere depinde de specificul aplicatiei : - 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 exemplu. - In cazul a cteva componente ce trebuie s apar n mrimea lor natural si ct mai compact se va folosi BoxLayout sau BorderLayout: pentru cteva butoane sau cmpuri text, de exemplu. - In cazul mai multor componente de aceeasi mrime se va folosi GridBagLayout: grupuri de butoane, de exemplu. - In cazul unor componente de diferite dimensiuni BoxLayout sau GridBagLayout permite un control mai bun al plasrii componentelor si al intervalelor dintre ele. Asezarea componentelor ntr-un panou se poate modifica automat atunci cnd se modific dimensiunile panoului, dimensiunile sau numrul componentelor (n modul FlowLayout). Exist diverse metode de a mentine pozitia relativ a dou sau mai multe componente vizuale, indiferent de dimensiunile panoului unde sunt plasate. De exemplu, o etichet (obiect JLabel) trebuie s apar ntotdeauna la stnga unui cmp text sau deasupra unei zone text. Acest efect se poate obtine folosind un GridLayout cu dou coloane sau un BoxLayout cu asezare pe orizontal.

130

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), iar un cmp text (obiect JTextField) contine un text si permite modificarea textului de ctre operator sau prin program. Un cmp text (JTextField) permite afisarea, introducerea si editarea unei linii de text n cadrul unei ferestre text. La construirea unui obiect JTextField se foloseste ca parametru un sir de caractere sau un ntreg pentru dimensiunea ferestrei text. 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 cmp // permite sau interzice editarea de text

In exemplul urmtor se foloseste un cmp text (nemodificabil de la tastatur) pentru afisarea numelui directorului curent:
// afisare nume director curent class UseTextField { static JFrame frame = new JFrame(); static JTextField tf = new JTextField (20); public static void main (String args[]) { JPanel pane = new JPanel(); File dir =new File("."); // directorul curent pane.add (new JLabel("Current Directory")); // adaug etichet pe panou tf.setText ( dir.getAbsolutePath() ); // nume cu cale completa tf.setEditable(false); // interzice editare camp text pane.add(tf); // adaug cmp text pe panou frame.setContentPane(pane); // sau frame.add(pane) frame.setSize(300,100); frame.setVisible(true); // comand afisarea } }

Principala utilizare a unui cmp text este pentru introducerea de date de ctre operatorul aplicatiei. Pentru a informa operatorul asupra semnificatiei unui cmp text se poate aduga o etichet fiecrui cmp. Exemplu de utilizare cmpuri text, cu etichete asociate, ntr-un formular de introducere date:
class InputForm { public static void main (String arg[]) { JFrame f = new JFrame(); JPanel p =new JPanel(); p.add (new JLabel("Nume")); // o eticheta pentru rubrica nume p.add (new JTextField (20)); // rubrica pentru nume p.add (new JLabel("Vrsta")); // o eticheta ptr rubrica varsta p.add (new JTextField (8)) ; // rubrica pentru vrst f.setContentPane(p); // adauga panou la fereastra principala f.setSize(300,100); f.setVisible(true); // comanda afisarea pe ecran } }

Realizarea unui formular cu mai multe rubrici este n general mai complicat dect n exemplul anterior pentru c necesit controlul dispunerii cmpurilor text si etichetelor asociate, folosind alte panouri si margini de separare ntre aceste panouri. O zon text (JTextArea) permite afisarea mai multor linii de text, care pot fi introduse sau

131

Florian Moraru Programare Orientata pe Obiecte modificate de la tastatur sau prin program (cu metoda append). Dimensiunile zonei text se stabilesc la construirea unui obiectului JTextArea. Exemplu de utilizare zon text :
public static void main ( String args [ ]) { JFrame frm = new JFrame (); JTextArea ta = new JTextArea(10,5); frm.getContentPane().add (ta); for (int i=1;i<21;i++) ta.append (100*i+"\n"); frm.pack(); frm.setVisible(true); }

// 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, 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 vzute din cauza dimensiunii limitate a zonei text. Exemplu cu o zon text pentru afisarea numelor fisierelor din directorul curent:
public static void main (String av[ ]) { File dir = new File("."); // directorul curent String files[ ] = dir.list(); // fisiere din directorul curent JFrame win= new JFrame("Current Directory"); // titlul ferestrei JTextArea ta = new JTextArea(20,20); // o zona text for (int i=0;i<files.length;i++) ta.append (" "+files[i]+"\n"); // adauga nume fisiere la zona win.getContentPane().add (new JScrollPane(ta)); // n panou cu derulare win.pack(); win.setVisible(true); // comanda afisarea }

Operatorul poate deplasa cursorul n cadrul zonei text si poate modifica textul. Clasele JList, JComboBox si JSpinner permit selectarea unei linii (unei optiuni) din mai multe linii afisate (simultan la JList, succesiv la JSpinner sau la cerere). Un obiect JComboBox afiseaz pe ecran numai linia selectat, dar prin mouse se poate cere afisarea tuturor liniilor continute, pentru o alt selectie. Exemplu:
public static void main (String arg[ ]) { JFrame frm = new JFrame(); String s[ ]={"Name","Ext","Date","Size"}; // texte afisate in ComboBox JComboBox cbox = new JComboBox(s); // creare obiect ComboBox JLabel et = new JLabel ("Sort By: "); // o eticheta asociata Container cp = frm.getContentPane(); cp.add (et,"West"); cp.add (cbox,"Center"); // adauga eticheta si ComboBox frm.pack(); frm.show(); // comanda afisarea }

Pentru ca programul s poat reactiona la selectarea sau modificarea unor linii trebuie adugate programelor anterioare secvente de tratare a evenimentelor produse de aceste actiuni exterioare programului. Tratarea unui eveniment se face ntr-o metod cu nume si argumente impuse (functie de tipul evenimentului), metod inclus ntr-o clas care implementeaz o anumit interfat (functie de eveniment). In cazul unui panou cu mai multe componente text, cum ar fi un formular de introducere date sau o foaie de calcul, exist o singur component care primeste date de la tastatur; se spune c tastatura este focalizat pe acea component sau c acea component detine controlul tastaturii. Selectarea componentei focalizate se poate face fie prin program (metoda requestFocus sau grabFocus), fie de ctre operator prin clic pe mouse dup pozitionare pe component sau prin tasta Tab (care mut

132

Florian Moraru Programare Orientata pe Obiecte focalizarea pe urmtoarea component din fereastr). Orice obiect Swing care poate primi intrri de la tastatur ( inclusiv butoane) poate detine controlul (focus). Cstigarea sau pierderea controlului de ctre o component produce evenimente specifice (FocusEvent) si apelarea de metode focusGained sau focusLost. In cazul unui formular cu multe cmpuri text sau al unui tabel (JTable) pierderea focalizrii poate nsemna terminarea introducerii de date n acel cmp sau celul si deci poate nlocui evenimentul produs de tasta Enter (terminare introducere text). Panouri multiple In cadrul unei ferestre principale avem urmtoarele posibilitti de lucru cu panouri: - Panouri multiple afisate simultan, fr suprapunere ntre ele. - Panouri divizate n dou (pe orizontal sau pe vertical): JSplitPanel. - Panouri multiple afisate succesiv in aceeasi zon ecran : JTabbedPane. - Panouri multiple afisate simultan si partial suprapus: JLayeredPane. In fiecare dintre panourile unei aplicatii putem avea un panou cu derulare JScrollPane. 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. Pentru inserarea de spatii ntre panouri se pot crea margini (borduri) n jurul fiecrui panou. Gruparea unor componente n (sub)panouri permite manipularea unui panou separat de celelalte si alegerea altui mod de dispunere n fiecare panou. Exemplu cu dou panouri independente afisate simultan
class MFrame extends JFrame { JPanel p1 = new JPanel(); JPanel p2 = new JPanel(); JButton b[] = new JButton[4]; public MFrame () { Container w = getContentPane(); for (int k=0;k<4;k++) b[k]= new JButton( + k + ); p1.setLayout (new FlowLayout()); p1.add (b[0]); p1.add (b[1]); p2.setLayout (new FlowLayout() ); p2.add (b[2]); p2.add (b[3]); w.add (p1,North); w.add (p2,South); } // utilizare MFrame public static void main ( String args []) { JFrame f = new MFrame (); f.setSize (100,100); f.show (); } } // 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 urmtor afiseaz n dou panouri "legate" dou liste de fisiere diferite, dar el poate fi modificat astfel ca n panoul din dreapta s se afiseze continutul fisierului director selectat din lista afisat n panoul din stnga .
class SplitPane extends JFrame { public SplitPane() { JScrollPane p1 = dirlist("."); JScrollPane p2 = dirlist("c:\\"); JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, p1, p2); getContentPane().add(sp);

133

Florian Moraru Programare Orientata pe Obiecte


} public static JScrollPane dirlist (String dirname) { String files[] = new File(dirname).list(); JList lst = new JList(files); return new JScrollPane(lst); } public static void main(String s[]) { JFrame frame = new SplitPane(); frame.setSize(400,200); frame.setVisible(true); } }

In exemplul urmtor cele dou panouri sunt afisate alternativ, n aceeasi zon, n functie de selectia operatorului (fiecare panou are un tab de prindere, adic o mic portiune cu numele panoului, afisat permanent pe ecran alturi de tab-urile celorlalte panouri selectabile).
class TabbedPane extends JFrame { public TabbedPane() { String t1=".", t2="C:\\"; JScrollPane p1 = dirlist(t1); JScrollPane p2 = dirlist(t2); JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("Dir of "+t1, null, p1, ""); tabbedPane.addTab("Dir of "+t2, null, p2, ""); setLayout(new GridLayout(1, 1)); add(tabbedPane); } ... }

Apleti Java Cuvntul aplet (applet) desemneaz o mic aplicatie care foloseste ecranul n mod grafic, dar care depinde de un alt program gazd pentru crearea fereastrei principale (care nu trebuie creat de programatorul apletului). Programul gazd este fie un program navigator (browser), fie programul appletviewer, destinat vizualizrii rezultatului executiei unui aplet. Codul unui aplet (fisierul .class) poate fi adus de ctre browser si de la un alt calculator dect cel pe care se execut. Din punct de vedere sintactic un aplet este o clas Java, derivat din clasa Applet sau din JApplet. Clasa JApplet este indirect derivat din clasa Panel , care asigur oricrui aplet o fereastr cu butoane de nchidere, mrire si micsorare. Fereastra de afisare a unui aplet nu poate fi manipulat direct de operatorul uman ci numai indirect, prin fereastra programului browser. Programarea unei interfete grafice ntr-un aplet este putin mai simpl dect 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). Exemplu de aplet scris n varianta AWT:
import java.awt.*; import java.applet.*; public class LabelAplet extends Applet { Label et= new Label ("Eticheta",Label.CENTER); public void init () { add (et); } }

134

Florian Moraru Programare Orientata pe Obiecte Acelasi aplet n varianta JFC arat astfel:
import javax.swing.*; public class JAplet extends JApplet { JLabel et= new JLabel ("Eticheta",JLabel.CENTER); public void init () { getContentPane().add (et); } }

Clasa JApplet este derivat din clasa Applet si permite n plus folosirea unui meniu ntr-un aplet si a componentelor vizuale noi din JFC (fr echivalent n AWT). Fisierul class generat de compilator pentru un aplet este specificat ntr-un fisier html, mpreun cu dimensiunile ferestrei folosite de aplet, ntre marcajele <applet> si </applet>. Exemplu de fisier html necesar pentru executia apletului precedent: <applet code="JAplet.class" width="250" height="100"> </applet> In comanda appletviewer este specificat numele fisierului html si nu apare direct numele fisierului class. Dimensiunile ferestrei folosite de aplet se dau n fisierul de tip "html" si nu n codul Java. 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. De remarcat c o clas care corespunde unui aplet trebuie s aib atributul public si nu contine o metod main. Clasa aplet mosteneste si redefineste de obicei metodele init, start, "paint" si alte cteva metode, apelate de programul gazd la producerea anumitor evenimente. O clas aplet poate fi transformat ntr-o aplicatie prin adugarea unei functii main n care se construieste un obiect JFrame, la care se adaug un obiect aplet si se apeleaz metoda init:
public static void main (String args[ ]) { JFrame f = new JFrame(); JAplet aplet = new JAplet(); f.getContentPane().add (aplet); aplet.init(); f.setVisible (true); } // 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). Functia init este nlocuit cu functia main la trecerea de la un aplet la o aplicatie. Din punct de vedere functional un aplet contine cteva functii, care trebuie (re)definite de utilizator si sunt apelate de programul gazd. Un aplet care trateaz evenimente externe trebuie s contin si metodele de tratare a evenimentelor, pentru c nu se admit alte clase asculttor, separate de clasa aplet. Obiectul asculttor la evenimente este chiar obiectul aplet, ceea ce conduce la instructiuni de forma urmtoare
comp.addXXXListener(this); // comp este numele unei componente din aplet

Exemplul urmtor este un aplet care afiseaz un buton n centrul ferestrei puse la dispozitie de programul gazd si emite un semnal sonor ("beep") la "apsarea" pe buton, adic la actionarea butonului din stnga de pe mouse dup mutare mouse pe zona ecran ocupat de buton.
public class Aplet extends JApplet implements ActionListener {

135

Florian Moraru Programare Orientata pe Obiecte


JButton button; public void init() { button = new JButton("Click Me"); getContentPane().add(button, BorderLayout.CENTER); button.addActionListener(this); // obiectul receptor este chiar apletul } public void actionPerformed(ActionEvent e) { // tratare eveniment buton Toolkit.getDefaultToolkit().beep(); // semnal sonor } }

Metoda "init" este apelat o singur dat, la ncrcarea codului apletului n memorie, iar metoda "start" este apelat de fiecare dat cnd programul browser readuce pe ecran pagina html care contine si marcajul <applet ...>. Metoda "paint" are un parametru de tip Graphics, iar clasa Graphics contine metode pentru desenarea de figuri geometrice diverse si pentru afisarea de caractere cu diverse forme si mrimi:
public void paint (Graphics g) { g.drawRect (0, 0, getSize().width - 1, getSize().height - 1); // margini fereastra g.drawString ("text in aplet", 10, 30); // afisare text }

136

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

137

Florian Moraru Programare Orientata pe Obiecte Pentru a trata numai evenimentul nchidere fereastr este suficient s scriem numai metoda windowClosing. Dac s-ar defini o clas care s implementeze aceast interfat atunci ar trebui s definim toate cele sapte metode ale interfetei, majoritatea cu definitie nul (fr efect). Pentru a simplifica sarcina programatorului este prevzut o clas adaptor WindowAdapter care contine definitii cu efect nul pentru toate metodele interfetei, iar programatorul trebuie s redefineasc doar una sau cteva metode. Metoda windowClosing din aceast clas nu are nici un efect si trebuie redefinit pentru terminarea aplicatiei la nchiderea ferestrei principale. Pentru a compila exemplele care urmeaz se vor introduce la nceput urmtoarele instructiuni:
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*;

In exemplul urmtor se defineste o clas separat, care extinde clasa WindowAdapter si redefineste metoda windowClosing:
class WindExit extends WindowAdapter { public void windowClosing( WindowEvent e) { System.exit(0); } } class FrameExit { public static void main (String args[ ]) { JFrame f = new JFrame (); f.addWindowListener ( new WindExit()); f.show (); } }

Putem folosi si o clas inclus anonim pentru obiectul asculttor necesar:


public static void main (String args[ ]) { JFrame f = new JFrame (); f.addWindowListener ( new WindowAdapter() { public void windowClosing( WindowEvent e) { System.exit(0); } }); f.show (); }

Evenimente de mouse si de tastatur Interfata KeyListener contine trei metode : keyPressed, keyTyped si keyReleased, iar clasa KeyAdapter contine definitii nule pentru cele trei metode. In exemplul urmtor este tratat numai evenimentul de tast apsat prin afisarea caracterului corespunztor tastei:
class KeyEvent1 { static JFrame f= new JFrame(); static JTextArea ta; static JTextField tf; class KeyHandler extends KeyAdapter { public void keyPressed(KeyEvent e) { ta.append("\n key typed: " + e.getKeyChar() );

138

Florian Moraru Programare Orientata pe Obiecte


} } public static void main(String args[]) { KeyEvent1 kd = new KeyEvent1(); tf = new JTextField(20); // aici se introduc caractere tf.addKeyListener(kd.new KeyHandler()); ta = new JTextArea(20,20); // aici se afiseaza caracterele introduse Container cp = f.getContentPane(); cp.setLayout(new GridLayout()); cp.add(tf); cp.add(new JScrollPane (ta)); f.show( ); }

Evenimentele de la tastatur sunt asociate componentei selectate prin mouse. Este posibil si selectarea prin program a componentei pe care se focalizeaz tastatura folosind metoda care exist n orice component JFC numit requestFocus. In exemplul urmtor s-a adugat un buton de stergere a continutului zonei text, a crui apsare are ca efect focalizarea tastaturii pe cmpul text.
class KeyEvent2 { static JFrame f= new JFrame(); static JTextArea ta; static JTextField tf; static JButton button = new JButton("Clear"); class KeyHandler extends KeyAdapter { // tratare eveniment tastatura public void keyPressed(KeyEvent e) { ta.append("\n key typed: " + e.getKeyChar() ); } } // tratare eveniment buton class ActHandler implements ActionListener { public void actionPerformed(ActionEvent e) { ta.setText(""); tf.setText(""); tf.requestFocus(); // refocalizare pe TextField } } ... }

Programele anterioare lucreaz corect numai pentru taste cu caractere afisabile, dar ele pot fi extinse pentru a prezenta orice caracter introdus. Interfata MouseInputListener contine metode apelate la actiuni pe mouse (apsarea sau eliberare buton: mouseClicked, mousePressed, mouseReleased) si metode apelate la deplasare mouse (mouseMoved,mouseDragged). MouseInputAdapter ofer o implementare nul pentru toate cele 7 metode. Exemplul urmtor arat cum se poate reactiona la evenimentul de clic pe mouse prin numrarea acestor evenimente si afisarea lor.
class MouseClicks { static int clicks=0; // numar de apasari pe mouse public static void main (String args[]) { JFrame frame = new JFrame(); final JLabel label= new JLabel("0"); // o comp. neinteractiva label.addMouseListener (new MouseInputAdapter() { // receptor evenimente public void mouseClicked(MouseEvent e) { ++clicks; label.setText((new Integer(clicks)).toString()); // modifica afisarea }

139

Florian Moraru Programare Orientata pe Obiecte


}); frame.getContentPane().setLayout (new FlowLayout()); frame.getContentPane().add(label); frame.setVisible(true); } }

Evenimente asociate componentelor JFC Orice component vizual AWT sau JFC poate fi sursa unui eveniment sau chiar a mai multor tipuri de evenimente, cauzate de un clic pe imaginea componentei sau de apsarea unei taste. Majoritatea componentelor vizuale sunt interactive, 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, ceea ce genereaz evenimente. De asemenea editarea textului dintrun cmp text sau dintr-o zon text produce evenimente. Fiecare component genereaz anumite tipuri de evenimente, si pentru fiecare tip de eveniment este prevzut o metod de tratare a evenimentului. Metodele de tratare sunt grupate n interfete. Programatorul trebuie s defineasc clase ce implementeaz aceste interfete cu metode de tratare a evenimentelor. Astfel de clase adaptor au rolul de intermediar ntre component si aplicatie. Evenimentele generate de componentele JFC pot fi clasificate n: - Evenimente comune tuturor componentelor JFC: ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent - Evenimente specifice fiecrui tip de component: ChangeEvent, MenuEvent, ListDataEvent, ListSelectionEvent, DocumentEvent etc. Evenimentele comune, mostenite de la clasa Component, pot fi descrise astfel: - ActionEvent : produs de actiunea asupra unei componente prin clic pe mouse. - ComponentEvent : produs de o modificare n dimensiunea, pozitia sau vizibilitatea componentei. - FocusEvent: produs de focalizarea claviaturii pe o anumit component JFC, pentru ca ea s poat primi intrri de la tastatur. - KeyEvent : produs de apsarea unei taste si asociat componentei pe care este focalizat tastatura. - MouseEvent : produs de apsare sau deplasare mouse pe suprafata componentei. Numele evenimentelor apar n clasele pentru obiecte eveniment si n numele unor metode: add*Listener, remove*Listener. Un buton este un obiect Swing de tip JButton care genereaz cteva tipuri de evenimente: ActionEvent (dac s-a actionat asupra butonului), ChangeEvent (dac s-a modificat ceva n starea butonului) s.a. La apsarea 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). Tratarea evenimentului nseamn scrierea unei metode cu numele actionPerformed care s produc un anumit efect ca urmare a apsrii butonului (prin clic pe suprafata sa). In general nu se trateaz toate evenimentele ce pot fi generate de o component JFC. Desi un buton poate genera peste 5 tipuri de evenimente, n mod uzual se foloseste numai ActionEvent si deci se apeleaz numai metoda actionPerformed din obiectele nregistrate ca receptori pentru butonul respectiv. Se poate spune c se produc efectiv numai evenimentele pentru care s-au definit asculttori si deci metode de tratare a evenimentelor. Exemplu de tratare a evenimentului de clic pe un buton, prin emiterea unui semnal sonor la fiecare clic:
// clasa ptr obiecte ascultator de evenimente class Listener implements ActionListener { public void actionPerformed(ActionEvent e) { Toolkit.getDefaultToolkit().beep(); // produce semnal sonor (beep) } }

140

Florian Moraru Programare Orientata pe Obiecte


// creare buton si asociere cu ascultator class Beeper1 { public static void main ( String args[]) { JFrame frame = new JFrame(); JButton buton = new JButton("Click Me"); frame.getContentPane().add(buton, BorderLayout.CENTER); button.addActionListener(new Listener()); // inscriere receptor la buton frame.setVisible(true); } }

Pentru a obtine acelasi efect si prin apsarea unei taste asociate butonului (de exemplu combinatia de taste Ctrl-C) este suficient s adugm o instructiune:
buton.setMnemonic (KeyEvent.VK_C);

Se observ c am folosit clasa Listener o singur dat, pentru a crea un singur obiect. De aceea se practic frecvent definirea unei clase asculttor anonime acolo unde este necesar:
button.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ Toolkit.getDefaultToolkit().beep(); } }); // 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, n timp ce al doilea apare indiferent de pozitia mouse pe ecran. In plus, evenimentele generate de componente JFC contin n ele sursa evenimentului si alte informatii specifice fiecrei componente. Exemplul urmtor foloseste componente de tip JCheckBox (csute cu bifare) care genereaz alt tip de evenimente (ItemEvent), ilustreaz un asculttor la mai multe surse de evenimente si utilizarea unei metode removeXXXListener:
class CheckBox1 extends JFrame { JCheckBox cb[] = new JCheckBox[3]; JTextField a = new JTextField(30); String [ ] aa = {"A","B","C"}; // litere afisate in casute public CheckBox1() { CheckBoxListener myListener = new CheckBoxListener(); // ascultator casute for (int i=0;i<3;i++) { cb[i] = new JCheckBox (aa[i]); // creare 3 casute cb[i].addItemListener (myListener); // acelasi ascultator la toate casutele } Container cp = getContentPane(); cp.setLayout(new GridLayout(0, 1)); for (int i=0;i<3;i++) cp.add(cb[i]); cp.add(a); } // Ascultator la casute cu bifare. class CheckBoxListener implements ItemListener { public void itemStateChanged(ItemEvent e) { JCheckBox source = (JCheckBox)e.getSource(); for (int i=0;i<3;i++) if (source == cb[i]) { source.removeItemListener(this); // ptr a evita repetarea selectiei

141

Florian Moraru Programare Orientata pe Obiecte


a.setText(a.getText()+aa[i]); } } } // afisare nume casut selectata

Evenimente produse de componente text Pentru preluarea caracterelor introduse ntr-un cmp text trebuie tratat evenimentul ActionEvent, generat de un obiect JTextField la apsarea tastei "Enter". Pn la apsarea tastei Enter este posibil si editarea (corectarea) textului introdus. Exemplu de validare a continutului unui cmp text la terminarea introducerii, cu emiterea unui semnal sonor n caz de eroare :
class MFrame extends JFrame { JTextField tf; class TFListener implements ActionListener { // clasa interioara public void actionPerformed (ActionEvent ev) { String text = tf.getText(); // text introdus de operator if ( ! isNumber(text)) // daca nu sunt doar cifre Toolkit.getDefaultToolkit().beep(); // emite semnal sonor } } public MFrame () { Container w = getContentPane(); tf = new JTextField(10); w.add (tf,"Center"); tf.addActionListener (new TFListener()); } // verifica daca un sir contine numai cifre static boolean isNumber (String str) { for (int i=0;i<str.length();i++) if ( ! Character.isDigit(str.charAt(i)) ) return false; return true; } public static void main ( String av[]) { JFrame f = new MFrame (); f.pack(); f.setVisible(true); } }

Unele aplicatii prefer s adauge un buton care s declanseze actiunea de utilizare a textului introdus n cmpul text, n locul tastei Enter sau ca o alternativ posibil. Selectarea unui text dintr-o list de optiuni JComboBox produce tot un eveniment de tip ActionEvent. Exemplu:
// ascultator la ComboBox class CBListener implements ActionListener { JTextField tf; public CBListener (JTextField tf) { this.tf=tf; } public void actionPerformed (ActionEvent ev) { JComboBox cb = (JComboBox) ev.getSource(); String seltxt = (String) (cb.getSelectedItem());

// sursa eveniment // text selectat

142

Florian Moraru Programare Orientata pe Obiecte


tf.setText (seltxt); } } class A { public static void main (String arg[]) { JFrame frm = new JFrame(); String s[]={"Unu","Doi","Trei","Patru","Cinci"}; // lista de optiuni JComboBox cbox = new JComboBox(s); JTextField txt = new JTextField(10); // ptr afisare rezultat selectie Container cp = frm.getContentPane(); cp.add (txt,"South"); cp.add (cbox,"Center"); cbox.addActionListener ( new CBListener (txt)); // ascultator la ComboBox frm.pack(); frm.show(); } } // afisare in campul text

Mecanismul de generare a evenimentelor De cele mai multe ori, n Java, folosim surse de evenimente prefabricate (componente AWT sau JFC) dar uneori trebuie s scriem clase generatoare de evenimente, folosind alte clase si interfete JDK. Acest lucru este necesar atunci cnd trebuie creat o component standard Java (bean), deoarece evenimentele fac parte din standardul pentru JavaBeans (componentele standard comunic ntre ele si prin evenimente, chiar dac nu sunt componente vizuale). La o surs de evenimente se pot conecta mai multi asculttori interesati de aceste evenimente, dup cum este posibil ca un acelasi obiect s poat asculta evenimente produse de mai multe surse. O surs de evenimente trebuie s contin o list de receptori ai evenimentelor generate, care era un vector de referinte la obiecte de tipul Object (in JDK 1.3 s-a introdus clasa EventListenerList pentru a manipula mai sigur aceast list). Un eveniment este ncapsulat ntr-un obiect de un tip subclas a clasei generale EventObject, definite n pachetul java.util astfel:
public class EventObject extends Object implements Serializable { public EventObject (Object source); // primeste sursa evenimentului public Object getSource(); // produce sursa evenimentului public String toString(); // un sir echivalent obiectului }

Mecanismul de producere a evenimentelor si de notificare a obiectelor asculttor poate fi urmrit n cadrul claselor JFC de tip model, unde cuvntul model are sensul de model logic al datelor care vor fi prezentate vizual. O clas model contine date si poate genera evenimente la modificarea sau selectarea unor date continute. Modelul de buton (DefaultButtonModel) este o surs de evenimente si contine o list de asculttori sub forma unui obiect de tipul EventListenerList, care este n esent un vector de referinte la obiecte ce implementeaz interfata EventListener. Inregistrarea unui obiect asculttor la obiectul surs de evenimente se face prin apelarea metodei addActionListener. Un model de buton trebuie s respecte interfata ButtonModel, redat mai jos ntr-o form mult simplificat (prin eliminarea a cca 70 % dintre metode):
public interface ButtonModel { boolean isPressed(); public void setPressed(boolean b); public void setActionCommand(String s); public String getActionCommand(); void addActionListener(ActionListener l);

143

Florian Moraru Programare Orientata pe Obiecte


void removeActionListener(ActionListener l); }

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

144

Florian Moraru Programare Orientata pe Obiecte Apelarea metodei setPressed pentru un buton are acelasi efect cu actiunea operatorului de clic pe imaginea butonului, adic declanseaz un eveniment de tip ActionEvent si deci apeleaz metodele actionPerformed din toate obiectele ascultator nregistrate la butonul respectiv. Exist un singur obiect eveniment ChangeEvent transmis asculttorilor la orice clic pe buton, dar cte un nou obiect ActionEvent pentru fiecare clic, deoarece sirul actionCommand poate fi modificat prin apelul metodei setActionCommand. Producerea unui eveniment ActionEvent se traduce n apelarea metodei actionPerformed pentru toate obiectele de tip ActionListener nregistrate. Lista de asculttori la diferite evenimente este unic pentru un obiect EventListenerList, dar n list se memoreaz si tipul (clasa) obiectului asculttor, mpreun cu adresa sa. Intr-o form mult simplificat, clasa pentru liste de obiecte asculttor arat astfel:
public class EventListenerList { protected transient Object[ ] listenerList = null; // lista de obiecte ascultator // adauga un obiect ascultator la lista public synchronized void add (Class t, EventListener l) { if (listenerList == null) listenerList = new Object[ ] { t, l }; else { int i = listenerList.length; Object[ ] tmp = new Object[i+2]; // realocare pentru extindere vector System.arraycopy(listenerList, 0, tmp, 0, i); // copiere date in "tmp" tmp[i] = t; tmp[i+1] = l; // adaugare la vectorul "tmp" listenerList = tmp; } } }

O solutie mai simpl dar mai putin performant pentru clasa EventListenerList poate folosi o colectie sincronizat n locul unui vector intrinsec extins mereu. Structura programelor dirijate de evenimente Intr-un program care reactioneaz la evenimente externe trebuie definite clase asculttor pentru aceste evenimente. De multe ori clasele asculttor trebuie s comunice ntre ele, fie direct, fie prin intermediul unei alte clase. In astfel de situatii avem de ales ntre mai multe posibilitti de grupare a claselor din program, fiecare cu avantaje si dezavantaje. Pentru a ilustra variantele posibile vom folosi un exemplu simplu cu dou butoane si un cmp text. In cmpul text se afiseaz un numr ntreg (initial zero); primul buton (+) are ca efect mrirea cu 1 a numrului afisat iar al doilea buton (-) produce scderea cu 1 a numrului afisat. Desi foarte simplu, acest exemplu arat necesitatea interactiunii dintre componentele vizuale si obiectele asculttor. 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. Obiectele asculttor la butoanele + si - trebuie s actioneze asupra unui cmp text si deci trebuie s primeasc o referint la acest cmp text (n constructor).
// ascultator la buton "+" class B1L implements ActionListener { JTextField text; // referinta la campul text folosit public B1L (JTextField t) { text=t; } public void actionPerformed (ActionEvent ev) { int n =Integer.parseInt(text.getText()); // valoarea din campul text text.setText(String.valueOf(n+1)); // modifica continut camp text }

145

Florian Moraru Programare Orientata pe Obiecte


} // ascultator la buton "-" class B2L implements ActionListener { JTextField text; // referinta la campul text folosit public B2L (JTextField t) { text=t; } public void actionPerformed (ActionEvent ev) { int n =Integer.parseInt(text.getText()); // valoarea din campul text text.setText(String.valueOf(n-1)); // modifica continut camp text } } // Clasa aplicatiei: butoane cu efect asupra unui camp text class MFrame extends JFrame { JButton b1 = new JButton (" + "); JButton b2 = new JButton (" - "); JTextField text = new JTextField (6); public MFrame() { Container c = getContentPane(); text.setText(0); b1.addActionListener (new B1L(text) ); b2.addActionListener (new B2L(text) ); c.setLayout (new FlowLayout()); c.add(b1); c.add(b2); c.add (text); } // pentru verificare public static void main (String args[ ]) { JFrame f = new MFrame(); f.pack(); f.setVisible(true); } }

Numrul afisat n cmpul text ar putea fi util si altor metode si deci ar putea fi memorat ntr-o variabil, desi este oricum memorat ca sir de caractere n cmpul text. Avem o situatie n care trei clase trebuie s foloseasc n comun o variabil si s o modifice. O solutie uzual este ca variabila extern s fie transmis la construirea unui obiect care foloseste aceast variabil (ca parametru pentru constructorul clasei). Dac variabila este de un tip primitiv (int n acest caz), constructorul primeste o copie a valorii sale, iar metodele clasei nu pot modifica valoarea original. 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, care nu poate fi extins cu noi metode. Se poate defini o clas Int ce contine o variabil de tip int si o metod pentru modificarea sa :
class Int { int val; public Int (int n) { val=n; } // constructor public int getValue () { return val; } // citire valoare public void setValue (int n) { val=n; } // modificare valoare public String toString(){return String.valueOf(val); } // pentru afisare }

Clasele pentru obiecte asculttor la evenimente sunt aproape la fel:


// receptor la butonul de incrementare class B1L implements ActionListener { Int n; JTextField text;

146

Florian Moraru Programare Orientata pe Obiecte


public B1L (JTextField t, Int n) { text=t; this.n=n; } public void actionPerformed (ActionEvent ev) { int x=n.getValue(); n.setValue(++x); text.setText(n.toString()); } }

Clasa cu fereastra principal a aplicatiei :


// Doua butoane care comanda un camp text class MFrame extends JFrame { JButton b1 = new JButton (" + "); JButton b2 = new JButton (" - "); JTextField text = new JTextField (6); Int n= new Int(0); public MFrame() { Container c = getContentPane(); b1.addActionListener (new B1L(text,n) ); b2.addActionListener (new B2L(text,n) ); c.setLayout (new FlowLayout()); c.add(b1); c.add(b2); text.setText (n.toString()); c.add (text); // afiseaza val initiala n } }

O alt solutie, mai scurt, porneste de la observatia c metodele de tratare a evenimentelor difer foarte putin ntre ele si c putem defini o singur clas asculttor pentru ambele butoane:
class BListener implements ActionListener { static int n=0; JTextField text; public BListener (JTextField t) { text=t; text.setText (" "+ n); } public void actionPerformed (ActionEvent ev) { String b = ev.getActionCommand(); if (b.indexOf('+')>=0) ++n; else --n; text.setText(" "+ n); } }

O variant cu numr minim de clase este definirea clasei cu fereastra aplicatiei ca asculttor la evenimente, ceea ce elimin clasele separate cu rol de asculttor :
class MFrame extends JFrame implements ActionListener { JButton b1 = new JButton (" + "); JButton b2 = new JButton (" - "); JTextField text = new JTextField (6); int n=0; public MFrame() { Container c = getContentPane(); b1.addActionListener (this); b2.addActionListener (this);

147

Florian Moraru Programare Orientata pe Obiecte


c.setLayout (new FlowLayout()); c.add(b1); c.add(b2); text.setText(" "+n); c.add (text); } public void actionPerformed (ActionEvent ev) { Object source =ev.getSource(); if (source==b1) ++n; else if (source==b2) --n; text.setText(" "+n); }

Utilizarea de clase incluse Pentru reducerea numrului de clase de nivel superior si pentru simplificarea comunicrii ntre clasele asculttor la evenimente putem defini clasele receptor ca niste clase interioare cu nume, incluse n clasa cu fereastra aplicatiei:
class MFrame extends JFrame { JButton b1 = new JButton (" + "); JButton b2 = new JButton (" - "); JTextField text = new JTextField (6); int n= 0; public MFrame() { Container c = getContentPane(); b1.addActionListener (new B1L()); b2.addActionListener (new B2L()); c.setLayout (new FlowLayout()); c.add(b1); c.add(b2); text.setText(" "+n); c.add (text); } class B1L implements ActionListener { // clasa inclusa in MFrame public void actionPerformed (ActionEvent ev) { text.setText(" "+ ++n); } } class B2L implements ActionListener { // clasa inclusa in MFrame public void actionPerformed (ActionEvent ev) { text.setText(" "+ --n); } }

Definirea de clase incluse anonime reduce si mai mult lungimea programelor, dar ele sunt mai greu de citit si de extins n cazul unor interactiuni mai complexe ntre componentele vizuale. O problem poate fi si limitarea la constructori fr argumente pentru clasele anonime. Exemplul anterior rescris cu clase incluse anonime:
class MFrame extends JFrame { JButton b1 = new JButton (" + "), b2 = new JButton (" - "); JTextField text = new JTextField (6); int n= 0; public MFrame() { Container c = getContentPane(); b1.addActionListener (new ActionListener(){ // definire clasa anonima public void actionPerformed (ActionEvent ev) {

148

Florian Moraru Programare Orientata pe Obiecte


text.setText(" "+ ++n); } }); b2.addActionListener (new ActionListener(){ // alta clasa anonima public void actionPerformed (ActionEvent ev) { text.setText(" "+ --n); } }); c.setLayout (new FlowLayout()); c.add(b1); c.add(b2); text.setText(" "+n); c.add (text); } } // clasa MFrame poate contine si metoda "main'

Variabilele referint la componente vizuale b1si b2 sunt definite de obicei n afara functiilor (ca variabile ale clasei), deoarece este probabil ca mai multe metode s se refere la aceste variabile. Initializarea lor se poate face n afara functiilor (la ncrcarea clasei) deoarece se foloseste un singur obiect fereastr. Pe de alt parte, functia main sau alte metode statice nu pot folosi direct variabilele referint la butoane si la alte componente vizuale, dac aceste variabile nu sunt definite ca variabile statice. 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, dar care apare ntr-o alt clas, diferit de clasa buton. De aceea, sa propus definirea de subclase specializate pentru fiecare component Swing folosite ntr-o aplicatie. O astfel de clas contine si metoda actionPerformed.Obiectele acestor clase sunt n acelasi timp sursa evenimentelor dar si asculttorul care trateaz evenimentele generate. Exemplu:
// Buton "+" class IncBut extends JButton implements ActionListener { JTextField txt; public IncBut (JTextField t) { super(" + "); txt=t; addActionListener(this); } public void actionPerformed (ActionEvent ev) { int x=Integer.parseInt(txt.getText()); txt.setText(String.valueOf(++x)); } } // Buton "-" class DecBut extends JButton implements ActionListener { ... } // Doua butoane care comanda un camp text class MFrame extends JFrame { JButton b1,b2; JTextField text = new JTextField (6); public MFrame() { text.setText(0); b1= new IncBut(text); b2= new DecBut(text); Container c = getContentPane(); c.setLayout (new FlowLayout());

149

Florian Moraru Programare Orientata pe Obiecte


c.add(b1); c.add(b2); c.add (text); } ... // functia main }

Comunicarea ntre obiectele acestor clase specializate se realizeaz fie prin referinte transmise la construirea obiectului (sau ulterior, printr-o metod a clasei), fie prin includerea claselor respective ntr-o clas anvelop. O variant a acestei solutii este cea n care de defineste o singur metod actionListener ( mai exact, cte o singur metod pentru fiecare tip de eveniment), care apeleaz o alt metod din obiectele buton (sau alt fel de obiecte vizuale specializate). Metoda dispecer de tratare evenimente poate fi inclus n clasa derivat din JFrame. Alegerea metodei execute de tratare efectiv a unui tip de eveniment se face prin polimorfism, functie de sursa evenimentului. Exemplu :
// interfata comuna obiectelor vizuale care execut comenzi interface Command { public void execute(); } // Buton de incrementare class IncBut extends JButton implements Command { JTextField txt; public IncBut (JTextField t) { super(" + "); txt=t; txt.setText("0"); } public void execute () { int x=Integer.parseInt(txt.getText()); txt.setText(String.valueOf(++x)); } } // Un buton care comanda un camp text class MFrame extends JFrame implements ActionListener { JButton b1; JTextField text = new JTextField (6); public MFrame() { b1= new IncBut (text); b1.addActionListener(this); Container c = getContentPane(); c.setLayout (new FlowLayout()); c.add(b1); c.add (text); } public void actionPerformed (ActionEvent ev) { Command cmd = (Command) ev.getSource(); cmd.execute(); } }

Reducerea cuplajului dintre clase In examinarea unor variante pentru aplicatia anterioar am urmrit reducerea lungimii codului surs si al numrului de clase din aplicatie, dar acestea nu reprezint indicatori de calitate ai unui program cu obiecte si arat o proiectare fr perspectiva extinderii aplicatiei sau reutilizrii unor prti si n alte aplicatii. Un dezavantaj comun solutiilor anterioare este cuplarea prea strns ntre obiectele asculttor la butoane si obiectul cmp text unde se afiseaz rezultatul modificrii ca urmare a actionrii unui buton: metoda actionPerformed apeleaz direct o metod dintr-o alt clas (setText din JTextField). Desi

150

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

151

Florian Moraru Programare Orientata pe Obiecte


public MFrame() { b1= new IncBut (m); b2= new DecBut (m); m.registerTxt (text); Container c = getContentPane(); c.setLayout (new FlowLayout()); c.add(b1); c.add(b2); c.add (text); m.init(); } }

Comunicarea dintre obiecte se face aici prin apel direct de metode: obiectele buton apeleaz metodele inc si dec ale obiectului mediator, iar mediatorul apeleaz metoda setText a obiectului JTextField folosit la afisare. Interactiunea dintre un obiect vizual si mediator poate avea loc n ambele sensuri; de exemplu un buton care comand mediatorului o actiune dar care poate fi dezactivat de mediator. O variant de realizare a aplicatiei oarecum asemntoare foloseste schema de clase "observatobservator": butoanele nu actioneaz direct asupra cmpului text, ci sunt obiecte observate. Obiectul observator actioneaz asupra cmpului text, n functie de obiectul observat (butonul) care l-a notificat de modificarea sa. Comunicarea prin evenimente si clase model O alternativ la obiectul mediator este un obiect model, care difer de un mediator prin aceea c modelul genereaz evenimente n loc s apeleze direct metode ale obiectelor comandate. O clas model seamn cu o component vizual prin aceea c poate genera evenimente, 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. Inregistrarea unui obiect asculttor la un obiect model se face la fel ca si la o component vizual. Putem folosi o clas model existent sau s ne definim alte clase model cu o comportare asemntoare dar care difer prin datele continute si prin tipul evenimentelor. In exemplul nostru clasa model trebuie s memoreze un singur numr (un obiect) si putem alege ntre DefaultListModel (care contine un vector de obiecte Object) sau DefaultSingleSelectionModel (care contine un indice ntreg). Programul urmtor foloseste clasa DefaultSingleSelectionModel; numrul continut poate fi citit cu getSelectedIndex si modificat cu metoda setSelectedIndex; se genereaz eveniment de tip ChangeEvent la modificarea valorii din obiectul model. Pentru scurtarea codului am definit o subclas a clasei model, cu nume mai scurt:
class SModel extends DefaultSingleSelectionModel { public void setElement (int x) { setSelectedIndex(x); } public int getElement () { return getSelectedIndex(); } } // Buton "+" class IncBut extends JButton implements ActionListener { SModel mod; JTextField txt; public IncBut (SModel m, JTextField txt) { super(" + "); mod=m; this.txt=txt;

152

Florian Moraru Programare Orientata pe Obiecte


addActionListener(this); } public void actionPerformed (ActionEvent ev) { int n = Integer.parseInt(txt.getText().trim()); mod.setElement(n+1); } } // tratare evenimente generate de model class TF extends JTextField implements ChangeListener { public TF (int n) { super(n); } public void stateChanged (ChangeEvent ev) { SModel m = (SModel) ev.getSource(); int x = m.getElement(); setText (String.valueOf(x)); } } // Doua butoane care comanda un camp text class MFrame extends JFrame { JButton b1,b2; TF text = new TF(6); SModel m = new SModel(); public MFrame() { text.setText("0"); b1= new IncBut (m,text); b2= new DecBut (m,text); m.addChangeListener (text); Container c = getContentPane(); c.setLayout (new FlowLayout()); c.add(b1); c.add(b2); c.add (text); }... // main }

Pentru exemplul anterior putem s ne definim o clas model proprie, care s contin o variabil de tipul general Object si s genereze evenimente de tip ActionEvent, prin extinderea clasei AbstractListModel, care asigur partea de generare a evenimentelor si de gestiune a listei de asculttori la evenimente.

153

Florian Moraru Programare Orientata pe Obiecte 13. Componente Swing cu model Arhitectura MVC Un obiect vizual folosit ntr-o interfat grafic ndeplineste mai multe functii: - Prezint pe ecran date ntr-o imagine specific (controlabil prin program). - Preia actiunile transmise prin mouse sau prin tastatur obiectului respectiv. - Permite modificarea datelor ca rspuns la actiuni ale operatorului uman sau la cereri exprimate prin program si actualizeaz imaginea de pe ecran a acestor date. Arhitectura MVC (Model-View-Controller) separ n cadrul unei componente vizuale cele trei functii esentiale ale unui program sau ale unui fragment de program: intrri (Controller), date si prelucrri (Model), iesiri (View). Partea numit model reprezint datele si functionalitatea componentei, deci defineste starea si logica componentei. Imaginea (view) red ntr-o form vizual modelul, iar partea de comand (controller) interpreteaz gesturile utilizatorului si actioneaz asupra modelului (defineste comportarea componentei). Poate exista si o legtur direct ntre partea de control si partea de redare, n afara legturilor dintre model si celelalte dou prti (imagine si comand). Comunicarea dintre cele trei prti ale modelului MVC se face fie prin evenimente, fie prin apeluri de metode. Modelul semnaleaz prtii de prezentare orice modificare n starea sa, prin evenimente, iar partea de imagine poate interoga modelul, prin apeluri de metode. 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; n plus poate apela direct si metode ale obiectului de redare (pentru modificarea imaginii afisate). In figura urmtoare linia continu reprezint un apel de metod iar linia ntrerupt reprezint un eveniment transmis ntre obiecte. Model

View

Controller

De exemplu, un buton simplu este modelat printr-o variabil boolean cu dou stri (apsat sau ridicat), este comandat printr-un clic de mouse (si, eventual, printr-o tast) si are pe ecran imaginea unei ferestre dreptunghiulare (rotunde), cu sau fr contur, cu text sau cu imagine afisat pe buton. Imaginea butonului reflect de obicei starea sa, prin crearea impresiei de buton n pozitie ridicat sau cobort. Este posibil ca imaginea butonului s fie modificat direct de actiunea operatorului (de controler) si nu indirect prin intermediul modelului. Legtura de la imagine la controler const n aceea c trebuie mai nti pozitionat mouse-ul pe suprafata butonului si apoi apsat butonul de la mouse. 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, deoarece comunicarea dintre controler si imagine poate fi destul de complex. Componentele JComboBox, JList, JTable, JTree, JMenuBar includ ntotdeauna un obiect model care poate fi extras printr-o metod getModel pentru a se opera asupra lui. Obiectul model poate fi creat automat n constructor, pe baza unor colectii, sau poate fi creat de programator si transmis clasei care asigur prezentarea datelor din model. Exist clase predefinite pentru obiecte model (DefaultComboBoxModel,DefaultListModel, DefaultTreeModel) dup cum se pot defini si alte clase model care s respecte interfete impuse. Un model este o structur de date care poate genera evenimente la modificarea datelor si contine metode pentru adugarea si eliminarea de asculttori la evenimentele generate de model. Clasele

154

Florian Moraru Programare Orientata pe Obiecte JList,JTable s.a. sunt asculttori la evenimente generate de clasele model respective, deci modificarea datelor din model va modifica automat si afisarea pe ecran (se vor afisa noile date din obiectul model). Datele prezentate ntr-un obiect JList, JTable, JTree s.a. se pot modifica n cursul executiei programului, iar afisarea trebuie s reflecte aceste modificri. De exemplu, se afiseaz date din directoare sau din fisiere al cror nume se introduce sau se modific de ctre operator dup afisarea interfetei grafice (prin introducere n cmpuri text, de exemplu). Ca tehnic general, nu se construiesc alte obiecte vizuale (JList, JTable, JTree) cu noile date dup afisarea interfetei grafice si nici nu se apeleaz metode de reafisare (repaint sau altele), ci se apeleaz metode care transmit la obiectul vizual un alt model (setModel), sau se apeleaz metode de modificare a obiectului model. Modificrile asupra obiectului model sunt redate automat pe ecran deoarece obiectul vizual este asculttor la evenimente generate de model. In general, clasele model contin si metode de modificare a datelor (interfetele claselor model nu impun astfel de metode). 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. Utilizatorii au posibilitatea s-si defineasc alte clase model, dar aceste clase trebuie s respecte anumite interfete (ListModel, ComboBoxModel, TableModel, TreeModel). Pentru facilitarea definirii de noi clase model (model de list sau de tabel) exist clase abstracte care implementeaz partial interfetele mentionate: AbstractListModel, AbstractTableModel. Clasele model gata definite au un nume care ncepe cu Default (DefaultListModel, DefaultTableModel, DefaultTreeModel) si constructori ce primesc ca argument un vector (model de list), o matrice (model de tabel) sau un nod rdcin (model de arbore). Clasele cu model au att constructor cu argument model, ct si constructori cu diverse structuri de date, pe baza crora se construiesc automat obiecte model; de aceea exist metode getModel n toate aceste clase, indiferent dac s-a transmis un model creat separat sau nu (model creat implicit). Obiectele vizuale cu model permit si selectarea unor elemente afisate (elemente din list, celule sau coloane din tabel, noduri din arbore), cu generarea de evenimente de selectie; ele au de obicei nregistrate n lista de asculttori si obiecte asculttor la selectie. Lista de asculttori a unui obiect Swing este unic, dar n list se memoreaz att adresa ct si tipul obiectului asculttor. Utilizarea unui model de list Componenta vizual JList afiseaz pe ecran o list de valori, sub forma unei coloane, si permite selectarea uneia dintre valorile afisate, fie prin mouse, fie prin tastele cu sgeti. Datele afisate pot fi de orice tip clas si sunt memorate ntr-un obiect colectie (Vector) sau model; o referint la acest obiect este memorat n obiectul JList de constructorul obiectului JList. Exist mai multi constructori, cu parametri de tipuri diferite: vector intrinsec, obiect Vector sau obiect ListModel. Exemple:
String v ={unu,doi,trei}; JList list1 = new JList (v); // vector intrinsec Vector vec = new Vector ( Arrays.asList(v)); JList list2 = new JList(vec); // obiect de tip Vector JList list3 = new JList (new DefaultListModel());

Clasa model de list predefinit DefaultListModel are un singur constructor, fr argumente, dar are metode de adugare si de modificare a obiectelor din vectorul folosit de obiectul model. Un obiect JList cu continut variabil se poate folosi si fr obiect model creat explicit, prin retransmiterea unui vector cu date la obiectul JList (metoda setListData). Exemplu de afisare ntr-un obiect JList a numelor fisierelor dintr-un director al crui nume se introduce (sau se modific) ntr-un cmp text:

155

Florian Moraru Programare Orientata pe Obiecte


class FileList extends JFrame implements ActionListener{ JList jlist = new JList(); // pentru continut director JTextField tf = new JTextField(12); // pentru nume director public FileList() { Container cp = getContentPane(); cp.add (tf,"North"); cp.add (new JScrollPane(jlist)); tf.addActionListener (this); setSize(300,600); show(); } public void actionPerformed (ActionEvent ev) { File d=new File(tf.getText()); // creare ob. File cu nume director if (! d.isDirectory ()) return; String files[] = d.list(); // vector cu nume de fisiere Vector v = new Vector (Arrays.asList(files)); jlist.setListData(v); // transmite vector la JList } }

Modificarea datelor din vector (vector intrinsec sau obiect Vector) nu are efect asupra datelor afisate n obiectul JList dect dac se apeleaz la fiecare modificare si metoda setListData, care apeleaz metoda setModel din JList. Modificarea datelor din model (prin metode ca addElement, setElementAt, removeElementAt) se reflect automat pe ecran, deoarece obiectul JList este asculttor la evenimentele generate de model. Exemplu:
class FileList extends JFrame implements ActionListener{ DefaultListModel model = new DefaultListModel(); JList jlist = new JList(model); JTextField tf = new JTextField(12); public FileList() { Container cp = getContentPane(); cp.add (tf,"North"); cp.add (new JScrollPane(jlist)); tf.addActionListener (this); setSize(300,600); show(); } public void actionPerformed (ActionEvent ev) { File d=new File(tf.getText()); if (! d.exists()) return; String files[] = d.list(); model.clear(); // sterge continut anterior model for (int i=0;i<files.length;i++) // adauga nume fisiere la model model.addElement(files[i]); // modelul modifica si obiectul JList ! } }

Putem s nu folosim obiect model separat atunci cnd lista este folosit doar pentru selectia unor elemente din list iar continutul listei afisate nu se mai modific. Trebuie observat c orice constructor creeaz un obiect model simplu, dac nu primeste unul ca argument, iar metoda getModel poate fi apelat indiferent cum a fost creat lista. Modelul creat implicit are numai metode de acces la date (getElementAt, getSize) si nu permite modificarea datelor din acest model (date transmise printr-un vector).

156

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 orict de mari (care nu este limitat la numrul de linii din panoul de afisare). Ca aspect, o list JList seamn cu o zon text JTextArea, dar permite selectarea unuia sau mai multor elemente din list. Un obiect JList genereaz evenimente cauzate de selectarea unui element din list si evenimente cauzate de modificarea continutului listei. Selectarea unui element dintr-o list produce un eveniment ListSelectionEvent, tratat de metoda valueChanged (din interfata ListSelectionListener). Putem obtine fie obiectul selectat (metoda getSelectedValue), fie pozitia n vector (sau n model) a elementului selectat (metoda getSelectedIndex). Exemplul urmtor arat cum se poate programa o list de selectie; elementul selectat este afisat ntr-un cmp text, pentru verificarea selectiei corecte.
class ListSel extends JFrame implements ListSelectionListener { JTextField field; // ptr. afisare element selectat JList list; // lista de selectie public ListSel(Object a[]) { list = new JList(a); // creare obiect lista list.addListSelectionListener(this); // acest obiect este si ascultator la ev. Container cp = getContentPane(); cp.add(new JScrollPane(list),Center); field =new JTextField(10); // creare cimp text cp.add(field,South); // campul text sub lista pack(); setVisible(true); // afisare lista si cimp text } public void valueChanged(ListSelectionEvent e) { // lansata la selectie din lista String sel = (String) list.getSelectedValue(); // valoare element selctat field.setText(sel); // afisare element selectat } public static void main(String s[ ]) { String[ ] a ={"unu","doi","trei","patru"}; // date afisate in lista ListSel ls = new ListSel(a); } }

Programul anterior nu permite modificarea continutului listei de selectie deoarece interfata ListModel nu prevede metode de modificare a modelului. Exemplul urmtor arat cum se pot aduga elemente la sfrsitul unei liste de selectie, cu un buton pentru adugare (Add) si un cmp text unde se introduce elementul ce va fi adugat la list.
class ListAdd extends JFrame implements ActionListener { private JList list; private DefaultListModel listModel; private JButton addBut=new JButton("Add"); private JTextField toAdd = new JTextField(10); // constructor public ListAdd() { listModel = new DefaultListModel(); // model de date pentru lista list = new JList(listModel); addBut.addActionListener(this); // ascultator la buton toAdd.addActionListener(this); // ascultator la camp text Container cp = getContentPane(); cp.setLayout( new FlowLayout()); cp.add(toAdd); // adauga camp text cp.add(addBut); // adauga buton cp.add(new JScrollPane(list)); // adauga panou cu derulare pentru lista }

157

Florian Moraru Programare Orientata pe Obiecte


// receptor la buton si la campul text public void actionPerformed(ActionEvent e) { if (toAdd.getText().equals("")) { // verifica ce este in campul text Toolkit.getDefaultToolkit().beep(); return; // daca nimic in campul text } listModel.addElement(toAdd.getText()); // adauga element la model lista toAdd.setText (""); // sterge camp text } } }

Exemplul anterior poate fi extins cu un buton de stergere element selectat din list si o clas care implementeaz interfata ListSelectionListener, cu o metod valueChanged ; un obiect al acestei clase se va nregistra ca asculttor la list. Modificarea vectorului din modelul de list genereaz evenimente pentru obiecte nregistrate ca asculttori la list: metoda addElement din clasa DefaultListModel apeleaz metoda fireIntervalAdded, metoda removeElement apeleaz metoda fireIntervalRemoved si setElementAt apeleaz metoda fireContentsChanged. Lantul de apeluri care produce activarea unei metode scrise de programator ca urmare a unei modificri a colectiei de date din model este, n cazul listei, urmtorul:
DefaultListModel.addElement AbstractModel.fireIntervalAdded (ListDataEvent) ListDataListener.intervalAdded

Un observator la modificri ale listei trebuie s implementeaz interfata ListDataListener, deci s defineasc metodele intervalAdded, intervalRemoved si contentsChanged, toate cu argument ListDataEvent, care contine informatii despre sursa, tipul evenimentului si pozitia din vector unde s-a produs modificarea. Putem defini o clas adaptor cu implementri 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 urmtor arat cum putem reactiona la stergerea unui element din list prin afisarea dimensiunii listei dup stergere:
class LFrame extends JFrame implements ActionListener, ListSelectionListener { DefaultListModel model = new DefaultListModel(); // model de lista JTextField result = new JTextField(4); // rezultat modificare JButton but = new JButton("Delete"); // comanda operatia de stergere JList list; int index; // pozitia elementului sters // constructor public LFrame (Object a[]) { for (int i=0;i<a.length;i++) model.addElement(a[i]); list = new JList(model); list.addListSelectionListener(this); Container c = getContentPane(); c.add(new JScrollPane(list)); c.add(but,North); c.add(result,South); but.addActionListener (this); model.addListDataListener( new LDL());

158

Florian Moraru Programare Orientata pe Obiecte


result.setText(model.size()+" "); setSize(300,200); setVisible(true); } // ascultator la butonul de stergere public void actionPerformed (ActionEvent ev) { model.removeElementAt(index); } // ascultator la selectie din lista public void valueChanged(ListSelectionEvent e) { index = list.getSelectedIndex(); } // ascultator la stergere din lista class LDL extends ListDataAdapter { // clasa inclusa in LFrame public void intervalRemoved (ListDataEvent ev) { result.setText(" "+ model.size()); } }

Utilizarea unui model de tabel Componenta vizual JTable afiseaz pe ecran un tabel de celule structurat n linii si coloane, permitnd selectarea si editarea de celule, modificarea aspectului tabelului si obtinerea de informatii despre celulele selectate prin mouse. Fiecare coloan poate avea un titlu. Datele din celule pot fi de tipuri diferite, dar numai tipuri clas. Dimensiunile coloanelor sunt implicit egale si calculate n functie de numrul coloanelor, dar pot fi modificate fie prin program (metoda setPreferredWidth), fie prin deplasarea (tragerea=dragging) marginilor coloanelor folosind un mouse. Construirea unui obiect JTable se poate face folosind un vector de titluri si o matrice de celule (de obiecte Object), sau folosind un obiect model TableModel :
JTable (Object[][] date, Object[] numecol); JTable (Vector date, Vector numecol); JTable (TableModel model); // 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,int row,int col). Secventa urmtoare ilustreaz folosirea unor constructori:
// tabelul primeste direct datele String [ ] titles = { Romn, Englez,German}; // nume de coloane Object[ ][ ] cells = { {Unu,One,Eins}, {Doi,Two,Zwei},... }; JTable table1 = new JTable (cells,titles); // tabelul primeste un model al datelor DefaultTableModel model = new DefaultTableModel (cells,titles); JTable table2 = new JTabel (model);

Un obiect JTable include un obiect model, creat automat (implicit) sau primit de un constructor; modelul de date creat implicit este public accesibil si permite modificarea datelor din model prin metoda setValueAt. Clasa JTable are si ea metodele getValueAt si setValueAt, care apeleaz metodele cu acelasi nume ale obiectului model continut de obiectul tabel. Crearea unui model explicit este necesar n cazuri mai speciale: cnd nu vrem ca toate celulele s fie editabile, cnd vrem ca datele din celule s fie prezentate prin imagini sau altfel dect prin metoda toString (folosit implicit la afisarea datelor).

159

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

In varianta urmtoare se creeaz un nou model de tabel pentru fiecare nou director si se transmite acest model la acelasi obiect JTable, creat initial:
class FileTable extends JFrame implements ActionListener { JTextField tf= new JTextField (14); // pentru nume director JTable jtable; // pentru continut director String [] titles= {"File Name"," Dir "," Size "," Date "}; // titluri de coloane DefaultTableModel model= new DefaultTableModel() ; // un obiect model public FileTable() { jtable = new JTable(model); add (tf,"North"); add(new JScrollPane(jtable),"Center"); tf.addActionListener(this); setSize(400,400); } public void actionPerformed (ActionEvent ev) { File f = new File (tf.getText()); if ( ! f.isDirectory()) return; // daca s-a introdus gresit File [] fl =f.listFiles(); Object a[][] = new Object [fl.length][4]; // aloca memorie ptr matrice for (int i=0;i<fl.length;i++) { // completare matrice cu atribute fisiere

160

Florian Moraru Programare Orientata pe Obiecte


a[i][0]= fl[i].getName(); a[i][1]= new Boolean (fl[i].isDirectory()); a[i][2]= new Long(fl[i].length()); a[i][3]= new Date(fl[i].lastModified()).toLocaleString(); } model = new DefaultTableModel(a,titles); // crearea unui nou model jtable.setModel(model); // transmis la ob. JTable } }

Obiectul model separat transmis la crearea unui obiect JTable poate fi: din clasa DefaultTableModel; dintr-o subclas a clasei DefaultTableModel sau AbstractTableModel; dintr-o clas nou care s implementeze interfata TableModel. Exemplele urmtoare arat cum se pot defini alte clase model de tabel pentru un tabel cu celule nemodificabile de ctre operator (dar modificabile prin program): // tabel nemodificabil prin extindere DefaultTableModel class MyTableModel extends DefaultTableModel { public MyTableModel (Object[ ][ ] cells, Object[ ] cnames) { super (cells,cnames); } public boolean isCellEditable (int row,int col) { return false; // nici o celula editabila (modificabila din exterior) } } // tabel nemodificabil prin extindere AbstractTableModel class MyTableModel extends AbstractTableModel { Object [ ] titles; // titluri coloane Object [ ][ ] cells ; // continut celule public MyTableModel ( Object c[][], Object t[]) { cells=c; titles=t; } public int getColumnCount() { return titles.length; } // numar de coloane public int getRowCount() { return cells.length; } // numar de linii public Object getValueAt (int row,int col) { return cells[row][col]; } public String getColumnName(int col) { return titles[col].toString();} }

Pentru ambele variante crearea tabelului este identic :


JTable table = new JTable(new MyTableModel(cells,titles));

Selectarea unei coloane din tabel se face prin clic pe mouse dupa pozitionarea pe coloana dorit (pe o celula cu date, nu pe titlul coloanei). Numrul coloanei selectate se poate afla n dou moduri: - prin metoda getSelectedColumn din clasa JTable; - prin tratarea evenimentului de selectie coloan ListSelectionEvent; Pentru evenimente de selectie se foloseste modelul de selectie de la lista JList : fiecare linie din tabel este considerat ca o list, iar pentru a considera o coloan ca o list trebuie folosit un model de coloan ColumnModel. Un tabel poate genera mai multe tipuri de evenimente : - Evenimente produse la selectarea unei linii, unei coloane sau unei celule. - Evenimente produse de modificarea datelor din tabel - Evenimente produse de modificarea structurii tabelului In exemplul urmtor se permite selectarea unei celule (prin mouse sau prin tasta Tab) si se afiseaz ntr-un cmp text coordonatele (linie-coloan) celulei selectate.

161

Florian Moraru Programare Orientata pe Obiecte


class TableSel extends JFrame implements ListSelectionListener { JTextField rc = new JTextField(5); JTable table ; int r,c; // numar linie si coloana celula curenta ListSelectionModel rowSM ; // model ptr selectie linii ListSelectionModel colSM ; // model ptr selectie coloane public TableSel(Object a[][],Object b[]) { table= new JTable(a,b); table.setCellSelectionEnabled(true); rowSM = table.getSelectionModel(); colSM = table.getColumnModel().getSelectionModel(); rowSM.addListSelectionListener(this); colSM.addListSelectionListener (this); Container cp= getContentPane(); cp.add(rc,"South"); cp.add(new JScrollPane(table)); setSize(300,300); show(); } // actiune la selectie celula public void valueChanged(ListSelectionEvent e) { ListSelectionModel sm = (ListSelectionModel)e.getSource(); int i = sm.getMinSelectionIndex(); // indice selectat din lista if (sm==rowSM) r=i; // i este numar de linie else c=i; // i este numar de coloana rc.setText(r+","+c); } }

Utilizarea unui model de arbore Clasa JTree permite afisarea unor structuri arborescente (ierarhice), cu posibilitatea de a extinde orice nod intern prin subarborele su, de a comprima un subarbore la rdcina sa si de a selecta orice nod din arbore prin mouse. O aplicatie poate trata evenimentele de selectare a unor noduri sau de modificare structur arbore sau de extindere -contractie noduri corespunztor scopului aplicatiei. Un obiect JTree se poate construi n mai multe feluri: - Pe baza unui vector de vectori (Object[] sau Vector); - Pe baza unui obiect nod de arbore (rdcin), subtip al interfetei TreeNode; - Pe baza unui model de arbore, subtip al interfetei TreeModel. Exist si un constructor JTree fr argumente, care afiseaz un arbore cu date constante fr legtur cu aplicatia (colors, sports, food) dar de obicei putem evita aceast situatie; pentru un arbore de fisiere putem alege un director initial arbitrar (directorul curent, de exemplu). Modificarea datelor dintr-un obiect JTree, dup afisarea acestuia, se poate face numai prin modificarea total (crearea altui obiect model) sau partial a modelului folosit de obiectul JTree; din acest motiv este important s stim cum se creeaz si cum se modific modelul datelor dintr-un arbore. 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, obiect Vector, etc.). Modelul de arbore este fie un obiect din clasa DefaultTreeModel, fie obiect al unei clase definite de utilizator, dar care implementeaz interfata TreeModel. Acest model genereaz numai evenimente de modificarea datelor; pentru a trata evenimente legate de selectia unor noduri este folosit un model TreeSelectionModel. Clasa model de arbore foloseste un obiect nod, ca rdcin a arborelui; acesta poate fi un obiect din clasa DefaultMutableTreeNode sau obiecte din clase definite de utilizatori care implementeaz

162

Florian Moraru Programare Orientata pe Obiecte interfata TreeNode (noduri nemodificabile) sau interfata MutableTreeNode (cu operatii de modificare). Exemple de metode din interfata TreeNode:
int getChildCount(); // numar de succesori ai acestui nod TreeNode getChildAt(int childIndex); // succesor cu indice cunoscut int getIndex(TreeNode node); // indicele unui succesor dat TreeNode getParent(); // nodul parinte al acestui nod boolean isLeaf(); // daca acest nod este o frunza Enumeration children(); // enumerator pentru succesorii acestui nod

Exemple de metode din interfata MutableTreeNode:


void remove(int index); // elimina succesor cu indice dat void remove(MutableTreeNode node); // elimina succesor dat ca nod void setUserObject(Object object); // modifica datele din acest nod void removeFromParent(); // elimin acest nod void setParent(MutableTreeNode newParent); // modifica parintele acestui nod

Clasa DefaultMutableTreeNode implementeaz interfata MutableTreeNode si foloseste cte un vector de succesori la fiecare nod (obiect de tip Vector), plus o legtur ctre nodul printe. Exemple de metode n plus fat de interfete:
public Object getUserObject (); // obtine date din acest nod (orice obiect) public void add(MutableTreeNode child) ; // adauga acestui nod un fiu public Enumeration preorderEnumeration() ; // enumerare noduri din subarbore public Enumeration postorderEnumeration() ; // enumerare noduri din subarbore public Enumeration breadthFirstEnumeration() ; // 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, fie prin crearea unui nou arbore (cu o alt rdcin) si transmiterea adresei noii rdcini la modelul de arbore existent. Se poate modifica direct modelul, prin metode ale clasei DefaultTreeModel, dar este mai simplu de lucrat cu metode ale clasei nod de arbore. Pentru a ilustra aceste posibilitti de modificare dinamic a unui obiect JTree vom relua problema afisrii continutului oricrui director (si a subdirectoarelor sale) ntr-un obiect JTree (numele directorului este preluat dintr-un cmp text).
class DirTree extends JFrame implements ActionListener { JTree tree; JTextField dir= new JTextField (14); // nume director TNode r ; // ascultator la campul text (modificare director) public void actionPerformed (ActionEvent ev) { File f=new File (dir.getText()); if (! f.isDirectory()) return; TNode r = new TNode(f.getAbsolutePath()); // o noua radacina r dirtree(f,r); // adaugare noduri la radacina tree.setModel(new DefaultTreeModel(r)); // creare si transmitere model nou } // constructor public DirTree () { File f = new File("."); TNode r = new TNode(f.getAbsolutePath()); dirtree( f,r); tree= new JTree(new DefaultTreeModel(r)); Container cp = getContentPane();

163

Florian Moraru Programare Orientata pe Obiecte


cp.add (dir,"North"); cp.add (new JScrollPane(tree)); dir.addActionListener( this); setSize(300,500); } // creare arbore de fisiere private void dirtree (File d, TNode r) { if ( ! d.isDirectory() ) return; // daca nu e director (nod frunza) File [] files=d.listFiles(); // fisiere din directorul d for(int i=0;i<files.length;i++){ // pentru fiecare fisier din director File file=files[i]; TNode fiu=new TNode(file); // creare nod pentru acest fisier r.add(fiu); // adauga ca fiu la nodul r dirtree (file,fiu); // continua pentru nodul fiu } }

Urmeaz varianta cu modificarea rdcinii de arbore folosite de modelul existent:


class DirTree extends JFrame { JTree tree; JTextField dir= new JTextField (14); // nume director DefaultTreeModel model = new DefaultTreeModel(); // ascultator la campul text (modificare director) public void actionPerformed (ActionEvent ev) { File f=new File (dir.getText()); if (! f.isDirectory()) return; TNode r = new TNode(f.getAbsolutePath()); dirtree(f,r); model.setRoot(r); } ... }

Pentru ambele programe se afiseaz initial continutul directorului curent (atunci se creeaz modelul de arbore) deoarece constructorul fr 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 asculttorii de tip TreeSelectionListener care au fost nregistrati la obiectul JTree. Exemplu de afisare valoare nod selectat:
public void valueChanged (TreeSelectionEvent ev) { try { // TNode node= (TNode) tree.getLastSelectedPathComponent(); TNode node =(TNode) tree.getSelectionPath().getLastPathComponent(); if (node==null) return; tf.setText(node.getUserObject().toString()); // afisare valoare nod selectat } catch(Exception ex) {ex.printStackTrace();} }

164

Florian Moraru Programare Orientata pe Obiecte 14. Java si XML Fisiere XML n aplicatii Java Prelucrarea si generarea de documente XML n programe Java sunt operatii tot mai necesare datorit diverselor utilizri ale fisierelor XML: serializare continut obiecte si apeluri de proceduri la distant, transmiterea de mesaje ntre calculatoare (mesaje SOAP), fisiere de configurare, fisiere descriptor de componente Java (deployment descriptor), fisiere build folosite de programul ant pentru construire si instalare de aplicatii, s.a. Incepnd cu versiunea 4 clasele Java pentru aplicatii XML au devenit standard si s-au extins, n pachete ca javax.xml, org.xml si org.w3c. Mediile integrate Java (IDE) includ editoare de XML si permit verificri asupra sintaxei XML. Standardele formale sau impuse de practic (SAX, DOM, StAX s.a.) constituie si exemple de programare la nivel de interfat, artnd cum un API poate fi definit numai din interfete Java, fr nici o referire la o clas concret. Limbajul XML (Extensible Markup Language) este un limbaj cu marcaje (tag va fi tradus aici prin marcaj) extensibil, n sensul c multimea de marcaje folosite poate fi definit n functie de tipul aplicatiei si nu este fixat dinainte (ca n HTML). Marcajele au rolul de a descrie datele ncadrate de marcaje, deci semnificatia sau modul de interpretare a acestor date (si nu modul de prezentare, ca n HTML). Un fisier XML este un fisier text care contine marcaje; un marcaj este un sir ntre paranteze unghiulare (< si >). 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 sfrsit: <price> 540 </price> Un element are un nume (acelasi cu marcajul de nceput) si poate avea mai multe atribute si un continut; continutul poate include un text sau alte elemente sau elemente si text. Un element poate contine alte elemente, pe oricte niveluri de includere. In afara marcajelor pereche pot exista si marcaje singulare de forma <tag/>; 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; fiecare atribut are un nume si o valoare. Exemple:
<computer type =desktop> . . . </ computer > <computer name=SDS price=495 />

Asa cum se vede din ultimul exemplu, propriettile unor entitti (cum sunt numele si pretul unui produs) pot fi transmise fie ca atribute ale marcajului ce specific entitatea, fie ca elemente incluse ntre marcajele ce definesc o entitate.

165

Florian Moraru Programare Orientata pe Obiecte Multimea marcajelor folosite ntr-un document XML si relatiile dintre ele sunt definite ntr-un fisier schem XML, utilizat si la validarea fisierelor XML care folosesc aceste marcaje. Se folosesc dou tipuri de fisiere schem: fisiere DTD (Document Type Definition) si fisiere XSD (XML Schema Definition). Exemplu de fisier DTD pentru lista de preturi:
<!ELEMENT priceList (computer)+> <!ELEMENT computer (name, 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). Fisierul XSD este un fisier XML care foloseste marcaje predefinite si ofer mai multe posibilitti dect fisierul DTD n descrierea multimii de marcaje. 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, atunci numele de marcaje trebuie prefixate de un simbol asociat spatiului de nume. Exemplu de mesaj (cerere la un serviciu Web) n protocolul SOAP :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.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; primul are precizat adresa Web (URI) unde este definit, dar locul unde este definit m nu este precizat. Un parser XML este un analizor de documente XML, care poate permite si crearea de noi documente XML prin program. Un document XML este o instantiere a unei scheme, asa cum un obiect este o instantiere a unei clase. Se pot defini clase corespunztoare elementelor XML, iar trecerea ntre fisiere XML si clase Java (n ambele sensuri) este asigurat de clasele din interfata API numit JAXB (Java XML Binding). Clasele Java sunt generate pe baza unei scheme XML (de obicei un fisier DTD). Un parser XML primeste ca intrare un fisier XML, verific utilizarea corect a marcajelor si (optional) face o validare fat de fisierul DTD sau XSD specificat. Un fisier XML bine format (well formed) are toate perechile de marcaje incluse corect, la fel cum incluse perechile de paranteze dintro expresie sau perechile de acolade din Java. Exemple de erori de utilizare a marcajelor semnalate de un parser: <a> <b> </a> </b> <a> <b> </b> </c> (corect este <a><b></b>..</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, cu relatiile, atributele si tipurile de date descrise n schem. Validarea mreste timpul de analiz si este inutil atunci cnd fisierele XML sunt generate de ctre programe si nu sunt editate manual.

166

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

167

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 transformrii). In felul acesta se poate trece ntre diferite reprezentri posibile ale documentelor XML, cu sau fr modificri 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 cteva interfete (ContentHandler, DTDHandler, ErrorHandler), definite n pachetul org.xml.sax, dintre care numai ContentHandler este important atunci cnd 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 ntlnirea 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 modificri, 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 urmtor 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 nti 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 fr 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 rdcin (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 numrul 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 ctre aplicatii : - Interfata Node contine metode de acces la un nod de arbore DOM si la succesorii si, 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 cruia 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 cteva 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 nssi 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

Florian Moraru Programare Orientata pe Obiecte


public static void printNode(Node node) { System.out.println ( node.toString()); // afisare nod curent (fara atribute) NodeList kids = node.getChildNodes(); // lista de fii if (kids != null) // daca exista fii ai nodului curent for (int k=0; k<kids.getLength(); k++) // enumerare fii printNode (kids.item(k)); // apel recursiv pentru fiecare fiu } // creare si afisare arbore DOM public static void main (String arg[]) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse( new File(arg[0]) ); printNode(doc.getDocumentElement()); } }

La functiile anterioare se poate aduga un parametru suplimentar ce reprezint nivelul sau indentarea necesar la fiecare nivel:
public static void printNode(Node node, String indent) { System.out.println ( indent + node.toString()); // afisare nod curent, cu indentare NodeList kids = node.getChildNodes(); // lista de fii if (kids != null) // daca exista fii ai nodului curent for (int k=0; k<kids.getLength(); k++) // enumerare fii printNode (kids.item(k),indent+ ); // apel recursiv pentru fiecare fiu }

Metoda Node.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, iar pentru nodurile element nu se afiseaz valoarea).
static String toString (Node node){ String s=""; // sir cu nume si/sau valoare nod int type = node.getNodeType(); if (type==1) { // nod Element s+= node.getNodeName()+" "; // nume element (marcaj) NamedNodeMap attrs = node.getAttributes(); // lista de atribute for (int i=0; i<attrs.getLength(); i++) s+= attrs.item(i)+" "; // foloseste toString din clasa Attribute } else if (type==3) // daca nod text s = node.getNodeValue().trim(); // valoare nod cu eliminare de spatii albe return s; }

In general, operatiile efectuate la fiecare nod depind de tipul nodului iar functia recursiv va contine o selectie dup tipul nodului (un bloc switch). Exemplul urmtor afiseaz frumos, cu indentare, rezultatul unui parser DOM, dar numai pentru noduri cu elemente si noduri text: Functia anterioar poate fi modificat pentru serializarea unui arbore DOM, arbore care poate fi creat prin program (nu pe baza unui fisier XML) si apoi folosit pentru crearea unui fisier XML. Pentru serializare trebuie adugate marcaje de sfrsit si paranteze unghiulare la marcaje. Ca exemplu de creare a unui arbore DOM prin program (folosind metode ca createElement, createAttribute, createTextNode), urmeaz fragmente dintr-un program care creeaz un obiect DOM folosind un parser SAX:

172

Florian Moraru Programare Orientata pe Obiecte


class SAXDOM extends DefaultHandler { private Node crt; // nod curent din arbore DOM private Document dom; public SAXDOM (String xmlFile) throws Exception { DocumentBuilderFactory domfact = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = domfact.newDocumentBuilder(); dom = builder.newDocument(); crt=dom; // radacina arbore SAXParserFactory saxfact = SAXParserFactory.newInstance(); SAXParser saxParser = saxfact.newSAXParser(); saxParser.parse( new File(xmlFile), this); } // metode callback apelate de parserul SAX public void startElement(String uri, String lName, String qName, Attributes atts) throws SAXException { Element element = dom.createElement(qName); for (int i = 0; i < atts.getLength(); i++) { Attr a = dom.createAttribute (atts.getQName(i)); a.setValue(atts.getValue(i)); element.setAttributeNode (a); } crt.appendChild(element); crt = element; } public void endElement(String nsURI, String lName, String qName) throws SAXException { crt = (Node)crt.getParentNode(); } public void characters(char[] ch, int start, int length) throws SAXException { String s = new String(ch, start, length).trim(); if (s.length() > 0) crt.appendChild (dom.createTextNode(s.trim())); }

Utilizarea unui parser StAX StAX reuneste dou interfete API, iar o aplicatie trebuie s aleag ntre cele dou, numite API cursor si API iterator. Modelul cursor are dou interfete Java principale: XMLStreamReader si XMLStreamWriter. Fragment din interfata pentru analiza unui document XML:
public interface XMLStreamReader { public int next() throws XMLStreamException; // rezultat=tip eveniment public boolean hasNext() throws XMLStreamException; public String getText(); public String getLocalName(); public String getNamespaceURI(); ... }

Metoda next are ca rezultat tipul atomului XML din dreptul cursorului si avansul cursorului pe urmtorul atom. 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. Exemplu:
XMLStreamReader xmlr;

173

Florian Moraru Programare Orientata pe Obiecte


while(xmlr.hasNext()){ int eventType = xmlr.next(); // cu metode ale clasei parser if (xmlr.hasText()) // daca este text in pozitia curenta System.out.print(xmlr.getText()); // cu determinare tip eveniment if (xmlr.CHARACTERS == eventType) // sau ==xmlr.getEventType()) System.out.print(xmlr.getText());

Exemplu de afisare simpl, fr indentare, a unui fisier XML care contine numai elemente cu atribute si caractere:
import java.io.*; import javax.xml.stream.*; import javax.xml.stream.events.*; // Afisare in ecou cu StAX cursor class PPCursor { public static void main(String[] args) throws Exception { XMLInputFactory xif = XMLInputFactory.newInstance(); XMLStreamReader xr = xif.createXMLStreamReader(new FileReader(args[0])); while(xr.hasNext()){ int et = xr.next(); // tip eveniment String s = toString(xr); if (s.length()>0) System.out.println (s); } } public static String toString (XMLStreamReader xr){ if (xr.isStartElement()|| xr.isEndElement()) return xr.getLocalName(); if (xr.isCharacters() && !xr.isWhiteSpace()) return (xr.getText()); return ""; } }

Pentru crearea de fisiere XML se foloseste interfata XMLStreamWriter:


public interface XMLStreamWriter { public void writeStartElement(String localName) throws XMLStreamException; public void writeEndElement() throws XMLStreamException; public void writeCharacters(String text) throws XMLStreamException; ... }

Modelul iterator lucreaz cu obiecte eveniment, obtinute prin metoda nextEvent, si care contin toate informatiile despre urmtorul element de limbaj XML. Interfata XMLEvent contine constante si metode pentru analiza evenimentelor XML. Interfetele pentru analiza si respectiv sinteza unui document XML din modelul iterator sunt XMLEventReader si XMLEventWriter:
public interface XMLEventReader extends Iterator { public boolean hasNext(); public XMLEvent nextEvent() throws XMLStreamException;

174

Florian Moraru Programare Orientata pe Obiecte


public Object next(); ... } public interface XMLEventWriter { public void flush() throws XMLStreamException; public void close() throws XMLStreamException; public void add(XMLEvent e) throws XMLStreamException; public void add(Attribute attribute) \ throws XMLStreamException; ... }

Exemplu de afisare n ecou a continutului unui fisier XML folosind clase si metode din modelul StAX iterator, inclusiv metoda toString a clasei care implementeaz interfata XMLEvent:
import java.io.*; import javax.xml.stream.*; import javax.xml.stream.events.* ; class EventEcho { public static void main(String[] args) throws Exception { XMLInputFactory factory = XMLInputFactory.newInstance(); XMLEventReader r = factory.createXMLEventReader(new FileReader (args[0])); while(r.hasNext()) { XMLEvent e = r.nextEvent(); if ( !e.isEndDocument()) System.out.println(e.toString()); } } }

Pentru afisare cu indentare trebuie determinat tipul evenimentului. Putem redefini metoda toString a obiectului eveniment XML pentru a aduga indentarea, dar nu cunoastem numele clasei care implementeaz interfata XMLEvent. Avem cteva posibilitti de a schimba sirul ce corespunde unui eveniment StAX: - O metod static cu argument XMLEvent; - O alt clas care contine o variabil XMLEvent si o metod toString (nestatic), definit de noi. 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.newInstance(); XMLEventReader r = factory.createXMLEventReader( new FileReader(args[0])); while(r.hasNext()) { XMLEvent e = r.nextEvent(); String txt= toString(e).trim(); if (txt.length()>0 && !e.isEndDocument()) System.out.println(txt); } } static String sp=""; // spatii ptr indentare static String toString (XMLEvent ev) { // sir asociat unui eveniment (inclusiv text cu spatii albe) switch (ev.getEventType()){ case XMLEvent.START_ELEMENT:

175

Florian Moraru Programare Orientata pe Obiecte


String s= sp+ ev.toString(); sp+=" "; return s; case XMLEvent.END_ELEMENT: sp=sp.substring(2); return sp+ev.toString(); case XMLEvent.CHARACTERS: default: return sp+ ev.toString(); } }

Variabila sp trebuie s fie static pentru a-si pstra valoarea ntre apeluri ale functiei toString. 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). Vizualizarea arborelui XML Afisarea ntr-un obiect JTree a rezultatului unui parser XML pune cteva probleme interesante de programare cu obiecte si admite mai multe solutii: - Crearea unui obiect JTree cu datele din arborele DOM - Stabilirea unei corespondente nod la nod ntre arborii DOM si JTree - Folosirea unei clase adaptor de la un tip de nod la un alt tip de nod, deoarece metodele necesare pentru pentru operatii cu noduri sunt prezente n ambele tipuri de noduri, dar sub alte forme. Clasa adaptor primeste apeluri de metode din interfata TreeeNode si le transform n apeluri de metode din interfata Node (DOM). Avem dou variante: - Clasa adaptor implementeaz interfata TreeNode si primeste un obiect org.w3c.dom.Node; - Clasa adaptor extinde clasa DefaultMutableTreeNode si primeste un obiect de tip Node. 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; public NodeAdapter(Node node) { domNode = node; } public TreeNode getChildAt(int i) { Node node = domNode.getChildNodes().item(i); return new NodeAdapter (node); } public int getChildCount() { return domNode.getChildNodes().getLength(); } public String toString() { // sir cu continut nod, care depinde de tipul nodului int nt = domNode.getNodeType(); String s=""; if ( nt==Node.TEXT_NODE) s=domNode.getNodeValue(); if ( nt==Node.ELEMENT_NODE){ s=domNode.getNodeName(); NamedNodeMap attrs = domNode.getAttributes(); for (int i=0; i<attrs.getLength(); i++) {

176

Florian Moraru Programare Orientata pe Obiecte


Node attr = attrs.item(i); s+=attr.getNodeName()+"="+'\"'+ attr.getNodeValue()+'\"'; } } return s; } }

Utilizarea clasei adaptor se va face astfel:


public static void main(String argv[ ]) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse( new File(argv[0]) ); JFrame frame = new JFrame(argv[0]); JTree tree = new JTree(new NodeAdapter(doc)); // sau cu model de arbore frame.getContentPane().add ( new JScrollPane(tree) ); frame.setSize(500,480); frame.setVisible(true); }

O alt solutie de afisare a unui arbore DOM ntr-un obiect JTree se bazeaz pe definirea unei clase model de arbore, model care s foloseasc ca noduri (inclusiv nodul rdcin) chiar noduri DOM (rdcina poate fi de orice subtip al clasei Object). In principiu se defineste o clas care implementeaz metodele interfetei TreeModel prin apeluri de metode din interfata Node (DOM), 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). Putem s definim o clas pentru o variant de nod DOM cu metoda toString redefinit si cu cteva metode noi, utile n clasa model de arbore:
class DomNode { Node domNode; public DomNode(Node node) { domNode = node; } public String toString() { String s= ""; String nodeName = domNode.getNodeName(); if ( ! nodeName.startsWith("#")) { s += nodeName; } if (domNode.getNodeValue() != null) { // elimina spatii albe din noduri text String t = domNode.getNodeValue().trim(); s += t; } return s; } // Metode noi pentru indici si numar fii public int index( DomNode child) { int count = childCount(); for (int i=0; i<count; i++) { DomNode n = this.child(i); if (child.domNode == n.domNode) return i; } return -1;

177

Florian Moraru Programare Orientata pe Obiecte


} public DomNode child(int searchIndex) { Node node = domNode.getChildNodes().item(searchIndex); return new DomNode(node); } public int childCount() { return domNode.getChildNodes().getLength(); } }

Clasa model de arbore cu noduri DOM arat astfel:


class DomTreeModel implements TreeModel { Node root; // radacina arbore DOM public DomTreeModel (Node root) { this.root=root; } // Metode din interfata TreeModel public Object getRoot() { return new DomNode(root); } public boolean isLeaf(Object aNode) { DomNode node = (DomNode) aNode; return node.childCount() == 0; } public int getChildCount(Object parent) { DomNode node = (DomNode) parent; return node.childCount(); } public Object getChild(Object parent, int index) { DomNode node = (DomNode) parent; return node.child(index); } public int getIndexOfChild(Object parent, Object child) { DomNode node = (DomNode) parent; return node.index((DomNode) child); } public void valueForPathChanged(TreePath path, Object newValue) { // Nu se permit modificari in arborele afisat } private Vector listenerList = new Vector(); public void addTreeModelListener(TreeModelListener listener) { listenerList.addElement( listener ); } public void removeTreeModelListener(TreeModelListener listener) { listenerList.removeElement( listener ); } }

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)); add( new JScrollPane(tree)); setSize(400,500);

178

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 dect cu anumite artificii). O solutie mai simpl construieste un arbore JTree avnd 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 oricrei 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 ctre 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 pstreaz 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 prti 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 fiecrui task dintr-o actiune, functie de tipul operatiei. Toate elementele de tip target vor fi introduse ntr-o list (un vector) pe msur ce sunt ntlnite 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, punnd elementele ntr-o

180

Florian Moraru Programare Orientata pe Obiecte stiv (lantul de executie este parcurs n ordine invers folosing atributul depends al fiecrui 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 cteva 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

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

183

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

184

Florian Moraru Programare Orientata pe Obiecte In principiu ar fi posibil si utilizarea unui iterator pe arborele de fisiere, la care metoda next ar furniza urmtorul fisier ntr-o parcurgere prefixat a arborelui de fisiere. Un astfel de iterator exist n clasa DefaultMutableTreeNode sub forma metodei depthFirstEnumeration dar pentru un arbore cu legturi explicite ntre noduri. 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, iterator folosit de metoda list din clasa Java File. Programarea acestui iterator este mai dificil dect scrierea functiei recursive care foloseste metoda list. 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; // filtru aplicat fisierelor de metoda listFiles private File dir; // director initial // constructori public Finder (String dirname, FileFilter filter) { dir = new File (dirname); this.filter = filter; } public Finder (String d) { this(d,null);} // singura metoda publica a clasei public void find () { findrec(dir); } // functie recursiva de parcurgere directoare private void findrec (File dir) { File[] files; if ( filter == null) files= dir.listFiles(); else files= dir.listFiles(filter); for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.isDirectory()) findrec (file); else System.out.println(file + " "+ file.length()); } } } // exemple de utilizare new Finder("C:/Sun").find(); // cautare fara filtru intr-un director new Finder("..", new SizeFilter(1000)).find(); // cautare cu filtru

Numele directorului initial putea fi transmis ca argument metodei find si nu constructorului clasei Finder. Mai important ns este observatia c evitarea aplicrii filtrului asupra directoarelor se va face n clasa filtru si nu n metoda find, deci cade n sarcina aplicatiei. Dac se aplic un filtru de forma se accept numai fisierele cu dimensiune peste o valoare dat asupra tuturor fisierelor, atunci nu se intr n subdirectoare doarece toate fisierele director au lungimea zero. Exemplu de filtru:
class SizeFilter implements FileFilter { // filtrare dupa lungime fisiere long size; public SizeFilter (long size) { this.size=size;} public boolean accept (File f) { return f.isDirectory() || f.length()>size; } }

185

Florian Moraru Programare Orientata pe Obiecte Anumite aplicatii pot fi interesate de momentele n care se intr (si se iese) dintr-un subdirector; de exemplu, un program de afisare cu indentare va modifica indentarea la aceste momente. De aceea, evenimentele care trebuie notificate observatorului sunt trei: intrarea ntr-un nou director (schimbarea directorului curent), gsirea unui nou fisier (normal) si iesirea din directorul curent. Vom aduga acum functiei recursive findrec evitarea filtrrii directoarelor si semnalarea intrrii si iesirii din subdirectoare.
private void findrec (File f) { File[] files; File file; if (f.isDirectory()) { // daca este un (sub)director files=f.listFiles(); startDir(f); // intrare intr-un director for (int i = 0; i < files.length; i++) { file = files[i]; if ( filter.accept(file)) // daca fisier acceptat de filtru foundFile(file); // fisier gasit findrec (file); } endDir(f); // iesire din director } } // prelucrare fisier gasit public void foundFile (File file) { System.out.println(file); } // operatii la schimbare director public void startDir (File file) { System.out.println("intra in: " +file); } public void endDir (File file) { System.out.println("iese din: "+file); }

Ultimele trei metode nu trebuie s fac parte din clasa Finder; ele trebuie s fie metode callback, parte din aplicatia care prelucreaz fisierele gsite (prin afisare, arhivare, indexare, etc.). Separarea algoritmului de traversare (parcurgere) de operatia aplicat asupra fisierelor gsite permite reutilizarea acestui algoritm ntr-o diversitate de aplicatii, care pot fi folosite att n modul text (linie de comand) ct si cu interfat grafic. In plus, este izolat partea din aplicatie care nu se mai modific (procesul de cutare n arborele de subdirectoare) de partea susceptibil de modificare (modul de folosire a rezultatelor cutrii, obtinerea unor optiuni de cutare, s.a). Pentru evitarea unor cutri de durat se poate specifica o adncime maxim de subdirectoare n care se caut sau posibilitea de ntrerupere fortat a cutrii de ctre utilizator. Optiunile (conditiile) de cutare se pot transmite sub forma unui dictionar cu nume de optiuni si valori ale acestora, dictionar completat fie pe baza argumentelor din linia de comand, fie pe baza datelor introduse de operator n componente vizuale (cmpuri text sau alte obiecte grafice). Problema seamn cu o schem observat-observator, n care obiectul observat este cel care viziteaz sistemul de fisiere (cu metoda recursiv), iar obiectul observator este specific aplicatiei si este notificat la fiecare schimbare de stare n obiectul observat, adic la fiecare nou fisier gsit. Solutii posibile pentru clasa Finder Putem observa o asemnare ntre fisierele gsite de acest utilitar si elementele extrase dintr-un fisier XML de ctre un parser XML (numr posibil mare de elemente si structur ierarhic) si deci ne putem inspira din modul n care diverse programe parser XML transmit aplicatiilor informatiile

186

Florian Moraru Programare Orientata pe Obiecte despre atomii XML gsiti ntr-un fisier. Asemnarea const n aceea c functia de traversare a sistemului de fisiere (corespunde unei functii parse) stie s gseasc fisiere dar nu stie ce s fac cu fisierele gsite; de aceea semnaleaz aplicatiei evenimentele importante si transmite acesteia numele fisierelor sau subdirectoarelor gsite. Putem distinge dou categorii mari de solutii: - Crearea unui arbore cu fisierele gsite sau unei liste de ci la fisiere, dup modelul DOM sau dup modelul XPath. - Apelarea de metode callback din aplicatie, dup modelul SAX, sau generarea de evenimente specifice la gsirea fiecrui fisier, intrarea si iesirea dintr-un director, dup modelul evenimentelor generate de componentele grafice Swing (cu asculttori la evenimente). Aceste solutii au avantajul c evit un consum mare de memorie n cazul unui numr foarte mare de fisiere gsite. Generarea de evenimente si utilizarea de clase asculttor are ca avantaj posibilitatea existentei mai multor asculttori, deci realizarea mai multor operatii diferite la producerea unui eveniment (cum ar fi arhivarea fisierelor gsite si afisarea lor ntr-un obiect JTree). Pentru a reduce dimensiunea programului nostru si a-l face mai usor de nteles vom folosi modelul SAX, cu metode "callback" apelate de "find" si definite de aplicatie. Si cu aceasta alegere avem (cel putin) dou variante: - Metodele "callback" grupate ntr-o interfat, care trebuie implementat de o clas a aplicatiei, iar o referint la aceast clas este transmis metodei "find" (sau la construirea unui obiect ce contine metoda "find"); - Metodele "callback" fac parte din aceeasi clas (abstract) cu metoda "find" (ca metode abstracte sau cu efect nul), iar un utilizator trebuie s-si defineasc o clas derivat, n care s implementeze si metodele "callback". Solutia clasei abstracte, care include si metodele ce apeleaz metodele "callback", 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. In cazul nostru metoda find" si cele cteva mici metode auxiliare pot fi incluse fr costuri ntr-o aplicatie ce implic cutarea de fisiere. Vom prefera o clas neinstantiabil, cu metode avnd efect nul si nu cu metode abstracte, 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, inclusiv nume de directoare, fr filtrare:
class FileLister extends Finder { private int indent=0,step=0; // indentare curenta si pas indentare public FileLister (String d,int step) { // d este directorul initial super(d); this.step= step; } // metode callback redefinite protected void foundFile(File file) { for (int i=0;i<indent;i++) System.out.print(" "); // scrie indent spatii System.out.println(file.getName()); // si apoi numele fisierului } protected void startDir(File dir) { // la intrarea intr-un director indent += step; // si creste indentarea

187

Florian Moraru Programare Orientata pe Obiecte


} protected void endDir(File dir) { // la iesirea dintr-un director indent -= step; // scade indentarea } public static void main (String args[]) { FileLister lister = new FileLister(args[0],3); // pas de indentare de 3 spatii lister.find(); } }

In varianta cu interfat, aceasta va contine numai trei metode callback:


public interface FileHandler { void startDir (File dir) ; void foundFile (File file) ; void endDir (File dir) ; }

Clasa Finder se modific pentru a primi un obiect care implementeaz interfata FileHandler:
private FileHandler h; // variabila a clasei Finder public void find (FileHandler h) { this.h=h; findrec(dir); } // cautare recursiva private void findrec (File f) { File[] files; File file; if (f.isDirectory()) { // daca este un (sub)director files=f.listFiles(); h.startDir(f); // apel metoda din interfata for (int i = 0; i < files.length; i++) { file = files[i]; if ( filter == null || filter.accept(file)) h.foundFile(file); // apel metoda din interfata findrec (file); } h.endDir(f); // apel metoda din interfata } }

Extinderea clasei Finder Vom extinde acum clasa Finder cu anumite optiuni de cutare transmise printr-un dictionar fie ca argument al metodei "find", fie al constructorului clasei. Aceste optiuni vor fi transformate n filtre individuale de tipul FileFilter si apoi ntr-un singur filtru combinat, de acelasi tip, pentru a fi folosit de ctre metoda listFiles. Ne vom rezuma la trei criterii de cutare uzuale: dup nume (cu sabloane wildcards), dup data ultimei modificri a fisierului si dup dimensiunea fisierului. Pentru ultimele dou criterii se d o limit, iar conditia de acceptare a unui fisier este ca data (lungimea) s fie mai mic sau mai mare ca aceast limit. In practic se folosesc mai multe criterii: fisiere cu anumite atribute: Hidden, Read-Only, fisiere care contin un anumit sir, s.a. Ca exemplu simplu de utilizare vom da un program de afisare a cilor la fisierele care satisfac simultan anumite conditii, n care se va defini numai metoda foundFile. In exemplu se creeaz dictionarul de optiuni n functia main, dar ntr-o aplicatie real optiunile sunt extrase fie din linia de comand, fie din cmpuri text sau alte componente vizuale de interfat grafic.

188

Florian Moraru Programare Orientata pe Obiecte


class FileLister extends Finder { public FileLister (String d, Map opt, int step) { // opt= dictionar de optiuni super(d,opt); this.step= step; } protected void foundFile(File file) { System.out.println(file); // afisare cale completa la fisier } public static void main (String args[]) { Map opts = new HashMap(); opts.put ("name", "*.*av?"); // nume si valoare optiune 1 opts.put ("after",new Date (107,7,11)); // nume si valoare optiune 2 opts.put ("less",new Long (1200)); // nume si valoare optiune 3 FileLister lister = new FileLister(args[0],opts); // obiect de listare fisiere cu optiuni lister.find(); } }

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; // filtru complex de cautare private File dir; // director initial public Finder (String dirname, Map opt) { dir = new File (dirname); if (opt !=null && opt.size()>0) // daca exista optiuni filter = new IOFilter(opt); // 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; // lista de filtre public ComboFilter() { filters = new ArrayList<FileFilter>(); } public void addFilter( FileFilter filter) { // adauga un nou filtru la lista filters.add (filter); } public boolean accept( File file) { // verificare succesiva filtre din lista if (file.isDirectory()) return true; // orice nume de director trece de filtru ! for (Iterator it = filters.iterator(); it.hasNext();) { FileFilter filter = (FileFilter) it.next(); if (! filter.accept(file)) { // daca un filtru nu accepta fisierul return false; // nu se mai continua cu verificarea } } return true; // daca file a trecut de toate filtrele } }

Clasa IOFilter, derivat din ComboFilter, creeaz lista de filtre:

189

Florian Moraru Programare Orientata pe Obiecte


class IOFilter extends ComboFilter { public IOFilter (Map options) { super(); if (options != null) { Collection entries = options.entrySet(); Iterator itr = entries.iterator(); while(itr.hasNext()) { // parcurge toata lista de optiuni Map.Entry entry = (Map.Entry)itr.next(); FileFilter filter = selectFilter(entry.getKey().toString(), entry.getValue()); if (filter != null) addFilter(filter); // adauga la lista de filtre } } } // selectarea unui filtru dupa numele optiunii private FileFilter selectFilter(final String opt,final Object val) { if ( opt.equals("name") ) // fisiere cu nume dat (care se potrivesc) return new WildcardFilter((String)val); if (opt.equals("after")) // fisiere ulterioare unei date return new DateFilter ((Date)val); if (opt.equals("before")) // fisiere anterioare unei date return new DateFilter ((Date)val,false); if (opt.equals("more")) // fisiere mai mari ca o valoare return new SizeFilter ((Long)val); if (opt.equals("less")) // fisiere mai mici ca o valoare return new SizeFilter ((Long)val,false); else return null; // optiune necunoscuta } }

Urmeaz clasele pentru filtre individuale:


// filtru dupa lungime fisier public class SizeFilter implements FileFilter { private Long size; // o dimensiune data private boolean more; // verificare la mai mare sau la mai mic public SizeFilter(Long size) { this(size, true); } // implicit la mai mare public SizeFilter(Long size, boolean more) { if (size < 0) { throw new IllegalArgumentException("The size is negative"); } this.size = size; this.more = more; } public boolean accept(File file) { boolean smaller = new Long(file.length()).compareTo(size) < 0; return more ? !smaller : smaller; } } // filtru dupa data ultimei modificari a fisierului public class DateFilter implements FileFilter { private Date limit; private boolean after; // false = before public DateFilter(Date limit) { this(limit, true); } // implicit after public DateFilter(Date limit, boolean after) { this.after = after; this.limit = limit;

190

Florian Moraru Programare Orientata pe Obiecte


} public boolean accept(File file) { Date filedate = new Date(file.lastModified()); return after ? filedate.after(limit) : filedate.before(limit); } } // filtru dupa nume (potrivire cu un sablon ce poate contine * si ?) public class WildcardFilter implements FileFilter { private String wildcard; private boolean caseSensi; // true=conteaza daca litere mari sau mici public WildcardFilter(String wildcard) { this(wildcard, true);} public WildcardFilter(String wildcard, boolean caseSensi) { this.wildcard = wildcard ; this.caseSensi = caseSensi ; } public boolean accept(File file) { String name = file.getName(); if (! caseSensi) { name= name.toUpperCase(); wildcard=wildcard.toUpperCase (); } return wildMatch(wildcard, name); } // daca sirul str se potriveste cu sablonul pat (pattern) private boolean wildMatch ( String pat, String str) { while (str.length() > 0 ) { if (pat.length()==0) return false; if (str.charAt(0) == pat.charAt(0)) { pat=pat.substring(1); str=str.substring(1); } else if (pat.charAt(0) == '?') { if (str.charAt(0) == '.') return false; pat=pat.substring(1); str=str.substring(1); } else if (pat.charAt(0) == '*') { do { pat=pat.substring(1); } while (pat.length()>0 && pat.charAt(0) == '*'); if (pat.length()==0) return true; while (str.length()>0) if (wildMatch(pat, str=str.substring(1))) return true; return false; } else { return false; } } while (pat.length()>0 && pat.charAt(0) == '*') pat=pat.substring(1); return pat.length()==0; }

Alte exemple de utilizare a clasei Finder Pentru a verifica ct 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. Vom defini mai nti o clas derivat din clasa abstract Finder pentru crearea unui arbore de fisiere, care va putea fi folosit fie n aplicatii cu interfat grafic, fie n aplicatii cu interfat text:

191

Florian Moraru Programare Orientata pe Obiecte


// clasa derivata pentru afisare cai la fisiere gasite (fara indentare) class FileTree extends Finder { private TNode root; private Map<File,TNode> nodes; // dictionar cu continut si adrese noduri public FileTree (String dir, Map opts ) { super(dir,opts); File d = new File(dir); // nodurile contin obiecte File root=new TNode (d); // radacina contine numele directorului nodes= new HashMap<File,TNode>(); nodes.put(d,root); // valoare si adresa nod radacina super.find(); // ptr a evita apelul explicit al metodei find din afara clasei } public FileTree (String dir) { this(dir,null); } // constructor fara optiuni // redefinirea metodei apelate la fiecare fisier gasit protected void foundFile(File file) { TNode kid = new TNode(file); // creare nod ptr file nodes.put(file,kid); // pune adresa nod in dict File parent = file.getParentFile(); // valoare nod parinte TNode par = nodes.get(parent); // adresa nod parinte par.add(kid); // adauga nod fiu la nod parinte } public TNode getRoot () { return root; } }

Nodurile arborelui ar fi putut contine obiecte String cu numele fisierelor n loc de obiecte File, dar atunci era mai complicat s aflm nodul printe. In plus, ar fi fost posibil s existe mai multe noduri cu acelasi continut (fisiere cu acelasi nume n directoare diferite); un obiect File contine calea complet la un fisier, care difer chiar pentru fisiere cu acelasi nume din directoare diferite. Clasa pentru interfata grafic foloseste un obiect JTree pentru afisare, cmpuri text pentru preluare nume director, masc de selectie fisiere si dimensiune limit fisiere, obiect JComboBox pentru alegere mod de utilizare limit (ca limit inferioar sau ca limit superioar) si un buton care comand citirea datelor din cmpuri text si afisarea arborelui cu fisierele selectate conform optiunilor:
class FinderGUI extends JFrame implements ActionListener { // obiecte vizuale din GUI private JTextField folder; // pentru introducere nume director private JTextField name; // pentru selectare nume fisiere cautate private JTextField size; // ptr. dimensiune limita a fisierelor cautate private JComboBox mm; // utilizare limita: at least / at most private JButton start; // buton pornire cautare private JTree jtree; // pentru afisare fisiere gasite private Map options; // dictionar cu nume si valori optiuni // initializari public FinderGUI() { super("File Finder"); options= new HashMap(); // dictionar optiuni de cautare fisiere initComponents(); // initializare obiecte vizuale setLayout(); // plasare componente vizuale pe ecran setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); pack(); show(); // comanda afisarea intr-o fereastra minimala } // initializare componente private void initComponents() { String mmopt[]={"at least","at most"}; // afisate in JComboBox folder = new JTextField(10); // creare camp text director folder.setText("."); // initial afisare din directorul curent

192

Florian Moraru Programare Orientata pe Obiecte


name = new JTextField(10); // creare camp text masca nume fisiere name.setText("*.*"); // initial toate fisierele size= new JTextField(10); // creare camp text ptr limita dimensiune size.setText("0"); // initial limita inferioara zero mm=new JComboBox(mmopt); // creare obiect selectie start = new JButton("Start"); // creare buton start.addActionListener(this); // ascultator la actionare buton FileTree ft=new FileTree("."); // creare arbore afisat initial jtree= new JTree(new DefaultTreeModel (ft.getRoot())); } // plasare obiecte vizuale pe ecran private void setLayout() { JPanel mainpanel = new JPanel(); JPanel controls = new JPanel(); JPanel display = new JPanel(); mainpanel.setLayout (new BorderLayout()); mainpanel.add (controls,"West"); mainpanel.add (display,"Center"); controls.setLayout( new GridLayout(0,2)); // controlalele pe o coloana setContentPane(mainpanel); display.setLayout (new BorderLayout()); display.add (new JScrollPane(jtree)); controls.add (new JLabel("Folder"));controls.add ( folder); controls.add (new JLabel("File Name"));controls.add( name); controls.add (new JLabel("File Size"));controls.add( size); controls.add(mm); controls.add(start); } // ascultator la eveniment produs de buton public void actionPerformed (ActionEvent ev) { options.clear(); // sterge continut anterior dictionar options.put ("name",name.getText()); // pune masca de selectie fisiere in dic. int ix=mm.getSelectedIndex(); // indice optiune selectata din ComboBox Long sz = new Long(size.getText().trim()); // limita dimensiuni fisiere String how = ix==0? "more":"less"; // nume optiune asupra dimensiunii de fisiere options.put (how,sz); // pune limita dimensiunii fisiere in dic. FileTree ft = new FileTree (folder.getText(),options); // creare arbore cu fisierele selectate TNode r= ft.getRoot(); // radacina arbore creat jtree.setModel (new DefaultTreeModel(r)); // modificare date afisate in JTree } public static void main (String args[]) { FinderGUI gui = new FinderGUI(); } }

Scrierea unui program de arhivare n format zip este facilitat de existenta clasei de bibliotec ZipOutputStream din pachetul java.util.zip. Metoda putNextEntry scrie antetul care precede fiecare fisier n arhiv, iar metoda write scrie datele din fisierul arhivat n arhiv (un bloc de octeti). 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 dm metodei putNextEntry calea complet la fisier si nu doar numele fisierului arhivat. Exemplu de metod static pentru arhivarea fisierelor dintr-un director care nu contine si subdirectoare:
public static void zipFolder(String dir, String outFile) final int BUFFER=4096; File folder = new File(dir); {

193

Florian Moraru Programare Orientata pe Obiecte


try { ZipOutputStream out = new ZipOutputStream( new BufferedOutputStream( new FileOutputStream(outFile))); BufferedInputStream in = null; byte[] data = new byte[BUFFER]; File files[] = folder.listFiles(); for (int i=0; i<files.length; i++) { in = new BufferedInputStream(new FileInputStream(files[i]), BUFFER); out.putNextEntry(new ZipEntry(files[i].getName())); // write data header (name, size, etc) int count; while((count = in.read(data,0,BUFFER)) != -1) out.write(data, 0, count); out.closeEntry(); //close each entry } in.close(); out.close(); } catch(Exception e) {e.printStackTrace();} }

Dac directorul dir contine si subdirectoare atunci apar exceptii la ncercarea de citire date din aceste subdirectoare. Metoda foundFile din clasa Finder este apelat att pentru fisiere normale ct si pentru subdirectoare, de aceea vom testa dac este sau nu director pentru a evita exceptiile
class FinderZip extends Finder { final int BUFFER=4096; // ptr reducerea timpului de citire din fisier private ZipOutputStream out; // obiect asociat cu fisierul arhiva public FinderZip(String dir, String outFile) { super (dir); try { out = new ZipOutputStream ( new BufferedOutputStream( new FileOutputStream(outFile))); } catch(Exception e) {e.printStackTrace();} super.find(); try {out.close();} catch(Exception e) {e.printStackTrace();} } protected void foundFile(File file) { BufferedInputStream in = null; byte[] data = new byte[BUFFER]; if (file.isDirectory()) return; try { in = new BufferedInputStream(new FileInputStream (file), BUFFER); out.putNextEntry(new ZipEntry(file.getAbsolutePath())); // write data header (name, size, etc) int count; while((count = in.read(data,0,BUFFER)) != -1) out.write(data, 0, count); out.closeEntry(); // close each entry in.close(); } catch(Exception e) {e.printStackTrace();} }

} Exemplu de utilizare:
new FinderZip ("F:/groovy-1.5.4", "C:/test.zip");

194

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

195

Florian Moraru Programare Orientata pe Obiecte - Ca metod fabric (de obicei metod static) dintr-o clas, care poate fi clasa abstract ce defineste tipul comun al obiectelor fabricate. Aceast solutie se practic atunci cnd obiectele fabricate nu difer mult ntre ele. - Ca o clas fabric, atunci cnd exist diferente mai mari ntre obiectele fabricate. Alegerea (sub)tipului de obiecte fabricate se poate face fie prin parametri transmisi metodei fabric sau constructorului de obiecte fabric, fie prin fisiere de proprietti (fisiere de configurare). Metode si clase fabric pot fi ntlnite n diverse pachete din JSE (Java Standard Edition ) si, mai ales, n JEE (Java Enterprise Edition). Putem deosebi dou situatii n care este necesar utilizarea unei metode fabric n locul operatorului new: - Pentru a evita crearea de obiecte identice (economie de memorie si de timp); metoda fabric va furniza la fiecare apel o referint la un obiect unic si nu va instantia clasa. Rezultatul metodei este de un tip clas instantiabil. - Pentru c tipul obiectelor ce trebuie create nu este cunoscut exact de programator, dar poate fi dedus din alte informatii furnizate de utilizator, la executie. Rezultatul metodei este de un tip interfat sau clas abstract, care include toate subtipurile de obiecte fabricate de metod (fabric "polimorfic"). Utilizarea fabricilor de obiecte poate fi privit ca un pas nainte pe calea separatiei interfatimplementare, n sensul c permite oricte implementri pentru o interfat, cu orice nume de clase si cu orice date initiale necesare fabricrii obiectelor. O metod fabric poate fi o metod static sau nestatic. Metoda fabric poate fi apelat direct de programatori sau poate fi apelat dintr-un constructor, ascunznd utilizatorilor efectul real al cererii pentru un nou obiect. O metod fabric de un tip clas instantiabil tine evidenta obiectelor fabricate si, n loc s produc la fiecare apel un nou obiect, produce referinte la obiecte deja existente. 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), b2= new JButton(Etched2); // chenar construit cu new b1.setBorder (new EtchedBorder ()); // chenar gravat // chenar construit cu metoda fabric Border eb = BorderFactory.createEtchedBorder(); b2.setBorder (eb);

Pentru acest fel de metode fabric exist cteva variante: - Metoda are ca rezultat o referint la un obiect creat la ncrcarea clasei:
public class BorderFactory { private BorderFactory () { } // neinstantiabil static final sharedEtchedBorder = new EtchedBorder(); public static Border createEtchedBorder() { return sharedEtchedBorder; } ... } // alte metode fabric n clas

Clasa BorderFactory (pachetul javax.swing) reuneste mai mai multe metode fabric ce produc obiecte chenar de diferite forme pentru componentele vizuale Swing. Toate obiectele chenar apartin unor clase care respect interfata comun Border si care sunt fie clase predefinite, fie clase definite de utilizatori. Exemple de clase chenar predefinite: EmptyBorder, LineBorder, BevelBorder, TitleBorder si CompundBorder (chenar compus). - Metoda creeaz un obiect numai la primul apel, dup care nu mai instantiaz clasa.

196

Florian Moraru Programare Orientata pe Obiecte Un exemplu de metod fabric controlabil prin argumente este metoda getInstance din clasa Calendar, care poate crea obiecte de diferite subtipuri ale tipului Calendar , unele necunoscute la scrierea metodei dar adugate ulterior. 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, care s tin seama de diferitele tipuri de calendare folosite pe glob. Clasa abstract Calendar (din java.util) contine cteva metode statice cu numele getInstance (cu si fr parametri), care fabric obiecte de tip Calendar. Una din clasele instantiabile derivat din Calendar este GregorianCalendar, pentru tipul de calendar folosit n Europa si n America. Metoda getInstance fr argumente produce implicit un obiect din cel mai folosit tip de calendar:
public static Calendar getInstance() { return new GregorianCalendar(); }

Obiectele de tip Calendar se pot utiliza direct sau transformate n obiecte Date:
Calendar azi = Calendar.getInstance(); Date now = azi.getTime(); // conversie din Calendar in Date System.out.println (now);

Metoda getInstance poate avea unul sau doi parametri, unul de tip TimeZone si altul de tip Local, pentru adaptarea orei curente la fusul orar (TimeZone) si a calendarului la pozitia geografic (Local). Tipul obiectelor fabricate este determinat de acesti parametri. Alte metode fabric care produc obiecte adaptate particularitatilor locale (de tar) se afl n clase din pachetul java.text: NumberFormat, DateFormat, s.a. Adaptarea se face printr-un parametru al metodei fabric care precizeaz tara si este o constant simbolic din clasa Locale. Afisarea unei date calendaristice se poate face n mai multe forme, care depind de uzantele locale si de stilul dorit de utilizator (cu sau fr numele zilei din sptmn, cu nume complet sau prescurtat pentru lun etc.). Clasa DateFormat contine metoda getDateInstance care poate avea 0,1 sau 2 parametri si care poate produce diferite obiecte de tip DateFormat. Exemple:
Date date = new Date(); DateFormat df1 = DateFormat.getDateInstance (); DateFormat df2 = DateFormat.getDateInstance (2, Locale.FRENCH); System.out.println ( df1.format(date)); //luna, an, zi cu luna in engleza System.out.println ( df2.format(date)); // zi,luna,an cu luna in franceza

Fabrici abstracte Clasele de obiecte fabric pot fi la rndul lor subtipuri ale unui tip comun numit fabric abstract (AbstractFactory). Altfel spus, se folosesc uneori fabrici de fabrici de obiecte. Uneori se stie ce fel de obiecte sunt necesare, dar nu se stie exact cum trebuie realizate aceste obiecte (cum trebuie implementat clasa); fie nu exist o singur clas posibil pentru aceste obiecte, fie existau anterior furnizori diferiti pentru aceste clase si se ncearc unificarea lor, fie sunt anticipate si alte implementri ale unei clase n viitor, dar care s nu afecteze prea mult aplicatiile existente. Portabilitatea dorit de Java cere ca operatiile s fie programate uniform, folosind o singur interfat API ntre client (cel care solicit operatia) si furnizorul de servicii (provider), indiferent de modul cum sunt implementate acele servicii, sau de faptul c exist mai multe implementri. Astfel de interfete API exist pentru servicii prin natura lor diversificate ca mod de realizare: pentru acces la orice baz de date relational (JDBC), pentru comunicarea prin mesaje (JMS, SAAJ), pentru analiz de fisiere XML etc.

197

Florian Moraru Programare Orientata pe Obiecte Pentru acces la servicii oferite de un server se creeaz un obiect conexiune, iar operatiile ulterioare se exprim prin apeluri de metode ale obiectului conexiune. Diversitatea apare n modul de creare a obiectului conexiune, care nu se poate face prin instantierea unei singure clase, cu nume si implementare cunoscute la scrierea aplicatiei. Obiectele conexiune sunt create de fabrici de conexiuni, care ascund particularitti de implementare ale acestor obiecte. Toate obiectele conexiune trebuie s prevad anumite metode, deci s respecte un contract cu utilizatorii de conexiuni. Fabricile de conexiuni pot fi uneori si ele foarte diferite si s necesite parametri diferiti (ca tip si ca utilizare); de aceea, obiectul fabric de conexiuni nu este obtinut prin instantierea unei singure clase ci este fabricat ntr-un fel sau altul. In JDBC clasa fabric de conexiuni, numit DriverManager foloseste o clas driver, livrat de obicei de ctre furnizorul bazei de date, sub forma unei clase cu nume cunoscut (ncrcat nainte de utilizarea clasei fabric) . Exemplu:
Class.forName (sun.jdbc.odbc.JdbcOdbcDriver); // clasa driver Connection con = DriverManager.getConnection (dbURL,user,passwd); Statement stm = con.createStatement(); // utilizare obiect conexiune

In exemplul anterior Connection si Statement sunt interfete, dar nu exist n pachetul java.sql nici o clas instantiabil care s implementeze aceste interfete. Obiectul conexiune este fabricat de metoda getConnection pe baza informatiilor extrase din clasa driver; la fel este fabricat si obiectul instructiune de ctre metoda createStatement. Exemplu de utilizare a unei fabrici de fabrici de conexiuni pentru mesaje SOAP:
SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance(); SOAPConnection con = factory.createConnection();

In exemplul anterior obiectul fabric factory este produs de o fabric abstract implicit (numele este ascuns n metoda newInstance), furnizat de firma Sun pentru a permite exersarea de mesaje SOAP, fr a exclude posibilitatea altor fabrici de conexiuni SOAP, de la diversi alti furnizori. Pentru a ntelege mai bine ce se ntmpl ntr-o fabric (de fabrici) de obiecte am imaginat urmtorul exemplu: o fabric pentru crearea de obiecte colectie (de orice subtip al tipului Collection) pe baza unui vector implicit de obiecte. Exemplu:
public static Collection newCollection (Object a[], Class cls) { Collection c=null; try { c = (Collection) cls.newInstance(); } catch (Exception e) { System.out.println(e);} for (int i=0;i<a.length;i++) c.add (a[i]); return c; } // utilizare (in main) String a[ ] = { "1","2","3","2","1"}; HashSet set = (HashSet) newCollection (a,HashSet.class);

In exemplul anterior am folosit o metod fabric de obiecte colectie, care va putea fi folosit si pentru clase colectie nc nedefinite, dar care implementeaz interfata. Utilizarea acestei fabrici este limitat deoarece nu permite transmiterea de parametri la crearea obiectelor colectie; de exemplu, la crearea unei colectii ordonate este necesar transmiterea unui obiect comparator (subtip al tipului Comparator). O solutie ar putea fi definirea mai multor clase fabric de colectii, toate compatibile cu un tip comun (tipul interfat CollectionFactory). Exemplu:

198

Florian Moraru Programare Orientata pe Obiecte


// interfata pentru fabrici de colectii interface CollectionFactory { public Collection newCollection (Object [] a); } // fabrica de colectii simple (neordonate) class SimpleCollectionFactory implements CollectionFactory { private Collection col; public SimpleCollectionFactory (String clsName) { try { Class cls = Class.forName(clsName); col =(Collection) cls.newInstance(); } catch (Exception e) { System.out.println(e);} } public Collection newCollection (Object a[]) { for (int i=0;i<a.length;i++) col.add (a[i]); return col; } } // fabrica de colectii ordonate class SortedCollectionFactory implements CollectionFactory { private Collection col; public SortedCollectionFactory (String clsName, Comparator comp) { try { Class cls = Class.forName(clsName); Class[] clsargs = new Class[] {Comparator.class}; Object[] args = new Object[] { comp }; Constructor constr = cls.getConstructor(clsargs); col =(Collection) constr.newInstance(args); } catch (Exception e) { System.out.println(e);} } public Collection newCollection (Object a[]) { for (int i=0;i<a.length;i++) col.add (a[i]); return col; }

Instantierea direct a acestor clase fabric ar obliga utilizatorul s cunoasc numele lor si ar necesita modificri n codul surs pentru adugarea unor noi clase fabric. De aceea, 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); } public static CollectionFactory getFactory (String clsName,Comparator comp) { return new SortedCollectionFactory (clsName,comp) ; } } // exemple de utilizare public static void main (String arg[]) throws Exception { String a[] = { "1","2","3","2","1"}; CollectionFactory cf1 = FFactory.getFactory ("java.util.HashSet"); HashSet set1 = (HashSet) cf1.newCollection (a); CollectionFactory cf2 = FFactory.getFactory ("java.util.TreeSet", new MyComp());

199

Florian Moraru Programare Orientata pe Obiecte


TreeSet set2 = (TreeSet) cf2.newCollection (a); System.out.println(set1); System.out.println(set2); }

In acest caz particular se putea folosi o singur clas fabric de colectii cu mai multi constructori, deoarece sunt diferente mici ntre cele dou fabrici. In situatii reale pot fi diferente foarte mari ntre clasele fabric pentru obiecte complexe. 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. Ea foloseste o interfat general, cu o singur functie, de felul urmtor:
public interface Command { public void execute(); } // execut o actiune nedefinit nc

In Swing exist mai multe interfete "asculttor" corespunztoare interfetei Command. Exemplu:
public interface ActionListener { public void actionPerformed( ActionEvent ev); }

Un obiect dintr-o clas asculttor este un obiect "comand" si realizeaz o actiune. El constituie singura legtur dintre partea de interfat grafic a aplicatiei si partea de logic specific aplicatiei (tratarea evenimentelor). Se realizeaz astfel decuplarea celor dou prti, ceea ce permite modificarea lor separat si chiar selectarea unei anumite actiuni n cursul executiei. Pentru a compara solutia schemei "Command" cu alte solutii de selectare a unei actiuni (comenzi), s examinm pe scurt programarea unui meniu cu clase JFC. Un meniu este format dintr-o bar meniu orizontal (obiect JMenuBar), 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). Un element de meniu este o optiune care declanseaz o actiune la selectarea ei, prin producerea unui eveniment de tip ActionEvent. Pentru programarea unui meniu cu o singur optiune "File", care se extinde ntr-un meniu vertical cu dou elemente ("Open" si "Exit") sunt necesare instructiunile:
JMenuBar mbar = new JMenuBar(); // creare bara meniu principal setJMenuBar (mbar); // adauga bara meniu la JFrame JMenu mFile = new JMenu ("File"); // optiune pe bara orizontala mbar.add (mFile); // adauga optiunea mFile la bara meniu JMenuItem open = new JMenuItem ("Open"); // optiune meniu vertical JMenuItem exit = new JMenuItem ("Exit"); // optiune meniu vertical mFile.add (open); mFile.addSeparator( ); // adauga optiuni la meniu vertical mFile.add (exit); open.addActionListener (this); // sau un alt obiect ca argument exit.addActionListener (this); // sau un alt obiect ca argument

Tratarea evenimentelor generate de diverse elemente de meniu (mai multe surse de evenimente, n aplicatiile reale) se poate face ntr-o singur metod:
public void actionPerformed (ActionEvent e) { Object source = e.getSource(); // sursa evenimentului if ( source == open) fileOpen( ); // actiune asociata optiunii "Open"

200

Florian Moraru Programare Orientata pe Obiecte


else if (source == exit) System.exit(0); } // actiune asociata optiunii "Exit"

Aplicarea schemei "Command", cu o interfat "Command", conduce la definirea mai multor clase care implementeaz aceast interfat (clase de nivel superior sau clase incluse n clasa JFrame, pentru acces la alte variabile din interfata grafic). Exemplu:
class FileExitCmd extends JMenuItem implements Command { public FileExitCmd (String optname) { super(optname); // nume optiune (afisat in meniu) } public void execute () { System.exit(0); // actiune asociata optiunii } }

In programarea anterioar a meniului apar urmtoarele modificri:


class CmdListener implements ActionListener { public void actionPerformed (ActionEvent e) { Commnad c = (Command) e.getSource(); c.execute(); } } ... ActionListener cmd = new CmdListener(); open.addActionListener (cmd); exit.addActionListener (cmd);

Metoda "execute" este o metod polimorfic, iar selectarea unei implementri sau alta se face n functie de tipul variabilei c, deci de tipul obiectului care este sursa evenimentului. Schema "Command" mai este folosit n programarea meniurilor si barelor de instrumente ("toolbar"), prin utilizarea obiectelor "actiune", care implementeaz interfata Action. O bar de instrumente contine mai multe butoane cu imagini (obiecte JButton), dar are acelasi rol cu o bar meniu: selectarea de actiuni de ctre operator. In loc s se adauge celor dou bare obiecte diferite (dar care produc aceeasi actiune) se adaug un singur obiect, de tip Action, care contine o metod "actionPerformed"; cte un obiect pentru fiecare actiune selectat. Interfata Action corespunde interfetei "Command", iar metoda "actionPerformed" corespunde metodei "execute". Interfata Action extinde interfata ActionPerformed cu dou metode "setValue" si "getValue", necesare pentru stabilirea si obtinerea propriettilor unui obiect actiune: text afisat n optiune meniu, imagine afisat pe buton din bara de instrumente, o scurt descriere ("tooltip") a "instrumentului" afisat. Clase cu un singur obiect Uneori este nevoie de un singur obiect de un anumit tip, deci trebuie interzis instantierea repetat a clasei respective, numit clas "Singleton". Exist cteva solutii: - O clas fr constructor public, cu o metod static care produce o referint la unicul obiect posibil ( o variabil static si final contine aceast referint). - O clas static inclus si o metod static (n aceeasi clas exterioar) care instantiaz clasa (pentru a-i transmite un parametru).

201

Florian Moraru Programare Orientata pe Obiecte Ambele solutii pot fi ntlnite n clasa Collections (neinstantiabil) pentru a se crea obiecte din colectii "speciale" : colectii vide (EmptySet, EmptyList, EmptyMap) si colectii cu un singur obiect (SingletonSet, SingletonList, SingletonMap). Exemplu:
public class Collections { public static Set singleton(Object o) { return new SingletonSet(o); } private static class SingletonSet extends AbstractSet { private Object element; SingletonSet(Object o) {element = o;} public int size() {return 1;} public boolean contains(Object o) {return eq(o, element);} ... // public Iterator iterator() { ... } } ... }

Exemplu de utilizare n problema claselor de echivalent :


public class Sets { // colectie de multimi disjuncte private List sets; // lista de multimi // constructor ( n= nr de elemente in colectie) public Sets (int n) { sets = new ArrayList (n); for (int i=0;i<n;i++) sets.add (new TreeSet (Collections.singleton ( new Integer(i) ) )); } . . . // metode find, union

} Schema claselor observat observator In aceast schem exist un obiect care poate suferi diverse modificri (subiectul observat) si unul sau mai multe obiecte observator, care ar trebui anuntate imediat de orice modificare n obiectul observat, pentru a realiza anumite actiuni. Obiectul observat contine o list de referinte la obiecte observator si, la producerea unui eveniment, apeleaz o anumit metod a obiectelor observator nregistrate la el. Relatia dintre o clas JFC generator de evenimente si o clas asculttor (receptor) care reactioneaz la evenimente este similar relatiei dintre o clas observat si o clas observator. De exemplu, un buton JButton are rolul de obiect observat, iar o clas care implementeaz interfata ActionListener si contine metoda actionPerformed are rolul de observator. Schema observat-observator a generat clasa Observable si interfata Observer din pachetul "java.util". Programatorul de aplicatie va defini una sau mai multe clase cu rol de observator, compatibile cu interfata Observer. 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. Exemplu:
public void addObserver(Observer o) { // adauga un nou observator la lista if (!observers.contains(o)) // "observers" este vector din clasa Observable observers.addElement(o); }

202

Florian Moraru Programare Orientata pe Obiecte Interfata Observer contine o singur metod "update", 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, Object arg); } // o este obiectul observat

Clasa Observable nu este abstract dar nici nu este direct utilizabil; programatorul de aplicatie va defini o subclas care preia toate metodele clasei Observable, dar adaug o serie de metode specifice aplicatiei care apeleaz metodele superclasei setChanged si notifyObservers. Metoda notifyObservers apeleaz metoda "update" pentru toti observatorii introdusi n vectorul "observers":
public class Observable { private boolean changed = false; // daca s-a produs o scimbare private Vector obs; // lista de obiecte observator public void addObserver(Observer o) { // adauga un observator la lista if (!obs.contains(o)) obs.addElement(o); } public void notifyObservers(Object arg) { // notificare observatori de schimbare if (!changed) return; for (int i = arrLocal.length-1; i>=0; i--) ((Observer)obs.elementAt(I)).update(this, arg); } protected void setChanged() { // comanda modificare stare ob. observat changed = true; } ...

Exemplu de definire a unei subclase pentru obiecte observate:


class MyObservable extends Observable { public void change() { // metoda adagata in subclasa setChanged(); // din clasa Observable notifyObservers(); // anunta observatorii ca s-a produs o schimbare } }

Clasa observator poate arta astfel, dac metoda update adaug cte un caracter la o bar afisat pe ecran (la fiecare apelare de ctre un obiect MyObservable).
class ProgressBar implements Observer { public void update( Observable obs, Object x ) { System.out.print('#'); } }

Exemplu de utilizare a obiectelor observat-observator definite anterior:


public static void main(String[ ] av) { ProgressBar bar= new ProgressBar(); MyObservable model = new MyObservable(); model.addObserver(bar); int n=1000000000, m=n/100;

203

Florian Moraru Programare Orientata pe Obiecte


for (int i=0;i<n;i++) if ( i%m==0) model.change(); } // modifica periodic stare obiect observat

Interfetele ActionListener, ItemListener s.a. au un rol similar cu interfata Observer, iar metodele "actionPerformed" s.a. corespund metodei "update". Programatorul de aplicatie defineste clase asculttor compatibile cu interfetele "xxxListener", clase care implementeaz metodele de tratare a evenimentelor ("actionPerformed" s.a). Corespondenta dintre metodele celor dou grupe de clase este astfel:
addObserver notifyObservers update addActionListener, addChangeListener fireActionEvent, fireChangeEvent actionPerformed, stateChanged

Clase model n schema MVC Schema MVC (Model-View-Controller) este o extindere a schemei "Observat-observator". Un obiect model este un obiect observat (ascultat), care genereaz evenimente pentru obiectele receptor nregistrate la model, dar evenimentele nu sunt produse ca urmare direct a unor cauze externe programului (nu sunt cauzate direct de actiuni ale operatorului uman). Arhitectura MVC foloseste clase avnd trei roluri principale: - Clase cu rol de model, adic de obiect observat care contine si date. - Clase cu rol de redare vizual a modelului, adic de obiect observator al modelului. - Clase cu rol de comand a unor modificri n model. Schema MVC a aprut n legtur cu programele de birotic (pentru calcul tabelar si pentru editare de texte), de unde si numele de Document-View-Controller. Separarea net a celor trei componente (M, V si C) permite mai mult flexibilitate n adaptarea si extinderea programelor, prin usurinta de modificare separat a fiecrei prti (n raport cu un program monolit la care legturile dintre prti nu sunt explicite). Astfel, putem modifica structurile de date folosite n memorarea foii de calcul (modelul) fr ca aceste modificri s afecteze partea de prezentare, sau putem aduga noi forme de prezentare a datelor continute n foaia de calcul sau noi forme de interactiune cu operatorul (de exemplu, o bar de instrumente tool bar). Separarea de responsabilitti oferit de modelul MVC este util pentru realizarea de aplicatii sau prti de aplicatii: componente GUI, aplicatii de tip client-server s.a. 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 urmtoarea problem simpl: Operatorul introduce un nume de fisier, aplicatia verific existenta acelui fisier n directorul curent si adaug numele fisierului la o list de fisiere afisat ntr-o alt fereastr. Ulterior vom aduga o a treia fereastr n care se afiseaz dimensiunea total a fisierelor selectate. 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). Obiectele folosite de aplicatie pot fi : JTextField pentru introducerea unui nume de fisier, JTextField pentru afisare dimensiune total si JTextArea pentru lista de fisiere. Ele vor avea atasate si etichete JLabel care explic semnificatia ferestrei. Pentru a avea mai mult libertate n plasarea ferestrelor pe ecran vom crea dou panouri. Fereastra de introducere are rolul de controler deoarece comand modificarea continutului celorlalte dou ferestre, care sunt dou imagini diferite asupra listei de fisiere selectate. Secventa urmtoare realizeaz crearea obiectelor necesare si gruparea lor si este comun pentru toate variantele discutate pentru aceast aplicatie.

204

Florian Moraru Programare Orientata pe Obiecte


class MVC extends JFrame { TextArea listView = new TextArea(10,20); // imagine lista TextField sizeView = new TextField(15); // imagine dimensiune totala TextField controler = new TextField(10); // Controler public MVC () { // constructor Container c = getContentPane(); c.setLayout (new FlowLayout ()); // Afisare controler JPanel panel1 = new JPanel(); // panou pentru controler si sizeView panel1.setLayout(new FlowLayout()); panel1.add(new JLabel("File")); panel1.add(controler); panel1.add( new JLabel("Size")); panel1.add (sizeView); c.add(panel1); // Afisare imagini JPanel panel2 = new JPanel(); // panou pentru listView panel2.setLayout( new FlowLayout()); panel2.add( new JLabel("List")); panel2.add(new JScrollPane(listView)); // pentru defilare in zona text c.add(panel2); // Legare imagini la controler ... } public static void main(String args[]) { MVC frame = new MVC(); frame.setSize(300,200); frame.setVisible(true); // afisare fereastra } }

Programul este utilizabil, dar poate fi extins pentru slefuirea aspectului ferestrei principale cu chenare pe componente, cu intervale de separare (strouts) si ntre ferestre si cu o alt grupare a componentelor atomice Swing n panouri. In varianta aplicatiei cu numai dou ferestre (cu un singur obiect imagine) legarea obiectului listView la obiectul controler se face prin nregistrarea unui obiect asculttor la sursa de evenimente de tip ActionEvent si prin implementarea metodei actionPerformed cu actiunea declansat n obiectul receptor:
// Legare listView la controler controler.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ev) { String newElem = controler.getText(); // sirul introdus in controler File f = new File (newElem); // ptr a verifica existenta fisierului if ( f.exists()) listView.append ( " "+ newElem +"\n"); // adauga nume la lista afisata else Toolkit.getDefaultToolkit().beep(); // daca nu exista fisier controler.setText(""); // sterge cimp de intrare } });

In secventa anterioar s-a definit o clas inclus anonim cu rol de adaptor ntre controler si imagine. Continutul clasei adaptor depinde si de tipul componentei de comand (aici un cmp text) si de rolul componentei imagine n aplicatie. Pentru extinderea acestei aplicatii cu afisarea dimensiunii totale a fisierelor introduse pn la un moment dat avem mai multe solutii:

205

Florian Moraru Programare Orientata pe Obiecte a) Adugarea unui nou receptor la controler printr-o alt clas adaptor ntre controler si noul obiect imagine. De remarcat c va trebui s repetm o serie de operatii din adaptorul deja existent, cum ar fi verificarea existentei fisierului cu numele introdus n cmpul text. Mai neplcut este faptul c succesiunea n timp a executiei metodelor actionPerformed din cele dou obiecte adaptor (receptor) nu este previzibil. De fapt aplicatia functioneaz corect numai dac este adugat mai nti adaptorul pentru cmpul text sizeView si apoi adaptorul pentru zona text listView (ordinea invers modific comportarea aplicatiei):
// Legare controler la sizeView controler.addActionListener( new ActionListener() { int sum=0; public void actionPerformed(ActionEvent ev) { String newElem = controler.getText(); File f = new File (newElem); if ( f.exists()) { sum+= f.length(); sizeView.setText(" "+ sum); } else Toolkit.getDefaultToolkit().beep(); } }); // Legare controler la listView controler.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ev) { String newElem = controler.getText(); File f = new File (newElem); if ( f.exists()) { listView.append ( " "+ newElem +"\n"); controler.setText(""); // sterge camp de intrare } });

b) Modificarea adaptorului astfel ca s lege obiectul controler la ambele obiecte imagine, actualizate ntr-o singur functie apelat la declansarea evenimentului de modificare a continutului cmpului text controler:
// Legare controler la sizeView si listView controler.addActionListener( new ActionListener() { int sum=0; // suma dimensiuni fisiere public void actionPerformed(ActionEvent ev) { String newElem = controler.getText(); // sirul introdus in controler File f = new File (newElem); // ptr a verifica existenta fisierului if ( f.exists()) { sum+= f.length(); sizeView.setText(" "+ sum); // afisare dimensiune totala listView.append ( " "+ newElem +"\n"); // adauga nume la lista afisata } else Toolkit.getDefaultToolkit().beep(); controler.setText(""); // sterge cimp de intrare } });

Desi este forma cea mai compact pentru aplicatia propus, modificarea sau extinderea ei necesit modificri ntr-o clas existent (clasa adaptor), clas care poate deveni foarte mare dac numrul

206

Florian Moraru Programare Orientata pe Obiecte obiectelor ce comunic si/sau functiile acestora cresc. Din acest motiv clasa adaptor este mai bine s fie o clas cu nume, definit explicit, chiar dac rmne inclus n clasa MVC. Dintre modificrile posibile mentionm : adugarea unor noi obiecte imagine (de exemplu o fereastr cu numrul de fisiere selectate si o bar care s arate proportia de fisiere selectate din fisierul curent ) si nlocuirea cmpului text din obiectul controler cu o list de selectie (ceea ce este preferabil, dar va genera alte evenimente si va necesita alte metode de preluare a datelor). c) Cuplarea obiectului sizeView la obiectul listView sau invers nu este posibil, pentru c un cmp 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). In variantele prezentate aplicatia nu dispune de datele introduse ntr-un obiect accesibil celorlalte prti din aplicatie, desi lista de fisiere ar putea fi necesar si pentru alte operatii dect afisarea sa (de exemplu, pentru comprimare si adugare la un fisier arhiv). Extragerea datelor direct dintr-o component vizual Swing nu este totdeauna posibil si oricum ar fi diferit pentru fiecare tip de component. Introducerea unui nou obiect cu rol de model care s contin datele aplicatiei (lista de fisiere validate) permite separarea celor trei prti din aplicatie si modificarea lor separat. Pentru problema dat se poate folosi modelul de list DefaultListModel, care contine un vector (si suport toate metodele clasei Vector), dar n plus poate genera trei tipuri de evenimente: adugare element, eliminare element si modificare continut. Sau, putem defini o alt clas care implementeaz interfata ListModel . Un receptor al evenimentelor generate de un model ListModel trebuie s implementeze interfata ListDataListener, care contine trei metode : intervalAdded, intervalRemoved si contentsChanged. Se poate defini o clas adaptor cu toate metodele interfetei ListDataListener, dar fr nici un efect (ListDataAdapter). Acum sunt necesare trei clase adaptor care s lege la model obiectul controler si cele dou obiecte imagine, 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.getText(); File f= new File(file); if (f.exists()) model.addElement ( file ); else Toolkit.getDefaultToolkit().beep(); controler.setText(""); // sterge cimp de intrare } } // clase adaptor intre model si imaginile sale class MV1Adapter extends ListDataAdapter { long sum=0; String file; File f; public void intervalAdded ( ListDataEvent ev) { file = (String) model.lastElement(); f= new File(file); sum += f.length(); sumView.setText(" "+ sum); } } class MV2Adapter extends ListDataAdapter { public void intervalAdded (ListDataEvent ev) { // obiect model

207

Florian Moraru Programare Orientata pe Obiecte


listView.append(" "+ model.lastElement()+"\n"); } } ... // Cuplare obiecte MVC prin obiecte adaptor controler.addActionListener( new CMAdapter()); // model la controler model.addListDataListener( new MV1Adapter()); // sumView la model model.addListDataListener( new MV2Adapter()); // 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. Desi textul surs al aplicatiei a crescut substantial fat de varianta monolit, extinderea sau modificarea aplicatiei a devenit mai simpl si mai sigur pentru c se face prin prin adugarea de noi obiecte sau prin nlocuirea unor obiecte, fr a modifica obiectele existente sau metodele acestor obiecte. Aplicatia se poate mbuntti prin modificarea modului de alegere a fisierelor de ctre 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. Schema MVC este mult folosit n realizarea aplicatiilor Web, unde partea de model contine clasele ce corespund tabelelor bazei de date (inclusiv logica de utilizare a acestor date), partea de vizualizare corespunde interfetei cu utilizatorul (obiecte grafice afisate de aplicatie n browser, validri asupra datelor introduse de operator, animatie si alte efecte vizuale), iar partea de comand (controller) corespunde prelurii cererilor HTTP si transmiterii rspunsurilor corespunztoare (care include de obicei date din model ).

208

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

209

Florian Moraru Programare Orientata pe Obiecte Un singur proces poate fi n executie, celelalte se afl fie n lista proceselor gata, fie n lista proceselor suspendate sau blocate. 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). Procesele paralele (concurente) si pot disputa anumite resurse comune (date din memorie, fisiere de date, s.a.) sau si pot transmite date ntre ele (procese numite productor si consumator). Interactiunile dintre procese (sau subprocese) paralele nu se fac prin apeluri directe ntre procese ci prin intermediul dispecerului. Coordonarea si sincronizarea proceselor paralele necesit existenta unor operatii specifice, prin care procesele se adreseaz planificatorului. Fire de executie n Java In Java un fir de executie este un obiect dintr-o subclas a clasei de bibliotec Thread. Operatiile prin care un fir interactioneaz cu planificatorul sunt fie metode din clasa Object (wait, notify), fie metode din clasa Thread (sleep, yield, join, s.a.). Planificatorul activeaz un fir prin apelarea metodei run, prezent n orice fir de executie (mostenit de la clasa Thread). In Java exist ntotdeauna un fir de executie implicit (cu numele main), creat automat si n care se execut functia main cu rol de program principal. Alte fire de executie pot fi create din firul main sau din alte fire create anterior. Clasele pentru fire de executie se definesc fie ca subclase ale clasei Thread (cu metoda run redefinit), fie pe baza unei clase care implementeaz interfata Runnable (interfat cu o singur metod run). Clasa Thread si interfata Runnable se afl n pachetul java.lang si, alturi de cuvntul cheie synchronized au existat nc din prima versiune Java. Incepnd cu versiunea 5 au fost introduse noi clase si metode pentru executia si sincronizarea firelor concurente cu resurse comune n pachetul java.util.concurrent. Clasa Thread contine cteva metode, unele apelate de dispecer iar altele care apeleaz dispecerul. Efectul metodei run (apelat de dispecer) depinde de rolul pe care l are firul de executie n cadrul unei aplicatii. 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() { ... } }

Crearea unui nou fir de executie nseamn crearea unui nou obiect si se face ntr-un fir activ (care poate fi cel implicit, pentru functia main). Exemplu:
Thread fir1 = new T(); fir1.start();

Firul printe apeleaz de obicei o singur metod a firului fiu (metoda start), pentru lansarea sa n competitie cu celelalte fire; de aceea nici nu se mai creeaz uneori variabile de tipul subclasei. Exemplu:
new T( ).start(); // obiect anonim de tipul T

Apelul metodei start dintr-un fir nu are ca efect imediat apelul metodei run din acel fir, ci introducerea firului respectiv n coada proceselor gata de executie, ntr-o pozitie care depinde de prioritatea firului. Dac nu exist alte fire gata mai importante si dac task-ul Java este activ, atunci firul este activat imediat dup apelul metodei start. Firele din clase ce extind clasa Thread pot avea un constructor cu parametru de tip String, prin care se poate da un nume firului creat. Metodele clasei Thread sunt de dou categorii: metode ale obiectelor si metode statice.

210

Florian Moraru Programare Orientata pe Obiecte Metode care pot fi aplicate oricrui obiect fir de executie: start : Cere lansarea unui fir dintr-un alt fir (printe), cnd este posibil. isAlive : Verifica dac firul pentru care se apeleaz s-a terminat definitiv sau nu. 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 ctre firul curent, 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 urmtoare 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, notifyAll : Firul activ notific (anunt) firele n asteptare c s-a produs evenimentul asteptat prin wait. Metodele wait si notify pot fi folosite numai n secvente sincronizate, conditie care nu poate fi verificat la compilare dar care produce la executie exceptia IllegalMonitorStateException. Metoda run din Thread se redefineste n fiecare fir definit de utilizatori si determin efectul executrii acestui fir. De obicei metoda run contine un ciclu n care se execut anumite operatii (afisare, desenare, operatii cu fisiere etc.) si se apeleaz una din metodele prin care se cedeaz controlul celorlalte fire concurente (yield, sleep, wait etc.). Este posibil ca mai multe fire concurente s execute aceleasi operatii. Exemplu:
class T extends Thread { // clasa pentru obiecte fire de executie public T (String nume) { super(nume); // apel constructor clasa Thread } public void run () { for (int i=1;i<10;i++) { System.out.println (i + " " + getName()); // scrie numele procesului try { sleep (time(i)); } // doarme un timp catch (InterruptedException e) { } } System.out.println (getName()+ " terminat"); // la terminarea procesului } private int time (int i) {return (int) (100*Math.random()); } // creare si lansare procese paralele public static void main (String [] arg) { T p1 = new T("Unu"); T p2 = new T("Doi"); p1.start(); p2.start(); } }

Succesiunea de afisare depinde de secventa de numere aleatoare generate, dar si de alte evenimente din sistem (pentru un sistem de operare multi-tasking care aloc intervale de timp fiecrui task, cum este Microsoft Windows ). Metoda run, asa cum este definit n interfata Runnable (pe care o implementeaz si clasa Thread), nu poate arunca mai departe exceptia InterruptedException si de aceea exceptia trebuie prins si tratat n metoda run.

211

Florian Moraru Programare Orientata pe Obiecte Exceptia InterruptedException apare atunci cnd se ncearc ntreruperea unui fir aflat ntr-o stare de asteptare ca urmare a unui apel wait, sleep sau join; n aceste cazuri nu se produce ntreruperea solicitat. Fiecare fir are asociat o variabil (boolean) cu starea sa de ntrerupere. Metoda main este si ea parte dintr-un fir de executie creat automat la activarea masinii virtuale Java pentru interpretarea unui program. Fiecare fir de executie primeste automat un nume, dac nu primeste explicit un nume la construirea obiectului de un subtip al tipului Thread . Cedarea controlului de ctre un fir se face si implicit, la initierea unei operatii de I/E; terminarea operatiei respective este un eveniment tratat de planificator. Exemplul urmtor pune n evident aceast situatie:
// proces de citire de la tastatura class Fir extends Thread { public void run () { try { while ( System.in.read() > 0 ); } // asteapta tastarea unei clape catch (IOException e) {} } public static void main (String [] arg) { Fir t = new Fir (); t.start(); // activare fir de citire de la tastatura while (t.isAlive()) { System.out.print("."); // afisare punct la fiecare 100 ms try { Thread.sleep(100);} catch (Exception e) { } } } }

Un fir este terminat definitiv fie prin terminarea instructiunilor din metoda run, fie datorit unei exceptii propagate n afara metodei run. Un program cu mai multe fire paralele se termin atunci cnd s-au terminat toate firele (mai pot rmne fire de tip daemon dup terminarea unui program). Fiecare fir are o prioritate asociat (un ntreg ntre 1 si 10), care arat importanta relativ a acelui fir fat de alte fire. La creare un fir primeste prioritatea implicit 5, dar aceas prioritate poate fi modificat prin metoda setPriority.Dac mai multe fire sunt gata de executie, atunci cnd planificatorul preia controlul ( la apelul unei metode sau la terminarea unei operatii de I/E), este ales firul cu prioritate maxim pentru a fi activat. Definirea unei subclase a clasei Thread nu mai este posibil pentru procese paralele din cadrul unui aplet, 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. De aceea a fost creat si o interfat numit Runnable, care contine numai metoda run. Metodele wait, 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, ca metode native si finale. Crearea unui fir de executie folosind interfata Runnable necesit definirea unei clase care implementeaz aceast interfat si definirea metodei run din aceast clas. In aceast clas, care nu este subclas a clasei Thread, se pot folosi metodele statice din clasa Thread : Thread.currentThread si Thread.sleep ( milisec). Lansarea n executie a unui fir se poate face numai prin apelul metodei start din clasa Thread , fie n functia main, fie ntr-o alt metod (dintr-un alt fir, de exemplu). De aceea trebuie creat cte un obiect Thread pe baza fiecrui obiect Runnable, folosind un constructor al clasei Thread care admite un parametru de tip Runnable. Exemplu:
class Proces implements Runnable { // fire cu interfata Runnable public void run () { for (int i=1;i<8;i++) { System.out.println (Thread.currentThread() + + i); try { Thread.sleep(20); } catch (InterruptedException e) {}

212

Florian Moraru Programare Orientata pe Obiecte


} } // creare si lansare procese paralele public static void main (String [] arg) { new Thread ( new Proces ()).start(); new Thread (new Proces ()).start(); } }

Fire de executie cu date comune Programarea cu fire concurente are o serie de particularitti fat de programarea secvential; unele vor fi mentionate aici, iar altele rmn a fi descoperite n lucrrile destinate acestei problematici. Verificarea corectitudinii unui program cu activitti paralele este mai complicat dect n cazul unui program secvential, deoarece rezultatele sale pot s depind si de alt factori care nu au legtur cu algoritmii folositi dar tin de interactiunile voluntare sau involuntare dintre aceste activitti. Altfel spus, rezultatele unor programe pot fi diferite dac sunt rulate n conditii diferite si nu s-a tinut seama la scrierea lor de toate eventualittile. Exemplul clasic este cel al unui sistem de rezervare a locurilor (trenuri, avioane, hoteluri, etc.) 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 solicitrile de rezervare si continutul acestora). Sistemul poate functiona corect ct timp cererile simultane nu se refer la un acelasi loc si cererile ctre acelasi loc sunt secventiale n timp. Dac secventa de operatii pentru rezervarea unui loc poate fi ntrerupt de ctre o alt cerere ctre acelasi loc, atunci poate avea loc o actualizare eronat a datelor si o informare gresit a clientilor. In general, actualizarea bazelor de date din mai multe procese paralele poate conduce la astfel de erori si este prevzut gruparea operatiilor de actualizare a unui articol ntr-o tranzactie, care nu poate fi ntrerupt si care poate fi anulat n caz de eroare la una din aceste operatii. Firele paralele pot evolua un timp independent unele de altele, dar n anumite momente pot s-si dispute resurse comune sau doresc s-si transmit date prin variabile comune. In ambele situatii trebuie reglementat accesul la resursele comune, deoarece n caz contrar pot apare situatii de blocare definitiv a unor procese, sau situatii de actualizare eronat a unor date comune sau situatii de transmitere incorect a unor date ntre fire de executie. Sincronizarea firelor concurente cu resurse comune trebuie s asigure rezultate corecte indiferent de numrul firelor, de ordinea lansrii lor, de timpul necesar fiecrui fir pentru operatiile cu resursa comun sau de alti parametri care nu au legtur cu algoritmul aplicatiei cu fire multiple. Altfel spus, comportarea unui program corect trebuie s fie reproductibil, indiferent de conditiile n care se execut programul respectiv. Pentru a ilustra efectele nesincronizrii a dou fire cu o resurs comun vom considera exemplul a dou fire care actualizeaz aceleasi date (incrementeaz un acelasi contor). Contorul este un obiect dintr-o clas Contor definit de noi, cu dou metode publice: get care citeste valoarea curent a variabilei contor (de tip private) si incr care mreste cu 1 valoarea variabilei contor. 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), iar un alt proces reactivat poate dori s execute aceeasi secvent de incrementare a aceluiasi contor (cazul a dou oficii din localitti diferite de la care se cere simultan rezervarea unui loc la un zbor sau la un tren). 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.
class Contor { private int m; public Contor () { m=0; } public void incr () { // pentru obiecte folosite de fire // incrementare m in trei pasi

213

Florian Moraru Programare Orientata pe Obiecte


int aux; try { aux=m; // pasul 1 Thread.sleep((int) (Math.random()*20)); aux=aux+1; // pasul 2 Thread.sleep((int) (Math.random()*20)); m=aux; // pasul 3 } catch (InterruptedException e) {} } public int get () { return m; } } // pentru obiecte fir de executie class Fir extends Thread { Contor c; public Fir ( Contor c) { this.c=c; } public void run () { for (int i=0;i<5;i++){ try { sleep(10); } catch(InterruptedException e) {} c.incr(); System.out.println (getName()+" "+c.get()); } } } class ExFir { public static void main (String args[]) { Contor c= new Contor(); // contor folosit de cele doua fire Fir f1 = new Fir ( c ); Fir f2 = new Fir ( c ); f1.start(); f2.start(); while ( f1.isAlive() && f2.isAlive() ) // asteapta terminare f1 si f2 try {Thread.sleep(100);} catch (Exception e) {} System.out.println (c.get()); // valoare finala contor } } // citire valoare contor

Fiecare din cele dou fire face cte 5 incrementri ale contorului comun, dar valoarea final a contorului nu este 10, ci este o valoare mai mic ( 5 pentru programul anterior). Rezultatul final depinde n general de timpii de asteptare ai fiecrui fir (ntre operatii), de ordinea lansrii si de alte procese din sistem. Explicatia este simpl: firul f1 citeste n aux o valoare m a contorului, este ntrerupt de firul f2 care citeste si el n variabila sa proprie aux aceeasi valoare m; fiecare proces incrementeaz apoi si scrie napoi n m aceeasi valoare a variabilei sale aux. 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. Un exemplu asemntor foloseste un contor memorat ntr-un fisier disc si incrementat din mai multe fire concurente; n acest caz pierderea controlului de ctre fiecare fir concurent se face automat, ca urmare a initierii unor operatii de citire/scriere pe disc si nu prin cedarea voluntar a controlului:
class Fir extends Thread { String file; public Fir ( String file) { this.file=file; } public void run () { for (int i=0;i<250;i++)

214

Florian Moraru Programare Orientata pe Obiecte


try { RandomAccessFile f = new RandomAccessFile (file,"rw"); int n = f.readInt(); // citire contor f.seek(0); // repozitionare pentru scriere contor modificat f.writeInt (n+1); // scrie contor modificat f.close(); } catch (IOException ex){ ex.printStackTrace();} } }

In functia main au fost lansate 4 fire ca cel prezentat si s-a afisat valoarea din fisier dup terminarea lor; rezultatul a fost mereu altul dar oricum mai mic dect 1000 (4*250). Acest mod de comportare este tipic pentru programe cu fire de executie concurente nesincronizate: rezultate diferite n functie de numrul de operatii, de numrul firelor concurente si de alti parametrii care nu ar trebui s influenteze rezultatele programului. In general, cnd 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. Altfel spus, trebuie serializat accesul la resursa comun pentru procese (fire) concurente sau trebuie asigurat excluderea mutual a firelor n raport cu resursa comun. Sectiunile de cod din fiecare proces care acceseaz resursa comun se numesc sectiuni critice sau regiuni critice. Este important ca instructiunile dintr-o sectiune critic s se execute nentrerupt pentru un anumit proces si pentru un anumit obiect, fr ca un alt proces s poat executa si el aceleasi operatii pentru un acelasi obiect. In Java o sectiune critic este fie o metod, fie un bloc de instructiuni precedate de cuvntul cheie synchronized. Sincronizarea firelor de executie concurente Prima solutie Java pentru sincronizarea firelor de executie a fost utilizarea cuvntului cheie synchronized n declararea unor metode de acces la resurse comune (nainte de tipul metodei) sau ca atribut al unui bloc de instrcutiuni. Sincronizarea se face la nivel de obiect si nu la nivel de functie. Fiecare obiect are asociat un zvor (lock,mutex,monitor); la intrarea ntr-un bloc sincronizat zvorul se nchide si nu permite accesul din alte fire la obiectul protejat, iar la iesirea din blocul sincronizat zvorul este deschis pentru a permite executarea secventei sincronizate din alte fire. In exemplul anterior cu incrementarea unui contor din memorie, acest cuvnt se adaug metodei incr (eventual si metodei get):
public synchronized void incr () { int aux; try { aux=m; // pasul 1 Thread.sleep((int) (Math.random()*20)); aux=aux+1; // pasul 2 Thread.sleep((int) (Math.random()*20)); m=aux; // pasul 3 } catch (InterruptedException e) {} }

Dup ce firul f1 a intrat n executia metodei sincronizate incr pentru un obiect c, nu se permite altui fir s mai execute o metod sincronizat pentru acelasi obiect, iar firul f2 care ncearc acest lucru este pus n asteptare pn la terminarea metodei sincronizate. Putem generaliza solutia de mai sus prin definirea unui obiect sincronizat, astfel ca s o putem aplica si la actualizarea unor date dintr-un fisier din mai multe fire concurente:

215

Florian Moraru Programare Orientata pe Obiecte


class Fir extends Thread { SyncObject obj; // obiect cu metode sincronizate public Fir ( SyncObject obj) { this.obj=obj; } public void run () { for (int i=0;i<250;i++){ obj.update(); // metoda sincronizata a obiectului } } }

Pentru fisierul cu un singur articol obiectul sincronizat va fi:


class SyncObject { String file; // nume fisier public SyncObject(String file){ this.file=file; try { RandomAccessFile f = new RandomAccessFile (file,"rw"); f.writeInt(0); f.close(); } catch(IOException ex){ ex.printStackTrace();} } synchronized void update() { // metoda sincronizata try { RandomAccessFile f = new RandomAccessFile (file,"rw"); int n = f.readInt(); f.seek(0); f.writeInt (n+1); f.close(); } catch (IOException ex) { ex.printStackTrace();} } }

In exemplul anterior este interzis accesul simultan din mai multe fire la orice operatie si la ntregul fisier, ceea ce nu este bine ca timp pentru fisiere mari accesate din multe fire paralele; este preferabil s se sincronizeze numai operatiile de actualizare a unui aceluiasi articol din fire concurente, fiind permis citirea unui articol din diferite fire si actualizarea paralel a unor articole diferite din acelasi fisier de ctre fire diferite. O metod sincronizat poate fi ntrerupt, dar nu poate fi reapelat pentru acelasi obiect dintr-un alt fir concurent. De asemenea, nu este permis apelarea din fire diferite a unor metode sincronizate diferite pentru acelasi obiect. De exemplu, nu este posibil ca dou fire s adauge n paralel elemente la un acelasi vector (addElement). Toate metodele de acces la un vector (din clasa Vector) sunt sincronizate pentru a fi sigure la apelare din fire concurente. Cuvntul synchronized ar trebui adugat oricrei metode care modific un obiect ce poate fi folosit n comun de mai multe fire de executie paralele. Multe din metodele claselor JDK sunt sincronizate (dar nu toate). Dup apelarea unei metode sincronizate pentru un obiect acest obiect este blocat cu un zvor sau lact (lock) si nu mai poate fi apelat o alt metod sincronizat pentru acelasi obiect (dintrun alt proces). Deblocarea obiectului are loc la terminarea executiei metodei sincronizate. Deoarece sincronizarea se face la nivel de obiecte, datele comune proceselor trebuie ncapsulate n obiecte

216

Florian Moraru Programare Orientata pe Obiecte utilizate n comun. La crearea unui proces care foloseste o astfel de resurs comun (variabil, colectie, fisier) se transmite adresa obiectului ce constituie resursa comun. O alt denumire folosit este cea de monitor : fiecare obiect poate avea un monitor asociat (creat efectiv numai atunci cnd este necesar). Un monitor este un zvor initial deschis si care se nchide dup ce un fir a apelat o metod synchronized pentru obiectul respectiv; firul respectiv devine proprietarul monitorului pn la terminarea metodei. Prin monitoare se asigur accesul strict secvential al firelor concurente la operatii critice asociate unui obiect. Un monitor este asociat unui obiect (sau unei clase) si nu unei metode. Dou fire pot executa n paralel secvente din dou metode sincronizate, dar pentru obiecte diferite. De asemenea, un fir poate executa o metod sincronizat si un alt fir o metod nesincronizat pentru un acelasi obiect. Sincronizarea unei metode se poate exprima n dou moduri:
synchronized void f() { }

sau:
void f() { synchronized (this) { } }

Exemplul urmtor arat cnd poate fi necesar sincronizarea unor secvente de instructiuni care opereaz asupra unui obiect, din fire concurente. Dou procese modific un acelasi vector, prin adugarea unui caracter la fiecare sir din vector. Desi fiecare metod din clasa Vector este sincronizat, totusi o secvent de apeluri de metode nu este automat sincronizat, iar firul activ poate pierde controlul ntre apeluri de metode sincronizate:
class Fir extends Thread { Vector c; public void run () { for (int i=0;i<c.size();i++){ synchronized (c) { try { // pentru exceptii generate de sleep String s = (String) (c.elementAt(i)); // scoate sir din vector sleep(10); // firul activ pierde controlul s= i+" "+ s; // modifica sir din pozitia i sleep(10); // firul activ pierde controlul c.setElementAt(s,i); // pune in pozitia i sirul modificat } catch(InterruptedException e) {} } } } public Fir ( Vector c) {this.c=c; } } class ModifyVector { public static void main (String args[]) { String s[]={"unu","doi","trei"}; Vector c= new Vector(Arrays.asList(s)); Fir f1 = new Fir ( c ); Fir f2 = new Fir ( c ); f1.start(); f2.start(); while ( f1.isAlive() && f2.isAlive() ) try {Thread.sleep(100);} catch (Exception e) {} System.out.println (c); // continut final vector } }

O clas este sigur ntr-un context cu fire concurente (thread-safe) dac rezultatele metodelor sale sunt aceleasi indiferent din cte fire paralele sunt apelate aceste metode pentru aceleasi obiecte.

217

Florian Moraru Programare Orientata pe Obiecte Practic, aceast calitate a unei clase se obtine prin adugarea atributului synchronized metodelor care modific date din clas. Metodele sincronizate au performante mai slabe dect metodele nesincronizate si de aceea nu toate clasele JDK sunt thread-safe. Primele clase colectie (Vector, Hashtable) au avut metode sincronizate, dar clasele colectie din Java 2 nu sunt implicit thread-safe din motive de performant. Sunt prevzute ns metode de obtinere a unor clase colectie echivalente sincronizate. Exemplu:
List safelist = Collections.synchronizedList (new ArrayList());

Probleme specifice programrii cu fire concurente In afara erorilor de actualizare pot apare chiar blocri definitive ale unor procese care se asteapt reciproc pentru c fiecare detine o resurs de care are nevoie cellalt proces. Pentru familiarizare cu problematica programrii cu fire concurente n Java, vom ncepe cu cteva situatii mai usor de nteles si de tratat, mentionate n toate lucrrile introductive. 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. Uneori este nevoie ca firul main care a lansat alte fire s afiseze rezultatele ce reprezint efectul lor, dup terminarea lor definitiv. Avem cel putin dou variante ca firul main s astepte terminarea celorlalte fire active: - Folosind metoda isAlive - Folosind metoda join Exemplele urmtoare ilustreaz cele dou posibilitti:
// asteptare terminare cu metoda isAlive public static void main (String [] arg) throws Exception { Fir t1=new Fir(); t1.start(); Fir t2=new Fir(); t2.start(); System.out.println ("Pornire fire"); while (t1.isAlive() && t2.isAlive()) Thread.yield(); // cedare procesor catre t1 si t2 System.out.println ("Terminare fire"); } // asteptare terminare cu metoda join public static void main (String [] arg) throws Exception { Fir t1=new Fir(); t1.start(); Fir t2=new Fir(); t2.start(); System.out.println ("Pornire fire"); t1.join(); t2.join(); System.out.println ("Terminare fire"); }

Un proces poate fi blocat definitiv n cteva situatii, dintre care mentionm : - Procesul asteapt eliberarea unei resurse de ctre un alt proces (prin wait), iar procesul care detine resursa fie nu poate fi activat, fie nu transmite semnal de eliberare a resursei (prin notify sau notifyAll). Acest tip de blocare (numit starvation) poate fi evitat prin programarea corect a proceselor cu resurse comune. Exemplu:
// main lanseaza doua fire de tip Fir class Fir extends Thread { public synchronized void run () { for (int i=1;i<10;i++) { System.out.println (i + " " + getName()); try { wait(); // asteapta semnal de la celalalt fir

218

Florian Moraru Programare Orientata pe Obiecte


} catch (InterruptedException e) { } notify(); // anunta celalalt fir ca poate continua } System.out.println (getName()+ " terminat"); } }

- Un proces P1 asteapt eliberarea unei resurse (asteapt un semnal) de la un alt proces P2, iar P2 asteapt eliberarea unei alte resurse detinute de P1 (asteapt semnal de la P1). 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). Exemplu:
String o1 = "Lock "; String o2 = "Step "; Thread t1 = (new Thread() { public void run() { while (true) { synchronized (o1) { // pune zavor pe o1 yield(); // cedeaza controlul synchronized (o2) { // pune zavor pe o2 System.out.println(o1 + o2); } } } } }); Thread t2 = (new Thread() { public void run() { while (true) { synchronized (o2) { // pune zavor pe o2 yield(); // cedeaza controlul synchronized (o1) { // pune zavor pe o1 System.out.println(o2 + o1); } } } } });

Blocarea reciproc poate apare n cazul proceselor cu resurse comune si nu are solutie dup ce s-a produs; de aceea trebuie luate msuri pentru evitarea blocrii mutuale. O alt situatie este aceea n care un fir trebuie s termine executia unui alt fir; de obicei firul printe termin firul fiu pe care el l-a creat si lansat. In general folosim o variabil comun, la care au acces ambele fire; firul fiu testeaz periodic starea variabilei, iar firul printe modific starea variabilei comune cnd doreste terminarea firului fiu. Avem cel putin dou solutii, 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 activittii de afisare. De observat c trebuie s existe cel putin dou fire de executie, n caz contrar butonul de stop nu poate fi actionat si deci operatorul nu poate transmite semnalul de oprire.
// solutia 1: cu metoda interrupt // tratare evenimente de la butoane public void actionPerformed (ActionEvent ev) { Object source = ev.getSource(); if (source==stop){ // buton de stop System.out.println("STOP");

219

Florian Moraru Programare Orientata pe Obiecte


mark=true; t.interrupt(); } // 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.out.print("."); // afiseaza puncte } System.exit(0); // poate lipsi daca terminarea are loc la inchiderea ferestrei } }; t.start(); } } // 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.getSource(); if (source==stop){ // buton de stop System.out.println("STOP"); mark=true; } // camp text cu nume fisiere if (source==start){ // buton de start Thread t=new Thread() { public void run(){ while (!mark){ System.out.print("."); yield(); } System.exit(0); } }; t.start(); } } // 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: - Secventa de activare a mai multor fire nu este dictat strict de prioritatea lor, iar n cazul firelor cu aceeasi prioritate nu se poate anticipa ordinea de lansare; - Dac sunt mai multe fire care asteapt producerea unui eveniment cu metoda wait, nu se poate preciza care dintre ele este scos din asteptare de executia metodei notify; metoda notifyAll scoate toate firele n asteptare din starea wait si le face gata de executie. - Metodele wait si notify pot fi folosite numai n blocuri sincronizate , dar operatiile de intrareiesire nu trebuie s apar n metode sincrinizate, dac este nevoie ca alte fire s termine fortat aceste operatii de I/E; - Nici metoda yield nu trebuie folosit n metode sincronizate, pentru c poate conduce la blocare. Fire de tip productor-consumator Putem deosebi dou situatii de utilizare a unei resurse comune de ctre dou fire concurente:

220

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

221

Florian Moraru Programare Orientata pe Obiecte


} }

Metodele wait, notify si notifyAll pot fi apelate numai n metode sincronizate; n caz contrar apare o exceptie cauzat de efectul acestor metode: wait elibereaz monitorul asociat obiectului respectiv, pentru a permite altor fire s modifice starea obiectului. De obicei comunicarea ntre procese (fire) se face printr-o coad (un buffer), dar ideea este c operatiile de introducere n buffer si de extragere din buffer trebuie sincronizate pentru executarea lor strict secvential n procese diferite. Clasele PipedWriter (PipedOutputStream) si PipedReader (PipedInputStream) din pachetul java.io permit comunicarea simpl de date ntre procese de tip productor si consumator printr-un flux de date. Operatiile de acces la date sunt sincronizate iar gestiunea zonei comune de date (o coad) nu cade n sarcina programatorului. 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, sau se foloseste metoda connect.
// Producator class Producer implements Runnable { private PipedWriter buffer; public Producer(PipedWriter b) { buffer = b; } public void run() { for (char c='0'; c<='z'; c++) { try {buffer.write(c);} catch(IOException e){} // pune caracter in flux try { Thread.sleep((int)(100*Math.random()));} catch (Exception e) { } } } } // Consumator class Consumer implements Runnable { private PipedReader buffer; public Consumer(PipedReader b) { buffer = b; } public void run() { char c; try { Thread.sleep((int)(100*Math.random()));} catch (Exception e) { } try { while ( (c= (char)buffer.read()) > 0) // citire caracter din flux System.out.print(c +" "); // si afisare pe ecran } catch (IOException e) {} } } // Program de test class Pipes { public static void main ( String args[])throws IOException { PipedWriter wr = new PipedWriter(); // canal de scriere PipedReader rd = new PipedReader(wr); // conectare citire cu scriere // sau rd= new PipedReader() si rd.connect(wr) Thread t1 = new Thread ( new Producer (wr)); Thread t2 = new Thread ( new Consumer (rd)); t1.start(); t2.start(); } }

Incepnd cu Java 5 s-au introdus cteva interfete si clase pentru cozi sincronizate, care permit comunicarea mai simpl ntre procese de tip productor-consumator: interfata BlockingQueue si clasele ArrayBlockingQueue si LinkedBlockingQueue. Exemplu de utilizare:

222

Florian Moraru Programare Orientata pe Obiecte


import java.util.concurrent.*; class Producer implements Runnable { private final BlockingQueue queue; private int x; public Producer(BlockingQueue q) { queue = q; x=0; } public void run() { try { for (int i=0;i<9;i++) queue.put(produce()); } catch (InterruptedException ex) { } } Object produce() { try { Thread.sleep(150); } catch (InterruptedException e) { } System.out.println( Thread.currentThread().getName()); x++; Thread.currentThread().yield(); return x; } } class Consumer implements Runnable { private final BlockingQueue queue; private int x; public Consumer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { consume(x=(Integer)queue.take()); } } catch (InterruptedException ex) { } } void consume(Object x) { System.out.println(x+ " "+ Thread.currentThread().getName()); Thread.currentThread().yield(); try { Thread.sleep(100); } catch (InterruptedException e) { } } } class Setup { public static void main(String a[]) { BlockingQueue q = new ArrayBlockingQueue(100); Thread p = new Thread(new Producer(q)); Thread c = new Thread (new Consumer(q)); p.start(); c.start(); while ( p.isAlive() && c.isAlive()){ System.out.println(q); // afisare continut coada try { Thread.sleep(10); } catch (InterruptedException e) { } } } }

Cu programele anterioare se poate experimenta, modificnd timpii folositi n metoda sleep

223

Florian Moraru Programare Orientata pe Obiecte din procesele productor si consumator si observnd continutul cozii de mesaje. Fire de executie n aplicatii Swing Aplicatiile Swing ridic dou probleme din punct de vedere al firelor de executie: - Metodele claselor Swing nu sunt sincronizate (nu sunt asigurate la apeluri din fire de executie concurente), iar tratarea evenimentelor Swing trebuie s se fac ntr-un acelasi fir de executie, adic ele trebuie tratate numai n ordinea n care s-au produs aceste evenimente; - Interfata grafic ar putea ngheta (nu mai rspunde 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). Astfel de activitti trebuie delegate altor fire de executie. Pentru prima problem exist o solutie simpl, 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, prin intermediul metodei invokeLater:
public static void main (String args[]) { SwingUtilities.invokeLater (new Runnable() { public void run() { new MyFrame (); } }); }

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. Pentru a ilustra problema nghetrii interfetei grafice vom considera cazul operatiei Search din Windows Explorer: dup alegerea unor parametri de cutare se lanseaz operatia de cutare (printr-un buton Search), operatie care poate dura foarte mult pentru c poate implica si citirea continutului unui numr mare de fisiere n care se caut un sir dat. Operatorul are posibilitatea s termine fortat procesul de cutare prin actionarea unui buton Stop. Pentru ca butonul Stop s poat fi actionat si s produc terminarea cutrii este necesar ca actiunea de cutare s se produc ntr-un alt fir de executie dect firul evenimentelor Swing (EventThread). Firul n care se trateaz evenimentul de la butonul de oprire cere terminarea firului creat si lansat de butonul Start. Exemplul urmtor foloseste un singur fir de executie (n care se trateaz evenimentele Swing), iar operatia de cutare 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; // sir continut de fisierele cautate private JButton stop; // pentru oprire cautare private JTextArea area; // pentru afisare nume fisiere gasite private boolean end=false; // variabila ptr comunicare intre activitati // initializari public Search () { area = new JTextArea (30,50); add(new JScrollPane(area), "Center"); text = new JTextField(10); add (text,"North"); text.addActionListener(this); stop = new JButton("Stop"); add(stop,"South"); stop.addActionListener(this); setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); pack(); show();

224

Florian Moraru Programare Orientata pe Obiecte


} // ascultatori la evenimente public void actionPerformed (ActionEvent ev) { Vector v= new Vector(); Object source = ev.getSource(); if (source==stop) // buton de stop end=true; // camp text cu nume fisiere if (source==text) { // nume fisiere cautate findAll (new File("C:/"),text.getText(),v); area.append(v.toString()); // afisare simpla continut vector } } // functie de cautare void findAll (File dir,String txt,Vector v) { if (end) return; File[] files=dir.listFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.isDirectory()) findAll (file,txt,v); // cautare recursiva in subdirectoare else if ( file.getName().contains(txt)) v.add(file.getName()+"\n"); // fisier gasit else area.append(". \n"); // fisier care nu satisface conditia } } public static void main (String args[]) { SwingUtilities.invokeLater(new Runnable() { public void run() { new Search(); } }); } }

Solutia problemei este crearea si lansarea unui alt fir ca urmare a evenimentului de cmp text (cnd se afl numele sirului cutat, eventual si numele unui director unde se caut).

225

You might also like