Documente Academic
Documente Profesional
Documente Cultură
Acest curs nu poate fi reprodus sub nici o forma in alte documente tiparite sau electronice fara incuviintarea autorului (Cristian Frasinaru, e-mail: acf@infoiasi.ro).
1. Introducere in tehnologia Java 2. Obiecte si clase 3. Exceptii Fluxuri (Intrari / Iesiri) 4. Interfete Pachete Organizarea fisierelor .java si .class Serializarea obiectelor Colectii 5. Interfata grafica 6. Desenarea si tiparirea Swing 7. Fire de executie 8. Programarea n retea 9. Applet-uri 10. Lucrul cu baze de date pe platforma Java 11.Lucrul dinamic cu clase Java Web Start 12.Introducere in Java ME / EE
Ce este Java ? Crearea unei aplicatii simple Crearea unui applet simplu Structura lexicala a limbajului Java o Setul de caractere o Cuvinte cheie o Identificatori o Literali o Separatori o Operatori o Comentarii Tipuri de date 1
Variabile Controlul executiei Vectori Siruri de caractere Folosirea argumentelor de la linia de comanda
Ce este Java ?
Limbajul de programare Java
Java este un limbaj de programare de nivel nalt, dezvoltat de JavaSoft, companie n cadrul firmei Sun Microsystems. Dintre caracteristicile principale ale limbajului amintim: simplitate, elimina suprancarcarea operatorilor, mostenirea multipla si toate "facilitatile" ce pot provoca scrierea unui cod confuz. robustete, elimina sursele frecvente de erori ce apar in programare prin eliminarea pointerilor, administrarea automata a memoriei si eliminarea fisurilor de memorie printr-o procedura de colectare a 'gunoiului' care ruleaza n fundal. Un program Java care a trecut de compilare are proprietatea ca la executia sa nu "crapa sistemul". complet orientat pe obiecte - elimina complet stilul de programare procedural usurinta in ceea ce priveste programarea in retea securitate, este cel mai sigur limbaj de programare disponibil n acest moment, asigurnd mecanisme stricte de securitate a programelor concretizate prin: verificarea dinamica a codului pentru detectarea secventelor periculoase, impunerea unor reguli stricte pentru rularea programelor lansate pe calculatoare aflate la distanta, etc este neutru din punct de vedere arhitectural portabililtate, cu alte cuvinte Java este un limbaj independent de platforma de lucru, aceeasi aplicatie ruland, fara nici o modificare, pe sisteme diferite cum ar fi Windows, UNIX sau Macintosh, lucru care aduce economii substantiale firmelor care dezvolta aplicatii pentru Internet. compilat si interpretat asigura o performanta ridicata a codului de octeti permite programarea cu fire de executie (multitheaded) dinamicitate este modelat dupa C si C++, trecerea de la C, C++ la Java facndu-se foarte usor. permite creearea unor documente Web mbunatatite cu animatie si multimedia.
interpretate : instructiunile sunt citite linie cu linie de un program numit interpretor si traduse n instructiuni masina; avantaj : simplitate; dezavantaje : viteza de executie redusa compilate : codul sursa al programelor este transformat de compilator ntr-un cod ce poate fi executat direct de procesor; avantaj : executie rapida; dezavantaj : lipsa portabilitatii, codul compilat ntr-un format de nivel scazut nu poate fi rulat dect pe platforma pe care a fost compilat.
Programele Java sunt fi att interpretate ct si compilate. Codul de octeti este diferit de codul masina. Codul masina este reprezentat de o succesiune de 0 si 1; codurile de octeti sunt seturi de instructiuni care seamana cu codul scris n limbaj de asamblare. Codul masina este executat direct de catre procesor si poate fi folosit numai pe platforma pe care a fost creat; codul de octeti este interpretat de mediul Java si de aceea poate fi rulat pe orice platforma care foloseste mediul de executie Java.
Cod sursa Java -> (compilare) -> Cod de octeti -> (interpretare)
Toate aplicatiile Java contin o clasa principala(primara) n care trebuie sa se gaseasca metoda main. Clasele aplicatiei se pot gasi fie ntr-un singur fisier, fie n mai multe. 7. Salvarea fisierelor sursa Se va face n fisiere cu extensia .java Fiserul care contine codul sursa al clasei primare trebuie sa aiba acelasi nume cu clasa primara a aplicatiei (clasa care contine metoda main) Obs: Java face distinctie ntre literele mari si mici.
C:/java/FirstApp.java
8. Compilarea aplicatiei
Se foloseste compilatorul Java, javac Apelul compilatorului se face pentru fisierul ce contine clasa principala a aplicatiei. Compilatorul creeaza cte un fisier separat pentru fiecare clasa a programului; acestea au extensia .class si sunt plasate n acelasi director cu fisierele sursa.
javac FirstApp.java -> FirstApp.class
9. Rularea aplicatiei Se face cu interpretorul java, apelat pentru unitatea de compilare corespunzatoare clasei principale, fiind nsa omisa extensia .class asociata acesteia.
java FirstApp
Rularea unei aplicatii care nu foloseate interfata grafica, se va face ntr-o fereastra sistem.
16. Rularea applet-ului Applet-urile nu ruleaza independent. Ele pot fi rulate doar prin intermediul unui browser : Internet Explorer, Netscape sau printr-un program special cum ar fi appletviewer-ul din setul JDK. 1. Creearea unui fisier HTML pentru miniaplicatie (exemplu.html)
2. 3. 4. 5. 6. 7. <html> <head> </head> <body> height=400> </body> </html> <applet code=FirstApplet.class width=400 </applet> <title>First Java Applet</title>
8. 9. 10.
Ex:
\u0030 - \u0039 : cifre ISO-Latin 0 - 9 \u0660 - \u0669 : cifre arabic-indic 0 - 9 \u4e00 - \u9fff : litere din alfabetul Han (Chinez, Japonez, Coreean)
Cuvinte cheie
Cuvintele rezervate n Java sunt cele din C++, cu cteva exceptii
Identificatorii
Sunt secvente nelimitate de litere si cifre Unicode, ncepand cu o litera. Identificatorii nu au voie sa fie identici cu cuvintele rezervate.
Literalii (constantele)
Literalii pot fi de urmatoarele tipuri literali ntregi Sunt acceptate 3 baze de numeratie : baza 10, baza 16 (ncep cu caracterele 0x) si baza 8 (ncep cu cifra 0) si pot fi de doua tipuri: o normali, (se reprez pe 4 octeti - 32 biti) o lungi (8 octeti - 64 biti) : se termina cu caracterul L (sau l). literali flotanti Pentru ca un literal sa fie considerat flotant el trebuie sa aiba cel putin o zecimala dupa virgula, sa fie n notatie exponentiala sau sa aiba sufixul F sau f pentru valorile normale (reprez. pe 32 biti), respectiv D sau d pentru valorile duble (reprez. pe 64 biti) literali logici true : valoarea booleana de adevar false : valoarea booleana de fals Atentie: spre deosebire de C++, literalii ntregi 1 si 0 nu mai au rolul de adevarat si false literali caracter Un literal de tip caracter este utilizat pentru a exprima caracterele codului Unicode. Reprezentarea se face fie folosind o litera, fie o secventa escape scrisa ntre apostrofuri. Secventele escape permit reprezentarea caracterelor care nu au reprezentare grafica si reprezentarea unor caractere speciale precum backslash, caracterul apostrof, etc. Secvente escape predefinite n Java: Cod Secventa Escape Caracter \u0008 '\b' \u0009 '\t' \u000a '\n' \u000c '\f' Backspace(BS) Tab orizontal (HT) Linie noua - linefeed (LF) Pagina noua - formfeed (FF)
\u005c '\\' Backslash literali siruri de caractere Un literal sir de caractere este format din zero sau mai multe caractere ntre ghilimele. Caracterele care formeaza sirul de caractere pot fi caractere grafice sau secvente escape ca cele definite la literalii caracter. Daca sirul este prea lung el poate fi scris ca o concatenare de subsiruri de dimensiune mai mica. Concatenarea sirurilor se face cu operatorul + ("Ana " + " are " + " mere "). Sirul vid este "". Dupa cum vom vedea, orice sir este de fapt o instanta a clasei String, definita n pachetul java.lang.
Separatori
Un separator este un caracter care indica sfrsitul unei unitati lexicale si nceputul alteia. In Java separatorii sunt urmatorii: ( ) { } [ ] ; , . Instructiunile unui program se separa cu punct si virgula
Operatori
atribuirea: = operatori matematici: +, -, *, /, % Este permisa notatia prescurtata de forma lval op= rval (ex: n += 2) Exista operatorii pentru autoincrementare si autodecrementare (post si pre)
ex: x++, ++x, n--, --n
Observatie: evaluarea expresiilor logice se face prin metoda scurtcircuitului (evaluarea se opreste n momentul n care valoarea de adevar a expresiei este sigur determinata) operatori logici: &&(and), ||(or), !(not) operatori relationali: <, <=, >, <=, ==, != operatori pe biti: & (and), |(or), ^(xor), ~(not) operatori de translatie <<, >>, >>> (shift la dreapta fara semn) operatorul if-else: expresie_logica ? val_pt_true : val_pt_false ; operatorul , (virgula) folosit pentru evaluarea secventiala a operatiilor int x=0,
y=1, z=2;
Comentarii
In Java exista trei feluri de comentarii:
Comentarii pe mai multe linii, nchise ntre /* si */. Comentarii pe mai multe linii care tin de documentatie, nchise ntre /** si */. Textul dintre cele dou secvente este automat mutat n documentatia aplicatiei de catre generatorul automat de documentatie javadoc. comentarii pe o singura linie care ncep cu //.
Observatii: 1. nu putem sa scriem comentarii n interiorul altor comentarii. 2. nu putem introduce comentarii n interiorul literalilor caracter sau sir de caractere. 3. secventele /* si */ pot sa apara pe o linie dupa secventa // dar si pierd semnificatia; la fel se ntmpl cu secventa // n comentarii care ncep cu /* sau /**.
Tipuri de date
In Java tipurile de date se mpart n doua categorii: tipuri primitive de date si tipuri referinta. Java porneste de la premiza ca "orice este un obiect". Asadar tipurile de date ar trebui sa fie de fapt definite de clase si toate variabilele ar trebui sa memoreze de fapt instante ale acestor clase (obiecte). In principiu acest lucru este adevarat, nsa, pentru usurinta programarii, mai exista si asa numitele tipurile primitive de date, care sunt cele uzuale :
aritmetice o ntregi: byte (1 octet), short (2), int (4), long (8) o reale: float (4 octeti), double (8) caracter : char (2 octeti) logic : boolean (true si false)
In alte limbaje formatul si dimensiunea tipurilor primitive de date folosite ntr-un program pot depinde de platforma pe care ruleaza programul. In Java acest lucru nu mai este valabil, orice dependenta de o anumita platforma specifica fiind eliminata. Vectorii, clasele si interfetele sunt tipuri referinta. Valoarea unei variabile de acest tip este, n contrast cu tipurile primitive, o referinta (adresa de memorie) catre valoarea sau multimea de valori reprezentata de variabila respectiva. Exista trei tipuri de date C care nu sunt suportate de limbajul Java. Acestea sunt: pointer, struct si union. Pointerii au fost eliminati din cauza ca erau o sursa 8
constanta de erori, locul lor fiind luat de tipul referinta, iar struct si union nu si mai au rostul att timp ct tipurile compuse de date sunt formate n Java prin intermediul claselor.
Variabile
Variabilele pot avea ca tip fie un tip primitiv de data, fie o referinta la un obiect. Declararea variabilelor Tip nume_variabila Initializarea variabilelor Tip nume_variabila = valoare Declararea constantelor final Tip nume_variabila Conventia de notare a variabilelor in Java este data de urmatoarele criterii: 1. variabilele finale (constante) se scriu cu majuscule 2. variabilele normale se scriu astfel : prima litera cu litera mica, daca numele variabilei este format din mai multi atomi lexicali, atunci primele litere ale celorlalti atomi se scriu cu majuscule, de exemplu:
3. 4. 5. 6. final double PI = 3.14; int valoare = 100; long numarElemente = 12345678L; String bauturaMeaPreferata = "apa";
In functie de locul n care sunt declarate variabile se mpart n urmatoatele categorii: 1. Variabile membre, declarate n interiorul unei clase, vizibile pentru toate metodele clasei respective si pentru alte clase n functie de nivelul lor de acces (vezi "Declararea variabilelor membre") 2. Variabile locale, declarate ntr-o metoda sau ntr-un bloc de cod, vizibile doar n metoda/blocul respectiv 3. Parametri metodelor, vizibili doar n metoda respectiva 4. Parametrii de la tratarea exceptiilor
Obs: variabilele declarate ntr-un for pentru controlul ciclului, ramn locale corpului ciclului.
for(int i=0; i<100; i++) { } int i; //ok n Java, eroare n C++
Controlul executiei
Instructiunile Java pentru controlul executiei sunt asemanatoare celor din C. Instructiuni de decizie Instructiuni de salt Alte instructiuni Instructiuni de decizie
if-else switch-case if (exp_booleana) { /*...*/} if (exp_booleana) { /*...*/} else { /*...*/} if-else, switch-case for, while, do-while
10
switch (variabila) { case val1 : /* ... */ break; case val2 : /* ... */ break; /*...*/ default : /*...*/ }
Instructiuni de salt
for(initializare; exp_booleana; pas_iteratie) Ex: for(int i=0, j=100 ; i<100 && j>0; i++, j--) {/* ... /*}
for
Obs: att la initializare ct si n pasul de iteratie pot fi mai multe instructiuni despartite prin virgula.
while while (exp_booleana) { /*...*/ } do { do-while /*...*/ } while (exp_booleana) ;
"Tratarea exceptiilor")
Alte instructiuni
break continue return label:
Atentie: In Java nu exista goto. Se pot nsa defini etichete de forma nume_eticheta:, folosite n expresii de genul: break nume_eticheata sau continue nume_eticheta
Exemplu: i=0; eticheta: while (i<10) { System.out.println("i="+i); j=0; while (j<10) { j++; if (j==5) continue eticheta; if (j==7) break eticheta; System.out.println("j="+j); } i++; }
11
Vectori
Crearea unui vector 1. Declararea vectorului
2. Tip[] numeVector; sau 3. Tip numeVector[]; 4. Ex: int[] intregi; 5. String adrese[];
6. Instantierea Se realizeaza prin intermediul operatorului new si are ca efect alocarea memoriei pentru vector, mai precis specificarea numarului maxim de elemente pe care l va avea vectorul;
7. numeVector = new Tip[dimensiune]; 8. Ex: v = new int[10]; //se aloca spatiu pentru 10 intregi 9. adrese = new String[100];
10. Initializarea (optional) Dupa declararea unui vector, acesta poate fi initializat, adica elementele sale pot primi niste valori initiale, evident daca este cazul pentru asa ceva. In acest caz instantierea lipseste, alocarea memoriei facndu-se automat n functie de numarul de elemente cu care se initializeaza vectorul.
11. Ex: 12. String culori[] = {"Rosu", "Galben", "Verde"}; int []factorial = {1, 1, 2, 6, 24, 120};
Observatii: Primul indice al unui vector este 0, deci pozitiile unui vector cu n elemente vor fi cuprinse ntre 0 si n-1 Nu sunt permise constructii de genul Tip numeVector[dimensiune], alocarea memoriei facndu-se doar prin intermediul opearatorului new.
Ex: int v[10]; //ilegal int v[] = new int[10]; //corect
Dimensiunea unui vector Cu ajutorul cuvntului cheie length se poate afla dimensiunea unui vector.
12
int []a = new int[5]; a.length are valoarea 5 int m = new int[5][10]; m[0].length are valoarea 10
Copierea vectorilor Copierea unui vector n alt vector se face cu ajutorul metodei System.arraycopy:
int x[] = {1, 2, 3, 4}; int y[] = new int[4]; System.arraycopy(x,0,y,0,x.length);
Vectori cu dimensiune variabila Implementarea vectorilor cu numar variabil de elemente este oferita de clasa Vector din pachetul java.util. Un obiect de tip Vector contine numai elemente de tip Object.
Siruri de caractere
In Java, un sir de caractere poate fi reprezentat printr-un vector format din elemente de tip char, un obiect de tip String sau un obiect de tip StringBuffer. Declararea unui sir Daca un sir de caractere este constant atunci el va fi declarat de tipul String, altfel va fi declarat cu StringBuffer. (vezi "Clasele String, StringBuffer") Exemple echivalente de declarare a unui sir:
String str = "abc"; char data[] = {'a', 'b', 'c'}; String str = new String(data); String str = new String("abc");
In Java, operatorul de concatenare + este extrem de flexibil n sensul ca permite concatenarea sirurilor cu obiecte de orice tip care au o reprezentare de tip sir de caractere.
System.out.print("Vectorul v are" + v.length + " elemente")
optiuni legate de functionarea aplicatiei sau sa furnizeze anumite date initiale programului. Atentie: Programele care folosesc argumente de la linia de comanda nu sunt 100% pure Java deoarece unele sisteme de operare cum ar fi Mac OS nu au n mod normal linie de comanda. Argumentele de la linia de comanda sunt introduse la lansarea unei aplicatii, fiind specificate dupa numele aplicatiei si separate prin spatiu. De exemplu, sa presupunem ca aplicatia Sort ordoneaza lexicografic liniile unui fisier si primeste ca argument numele fisierului pe care sa l sorteze. Pentru a ordona fisierul "persoane.txt" lansarea aplicatiei se va face astfel:
java Sort persoane.txt
Asadar, formatul general pentru lansarea unei aplicatii care primeste argumente de la linia de comanda este:
java NumeAplicatie [arg1 arg2 . . . argn]
In cazul n care sunt mai multe, argumentele trebuie separate prin spatii iar daca unul dintre argumente contine spatii, atunci el trebuie pus ntre ghilimele. Evident, o aplicatie poate sa nu primeasca nici un argument sau poate sa ignore argumentele primite de la linia de comanda. In momentul lansarii unei aplicatii interpretorul parcurge linia de comanda cu care a fost lansata aplicatia si, n cazul n care exista, transmite aplicatiei argumentele specificate sub forma unui vector de siruri. Acesta este primit de aplicatie ca parametru al metodei main. Reamintim ca formatul metodei main din clasa principala este:
public static void main ( String args[])
Vectorul primit ca parametru de metoda main va contine toate argumentele transmise programului de la linia de comanda. In cazul apelului java Sort persoane.txt vectorul args va contine un singur element args[0]="persoane.txt". Numarul argumentelor primite de un program este dat deci de dimensiunea vectorului args si acesta poate fi aflat prin intermediul atributului length al vectorilor:
numarArgumente = args.length ;
Spre deosebire ce C/C++ vectorul primit de metoda main nu contine pe prima pozitie numele aplicatiei, ntruct n Java numele aplicatiei este numele clasei principale, adica a clasei n care se gaseste metoda main. Exemplu: afisarea argumentelor primite la linia de comanda
public class Echo { public static void main (String[] args) { for (int i = 0; i < args.length; i++) System.out.println(args[i]); } } Un apel de genul java Echo Drink Hot Java va produce urmatorul Drink Hot Java
rezultat:
(aplicatia Echo a primit 3 argumente). Una apel de genul java Echo "Drink Hot Java" va produce urmatorul rezultat:
Drink Hot Java
14
Argumente numerice la linia de comanda Argumentele de la linia de comanda sunt primite sub forma unui vector de siruri (obiecte de tip String). In cazul n care unele dintre acestea reprezinta valori numerice ele vor trebui convertite din siruri n numere. Acest lucru se realizeaza cu metode de tipul parseXXX aflate n clasa corespunzatoare tipului n care vrem sa facem conversia: Integer, Float, Double, etc. Sa consideram, de exemplu, ca aplicatia Power ridica un numar real la o putere ntreaga, argumentele fiind trimise de la linia de comanda:
java Power "12.1" "3" //ridica 12.1 la puterea 3
double numar; int putere; numar = Double.parseDouble(args[0]); putere = Integer.parseInt(args[1]); Metodele de tipul parseXXX pot produce exceptii (erori) de tipul NumberFormatException n cazul n care sirul primit ca parametru
nu reprezinta un
Ciclul de viata al unui obiect o Crearea obiectelor o Folosirea obiectelor o Distrugerea obiectelor Crearea claselor o Declararea claselor o Corpul unei clase o Constructorii unei clase o Declararea variabilelor membre o Implementarea metodelor o Specificatori de acces pentru membrii unei clase o Membri de instanta si membri de clasa Clase imbricate Clase si metode abstracte Clasa Object
15
Crearea obiectelor
In Java obiectele sunt create prin instantierea unei clase, cu alte cuvinte prin crearea unei instante a unei clase.Crearea unui obiect presupune trei lucruri: 1. Declararea obiectului
2. 3. NumeClasa numeObiect; Ex: Rectangle patrat;
4. Instantierea Se realizeaza prin intermediul operatorului new si are ca efect crearea efectiva a obiectului cu alocarea spatiului de memorie corespunzator.
5. patrat = new Rectangle();
6. Initializarea Se realizeaza prin intermediul constructorilor clasei respective. Rectangle() este un apel catre constructorul clasei Rectangle care este responsabil cu initializarea obiectului. Initializarea se poate face si cu anumiti parametri, cu conditia sa existe un constructor al clasei respective care sa accepte parametrii respectivi;
7. patrat = new Rectangle(0, 0, 100, 200);
Fiecare clasa are un set de constructori care se ocupa cu initializare obiectelor nou create. De exemplu clasa Rectangle are urmatorii constructori:
public public public public Rectangle(Point p) Rectangle(int w, int h) Rectangle(Point p, int w, int h) Rectangle()
(vezi "Constructorii unei clase") Declararea, instantierea si initializarea obiectului pot aparea pe aceesi linie (cazul cel mai uzual):
Rectangle patrat = new Rectangle(0, 0, 100, 200);
Este posibila si crearea unor obiecte anonime care servesc doar pentru initializarea altor obiecte, caz n care etapa de declarare a obiectului nu mai este prezenta:
patrat.origin = new Point(0, 0); //se creeaza un obiect anonim de tip Point care, //dupa executarea atribuirii este automat distrus de catre
sistem
Atentie Spatiul de memorie nu este pre-alocat Declararea unui obiect nu implica alocarea de spatiu de memorie pentru acel obiect.
Rectangle patrat; patrat.x = 10; //EROARE!
Folosirea obiectelor
16
Odata un obiect creat, el poate fi folosit n urmatoarele sensuri: aflarea unor informatii despre obiect, schimbarea starii sale sau executarea unor actiuni. Aceste lucruri se realizeaza prin: aflarea sau schimbarea valorilor variabilelor sale apelarea metodelor sale. Referirea valorii unei variabile se face prin obiect.variabila De exemplu clasa Rectangle are variabilele publice x, y, width, height, origin. Aflarea valorilor acestor variabile sau schimbarea lor se face prin constructii de genul:
Rectangle patrat = new Rectangle(0, 0, 100, 200); System.out.println(patrat.width); //afiseaza 100 patrat.x = 10; patrat.y = 20; //schimba originea patrat.origin = new Point(10, 20); //schimba originea
Obs: Accesul la variabilele unui obiect se face n conformitate cu drepturile de acces pe care le ofera variabilele respective celorlalte clase. (vezi "Specificatori de acces pentru membrii unei clase") Apelul unei metode se face prin obiect.metoda([parametri])
Rectangle patrat = new Rectangle(0, 0, 100, 200); patrat.setLocation(10, 20); //schimba originea patrat.setSize(200, 300); //schimba dimensiunea
Se observa ca valorile variabilelor pot fi modificate indirect prin intermediul metodelor. Programarea orientata obiect descurajeaza folosirea directa a variabilelor unui obiect deoarece acesta poate fi adus n stari inconsistente (ireale). In schimb, pentru fiecare variabila a obiectului trebui sa existe metode care sa permita aflarea/schimbarea valorilor variabilelor sale.
patrat.width = -100; //stare inconsistenta patrat.setSize(-100, -200) //metoda setSize poate sa testeze daca valorile sunt corecte si //valideze sau nu schimbarea lor
sa
Distrugerea obiectelor
Multe limbaje de programare impun ca programatorul sa tina evidenta obiectelor create si sa le distruga n mod explicit atunci cnd nu mai este nevoie de ele, cu alte cuvinte sa administreze singur memoria ocupata de obiectele sale. Practica a demonstart ca aceasta tehnica este una din principala furnizoare de erori ce duc la functionarea defectuoasa a programelor. In Java programatorul nu mai are responsabilitatea distrugerii obiectelor sale ntruct, n momentul rularii unui program, simultan cu interpretorul Java ruleaza si un proces care se ocupa cu distrugerea obiectelor care nu mai sunt folosite. Acest proces pus la dispozitie de platforma Java de lucru se numeste garbage collector (colector de gunoi), prescurtat gc. Un obiect este elimnat din memorie de procesul de colectare atunci cnd nu mai exista nici o referinta la acesta. Referintele (care sun de fapt variabile) sunt distruse n mod :
17
natural, atunci cnd variabila respectiva iese din domeniul sau, de exemplu la terminarea unei metode explicit, daca atribuim variabilei respective valoare null.
Finalizare
Inainte ca un obiect sa fie eliminat din memorie, procesul gc da acelui obiect posibilitatea "sa curete dupa el", apelnd metoda de finalizare a obiectului respectiv. Uzual, n timpul finalizarii un obiect si nchide fisierele si socket-urile folosite, distruge referintele catre alte obiecte (pentru a usura sarcina colectorului de gunoaie), etc. Codul pentru finalizare unui obiect trebuie scris ntr-o metoda speciala numita finalize a clasei ce descrie obiectul respectiv. De exemplu, clasa Rectangle are urmatorul cod pentru finalizarea obiectelor sale:
class Rectangle { ... protected void finalize() throws Throwable { origin = null; super.finalize(); } }
Crearea claselor
Declararea claselor
Declararea unei clase
[public][abstract][final] class NumeClasa [extends NumeSuperclasa] [implements Interfata1 [, Interfata2 ... ]] { //corpul clasei }
Asadar, prima parte a declaratie o ocupa modificatorii clasei. Acestia sunt: Modificatorii unei clase
public
Implicit, o clasa poate fi folosita doar de clasele aflate n acelasi pachet cu clasa
18
respectiva (daca nu se specifica un anume pachet, toate clasele din directorul curent sunt considerate a fi n acelasi pachet). O class declarata cu public poate fi folosita de orice clasa, indiferent de pachetul n care se gaseste.
abstract
Declara o clasa abstracta (sablon). O clasa abstracta nu poate fi instantiata, fiind folosita doar pentru a crea un model comun pentru o serie de subclase; (vezi "Clase si metode abstracte") Declara ca respectiva clasa nu poate avea subclase. Declarare claselor finale are doua scopuri: securitate : unele metode pot astepta ca parametru un obiect al unei anumite clase si nu al unei subclasei, dar tipul exact al unui obiect nu poate fi aflat cu exactitate dect n momentul executiei; n felul acesta nu s-ar mai putea realiza obiectivul limbajului Java ca un program care a trecut compilarea nu mai este susceptibil de nici o eroare (nu "crapa sistemul").
final
programare n spririt orientat-obiect : "O clasa perfecta nu trebuie sa mai aiba subclase"
Dupa numele clasei putem specifica, daca este cazul, faptul ca respectiva clasa este subclasa a unei alte clase cu numele NumeSuperclasa sau/si ca implementeaza una sau mai multe interfete, ale caror nume trebuie separate prin virgula. Se observa ca, spre deosebire de C++, Java permite doar mostenirea simpla, asadar o clasa poate avea un singur un singur parinte (superclasa). Evident o clasa poate avea oricti mostenitori (subclase). Extinderea unei clase se realizeaza deci astfel:
class B extends A {...} //A este superclasa clasei B
19
} } A::metoda1() { //implementare }
Obs: variabilele unei clase pot avea acelasi nume cu metodele clasei.
O clasa poate avea unul sau mai multi constructori care trebuie nsa sa difere prin lista de argumente primite. In felul acesta sunt permise diverse tipuri de initializari ale obiectului la crearea sa, n functie de numarul parametrilor cu care este apelat constructorul.
class Dreptunghi { double x, y, w, h; Dreptunghi(double x0, double y0, double wo, double h0) { x=x0; y=y0; w=w0; h=h0; } Dreptunghi(double x0, double y0) { this(x0, y0, 0, 0); } Dreptunghi() { //initializare implicita this(0, 0, 100, 100); }
Constructorii sunt apelati automat la instantierea unui obiect. In cazul n care dorim sa apelam explicit constructorul unei clase folosim metoda this( argumente ), care apeleaza constructorul corespunzator (ca argumente) al clasei respective. Aceasta metoda este folosita atunci cnd sunt implementati mai multi constructori pentru o clasa pentru a nu repeta secventele de cod scrise la constructorii cu mai putine argumente. Dintr-o subclasa putem apela si constructorii superclasei cu metoda super( argumente ).
class Patrat extends Dreptunghi { double size; Patrat(double x0, double y0, double s0) { super(x0, y0, s0, s0); //se apeleaza constructorul superclasei size = s0; } }
20
Constructorii sunt apelati automat la instantierea unui obiect. In cazul n care scrieti o clasa care nu are declarat nici un constructor, sistemul i creeaza automat un constructor implicit care nu primeste nici un argument si care nu face nimic. Deci prezenta constructorilor n corpul unei clase nu este obligatorie. Daca nsa ati scris un constructor pentru o clasa care are mai mult de un argument, atunci constructorul implicit (fara nici un argument) nu va mai fi furnizat implicit de catre sistem.
class Dreptunghi { //nici un constructor } ... Dreptunghi d; d=new Dreptunghi(); -> OK (a fost generat constructorul implicit) class Dreptunghi { double x, y, w, h; Dreptunghi(double x0, double y0, double wo, double h0) { x=x0; y=y0; w=w0; h=h0; } } ... Dreptunghi d; d=new Dreptunghi(); -> Eroare la compilare
Constructorii unei clase pot avea urmatorii specificatori de acces: Specificatorii de acces ai constructorilor
private
Nici o alta clasa nu poate instantia obiecte ale acestei clase. O astfel de clasa poate contine metode publice (numite "factory methods") care sa-si creeze propriile obiecte si sa le returneze altor clase, controlnd n felul acesta diversi parametri legati de creare obiectelor sale. Doar subclasele pot crea obiecte de tipul clasei respective. Orice clasa poate crea instante ale clasei respective Doar clasele din acelasi pachet pot crea instante ale clasei respective
Variabilele unei clase se declara n corpul clasei dar nu n corpul unei metode. Variabilele declarate n cadrul unei metode sunt locale metodei respective. Declararea unei variabile presupune specificarea urmatoarelor lucruri: numele variabilei
21
tipul de date nivelul de acces la acea variabila de catre alte clase daca este constanta sau nu daca este variabila de instanta sau de clasa
unde un modificator poate fi : un specificator de acces : public, protected, private (vezi "Specificatori de acces pentru membrii unei clase") unul din cuvintele rezervate: static, final, transient, volatile
Ex: double x; protected static int n; public String s = "abcd"; private Point p = new Point(10, 10); final long MAX = 100000L;
static
Prezenta lui declara ca o variabila este variabila de clasa si nu de instanta: Declararea int x ; //variabila de instanta static int nrDeObiecteCreate; //variabila de clasa variabilelor de (vezi "Membri de instanta si membri de clasa") clasa Indica faptul ca valoarea variabilei nu mai poate fi schimbata, cu alte cuvinte este folosit pentru declararea constantelor.
final double PI = 3.14 ; ... PI = 3.141 -> eroare la compilare
final
Declararea constantelor
Prin conventie numele variabilelor finale se scriu cu litere mari. Folosirea lui final aduce o flexibilitate sporita n lucrul cu constante, n sensul ca valoarea unei variabile nu trebuie specificata neaparat la declararea ei (ca n exemplul de mai sus), ci poate fi specificata si ulterior, dupa care ea nu va mai putea fi modificata.
class Test { final int MAX; Test() { MAX = 100; MAX = 200; compilare } }
transient volatile
Este folosit la serializarea obiectelor, pentru a specifica ce variabile membre ale unui obiect nu participa la serializare (vezi "Serializarea obiectelor") Este folosit pentru a semnala compilatorului sa nu execute anumite optimizari asupra membrilor unei clase. Este o facilitate avansata a
22
limbajului Java.
Implementarea metodelor
Metodele sunt responsabile cu descrierea comportamentului unui obiect. Generic, o metoda se declara astfel: Declararea metodelor membre
[modificatori] TipReturnat numeMetoda ( [argumente] ) [throws TipExceptie] { //corpul metodei }
unde un modificator poate fi : un specificator de acces : public, protected, private (vezi "Specificatori de acces pentru membrii unei clase") unul din cuvintele rezervate: static, abstract, final, native,
synchronized
static
void metoda1() ; //metoda de instanta Declararea static void metoda2(); //metoda de clasa metodelor de clasa (vezi "Membri de instanta si membri de clasa") abstract
O metoda abstracta este o metoda care nu are implementare si trebuie sa faca parte dintr-o clasa abstracta.(vezi "Clase si metode abstracte") Specifica faptul ca acea metoda nu mai poate fi supradefinita n subclasele clasei n care ea este definita ca fiind finala. (vezi "Metode finale") In cazul n care aveti o librarie nsemnata de functii scrise n alt limaj de programare dect Java (C de exemplu), acestea pot fi refolosite din programele Java. Este folosit n cazul n care se lucreaza cu mai multe fire de executie iar metoda respectiva se ocupa cu partajarea unei resurse comune. Are ca efect construirea unui semafor care nu permite executarea metodei la un moment dat dect unui singur fir de executie. (vezi "Fire de executie")
native
synchronized
23
In cazul n care n declaratia functiei tipul returnat este un tip primitiv de date, valoarea returnata la terminarea functiei trebuie sa aiba obligatoriu acel tip, altfel va fi furnizata o eroare la compilare. Daca valoarea returnata este o referinta la un obiect al unei clase, atunci clasa obiectului returnat rebuie sa coincida sau sa fie o subclasa a clasei specificate la declararea metodei. De exemplu, fie clasa Poligon si subclasa acesteia Patrat.
Poligon metoda1( ) { Poligon p = new Poligon(); Patrat t = new Patrat(); if (...) return p; // legal else return t; // legal } Patrat metoda2( ) { Poligon p = new Poligon(); Patrat t = new Patrat(); if (...) return p; // ilegal else return t; // legal }
Tipul de date al unui argument poate fi orice tip valid al limbajului, att tip primitiv de date ct si referinta la un obiect.
Ex: adaugarePersoana(String nume, int varsta, float salariu) String este tip referinta, int si float sunt tipuri primitive
Spre deosebire de alte limbaje, n Java nu pot fi trimise ca parametri ai unei metode referinte la alte metode (functii), nsa pot fi trimise obiecte care sa contina implementarea acelor metode, pentru a fi apelate. De asemenea, n Java o metoda nu poat primi un numar variabil de argumente, ceea ce nseamna ca apelul unei metode trebuie sa se faca cu specificarea exacta a numarului si tipurilor argumentelor. 24
Numele argumentelor primite trebuie sa difere ntre ele si nu trebuie sa coincida cu numele nici uneia din variabilele locale ale metodei. Pot nsa sa coincida cu numele variabilelor membre ale clasei caz n care diferentierea se va face prin intermediul variabile this.
class Cerc { int x, y, raza; public Cerc(int x, int y, int raza) { this.x = x; this.y = y; this.raza = raza; } }
Atentie: In Java argumentele sunt trimise doar prin valoare (pass-by-value) Acest lucru nsemna ca metoda receptioneaza doar valorile variabilelor primite ca parametri. Cnd argumentul are tip primitiv de date metoda nu-i poate schimba valoarea dect local (n cadrul metodei); la revenirea din metoda variabila are aceeasi valoare ca la apelul inittial al metodei (modificarile facute n cadrul metodei sunt pierdute). Cnd argumentul este de tip referinta metoda nu poate schimba referinta obiectului nsa poate apela metodele acelui obiect si poate modifica orice variabila membra accesibila. Asadar, daca dorim ca o metoda sa schimbe starea(valoarea) unui argument primit, atunci el trebuie sa fie neaparat de tip referinta (trebuie sa fie un obiect!). De exemplu, sa consideram clasa Cerc descrisa anterior si dorim sa implementam o metoda care sa returneze parametrii cercului: Varianta incorecta
... int x = -1, y = -1, r = -1; cerc.aflaParametri(x, y, r); System.out.println("x = " + x + ", y = " + y + ", raza = " + r); . . . In acesct scop n clasa Cerc ar trebui sa avem o metoda de genul: class Cerc { int x, y, raza; ... public void aflaParametri(int valx, int valy, int valr) { //metoda nu functioneaza cum ar trebui! valx = x; valy = y; valr = raza; } }
Aceasta metoda nsa nu va realiza lucrul propul ntruct ea primeste doar valorile variabilelor x, y si r adica (-1, -1, -1) si nu referinte la ele (adresele lor de memorie) astfel nct sa le poata modifica valoarea. In concluzie, metoda nu realizeaza nimic pentru ca nu poate schimba valorile unor variabile aflate n afara corpului ei. Varianta corecta Definim o clasa suplimentara care descrie parametrii pe care dorim sa-i aflam:
25
class CParam { public int x, y, raza; } class Cerc { int x, y, raza; public void aflaParametri(CParam param) { param.x = x; param.y = y; param.raza = raza; } } ... CParam p = new CParam(); cerc.aflaParametri(p); System.out.println("x = " + p.x + ", y = " + p.y + ", raza = " + p.raza);
O metoda supradefinita poate sa: ignore complet codul metodei corespunzatoare din superclasa (cazul de mai sus)
B b = new B(); b.metoda(); -> afiseaza "B: metoda fara parametru"
26
sa extinda codul metodei parinte, executnd nainte de codul propriu si functia parinte.
class B extends A { //supradefinire prin extensie void metoda() { super.metoda(); System.out.println("B: metoda fara parametru"); } } . . . B b = new B(); b.metoda(); -> afiseaza : "A: metoda fara parametru" "B: metoda fara parametru"
Metode finale
Specifica faptul ca acea metoda nu mai poate fi supradefinita n subclasele clasei n care ea este definita ca fiind finala. Acest lucru este util daca respectiva metoda are o implementare care nu trebuie schimbata sub nici o forma n subclasele ei, fiind critica pentru consistenta starii unui obiect. De exemplu studentilor unei universitati trebuie sa li se calculeze media finala n functie de notele obtinute la examene n aceeasi maniera, indiferent de facultatea la care sunt.
class Student { . . . final float calcMedie(int nrExamene, float note[], float ponderi[]) { ... } . . . } class StudentInformatica extends Student{ float calcMedie(int nrExamene, float note[], float ponderi[]) { return 10.00; } }//eroare la compilare!
27
X X X
X** X X
X X X
private int secretPersonal; proteceted String secretDeFamilie; public Vector elemente; long doarIntrePrieteni ; -> package private void metodaInterna(); public double calculeazaRezultat();
Obs1(*): Daca nu este specificat nici un modificator de acces, implicit nivelul de acces este la nivelul pachetului (package). Asadar, modificatorul "package" nu apare explicit n declararea unei variabile/metode (n cazul n care apare, compilatorul va furniza o eroare). Obs2(**): In cazul n care declaram un membru "protected" atunci accesul la acel membru din subclasele clasei n care a fost declarata variabila depinde si de pachetul n care se gaseste subclasa: daca sunt n acelasi pachet accesul este permis, daca nu sunt n acelasi pachet accesul nu este permis dect pentru obiecte de tipul subclasei.
Variabile
Cnd declarati o variabila membra cum ar fi x n exemplul de mai jos:
class MyClass { int x ; //variabila de instanta }
se declara de fapt o variabila de instanta, cee ce nsemna ca la fiecare creare a unei instante a clasei MyClass sistemul aloca o zona de memorie separata pentru memorarea valorii lui x.
MyClass o1 = new MyClass(); o1.x = 100; MyClass o2 = new MyClass(); o2.x = 200; System.out.println(o1.x) -> afiseaza 100 System.out.println(o2.x) -> afiseaza 200
Asadar, fiecare obiect nou creat va putea memora valori diferite pentru variabilele sale de instanta. Pentru variabilele de clasa (statice) sistemul aloca o singura zona de memorie la care au acces toate instantele clasei respective, ceea ce nseamna ca daca un obiect modifica valoarea unei variabile statice ea se va modifica si pentru toate celelate obiecte.
class MyClass { int x ; //variabila de instanta
28
static long n; //variabila de clasa } . . . MyClass o1 = new MyClass(); MyClass o2 = new MyClass(); o1.n = 100; System.out.println(o2.n) -> afiseaza 100 o2.n = 200; System.out.println(o1.n) -> afiseaza 200
Metode
Similar ca la variabile, metodele declarate fara modificatorul static sunt metode de instanta iar cele declarate cu static sunt metode de clasa (statice). Diferenta ntre cele doua metode este urmatoarea: metodele de instanta opereaza att pe variabilele de instanta ct si pe cele statice ale clasei metodele de clasa opereaza doar pe variabilele statice ale clasei
class MyClass { int x ; //variabila de instanta static long n; //variabila de clasa void metodaDeInstanta() { n ++; //legal x --; //legal static void metodaStatica() { n ++; //legal x --; //ilegal }
Intruct metodele de clasa nu depind de starea obiectelor clasei respective, apelul lor se poate face astfel:
MyClass.metodaStatica(); MyClass obj = new MyClass(); obj.metodaStatica(); //legal, echivalent cu //legal
spre deosebire de metodele de instanta care nu pot fi apelate dect unei instante a clasei respective:
MyClass.metodaDeInstanta(), MyClass obj = new MyClass(); obj.metodaDeInstanta(); //ilegal //legal
La fiecare instantiere a clasei MyClass va fi rezervata zona de memorie pentru variabilele finale ale obiectului respectiv, ceea ce este o risipa ntruct aceste 29
constante au aceleasi valori pentru toate instantele clasei. Declararea corecta a constantelor trebuie asadar facuta cu modificatorii static si final, pentru a le rezerva o singura zona de memorie, comuna tuturor obiectelor:
class MyClass { static final double PI = 3.14; //variabila finala de clasa }
class MyClass { static long nrInstante = 0; MyClass() { //constructorul este apelat la fiecare instantiere 9. nrInstante ++; 10. } 11. }
Folosind variabile statice putem controla diversi parametri legati de crearea obiectelor unei clase 12. Implementarea functiilor globale Spre deosebire de C++, n Java nu putem avea functii globale definite ca atare, ntruct "orice este un obiect". Din acest motiv si metodele care au o functionalitate globala trebuie implementate n cadrul unei clase. Acest lucru se va face prin intermediul metodelor de clasa (globale), deoarece acestea nu depind de starea particulara a obiectelor din clasa respectiva. De exemplu, sa consideram functia globala sqrt care extrage radicalul unui numar si care se gaseste n clasa Math. Daca nu ar fi fost functie de clasa, apelul ei ar fi trebuit facut astfel (incorect, de altfel):
13. 14. Math obj = new Math(); double rad121 = obj.sqrt(121);
ceea ce ar fi fost extrem de neplacut pentru programatori. Fiind nsa functie statica ea poate fi apelata prin: Math.sqrt(121) .
30
static { final double PI = 3.14; long nrInstante = 0; final double EPS = 0.01; }
Clase imbricate
O clasa imbricata este, prin definitie, o clasa membra a unei alte clase
class ClasaDeAcoperire{ . . . class ClasaImbricata { . . . } }
Folosirea claselor imbricate se face atunci cnd o alta clasa are nevoie n implementarea ei de o alta clasa si nu exista nici un motiv pentru care clasa imbricata sa fie declarata de sine statatoare (nu mai este folosita nicaieri).
public class Pachet { //clasa de acoperire class Continut { //clasa imbricata private String marfa; private float cantitate; Continut (String marfa, float cantitate) { this.marfa = marfa; this.cantitate = cantitate; } } class Destinatie { //clasa imbricata private String dest; private int termen; Destinatie(String undePleaca, int inCateZile) { dest = undePleaca; termen = inCateZile; } } dest, public void trimite(String marfa, float cant, String int termen) { Continut c = new Continut(marfa, cant); Destinatie d = new Destinatie(dest, termen); } public static void main(String[] args) { Pachet p = new Pachet(); p.trimite("banane", 100, "Romania", 7); }
31
Ca membra a unei clase, o clasa imbricata are un privilegiu special fata de celelalte clase: acces nelimitat la variabilele clasei de acoperire, chiar daca acestea sunt private.
Clase interne
Ca orice alta clasa o clasa imbricata poate fi declarata statica sau nu. O clasa imbricata nestatica se numeste clasa interna.
class ClasaDeAcoperire{ . . . static class ClasaImbricataStatica { . . . } class ClasaInterna { . . . } }
o "clasa imbricata" reflecta relatia sintactica a doua clase; codul unei clase apare n interiorul dodului altei clase; o "clasa interna" reflecta relatia dintre instantele a doua clase, n sensul ca o instanta a unei clase interne nu poate exista dect n cadrul unei instante a clasei de acoperire.
In general cele mai folosite clase imbricate sunt clasele interne Asadar, o clasa interna este o clasa imbricata ale carei instante nu pot exista dect n cadrul instantelor clasei de acoperire si care are acces direct la toti membrii clasei sale de acoperire.
32
In cazul n care clasele imbricate au la rndul lor alte clase imbricate (situatie mai putin uzuala) denumirea lor se face dupa aceeasi metoda : adaugarea unui '$' si apoi numele clasei imbricate.
Daca vom ncerca sa instantiem un obiect al clasei ClasaAbstracta vom obtine o eroare la compilarea programului de genul:class ClasaAbstracta is an abstract class. It can't be instantiated.
Metode abstracte
Spre deosebire de clasele obisnuite care trebuie sa furnizeze implementari pentru toate metodele declarate o clasa abstracta poate contine metode fara nici o implementare. Metodele fara nici o implementare se numesc metode abstracte si pot aparea doar n clase abstracte. In fata unei metode abstracte trebuie sa apara cuvntul cheie abstract, altfel va fi furnizata o eroare de compilare.
abstract void metodaAbstracta(); void metoda(); //incorect //corect
33
In felul acesta o clasa abstracta poate pune la dispozitia subclaselor sale un model complet pe care trebuie sa-l implementeze, furniznd chiar implementarea unor metode comune tuturor claselor sau lasnd explicitarea unor metode fiecarei subclase n parte. Un exemplu elocvent de folosire a claselor si metodelor abstracte este descrierea obiectelor grafice ntr-o maniera orientata-obiect.
o o o
Obiecte grafice : linii, dreptunghiuri, cercuri, curbe Bezier, etc Stari comune : pozitia(originea), dimensiunea, culoarea, etc Comportament : mutare, redimensionare, desenare, colorare, etc.
Pentru a folosi starile si comportamentele comune acestor obiecte n avantajul nostru putem declara o clasa generica GraphicObject care sa fie superclasa pentru celelalte clase. Metodele abstracte vor fi folosite pentru implementarea comportamentului specific fiecarui obiect, cum ar fi desenarea iar cele obisnuite pentru comportamentul comun tuturor, cum ar fi schimbarea originii. Implementarea clasei abstracte GraphicObject:
abstract class GraphicObject { int x, y; . . . void moveTo(int newX, int newY) { . . . } abstract void draw(); } ici o implementare
Atentie: Orice subclasa non-abstracta a unei clase abstracte trebui sa furnizeze implementari ale metodelor abstracte din superclasa. Implementarea claselor pentru obiecte grafice ar fi:
class Circle extends GraphicObject { void draw() { . . . //obligatoriu implementarea }
34
Observatii: O clasa abstracta poate sa nu aiba nici o metoda abstracta. O metoda abstracta nu poate aparea dect ntr-o clasa abstracta. Orice clasa care are o metoda abstracta trebuie declarata ca fiind abstracta.
Clasa Object
Orice clasa are o superclasa
Dupa cum am vazut la crearea unei clase clauza "extends" specifica faptul ca acea clasa este o subclasa a altei clase, numita superclasa. O clasa poate avea o singura superclasa (Java nu suporta mostenirea multipla) si chiar daca nu specificati clauza "extends" la crearea unei clase ea totusi va avea o superclasa. Cu alte cuvinte, in Java orice clasa are o superclasa si numai una.
Superclasa tuturor celorlalte clase este clasa Object, care este radacina ierarhiei de clase n Java. Clasa Object este si superclasa implicita a claselor care nu specifica o alta superclasa. Declaratia class MyClass {} este echivalenta cu class MyClass extends Object {}.
Clasa Object
Este cea mai generala dintre clase, orice obiect fiind, direct sau indirect, descendent al acestei clase. Defineste si implementeaza comportamentul comun al tuturor claselor Java cum ar fi:
35
o o o o
posibilitatea compararii obiectelor ntre ele specificarea unei reprezentari ca sir de caractere a unui obiect returnarea clasei din care face parte un obiect notificarea altor obiecte ca o variabila de conditie s-a schimbat, etc
Fiind sublcasa a lui Object, orice clasa poate supradefini metodele clasei Object care nu sunt finale. Metode care pot fi supradefinite sunt: clone,
equals/hashCode, finalize ,toString
clone
Acesasta metoda este folosita pentru duplicarea obiectelor (creearea unor clone). Clonarea unui obiect presupune crearea unui nou obiect de acelasi tip si care sa aiba aceeassi stare (aceleasi valori pentru variabilele sale)
Aceste metode trebuie supradefinite mpreuna. In metoda equals este scris codul pentru compararea a doua obiecte. Implicit (implementarea din clasa Object) aceasta metoda compara referintele obiectelor. Uzual este redefinita pentru a testa daca starile obiectelor coincid sau daca equals, doar o parte din variabilele lor coincid. hashCode Metoda hashCode returneaza un cod ntreg pentru fiecare obiect pentru a testa consistenta obiectelor: acelasi obiect trebuie sa returneze acelasi cod pe durata executiei programului. Daca doua obiecte sunt egale conform metodei equals atunci apelul metodei hashCode pentru fiecare din cele doua obiecte trebuie sa returneze acelasi ntreg.
finalize
In aceasta metoda se scrie codul care "curata dupa un obiect" nainte de a fi eliminat din memorie de colectorul de gunoaie.(vezi "Distrugerea obiectelor") Este folosita pentru a returna o reprezentare ca sir de caractere a unui obiect. Este utila pentru concatenarea sirurilor cu diverse obiecte n vederea tiparirii.
MyObject obj = new MyObject(); System.ot.println("Obiect=" + obj); //echivalent cu System.ot.println("Obiect=" + obj.toString());
toString
36
public synchronized Object clone() { MyObject m = new MyObject(); m.x = x; return m; } public final synchronized String toString() { return "[" + x + "]"; } public void finalize() { System.out.println("Finalizare"); x = -1; } } class OtherObject { public int x; //clasa in care NU este supradefinita metoda equals } class TestObject { //clasa principala - contine metoda main public static void main(String args[]) { OtherObject o1 = new OtherObject(); OtherObject o2 = new OtherObject(); o1.x = 1; o2.x = 1; if (o1.equals(o2)) System.out.println("test1: o1 == o2"); else System.out.println("test1: o1 != o2");//corect //Desi x=1 pt ambele obiecte egalitatea se obtine doar cand //adresele lor de memorie coincid deoarece nu este implementata //metoda equals o2 = o1; if (o1.equals(o2)) System.out.println("test2: o1 == o2");//corect else System.out.println("test2: o1 != o2"); ////////////////////////////////////////////// MyObject m1 = new MyObject(); MyObject m2 = new MyObject(); m1.x = 1; m2.x = 1; if (m1.equals(m2)) System.out.println("test3: m1 == m2");//corect else System.out.println("test3: m1 != m2"); //x=1 pt ambele obiecte -> metoda equals returneaza true MyObject m3; m3 = (MyObject) m1.clone(); System.out.println("Obiectul clonat: " + m3); //echivalent cu System.out.println("Obiectul clonat: " + m3.toString());
37
} }
Curs 3 Exceptii
Ce sunt exceptiile ? Avantajele exceptiilor "Prinderea" si tratarea exceptiilor (Instructiunile try-catch-finally) "Aruncarea" exceptiilor (Clauza throws, Instructiunea throw) Ierarhia claselor ce descriu exceptii (Clasa Throwable) Exceptii la executie Crearea propriilor exceptii
Ce sunt exceptiile?
Termenul exceptie este o prescurtare pentru "eveniment exceptional" si poate fi definit astfel: Definitie O exceptie este un eveniment ce se produce n timpul executiei unui program si care provoaca ntreruperea cursului normal al executiei. Exceptiile pot aparea din diverse cauze si pot avea nivele diferite de gravitate: de la erori fatale cauzate de echipamentul hardware pna la erori ce tin strict de codul programului, cum ar fi accesarea unui element din afara spatiului alocat unui vector. In momentul cnd o asemenea eroare se produce n timpul executiei sistemul genereaza automat un obiect de tip exceptie ce contine: informatii despre exceptia respectiva starea programului n momentul producerii acelei exceptii
public class Exceptii { public static void main(String argsst) { int v[] = new int[10]; v[10] = 0; //exceptie, vectorul are elementele v[0]...v[9] System.out.println("Aici nu se mai ajunge..."); } }
Crearea unui obiect de tip exceptie se numeste aruncarea unei exceptii ("throwing an exception"). In momentul n care o metoda genereaza o exceptie (arunca o exceptie) 38
sistemul de executie este responsabil cu gasirea unei secvente de cod dintr-o metoda care sa trateze acea exceptie. Cautarea se face recursiv, ncepnd cu metoda care a generat exceptia si mergnd napoi pe linia apelurilor catre acea metoda. Secventa de cod dintr-o metoda care trateaza o anumita exceptie se numeste analizor de exceptie ("exception handler") iar interceptarea si tratarea exceptie se numeste prinderea exceptiei ("catch the exception"). Cu alte cuvinte la aparitia unei erori este "aruncata" o exceptie iar cineva trebuie sa o "prinda" pentru a o trata. Daca sistemul nu gaseste nici un analizor pentru o anumita exceptie atunci programul Java se opreste cu un mesaj de eroare (n cazul exemplului de mai sus mesajul "Aici nu se mai ajunge..." nu va fi tiparit). Atentie: In Java tratarea erorilor nu mai este o optiune ci o constrngere. Orice cod care poate provoca exceptii trebui sa specfice modalitatea de tratare a acestora.
Avantajele exceptiilor
Prin modalitatea sa de tratare a exceptiilor Java are urmatoarele avantaje fata de mecanismul traditional de tratare a erorilor: 1. Separarea codului pentru tratarea unei erori de codul n care ea poate sa apara 2. Propagarea unei erori pna la un analizor de exceptii corespunzator 3. Gruparea erorilor dupa tipul lor
Separarea codului pentru tratarea unei erori de codul n care ea poate sa apara
In programarea traditionala tratarea erorilor se combina cu codul ce poate produce aparitia lor conducnd la ada numitul "cod spaghetti". Sa consideram urmatorul exemplu: o functie care ncarca un fisier n memorie:
citesteFisier { deschide fisierul; determina dimensiunea fisierului; aloca memorie; citeste fisierul in memorie; inchide fisierul; }
Problemele care pot aparea la aceasta functie, aparent simpla sunt de genul: "Ce se ntmpla daca: ... ?" fisierul nu poate fi deschis nu se poate determina dimensiunea fisierului nu poate fi alocata suficienta memorie nu se poate face citirea din fisier fisierul nu poate fi nchis Un cod traditional care sa trateze aceste erori ar arata astfel:
int citesteFisier { int codEroare = 0; deschide fisier;
39
if (fisierul s-a deschis) { determina dimensiunea fisierului; if (s-a determinat dimensiunea) { aloca memorie; if (s-a alocat memorie) { citeste fisierul in memorie; if (nu se poate citi din fisier) { codEroare = -1; } } else { codEroare = -2; } } else { codEroare = -3; } inchide fisierul; if (fisierul nu s-a inchis && codEroare == 0) { codEroare = -4; } else { codEroare = codEroare & -4; } } else { codEroare = -5; } return codEroare; }//cod "spaghetti"
Acest stil de progamare este extrem de susceptibil la erori si ngreuneaza extrem de mult ntelegerea sa. In Java, folosind mecansimul exceptiilor, codul ar arata astfel:
int citesteFisier { try { deschide fisierul; determina dimensiunea fisierului; aloca memorie; citeste fisierul in memorie; inchide fisierul; } catch (fisierul nu s-a deschis) {trateaza eroarea;} catch (nu s-a determinat dimensiunea) {trateaza eroarea;} catch (nu s-a alocat memorie) {trateaza eroarea } catch (nu se poate citi dun fisier) {trateaza eroarea;} catch (nu se poate inchide fisierul) {trateaza eroarea;} }
40
Sa presupunem de asemenea ca dorim sa facem tratarea erorilor doar n metoda1. Traditional, acest lucru ar trebui facut prin propagarea erorii ntoarse de metoda citesteFisier pna la metoda1.
int metoda1 { int codEroare = apel metoda2; if (codEroare != 0) proceseazaEroare; . . . } int metoda2 { int codEroare = apel metoda3; if (codEroare != 0) return codEroare; . . . } int metoda3 { int codEroare = apel citesteFisier; if (codEroare != 0) return codEroare; . . . }
Java permite unei metode sa arunce exceptiile aparute n cadrul ei la un nivel superior, adica functiilor care o apeleaza sau sistemului. Cu alte cuvinte o metoda poate sa nu si asume responsabilitatea tratarii exceptiilor aparute n cadrul ei:
metoda1 { try { apel metoda2; } catch (exceptie) { proceseazaEroare; } . . .
} metoda2 throws exceptie{ apel metoda3; . . . } metoda3 throws exceptie{ apel citesteFisier; . . . }
41
La rndul ei clasa IOException se ncadreaza ntr-o categorie mai larga de exceptii si anume clasa Exception. Radacina acestei ierarhii este clasa Throwable (vezi "Ierarhia claselor ce descriu exceptii"). Interceptarea unei exceptii se poate face fie la nivelul clasei specifice pentru acea exceptie fie la nivelul uneia din superclasele sale, n functie de necesitatile programului:
try { FileNotFoundException FileReader f = new FileReader("input.dat"); //acest apel poate genera exceptie de tipul
//tratarea ei poate fi facuta in unul din modurile de mai jos } catch (FileNotFoundException e) { //exceptie specifica provocata de absenta fisierului 'input.dat' } //sau catch (IOException e) { //exceptie generica provocata de o operatie de intrare/iesire } //sau catch (Exception e) { //cea mai generica exceptie - NERECOMANDATA! }
42
FileInputStream sursa = null; //s este flux de intrare int octet; sursa = new FileInputStream("fisier.txt"); octet = 0; //citesc fisierul caracter cu caracter while (octet != -1) { octet = sursa.read(); System.out.print((char)octet); } sursa.close(); } public static void main(String args[]) { citesteFisier(); }
} Acest cod va furniza erori la compilare deoarece n Java tratarea erorilor este obligatorie. Folosind mecanismul exceptiilor metoda citesteFisier si poate trata singura erorile pe care le poate provoca: //CORECT import java.io.*; public class CitireFisier { public static void citesteFisier() { FileInputStream sursa = null; //s este flux de intrare int octet; try { sursa = new FileInputStream("fisier.txt"); octet = 0; //citesc fisierul caracter cu caracter while (octet != -1) { octet = sursa.read(); System.out.print((char)octet); } catch (FileNotFoundException e) { System.out.println("Fisierul nu a fost gasit !"); System.out.println("Exceptie: " + e.getMessage()); System.exit(1); } catch (IOException e) { System.out.println("Eroare de intrare/iesire"); System.out.println("Exceptie: " + e.getMessage()); System.exit(2); } finally { if (sursa != null) { System.out.println("Inchidem fisierul..."); try { sursa.close(); } catch (IOException e) {
43
} Blocul "try" contine instructiunile de deschidere a unui fisier si de citire dintr-un fisier ambele putnd produce exceptii. Exceptiile provocate de aceste instructiuni sunt tratate n cele doua blocuri "catch", cte unul pentru fiecare tip de exceptie. Inchiderea fisierului se face n blocul "finally", deoarece acesta este sigur ca se va executa. Fara a folosi blocul "finally" nchiderea fisierului ar fi trebuit facuta n fiecare situatie n care fisierul ar fi fost deschis, ceea ce ar fi dus la scrierea de cod redundant: try { . . . sursa.close(); } . . . catch (IOException e) { . . . sursa.close(); //cod redundant } Atentie: Obligatoriu un bloc de instructiuni "try" trebuie sa fie urmat de unul sau mai multe blocuri "catch", n functie de exceptiile provocate de acele instructiuni sau (optional) de un bloc "finally"
"Aruncarea" exceptiilor
In cazul n care o metoda nu si asuma responsabilitatea tratarii uneia sau mai multor exceptii pe care le pot provoca anumite instructiuni din codul sau atunci ea poate sa "arunce" aceste exceptii catre metodele care o apeleaza, urmnd ca acestea sa implementeze tratarea lor sau, la rndul lor, sa "arunce" mai departe exceptiile respective. Acet lucru se realizeaza prin specificarea n declaratia metodei a clauzei throws: metoda throws TipExceptie1, TipExceptie2, ... { . . . } Atentie: O metoda care nu trateaza o anumita exceptie trebuie obligatoriu sa o "arunce". In exemplul de mai sus daca nu facem tratarea exceptiilor n cadrul metodei citesteFisier atunci metoda apelanta (main) va trebui sa faca acest lucru: import java.io.*; public class CitireFisier {
44
public static void citesteFisier() throws FileNotFoundException, IOException { FileInputStream sursa = null; //s este flux de intrare int octet; sursa = new FileInputStream("fisier.txt"); octet = 0; //citesc fisierul caracter cu caracter while (octet != -1) { octet = sursa.read(); System.out.print((char)octet); } sursa.close(); } public static void main(String args[]) { try { citesteFisier(); } catch (FileNotFoundException e) { System.out.println("Fisierul nu a fost gasit !"); System.out.println("Exceptie: " + e.getMessage()); System.exit(1); } catch (IOException e) { System.out.println("Eroare de intrare/iesire"); System.out.println("Exceptie: " + e.getMessage()); System.exit(2); } } } Observati ca, n acest caz, nu mai putem diferentia exceptiile provocate de citirea din fisier si de nchiderea fisierului ambele fiind de tipul IOException. Aruncarea unei exceptii se poate face si implicit prin instructiunea throw ce are formatul: throw obiect_de_tip_Exceptie . Exemple: throw new IOException(); if (index >= vector.length) throw new ArrayIndexOutOfBoundsException(); catch(Exception e) { System.out.println("A aparut o exceptie); throw e; } Aceasta instructune este folosita mai ales la aruncarea exceptiilor proprii care, evident, nu sunt detectate de catre mediul de executie. (vezi "Crearea propriilor exceptii")
45
Clasa Error Erorile (obiecte de tip Error) sunt cazuri speciale de exceptii generate de functionarea anormala a echipamentului hard pe care ruleaza un program Java si sunt invizibile programatorilor. Un program Java nu trebuie sa trateze aparitia acestor erori si este improbabil ca o metoda Java sa provoace asemenea erori. Clasa Exception Obiectele de acest tip sunt exceptiile standard care trebuie tratate de catre programele Java. In Java, tratarea exceptiilor nu este o optiune ci o constrngere. Exceptiile care pot "scapa" netratate sunt ncadrate n subclasa RuntimeException si se numesc exceptii la executie. In general metodele care pot fi apelate pentru un obiect exceptie sunt definite n clasa Throwable si sunt publice, astfel nct pot fi apelate pentru orice tip de exceptie. Cele mai uzuale sunt: String getMessage( ) tipareste detaliul unei exceptii void tipareste informatii despre localizarea exceptiei printStackTrace( ) metoda din clasa Object, da reprezentarea ca sir String toString( ) de caractere a exceptiei
46
anevoioasa. Din acest motiv compilatorul permite ca aceste exceptii sa ramna netratate, tratarea lor nefiind nsa ilegala. int v[] = new int[10]; try { v[10] = 0; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Atentie la indecsi!"); e.printStackTrace(); }//legal
47
eroare la iesirea standard. Rularea programului de mai sus va produce urmatorul rezultat: Exceptie in f() MyException() at TestMyException.f(TestMyException.java:12) at TestMyException.main(TestMyException.java:20) Exceptie in g() MyException(): aruncata din g at TestMyException.g(TestMyException.java:16) at TestMyException.main(TestMyException.java:23) Procesul de creare a unei noi exceptii poate fi dus mai departe prin adaugarea unor noi metode clasei ce descrie acea exceptie, nsa aceasta dezvoltare nu si are rostul n majoritatea cazurilor. In general, exceptiile proprii sunt descrise de clase foarte simple chiar fara nici un cod n ele, cum ar fi: class SimpleException extends Exception { } Aceasta clasa se bazeaza pe constructorul implicit creat de compilator nsa nu are constructorul SimpleException(String), care n practica nici nu este prea des folosit.
Ce sunt fluxurile ? Clasificarea fluxurilor Ierarhia claselor pentru lucrul cu fluxuri o Fluxuri de caractere o Fluxuri de octeti Metode comune fluxurilor Folosirea fluxurilor o Fluxuri pentru citirea/scrierea efectiva a datelor o Fluxuri pentru procesarea datelor o Crearea unui flux Fluxuri pentru lucrul cu fisiere (fluxuri de tip "File") Conectarea fluxurilor de caractere(fluxuri de tip "Pipe") Citirea si scrierea cu zona tampon Concatenarea fisierelor (clasa SequenceInputStream) Fluxuri pentru filtrarea datelor o Clasele DataInputStream si DataOutputStream o Scrierea propriilor fluxuri de filtrare Fluxuri standard de intrare/iesire (citirea de la tastatura/scrierea pe ecran) Analiza lexicala pe fluxuri (clasa StreamTokenizer) Alte clase pentru lucrul cu fisiere o Clasa RandomAccesFile (fisiere cu acces direct) o Clasa File o Interfata FilenameFilter (filtrarea fisierelor dintr-un director)
48
Ce sunt fluxurile?
Adeseori programele necesita citirea unor informatii care se gasesc pe o sursa externa sau trimiterea unor informatii catre o destinatie externa. Informatia se poate gasi oriunde : ntr-un fisier pe disc, n retea, n memorie sau n alt program si poate fi de orice tip: date primitive, obiecte, imagini, sunete, etc. Pentru a aduce informatii dintr-un mediu extern, un progam Java trebui sa deschida un canal de comunicatie (flux) catre sursa informatiilor (fisier, memorie, socket,etc) si sa citeasca serial informatiile respective:
Similar, un program poate trimite informatii catre o destinatie externa deaschiznd un canal de comunicatie (flux) catre acea destinatie si scriind serial informatiile respective:
Indiferent de tipul informatiilor, citirea/scrierea informatiilor de pe/catre un mediu extern respecta urmatorii algoritmi: Citirea Scrierea
deschide canal comunicatie deschide canal comunicatie while (mai sunt informatii) { while (mai sunt informatii) { citeste informatie scrie informatie } } inchide canal comunicati; inchide canal comunicati;
Pentru a generaliza, att sursa externa a unor informatii ct si destinatia lor sunt vazute ca fiind niste procese care produc, respectiv consuma informatii: Definitii: Un flux este un canal de comunicatie unidirectional ntre doua procese. Un proces care descrie o sursa externa de date se numeste proces producator. Un proces care descrie o destinatie externa pentru date se numeste proces consumator. Un flux care citeste date se numeste flux de intrare. Un flux care scrie date se numeste flux de iesire.
49
Observatii: Fluxurile sunt canale de comunicatie seriale pe 8 sau 16 biti. Fluxurile sunt unidirectionale, de la producator la consumator Fiecare flux are un singur proces producator si un singur proces consumator Intre doua procese pot exista oricte fluxuri, orice proces putnd fi att producator si consumator n acelasi timp, dar pe fluxuri diferite Consumatorul si producatorul nu comunica direct printr-o interfata de flux ci prin intermediul codului Java de tratare a fluxurilor Clasele si intefetele standard pentru lucu cu fluxuri se gasesc n pachetul java.io. Deci orice program care necesita operatii de intrare/iesire trebuie sa contina instructiunea de import a pachetului java.io: import java.io.*;
Clasificarea fluxurilor
Exista trei tipuri de clasificare a fluxurilor: 1. Dupa "directia" canalului de comunicatie deschis fluxurile se mpart n: o fluxuri de intrare (pentru citirea datelor) o fluxuri de iesire (pentru scrierea datelor) 2. Dupa tipul de date pe care opereaza: o fluxuri de octeti (comunicare seriala se realizeaza pe 8 biti) o fluxuri de caractere (comunicare seriala se realizeaza pe 16 biti) 3. Dupa actiunea lor: o fluxuri primare de citire/scriere a datelor (se ocupa efectiv cu citirea/scrierea datelor) o fluxuri pentru procesarea datelor
50
Au fost puse n evidenta (colorate cu gri) fluxurile care intra n categoria fluxurilor pentru procesarea datelor.
Fluxuri de octeti
Clasele radacina pentru ierarhia claselor ce se ocupa cu fluxurile de octeti sunt InputStream (pentru fluxuri de intrare) si OutputStream (pentru fluxuri de iesire). Acestea sunt superclase abstracte pentru clase ce implementeaza fluxuri specializate pentru citirea/scrierea datelor pe 8 biti. Ierarhia claselor pentru fluxuri de intrare pe octeti:
51
Au fost puse n evidenta (colorate cu gri) fluxurile care intra n categoria fluxurilor pentru procesarea datelor.
Atentie: Pentru majoritatea programelor scrierea si citirea datelor se vor face prin intermediul fluxurilor de caractere deoarece acestea permit manipularea caracterelor Unicode (16-biti), n timp ce fluxurile de octeti permit doar lucrul pe 8 biti - caractere ASCII.
InputStream
int read() int read(byte buf[]) int read(byte buf[], int offset,int length)
52
De asemenea ambele clase pun la dispozitie metode pentru marcarea unei locatii ntr-un flux, saltul peste un numar de pozitii, resetarea pozitiei curente, etc. Superclasele abstracte Writer si OutputStream sunt de asemenea paralele, definind metode similare pentru scrierea datelor. Writer
int write() int write(char buf[]) int write(char buf[], int offset,int length)
OutputStream
int write() int write(byte buf[]) int write(byte buf[], int offset,int length)
Inchiderea oricarui flux se realizeaza prin metoda close. In cazul n care aceasta nu este apelata explicit fluxul va fi automat nchis de catre colectorul de gunoaie atunci cnd nu va mai exista nici o referinta la el. Metodele referitoare la fluxuri pot genera exceptii de tipul IOException.
Folosirea fluxurilor
Asa cum am vazut fluxurile pot fi mpartite n functie de activitatea lor, n fluxuri care se ocupa efectiv cu citirea/scrierea datelor si fluxuri pentru procesarea datelor. In continuare vom vedea care sunt cele mai importante clase din cele doua categorii si la ce folosesc acestea:
Fluxuri octeti
ByteArrayInputStream, ByteArrayOutputStream
Memorie
Aceste fluxuri folosesc pentru scrierea/citirea informatiilor n memorie si sunt create pe un vector existent deja. Cu alte cuvinte permit tratarea vectorilor ca sursa/destinatie pentru crearea unor fluxuri de intrare/iesire. StringReader, StringWriter StringBufferInputStream
Permit tratarea sirurilor de caractere aflate n memorie ca sursa/destinatie pentru crearea unor fluxuri de intrare/iesire. StringReader si StringWriter sunt folosite cu obiecte de tip String iar StringBufferInputStream cu obiecte de tip StringBuffer.
Pipe
53
Implementeaza componentele de intrare/iesire ale unei conducte de date (pipe). Pipeurile sunt folosite pentru a canaliza iesirea unui program sau fir de executie catre intrarea altui program sau fir de executie (vezi "Conectarea fluxurilor"). FileReader, FileWriter FileInputStream, FileOutputStream
Fisier
Numite si fluxuri fisier, acestea sunt folosite pentru citirea datelor dintr-un fisier, respectiv scrierea datelor ntr-un fisier (vezi "Fluxuri pentru lucrul cu fisiere").
"Bufferizar Sunt folosite pentru a introduce un buffer n procesul de scriere/citire a informatiilor, reducnd astfel numarul de accese la dispozitivul ce reprezinta sursa e"
originala de date. Sunt mult mai eficiente dect fluxurile fara buffer si din acest motiv se recomanda folosirea lor ori de cte ori eset posibil (vezi "Citirea si scrierea cu zona tampon"). FilterReader, FilterWriter FilterInputStream, FilterOutputStream
Filtrare
Sunt clase abstracte ce definesc o interfata pentru fluxuri care filtreaza automat datele citite sau scrise (vezi "Fluxuri pentru filtrare"). InputStreamReader, OutputStreamWriter
Conversie Formeaza o punte de legatura ntre fluxurile de caractere si fluxurile de octeti. Un octetiflux InputStreamReader citeste octeti dintr-un flux InputStream ai i converteate la caractere caractere folosind codificarea standard a caracterelor sau o codificare specificata de
program. Similar, un flux OutputStreamWriter converteste caractere n octeti si trimite rezutatul catre un flux de tipul OutputStream. SequenceInputStream
Concatenar Concateneaza mai multe fluxuri de intrare ntr-unul singur (vezi "Concatenarea e
fisierelor").
Serializare
ObjectInputStream, ObjectOutputStream Folosite pentru serializarea obiectelor (vezi "Serializarea obiectelor"). DataInputStream, DataOutputStream
Conversie tipuri de Folosite la scrierea/citirea datelor de tip primitiv ntr-un format independent de masina pe care se lucreaza (vezi "Folosirea claselor DataInputStream si date
DataOutputStream"). LineNumberReader LineNumberInputStream
54
Asadar, crearea unui flux primitiv de date care scrie/citeste informatii de la un dispozitiv extern are formatul general:
FluxPrimitiv numeFlux = new FluxPrimitiv( dispozitiv extern )
Fluxurile de procesare nu pot exista de sine statatoare ci se suprapun pe un flux primitiv de citire/scriere a datelor. Din acest motiv constructorii claselor pentru fluxurile de procesare nu primesc ca argument un dispozitiv extern de memorare a datelor ci o referinta la un flux primitiv responsabil cu citirea/scrierea efectiva a datelor:
Exemple: //crearea unui flux de intrare printr-un buffer BufferedReader in = new BufferedReader(new FileReader("fisier.in")); //echivalent cu FileReader fr = new FileReader("fisier.in"); BufferedReader in = new BufferedReader(fr); //crearea unui flux de iesire printr-un buffer BufferedWriter out = new BufferedWriter(new FileWriter("fisier.out"))); //echivalent cu FileWriter fo = new FileWriter("fisier.out");
55
Asadar, crearea unui flux pentru procesarea datelor are formatul general:
FluxProcesare numeFlux = new FluxProcesare( referintaFluxPrimitiv )
Constructorii acestor clase accepta ca argument un obiect care sa specifice un anume fisier. Acesta poate fi un sir de caractere, on obiect de tip File sau un obiect de tip FileDesciptor (vezi "Clasa File"). Constructorii clasei FileReader:
public FileReader( String fileName ) throws FileNotFoundException public FileReader( File file ) throws FileNotFoundException public FileReader( FileDescriptor fd )
Cei mai uzuali constructori sunt cei care primesc ca argument numele fisierului. Acestia pot provoca exceptii de tipul FileNotFoundException n cazul n care fisierul cu numele specificat nu exista. Din acest motiv orice creare a unui flux de 56
acest tip trebuie facuta ntr-un bloc try sau metoda n care sunt create fluxurile respective trebuie sa arunce exceptiile de tipul FileNotFoundException sau de tipul superclasei IOException.
Exemplu: un program care copie con]inutul unui fisier n alt fisier: import java.io.*; public class Copy { public static void main(String[] args) throws IOException { FileReader in = new FileReader("in.txt"); FileWriter out = new FileWriter("out.txt"); int c; while ((c = in.read()) != -1) out.write(c); in.close(); out.close(); } }
Obs: metoda main arunca exceptii IOException care este superclasa pentru FileNotFoundException. Aceste exceptii nu vor "prinse" dect de interpretor si va fi afisat un mesaj de eroare la aparitia lor.
Sunt folosite pentru a introduce un buffer n procesul de scriere/citire a informatiilor, reducnd astfel numarul de accese la dispozitivul ce reprezinta sursa originala de date. Sunt mult mai eficiente dect fluxurile fara buffer si din acest motiv se recomanda folosirea lor ori de cte ori este posibil. Clasele BufferedReader si BufferedInputStream citesc n avans date si le memoreaza ntr-o zona tampon (buffer). Atunci cnd se executa o operatie read(), octetul citit va fi preluat din buffer. In cazul n care buffer-ul este gol citirea se face direct din flux si, odata cu citirea octetului, vor fi memorati n buffer si octetii care i urmeaza. Similar, se lucreaza si cu clasele BufferedWriter si BufferedOutputStream. Fluxurile de citire/scriere cu buffer sunt fluxuri de procesare si sunt folosite prin suprapunere cu alte fluxuri.
57
In cazul constructorilor n care dimensiunea buffer-ului nu este specificata, aceasta primeste valoarea implicita de 512 octeti. Metodele acestor clase sunt cele uzuale de tipul read si write (vezi "Metode comune fluxurilor"). Pe lnga acestea, clasele pentru scriere prin buffer mai au si metoda flush care goleste explicit zona tampon chiar daca aceasta nu este plina.
Exemplu: BufferedWriter out = new BufferedWriter( new FileWriter("out.dat"), 1024) //am creat un flux cu buffer de 1024 octeti for(int i=0; i<1024; i++) out.write(i); //bufferul nu este plin -> in fisier nu s-a scris nimic out.flush(); //bufferul este golit -> datele se scriu n fisier
Metoda readLine
BufferedReader br = new BufferedReader(new FileReader("in")) String input; while ((input = br.readLine()) != null) { . . . //readLine metoda specifica care citeste o linie }
Concatenarea fisierelor
58
Clasa SequenceInputStream permite unei aplicatii sa combine serial mai multe fluxuri de intrare astfel nct acestea sa apara ca un singur flux de intrare. Citirea datelor dintr-un astfel de flux se face astfel: se citeste din primul flux de intrare specificat pna cnd se ajunge la sfrsitul acestuia, dupa care primul flux de intrare este nchis si se deschide automat urmatorul flux de intrare din care se vor citi n continuare datele, dupa care procesul se repeta pna la terminarea tuturor fluxurilor de intrare. Constructorii acestei clase sunt: Construieste un flux secvential dintr-o multime
SequenceInputStream( Enumera de fluxuri de intrare. Fiecare obiect n tion e ) enumerarea primita ca parametru trebuie
sa fie
de tipul InputStream.
SequenceInputStream( InputSt Construieste un flux de intrare care combina ream s1, InputStream s2 ) doua fluxuri s1 si s2. Primul flux citit va fi s1.
Exemplul cel mai elocvent de folosirea a acestei clase este concatenarea a doua fisiere:
//Concatenarea a 2 fisiere ale caror nume sunt primite la linia de comanda //Rezultatul concatenarii este afisat pe ecran import java.io.*; public class Concatenare1 { public static void main(String args[]) throws IOException { FileInputStream f1 = new FileInputStream(args[0]); FileInputStream f2 = new FileInputStream(args[1]); SequenceInputStream s = new SequenceInputStream(f1, f2); int c; while ((c = s.read()) != -1) System.out.write(c); s.close(); //f1 si f2 sunt nchise automat
}//main }//class
folosirea unei enumerari - primul constructor concatenarea pe rnd a acestora folosind al 2-lea constructor; concatenarea a 3 fisiere va construi un flux de intrare astfel:
FileInputStream f1 = new FileInputStream(args[0]); FileInputStream f2 = new FileInputStream(args[1]); FileInputStream f3 = new FileInputStream(args[2]); SequenceInputStream s = new SequenceInputStream(
59
Observati ca toate aceste clase descriu fluxuri de octeti. Filtrarea datelor nu trebuie vazuta ca o metoda de a elimina anumiti octeti dintrun flux ci de transforma acesti octeti n date care sa poata fi interpretate sub alta forma. Asa cum am vazut la citirea/scrierea cu zona tampon clasele de filtrare BufferedInputStream si BufferedOutputStream grupeaza datele unui flux ntr-un buffer, urmnd ca citirea/scrierea sa se faca prin intermediu acelui buffer (vezi "Citirea si scrierea cu zona tampon"). Asadar fluxurile de filtrare nu elimina date citite sau scrise de un anumit flux, ci introduc o noua modalitate de manipulare a lor. Din acest motiv fluxurile de filtrare vor contine anumite metode specializate pentru citirea/scrierea datelor, altele dect cele comune tuturor fluxurilor (metode de tip read/write). Folosirea fluxurilor de filtrare se face prin atasarea lor de un flux care se ocupa efectiv de citirea/scrierea datelor:
FluxFiltrare numeFlux = new FluxFiltrare ( referintaAltFlux )
Cele mai importante clase din aceasta categorie sunt DataInputStream si DataOutputStream
60
Constructorii si metodele cele mai importante (altele dect read/write) sunt date n tabelul de mai jos : DataInputStream DataOuputStream
//Constructor //Constructor DataInputStream(InputStream in) DataOutputStream(OutputStream out) readBoolean( ) readByte( ) readChar( ) readDouble( ) readFloat( ) readInt( ) readLong( ) readShort( ) readUnsignedByte( ) readUnsignedShort( ) String readUTF( ) writeBoolean( boolean v ) writeByte( int v ) writeChar( int v ) writeDouble( double v ) writeFloat( float v ) writeInt( int v ) writeLong( long v ) writeShort( int v ) writeBytes( String s ) writeChars( String s ) writeUTF( String str )
Aceste metode au denumirile generice de readXXX si writeXXX specificate de interfetele DataInput si DataOutput. Pot provoca exceptii de tipul IOException.
Atentie: Un fisier n care au fost scrise informatii folosind metode writeXXX nu va putea fi citit dect prin metode readXXX.
In general intrarea standard este tastatura iar iesirea standard este ecranul. Intrarea si iesirea standard sunt de fapt niste obiecte pre-create ce descriu fluxuri de date pentru citirea respectiv scrierea la dispozitivele standard ale sistemului. Aceste obiecte sunt definite publice n clasa System si sunt: Variabila
System.in System.out System.err
Tip flux
InputStream PrintStream
61
Fluxurile de iesire pot fi folosite asadar fara probleme deoarece tipul lor este PrintStream, clasa primitiva pentru scrierea efectiva a datelor. In schimb fluxul standard de intrare System.out este de tip InputStream care este o clasa abstracta, deci pentru a-l putea utiliza va trebui sa-l folosim mpreuna cu un flux de procesare a datelor sau cu orice alt flux ce permite citirea efectiva a datelor.
62
Redirectarea iesirii este utila n special atunci cnd sunt afisate foarte multe date pe ecran si acestea se deruleaza mai repede dect putem citi. Putem redirecta afisarea catre un fisier pe care sa-l citim dupa executia programului. Secventa clasica de redirectare a iesirii este:
PrintStream out = new PrintStream(new BufferedOutputStream( new FileOutputStream("rezultate.out"))); System.setOut (out);
Redirectarea intrarii poate fi utila pentru un program consola care primeste niste valori de intrare. Pentru a nu le scrie de la tastatura de fiecare data n timpul testarii programului ele pot fi puse ntr-un fisier, redirectnd intrarea standard. In momentul cnd testarea programului a luat sfrsit redirectarea poate fi eliminata, datele fiind cerute din nou de la tastatura.
Exemplu de folosire a redirectarii: import java.io.*; class Redirectare { public static void main(String[] args) { try { BufferedInputStream in = new BufferedInputStream( new FileInputStream("Redirectare.java")); PrintStream out = new PrintStream(new BufferedOutputStream( new FileOutputStream("test.out"))); System.setIn(in); System.setOut(out); System.setErr(out); BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); String s; while((s = br.readLine()) != null) System.out.println(s); out.close(); // Atentie! } catch(IOException e) {
63
} } }
e.printStackTrace();
un identificator (un sir care nu este ntre ghilimele) un numar un sir de caractere un comentariu un separator
Atomii lexicali sunt despartiti ntre ei de separatori. Implicit acesti separatori sunt cei obisnuti( spatiu, tab, virgula, punct si virgula), nsa pot fi schimbati prin diverse metode ale clasei. Constructorii acestei clase sunt:
public StreamTokenizer( Reader r ) public StreamTokenizer( InputStream is )
Identificarea tipului si valorii unui atom lexical se face prin intermediul variabilelor:
TT_EOF - atom ce marcheaz sfrsitul fluxului TT_EOL - atom ce marcheaz sfrsitul unei linii TT_NUMBER - atom de tip numar TT_WORD - atom de tip cuvnt nval - valoarea unui atom numeric sval - sirul continut de un atom de tip cuvnt ttype - tipul ultimului atom citit din flux
Citirea atomilor din flux se face cu metoda nextToken(), care returneza tipul atomului lexical citit si scrie n variabilele nval sau sval valoarea corespunzatoare atomului. Exemplul tipic de folosire a unui analizor lexical este citirea unei secvente de numere si siruri aflate ntr-un fisier sau primite de la tastatura:
//Citirea unei secvente de numere si siruri import java.io.*;
64
public class TestTokenizer { public static void main(String args[]) throws IOException{ FileInputStream fis = new FileInputStream("test.dat"); BufferedReader br = new BufferedReader(new InputStreamReader(fis)); StreamTokenizer st = new StreamTokenizer(br); int tip = st.nextToken(); lexical //citesc primul atom
} }
while (tip != StreamTokenizer.TT_EOF) { switch (tip) { case StreamTokenizer.TT_WORD : //cuvant System.out.println(st.sval); break; case StreamTokenizer.TT_NUMBER : //numar System.out.println(st.nval); } tip = st.nextToken();//urmatorul atom }
Asadar, modul de utilizare tipic pentru un analizor lexical este ntr-o bucla "while" n care se citesc atomii unul cte unul cu metoda nextToken pna se ajunge la sfrsitul fluxului (TT_EOF). In cadrul buclei "while" se afla tipul atomul curent curent (ntors de metoda nextToken) si apoi se afla valoarea numerica sau sirul de caractere corespunzator atomului respectiv. Un exemplu mai simplu de folosire (dar nepractic) ar fi citirea unui ntreg sau a unui sir de caractere de la tastatura:
//Citirea unui ntreg de la tastatura import java.io.*; public class TestReadIn { public static void main(String args[]) { try{ Reader r = new InputStreamReader(System.in); StreamTokenizer st = new StreamTokenizer(r); System.out.print("n="); int tip = st.nextToken(); System.out.println("Valoarea lui n este " + (int)st.nval); } catch (IOException e) {} } }
Fluxurile sunt, asa cum am vazut procese secventiale de intrare/iesire. Acestea sunt adecvate pentru scrierea/citirea de pe medii secventiale de memorare a datelor cum ar fi banda magnetica,etc. desi sunt foarte utile si pentru dispozitive n care informatia poate fi accesata direct. Clasa RandomAccesFile:
o o o o
o o
permite accesul nesecvential (direct) la continutul unui fisier. este o clasa de sine statatoare, subclasa directa a clasei Object. se gaseste n pachetul java.io. implementeaza interfetele DataInput si DataOutput, ceea ce nseamna ca sunt disponibile metode de tipul readXXX, writeXXX (vezi "Clasele DataInputStream si DataOutputStream"). permite att citirea ct si scriere din/in fisiere cu acces direct permite specificarea modului de acces al unui fisier (read-only, read-write)
Exemple:
RandomAccesFile f1 = new RandomAccessFile("fisier.txt", "r"); //deschide un fisier pentru citire RandomAccesFile f2 = new RandomAccessFile("fisier.txt", "rw"); //deschide un fisier pentru scriere si citire
Clasa RandomAccesFile suporta notiunea de pointer de fisier. Acesta este un indicator ce specifica pozitia curenta n fisier. La deschiderea unui fisier pointerul are valoarea 0, indicnd nceputul fisierului. Apeluri la metode readXXX sau writeXXX deplaseaza pointerul fisierului cu numarul de octeti cititi sau scrisi de metodele respective.
66
In plus fata de metodele de citire/scriere clasa pune la dispozitie si metode pentru controlul pozitiei pointerului de fisier. Acestea sunt:
int skipBytes ( int n ) void seek (long pozitie) long getFilePointer ( )
Muta pointerul fisierului nainte cu un numar specificat de octeti Pozitioneaza pointerului fisierului naintea octetului specificat. Returneaza pozitia pointerului de fisier (pozitia de la care se citeste/la care se scrie)
Clasa File
Clasa File are un nume nselator, ntruct ea nu se refera doar la un fisier ci poate reprezenta fie un fisier anume, fie multimea fisierelor dintr-un director. O instanta a acestei clase poate sa reprezinte asadar: un fisier sau un director. Specificarea unui fisier/director se face prin specificare caii absolute spre acel fisier sau a caii relative fata de directorul curent. Acestea trebuie sa respecte conventiile de specificare a cailor si numelor fisierelor de pe masina gazda. Utilitate clasei File consta n furnizarea unei modalitati de a abstractiza dependentele cailor si numelor fisierelor fata de masina gazda precun si punerea la dispozitie a unor metode pentru lucrul cu fisere si directoare la nivelul sistemului de operare. Astfel, n aceasta clasa vom gasi metode pentru testarea existentei, stergerea, redenumirea unui fisier sau director, crearea unui director, listarea fisierelor dintrun director, etc. Trebuie mentionat si faptul ca majoritatea constructorilor fluxurilor care permit accesul la fisiere accepta ca argument un obiect de tip File n locul unui sir ce reprezinta numele fisierului accesat.
File f_in = new File("fisier.txt"); FileInputStream st_in = new FileInputStream(f_in)
Cel mai uzual constructor al clasei File este: public File( String Metodele mai importante ale clasei File sunt:
boolean isDirectory( ) boolean isFile( ) String String String String getName( ) getPath( ) getAbsolutePath() getParent()
fisier)
Afla numele (fara cale), calea fisierului sau directorului reprezentat de obiectul respectiv Testeaza daca exista un anumit fisier/director
boolean exists( )
67
Sterge fisierul/directorul reprezentat de obiect Creeaza un director Creeaza o succesiune de directoare Redenumeste un fisier/director Creeaza o lista cu numele fisierelor dintr-un director
String[] list( ) String[] list (FilenameFilter filter ) boolean canRead( ) boolean canWrite( ) long length( ) long lastModified( )
Creeaza o lista cu numele fisierelor dintr-un director filtrate dupa un anumit criteriu specificat.(vezi "Interfata FilenameFilter") Testeaza daca un anumit fisier poate fi folosit pentru citire, respectiv scriere Afla lungimea si data ultimei modificari a unui fisier.
68
"\n Data ultimei modificare: " + f.lastModified()); if(f.isFile()) System.out.println("Este fisier"); else if(f.isDirectory()) System.out.println("Este director"); } public static void main(String[] args) { for(int i=0; i < args.length; i++) fileInfo ( new File(args[i]) ); } }
Interfata FilenameFilter
vezi "Interfete"
Curs 4 Interfete
Ce este o interfata ? Definirea unei interfete Implementarea unei interfete Exemplu de interfata Diferente ntre o interfata si o clasa abstracta Mostenire multipla prin intermediul interfetelor Utilitatea interfetelor Crearea grupurilor de constante Transmiterea metodelor ca parametri (call-back) Interfata FilenameFilter
Ce este o interfata ?
Interfetele duc conceptul de clasa abstracta cu un pas nainte prin eliminarea oricarei implementari a metodelor, punnd n practica unul din conceptele POO de separare a modelului unui obiect (interfata) de implementarea sa. Asadar, o interfata poate fi privita ca un protocol de comunicare ntre obiecte. O interfata Java defineste un set de metode dar nu specifica nici o implementare pentru 69
ele. O clasa care implementeaza o interfata trebuie obligatoriu sa specifice implementari pentru toate metodele interfetei, supunndu-se asadar unui anumit comportament. Definitie O interfata este o colectie de metode fara implementare si declaratii de constante
tuturor claselor indiferent de pachetul din care fac parte. O interfata care nu este publica este accesibila doar claselor din pachetul din care face parte interfata. O clasa poate extinde oricte interfete. Acestea se numesc superinterfete si sunt separate prin virgula (vezi "Mostenirea multipla prin intermediul interfetelor"). Corpul unei interfete contine: constante: acestea pot fi sau nu declarate cu modificatorii public, static si final care sunt impliciti; nici un alt modificator nu poate aparea n declaratia unei variabile a unei interfete Constantele dintr-o interfata trebuie obligatoriu initializate.
interface NumeInterfata { int MAX = 100; //echivalent cu public static final MAX = 100; int MAX; //ilegal - fara initializare private int x = 1; //ilegal }
metode fara implementare: acestea pot fi sau nu declarate cu modificatorul public care este implicit; nici un alt modificator nu poate aparea n declaratia unei metode a unei interfete.
interface NumeInterfata { void metoda(); public void metoda(); protected void metoda2(); //echivalent cu //ilegal
Atentie Variabilele unei interfete sunt implicit publice chiar daca nu sunt declarate cu modificatorul public. Variabilele unei interfete sunt implicit constante chiar daca nu sunt declarate cu modificatorii static si final.
70
Metodele unei interfete sunt implicit publice chiar daca nu sunt declarate cu modificatorul public. In variantele mai vechi de Java era permis si modificatorul abstract n declaratia interfetei si n declaratia metodelor, nsa a fost eliminat deoarece att interfata ct si metodele sale sunt implicit abstracte.
O clasa poate implementa oricte interfete. (vezi "Mostenirea multipla prin intermediul interfetelor"). O clasa care implementeaza o interfata trebuie obligatoriu sa specifice cod pentru toate metodele interfetei. Din acest motiv, odata creata si folosita la implementarea unor clase, o interfata nu mai trebuie modificata , n sensul ca adaugarea unor metode noi sau schimbarea signaturii metodelor existente va duce la erori n compilarea claselor care o implementeaza. Modificarea unei interfete implica modificarea tuturor claselor care implementeaza acea interfata! Implementarea unei interfete poate sa fie si o clasa abstracta.
Exemplu de interfata
interface Instrument { //defineste o metoda fara implementare void play(); } class Pian implements Instrument { //clasa care implementeaza interfata //trebuie obligatoriu sa implementeze metoda play public void play() { System.out.println("Pian.play()"); } } class Vioara implements Instrument { //clasa care implementeaza interfata //trebuie obligatoriu sa implementeze metoda play public void play() { System.out.println("Vioara.play()"); }
71
} public class Muzica { //clasa principala static void play(Instrument i) { //metoda statica care porneste un instrument generic //ce implementeaza interfata Instrument i.play(); } static void playAll(Instrument[] e) { for(int i = 0; i < e.length; i++) play(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[2]; int i = 0; orchestra[i++] = new Pian(); orchestra[i++] = new Vioara(); playAll(orchestra); } }
Se observa ca folosind interfata Instrument putem adauga noi clase de instrumente fara a schimba codul metodelor play si playAll din clasa principala ntruct acestea primesc ca parametru un instrument generic. Atentie O interfata nu este o clasa, dar orice referinta la un obiect de tip interfata poate primi ca valoare o referinta la un obiect al unei clase ce implementeaza interfata respectiva (upcast). Din acest motiv interfetele pot fi privite ca tipuri de date.
72
O interfata mosteneste att constantele ct si declaratiile de metode de la superinterfetele sale. O clasa mosteneste doar constantele unei interfete. Exemplu de clasa care implementeaza mai multe interfete:
interface Inotator { void inoata(); } interface Zburator { void zboara(); } class Luptator { public void lupta() {} } class Erou extends Luptator implements Inotator, Zburator { public void inoata() {} public void zboara() {} }
73
Atentie O clasa nu poate avea dect o superclasa O clasa poate implementa oricte interfete O clasa mosteneste doar constantele unei interfete O clasa nu poate mosteni implementari de metode dintr-o interfata Ierarhia interfetelor este independenta de ierarhia claselor care le implementeaza
Utilitatea interfetelor
O interfata defineste un protocol ce poate fi implementat de orice clasa, indiferent de ierarhia de clase din care face parte. Interfetele sunt utile pentru: o o o definirea unor similaritati ntre clase independente fara a forta artificial o legatura ntre ele. asigura ca toate clasele care implementeaza o interfata pun la dipozitie metodele specificate n interfata; de aici rezulta posibilitatea implementarii unitare a unor clase prin mai multe modalitati. specificarea metodelor unui obiect fara a deconspira implementarea lor (aceste obiecte se numesc anonime si sunt folosite la livrarea unor pachete cu clase catre alti programatori: acestia pot folosi clasele respective dar nu pot vedea implementarile lor efective) definirea unor grupuri de constante transmiterea metodelor ca parametri (tehnica Call-Back) (vezi "Transmiterea metodelor ca parametri").
o o
74
else
75
Interfata FilenameFilter
Instantele claselor ce implementeaza aceasta interfata sunt folosite pentru a crea filtre pentru fisiere si sunt primite ca argumente de metode care listeaza continutul unui director, cum ar fi metoda list a clasei File. Aceasta interfata are o singura metoda accept care specifica criteriul de filtrare si anume, testeaza daca numele fisierului primit ca parametru ndeplineste conditiile dorite de noi. Definitia interfetei:
public interface FilenameFilter { // Metode public boolean accept( File dir, String numeFisier ); }
Asadar orice clasa de specificare a unui filtru care implementeza interfata FilenameFilter trebuie sa implementeze metoda accept a acestei interfete. Aceste clase mai pot avea si alte metode, de exemplu un constructor care sa primeasca criteriul de filtrare, adica masca dupa care se filtreaza fisierele. In general, o clasa de specificare a unui filtru are urmatorul format:
class DirFilter implements FilenameFilter { String filtru; //constructorul DirFilter(String filtru) { this.filtru = filtru; } //implementarea metodei accept public boolean accept(File dir, String nume) { //elimin informatiile despre calea fisierului String f = new File(nume).getName(); if (filtrul este indeplinit) return true; else return false; } }
Metodele cele mai uzuale ale clasei String folosite pentru filtrarea fisierelor sunt:
boolean endsWith(String s) //testeaza daca un sir se termina cu sirul specificat s int indexOf(String s) //testeaza daca un sirul are ca subsir sirul specificat
76
Instantele claselor pentru filtrare sunt primite ca argumente de metode de listare a continutului unui director. O astfel de metoda este metoda list a clsei File:
String[] list (FilenameFilter filtru )
Observati ca aici interfata este folosita ca un tip de date, ea fiind substituita cu orice clasa care o implementeaza. Acesta este un exemplu tipic de transmitere a unei functii (functia de filtrare accept) ca argument al unei metode. Listarea fisierelor din directorul curent care au extensia .java
import java.io.*; public class DirList2 { public static void main(String[] args) { try { File director = new File("."); String[] list; list = director.list(new FiltruExtensie("java")); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } catch(Exception e) { e.printStackTrace(); }
} } class FiltruExtensie implements FilenameFilter { String extensie; FiltruExtensie (String extensie) { this.extensie = extensie; } public boolean accept (File dir, String nume) { return ( nume.endsWith("." + extensie) ); } }
Exemplu de folosire a claselor anonime In cazul n care nu avem nevoie de filtrarea fisierelor dintr-un director dect o singura data, pentru a evita crearea unei noi clase care sa fie folosita pentru filtrare putem apela la o clasa interna anonima, aceasta situatie fiind un exemplu tipic de folosire a acestora.
import java.io.*; public class DirList3 { public static void main(String[] args) { try { File director = new File("."); String[] list;
77
filtrului
//folosim o clasa anonima pentru specificarea list = director.list(new FilenameFilter() { public boolean accept (File return }
dir,String nume)
( nume.endsWith(".java")); } );
Curs 4 Pachete
Crearea unui pachet Denumirea unui pachet Folosirea membrilor unui pachet Importul unei clase sau interfete Importul unui pachet (importul la cerere) Pachetele JDK
78
Graf.java
package grafuri; class Graf {...} class GrafPerfect extends Graf {...}
Arbore.java
package grafuri; class Arbore {...} class ArboreBinar extends Arbore {...}
Clasele Graf, GrafPerfect, Arbore, ArboreBinar vor face parte din acelasi pachet grafuri. Instructiunea package actioneaza asupra ntregului fisier sursa la nceputul caruia apare. Cu alte cuvinte nu putem specifica faptul ca anumite clase dintr-un fisier sursa apartin unui pachet iar altele altui pachet. Daca nu este specificat un anumit pachet, clasele unui fisier sursa vor face parte din pachetul implicit (care nu are nici un nume). In general, pachetul implicit este format din toate clasele si intefetele directorului curent. Este recomandabil ca toate clasele si intefetele sa fie plasate n pachete. Pachetul implicit este folosit doar pentru aplicatii mici sau la nceputul dezvoltarii unei aplicatii.
Ce se ntmpla nsa cnd doi programatori care lucreaza la un proiect comun folosesc clase cu acelasi nume ce se gasesc n pachete cu acelasi nume ? Pentru a evita acest lucru companiile folosesc inversul domeniului lor Internet n denumirea pachetelor implementate n cadrul companiei, cum ar fi com.company.numePachet. In cadrul unei aceeasi companii conflictele de nume trebuie rezolvate prin diverse conventii de uz intern.De exemplu, adresa mea de e-mail este acf@infoiasi.ro, ceea ce nseamna ca domeniul meu Internet este infoiasi.ro. Pachetele create de mine ar trebui denumite ro.infoiasi.NumePachet. Pentru a rezolva conflicte cu alti programatori din acelasi domeniu cu mine pachetele s-ar putea numi: ro.infoiasi.acf.NumePachet.
Conform specificatiilor de acces ale unei clase si ale mebrilor ei doar clasele publice si membrii declarati publici ai unei clase sunt accesibili n afara pachetului n care acestea se gasesc. (vezi "Specificatori de acces pentru membrii unei clase") Pentru a folosi o clasa publica dintr-un pachet sau pentru a apela o metoda publica a unei clase public a unui pachet exista trei solutii: specificarea numelui complet al clasei importul clasei respective importul ntregului pachet n care se gaseste clasa Specificarea numelui complet al calsei se face, asa cum am vazut, prin prefixarea numelui clasei cu numele pachetului: numePachet.NumeClasa. Aceasta metoda este recomandata doar pentru cazul n care folosirea acelei clase se face o singura data sau foarte rar. De exemplu ar fi extrem de neplacut sa scriem de fiecare data cnd vrem sa declaram un sir de caractere sau sa folosim un obiect grafic secvete de genul;
java.lang.String s = "neplacut"; java.awt.Rectangle r = new java.awt.Rectangle(); java.awt.Circle c = new java.awt.Circle();
In aceste situatii vom importa (include) clasa respective sau ntreg pachet din care face parte in aplicatia noastra. Acest lucru se realizeaza prin instructiunea import, care trebuie sa apara la nceputul fisierelor sursa, imediat dupa instructiunea package.
Din acest moment vom putea folosi n clasele fisierului n care am plasat instructiunea de import numele scurt al clasei Rectangle
Rectangle r = new Rectangle(0,0,100,100);
Aceasta abordare este eficienta n cazul n care nu avem nevoie dect de acea clasa sau doar de cteva clase din pachetul respectiv. Daca n exemplul nostru am avea nevoie si de clasele Circle, Line, Point, Polygon ar trebui sa avem cte o instructiune de import pentru fiecare dintre ele:
import import import import import java.awt.Rectangle; java.awt.Circle; java.awt.Line; java.awt.Point; java.awt.Polygon;
import la cerere deoarece ncarcarea claselor se face dinamic n momentul apelarii lor. Este cel mai uzual tip de import.
import java.awt.*;
Din acest moment vom putea folosi n clasele fisierului n care am plasat instructiunea de import numele scurt al tuturor claselor pachetului importat:
Rectangle r = new Rectangle(); Circle c = new Circle(); ...
Atentie * nu are semnificatia uzuala de wildcard (masca) si nu poate fi folosit dect ca atare.
import java.awt.C*; //eroare de compilare
In cazul n care sunt importate doua pachete care contin o clasa cu acelasi nume atunci referirea la ea trebuie facuta folosind numele complet al clasei respective.
//Stack.java package my_package; class Stack { ... } //alt fisier sursa import java.util.*; import my_package.*; ... Stack s = new Stack(); //ilegal -> conflict de nume java.util.Stack s1 = new java.util.Stack(); //corect my_package.Stack s2 = new my_package.Stack();//corect
Mediul Java importa automat trei pachete pentru toate fisierele sursa: pachetul java.lang pachetul curent pachetul implicit (fara nume)
Pachetele JDK
Limbajul Java se bazeaza pe o serie de biblioteci (pachete) cu ajutorul carora se pot construi aplicatiile. Exista deci un set de clase deja implementate, ceea ce reduce timpul de dezvoltare a unui program. Cele mai importante sunt:
java.applet java.awt java.beans java.io java.lang java.math java.net java.rmi
suport pt scrierea de appleturi suportul pentru grafica(Abstract Windowing Toolkit) suport pentru scrierea de componente reutilizabile intrari/iesiri, acces la fisiere clasele de baza ale limbajului operatii matematice acces la retea executie la distanta (Remote Message Interface)
81
mecanisme de securitate : criptare, autentificare interogari SQL suport pentru formatarea textelor clase utile : Vector, Stack, Random, etc
Pachetul java.lang contine elementele de baza ale limbajului si este importat automat.
Ce este serializarea ? Serializarea obiectelor o Clasa ObjectOutputStream o Clasa ObjectInputStream Obiecte serializabile o Implementarea interfetei Serializable o Personalizarea serializarii obiectelor o Implementarea interfetei Externalizable Controlul serializarii (cuvntul cheie transient) Exemplu de folosire a serializarii Folosirea serializarii pentru copierea obiectelor
Ce este serializarea ?
Definitie Serializarea este o metoda ce permite transformarea unui obiect ntr-o secventa de octeti din care sa poata fi refacut ulterior obiectul original. Cu alte cuvinte, serializarea permite salvarea ntr-o maniera unitara a datelor mpreuna cu signatura unui obiect pe un mediu de stocare a informatiei extern programului. Procesul invers de citirea a unui obiect serializat pentru a-i reface starea originala se numeste deserializare. Intr-un cadru mai larg, prin serializare se ntelege procesul de scriere/citire a obiectelor. Utilitatea serializarii consta n urmatoarele aspecte: Compensarea diferentelor ntre sisteme de operare, adica putem crea un obiect pe o masina Windows, l serializam, apoi l trimitem prin retea catre o masina UNIX unde va fi corect reconstruit. In acest fel comunicarea ntre sisteme diferite se
82
realizeaza unitar, independent de reprezentarea datelor, ordinea octetilor sau alte detalii specifice sistemelor repective. Permite persistenta obiectelor, ceea ce nseamna ca durata de viata a unui obiect nu este determinata de executia unui program n care acesta este definit - obiectul poate exista si ntre apelurile programelor care l folosesc. Acest lucru se realizeaza prin serializarea obiectului si scrierea lui pe disc nainte de terminarea unui program, apoi, la relansarea programului, obiectul va fi citit de pe disc si starea lui refacuta. Acest tip de persistenta a obiectelor se numeste persistenta usoara, ntruct ea trebuie efectuata explicit de catre programator si nu este realizeazata automat de catre sistem. RMI (Remote Method Invocation) - comunicarea obiectelor prin socket-uri: este o modalitate prin care obiectele de pe o alta masina se comporta ca si cnd ar exista pe masina pe care ruleaza programul nostru. Atunci cnd este trimis un mesaj catre un obiect "remote" (de pe alta masina), serializarea este necesara pentru transportul argumentelor prin retea si pentru returnarea valorilor. Java Beans - sunt componente grafice definite de utilizator si care pot fi folosite la fel ca si componentele grafice standard. Orice componenta Bean are o stare initiala a informatiilor sale, stare care este specificata la definirea sa. Atunci cnd ea este folosita ntr-un program aceasta stare trebuie ncarcata de undeva, ceea ce nseamna ca aceste componente trebuie serializate si salvate pe disc.
Un aspect important al serializarii este ca nu salveaza doar imaginea unui obiect ci si toate referintele la alte obiecte pe care acesta le contine. Acesta este un proces recusiv de salvare a datelor, ntruct celelalet obiectele referite de obiectul care se serializeaza pot referi la rndul lor alte obiecte, s.a.md. Asadar obiectele care construiesc starea altui obiect formeaza o ntreaga retea de obiecte, ceea ce nseamna ca un algoritm de salvare a starii unui obiect nu este facil. In cazul n care starea unui obiect este formata doar din valori ale unor variabile de tipuri primitive, atunci salvarea datelor napsulate n acel obiect se poate face si prin salvarea pe rnd a datelor, folosind clasa DataOutputStream, pentru ca apoi sa fie restaurate prin metode ale clasei DataInputStream, dar, asa cum am vazut, o asemenea abordare nu este n general suficienta, deoarece pot aparea probleme cum ar fi : datele obiectului pot fi instante ale altor obiecte, unele cmpuri fac referinta la acelasi obiect, etc. Serializarea obiectelor se realizeaza prin intermediul fluxurilor definite de clasele ObjectOutputStream (pentru salvare) si ObjectInputStream (pentru restaurare).
Serializarea obiectelor
Serializarea obiectelor se realizeaza prin intermediul fluxurilor definite de clasele ObjectOutputStream (pentru salvare) si ObjectInputStream (pentru restaurare). Acestea sunt fluxuri de procesare ceea ce nseamna ca ele vor fi folosite mpreuna cu alte fluxuri pentru citirea/scrierea efectiva a datelor pe mediul extern pe care va fi salvat sau de pe care va fi restaurat un obiect serializat. Mecanismul implicit de serializare a unui obiect va salva numele clasei obiectului, signatura clasei obicetului, valorile tuturor cmpurile serializabile ale obiectului (vezi 83
"Controlul serializarii"). Referintele la alte obiecte serializabile din cadrul obiectului curent vor duce automat la serializarea acestora iar referintele multiple catre un acelasi obiect sunt codificate utiliznd un algoritm care sa poata reface "reteaua de obiecte" la aceeasi stare ca atunci cnd obiectul original a fost salvat. Clasele ObjectInputStream si ObjectOutputStream implementeaza indirect interfetele DataInput, respectiv DataOutput, interfete ce declara metode de tipul readXXX, respectiv writeXXX pentru scrierea/citirea datelor primitive. Pe lnga aceste metode vor exista si metode pentru scrierea/citirea obiectelor. Metodele pentru serializarea obiectelor sunt:
obiect private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException; obiect private void writeObject(ObjectOutputStream stream) throws IOException; //refacere //salvare
Clasele care necesita o serializare speciala trebuie sa supradefineasca metodele writeObject si readObject (obligatoriu pe amndoua!) pentru a implementa metode specifice de serializare. (vezi "Personalizarea serializarii obiectelor").
Clasa ObjectOutputStream
Scrierea obiectelor pe un flux este un proces extrem de simplu. Exemplul de mai jos, afla timpul curent n milisecunde construind un obiect de tip Date si l salveaza ntr-un fisier theTime:
FileOutputStream out = new FileOutputStream("theTime"); ObjectOutputStream s = new ObjectOutputStream(out); s.writeObject("Today"); s.writeObject(new Date()); s.flush(); s.close(); Asadar metoda pentru scrierea unui obiect este writeObject, responsabila cu serializarea completa a obiectului. Deoarece implementeaza interfata DataOutput, pe lnga metoda de scriere a obiectelor, clasa pune la dispozitie si metode de tipul writeXXX pentru
scrierea tipurilor de date primitive, astfel nct apeluri ca cele de mai jos sunt permise :
FileOutputStream out = new FileOutputStream("t.tmp"); ObjectOutputStream s = new ObjectOutputStream(out); s.writeInt(12345); s.writeDouble(12.345); s.writeUTF("Sir de caractere"); s.flush(); s.close();
84
Metoda writeObject arunca exceptii de tipul NotSerializableException daca obiectul primit ca argument nu este serializabil. Vom vedea n continuare ca un obiect este serializabil daca este instanta a unei clase ce implementeaza interfata Serializable.
Clasa ObjectInputStream
Odata ce au fost scrise obiecte si tipuri primitive de date pe un flux, citirea acestora si reconstruirea obiectelor salvate se va face printr-un flux de intrare de tip ObjectInputStream. Acesta este de asemenea un flux de procesare si va trebui asociat cu un flux pentru citirea efectiva a datelor, de exemplu FileInputStream (pentru date salvate ntr-un fisier).
FileInputStream in = new FileInputStream("theTime"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject();
Asadar, metoda pentru citirea unui obiect serializat si refacerea starii lui este readObject. Clasa ObjectInputStream implementeaza interfata DataInput, deci, pe lnga metoda de citire a obiectelor clasa pune la dispozitie si metode de tipul readXXX pentru citirea tipurilor de date primitive.
FileInputStream in = new FileInputStream("t.tmp"); ObjectInputStream s = new ObjectInputStream(in); int n = s.readInt(); double d = s.readDouble(12.345); String sir = s.readUTF();
Atentie Ca si la celelate fluxuri de date (care implemeteaza interfata DataInput) citirea dintr-un flux de obiecte trebuie sa se faca exact n ordinea n carea acestea au fost scrise. Trebuie observat ca metoda readObject returneaza un obiect de tipul Object si nu de tipul corespunzator obiectului citit, conversia la acest tip trebuind sa se faca explicit:
Date date = s.readObject(); Date date = (Date)s.readObject(); // ilegal // corect
Obiecte serializabile
Un obiect este serializabil daca si numai daca clasa din care face parte implementeaza interfata Serializable. Asadar, daca dorim ca instantele unei clase sa poata fi serializate, clasa respectiva trebuie sa implementeze interfata Serializable. Aceasts interfata este mai deosebita, n sensul ca nu contine nici o declaratie de metoda, singurul ei scop fiind de a identifica clasele ale caror obiecte sunt serializabile.
85
// nimic!
Crearea claselor ale caror instante sunt serializabile este extrem de facila: la clasa respectiva trebuie sa adaugam n declaratia ei ca implementeze interfata Serializable si nimic mai mult:
public class ClasaSerializabila implements Serializable { //putem sa nu scriem nici o metoda deoarece //interfata nu declara nici o metoda! }
Asadar, clasa poate sa nu contina nici o metoda, ea va contine totusi metode altfel nu ar avea nici un rost, dar metodele vor fi specifice scopului pentru care ea a fost creata si nu vor avea legatura cu serializarea. Asa cum am vazut, serializarea implicita a obiectelor oricarei clase este definita n metoda defaultWriteObject a clasei ObjectOutputStream care va salva toate datele necesare reconstruirii obiectului : numele clasei, signatura, valorile variabilelor membre si obiectele referite de acestea. In majoritatea cazurilor aceasta metoda de serializare este suficienta, nsa o clasa poate avea nevoie de mai mult control asupra serializarii.
De asemenea, uzual, primul lucru pe care trebuie sa l faca aceste metode este apelul la metodele standard de serializare a obiectelor defaultWriteObject, respectiv defaultReadObject si abia apoi sa execute diverse operatiuni suplimentare. Forma lor generala este:
private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); // personalizarea serializarii }
private void readObject(ObjectInputStream s) throws IOException,ClassNotFoundException { s.defaultReadObject(); // personalizarea deserializarii . . . // actualizarea starii obiectului (daca e necesar) }
86
Metodele writeObject si readObject sunt responsabile cu serializarea clasei n care sunt definite, serializarea superclasei sale fiind facuta automat (si implicit). Daca nsa o clasa trebuie sa-si coordoneze serializarea proprie cu serializarea superclasei sale, atunci trebuie sa implementeze interfata Externalizable.
superclasa ei.
Controlul serializarii
Exista cazuri cnd dorim ca unele variabile membre sau sub-obiecte ale unui obiect sa nu fie salvate automat n procesul de serializare. Acestea sunt cazuri comune atunci cnd respectivele cmpuri reprezinta informatii confidentiale, cum ar fi parole, sau variabile auxiliare pe care nu are rost sa le salvam. Chiar declarate ca private n cadrul clasei aceste cmpuri participa la serializare. O modalitate de a controla serializare este implementarea interfetei Externalizable, asa cum am vazut anterior. Aceasta metoda este nsa incomoda atunci cnd clasele sunt greu de serializat iar multimea cmpurilor care nu trebuie salvate este redusa. Pentru ca un cmp sa nu fie salvat n procesul de serializare atunci el trebuie declarat cu modificatorul transient si trebuie sa fie ne-static. De exemplu, declararea unei parole ar trebui facuta astfel:
transient private String parola; //ignorat la serializare
//participa la
De asemenea, nu participa la serializare sub-obiectele neserializabile ale unui obiect, adica cele ale caror clase nu au fost declarate ca implementnd interfata Serializable (sau Externalizable).
Exemplu: (cmpurile marcate 'DA' participa la serializare, cele marcate 'NU', nu participa)
87
class A { ... } class B implements Serializable { ... } public class Test implements Serializable { private int x; // DA transient public int y; static int var1; // DA transient static var2; // DA A a; // NU B b1; // DA transient B b2; // NU }
// NU
Atunci cnd o clasa serializabila deriva dintr-o alta clasa, salvarea cmpurilor clasei parinte se va face doar daca si aceasta este serializabila. In caz contrar, subclasa trebuie sa salveze explicit si cmpurile mostenite.
Ex1: class Parinte implements Serializable { int x; } class Fiu extends Parinte implements Serializable { int y; }//La serializarea obiectelor de tip Fiu se salveaza att x ct si y. Ex2: class Parinte { int x; } class Fiu extends Parinte implements Serializable { int y; }//Serializarea nu decurge normal.
88
obj = (MyObject) sin.readObject(); } catch (ClassNotFoundException e) {} sin.close(); fin.close(); System.out.println("A fost restaurat obiectul " + obj); } }
class MyObject implements Serializable { int x; //este salvat transient private int y; //nu este salvat transient static int z; //nu este salvat public MyObject(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public String toString() { return new String("x=" + x + ", y=" + y + ", z=" + z); } }
A fost salvat obiectul x=10, y=20, z=30 Restauram... A fost restaurat obiectul x=10, y=0, z=30
nu face dect sa declare obiectul o2 ca fiind o referinta la obiectul o1 si prin urmarea orice schimbare ntr-unul din cele doua obiecte se va reflecta si n celalalt.
O posibilitate de a face o copie unui obiect este folosirea metodei clone() a clasei Object.
MyObject o1 = new MyObject(10, 20, 30); MyObject o2 = (MyObject) o1.clone();
89
Conversia la clasa MyObject este necesara deoarece metoda clone() returneaza un obiect de tip Object. Deficienta acestei metode este ca nu functioneaza corect dect atunci cnd clasa clonata nu are cmpuri referinta ca alte obiecte, obiectele referite nemaifiind copiate la rndul lor. O metoda clone() care sa realizeze o copie efectiva a unui obiect, mpreuna cu copierea tuturor obiectelor referite de cmpurile acelui obiect poate fi implementata prin mecanismul serializarii astfel:
public Object clone() { try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject(this); out.close(); ByteArrayInputStream bin = new ByteArrayInputStream(); ObjectInputStream in = new ObjectInputStream(bin); Object ret = in.readObject(); in.close(); return ret; } catch (Exception e) { System.out.println(e); return null; } }
Curs 4 Colectii
Ce sunt colectiile ? Interfetele de baza care descriu colectii o Collection o Set o List o Map o SortedSet o SortedMap Implementari ale colectiilor Folosirea eficienta a colectiilor
90
Ce sunt colectiile ?
Definitie O colectie este un obiect care grupeaza mai multe elemente ntr-o singura unitate. Prin colectii vom avea acces la tipuri de date cum ar fi vectori, multimi, tabele de dispersie, etc. Colectiile sunt folosite pentru memorarea si manipularea datelor, precum si pentru transmiterea datelor de la o metoda la alta. In Java colectiile sunt tratate intr-o maniera unitara, fiind organizate intr-o arhitectura ce cuprinde: Interfete: tipuri abstracte de date ce descriu colectiile. Interfetele permit utilizarea colectiilor independent de detaliile implementarilor. Implementari: implementari concrete ale interfetelor ce descriu colectii. Aceste clase reprezinta tipuri de date reutilizabile. Algoritmi: metode care efectueaza diverse operatii utile cum ar fi cautarea, sortarea definite pentru obiecte ce implementeaza interfete ce descriu colectii. Acesti algoritmi se numesc si polimorfici deoarece aceeasi metoda poate fi folosita pe implementari diferite ale unei colectii. Aceste tipuri de algoritmii descriu notiunea de functionalitate reutilizabila. Dintre avantajele oferite de utilizarea colectiilor amintim: Reducerea efortului de programare: prin punerea la dispozitia programatorului a unui set de tipuri de date si algoritmi ce modeleaza structuri si operatii des folosite in aplicatii. Cresterea vitezei si calitatii programului: implementarile efective ale colectiilor sunt de inalta performanta si folosesc algoritmi cu timp de lucru optim. In scrierea unei aplicatii putem sa ne concentram eforturile asupra problemei in sine si nu asupra modului de reprezentare si manipulare a informatiilor.
91
Collection
Collection descrie un grup de obiecte numite si elementele sale. Unele implementari ale acestei interfete permit existenta elementelor duplicate, alte implementari nu. Unele au elementele ordonate, altele nu. Modeleaza o colectie la nivelul cel mai general. In JDK nu exista nici o implementare directa a acestei interfete, ci exista doar implementari ale unor subinterfete mai concrete cum ar fi Set sau List.
Set
Modeleaza notiunea de multime n sens matematic. O multime nu poate avea elemente duplicate.
List
Descrie liste (secvente) de elemente indexate. Listele pot contine duplicate si permit un control precis asupra pozitiei unui element prin intermediul indexului acelui element. O clasa independenta ce implementeaza o functionalitate asemanatoare este clasa Vector.
Map
Implementarile acestei interfete sunt obiecte ce asociaza fiecarui element o cheie unica. Nu pot contine asadar chei duplicate si fiecare chei este asociata la un singur element. O clasa independenta ce implementeaza o functionalitate asemanatoare este clasa HashTable.
SortedSet
Este asemanatoare cu interfata Set la care se adauga faptul ca elementele dintr-o astfel de colectie sunt ordonate ascendent. Pune la dispozitie operatii care beneficiaza de avantajul ordonarii elementelor.
SortedMap
Este asemanatoare cu interfata Map la care se adauga faptul ca multimea cheilor dintr-o astfel de colectie este ordonata ascendent.
Interfata Collection
Interfata Collection modeleaza un grup de obiecte numite elemente. Scopul acestei interfete este de a folosi colectii la un nivel de maxima generalitate. In definitia interfetei vom observa ca metodele se impart in trei categorii.
public interface Collection {
92
// Operatii de baza la nivel de element int size(); boolean isEmpty(); boolean contains(Object element); boolean add(Object element); // Optional boolean remove(Object element); // Optional Iterator iterator(); // Operatii la nivel de colectie boolean containsAll(Collection c); boolean addAll(Collection c); // boolean removeAll(Collection c); // boolean retainAll(Collection c); // void clear(); // // Operatii de conversie in vector Object[] toArray(); Object[] toArray(Object a[]); }
Interfata Set
Modeleaza notiunea de multime n sens matematic. O multime nu poate avea elemente duplicate. Defineste aceleasi metode ca interfata Collection. Doua dintre clasele care ofera implementari concrete ale acestei interfete sunt HashSet si TreeSet. (vezi "Implementari")
Interfata List
Interfata List descrie liste (secvente) de elemente indexate. Listele pot contine duplicate si permit un control precis asupra pozitiei unui element prin intermediul indexului acelui element. In plus fata de elementele definite de interfata Collection avem metode pentru: acces pozitional cautare (aflarea indexului unui element) iterare ordonata extragerea unei subliste Definitia interfetei este data mai jos:
public interface List extends Collection { // Acces pozitional Object get(int index); Object set(int index, Object element); Optional void add(int index, Object element); Optional Object remove(int index); Optional
// // //
93
Optional
abstract boolean addAll(int index, Collection c); // // Cautare int indexOf(Object o); int lastIndexOf(Object o); // Iterare ListIterator listIterator(); ListIterator listIterator(int index); // Extragere sublista List subList(int from, int to);
Interfata Map
Implementarile acestei interfete sunt obiecte ce asociaza fiecarui element o cheie unica. Nu pot contine asadar chei duplicate si fiecare chei este asociata la un singur element. Definitia interfetei este data mai jos:
public interface Map { // Operatii de baza la nivel de element Object put(Object key, Object value); Object get(Object key); Object remove(Object key); boolean containsKey(Object key); boolean containsValue(Object value); int size(); boolean isEmpty(); // Operatii la nivel de colectie void putAll(Map t); void clear(); // Vizualizari ale colectiei public Set keySet(); public Collection values(); public Set entrySet(); // Interfata pentru manipularea unei inregistrari public interface Entry { Object getKey(); Object getValue(); Object setValue(Object value); }
Interfata SortedSet
94
Este asemanatoare cu interfata Set la care se adauga faptul ca elementele dintr-o astfel de colectie sunt ordonate ascendent conform ordinii lor naturale, sau conform cu ordinea data de un comparator specificat la crearea colectiei. Este subclasa a interfetei Set, oferind metode suplimentare pentru: extragere de subliste aflarea primului/ultimului element din lista aflarea comparatorului folosit pentru ordonare Definitia interfetei este data mai jos:
public interface SortedSet extends Set { // Subliste SortedSet subSet(Object fromElement, Object toElement); SortedSet headSet(Object toElement); SortedSet tailSet(Object fromElement); // Capete Object first(); Object last(); Comparator comparator(); }
Interfata SortedMap
Este asemanatoare cu interfata Map la care se adauga faptul ca multimea cheilor dintr-o astfel de colectie este ordonata ascendent conform ordinii naturale, sau conform cu ordinea data de un comparator specificat la crearea colectiei. Este subclasa a interfetei Map, oferind metode suplimentare pentru: extragere de subtabele aflarea primei/ultimei chei aflarea comparatorului folosit pentru ordonare Definitia interfetei este data mai jos:
public interface SortedMap extends Map { SortedMap subMap(Object fromKey, Object toKey); SortedMap headMap(Object toKey); SortedMap tailMap(Object fromKey); Object first(); Object last(); Comparator comparator(); }
95
Implementari
Set HashSet ArrayList TreeMap TreeSet LinkedList
Interfete
JDK 1.2 furnizeaza cte doua clase ce implementeaza fiecare tip de colectie, n fiecare caz prima implementare fiind cea de baza, care va fi in general folosita. Acestea sunt: HashSet, ArrayList si HashMap. Clasele care descriu colectii au multe trasaturi comune cum ar fi: permit elementul null sunt serializabile au definita metoda clone au definita metoda toString, care returneaza o reprezentare ca sir de caractere a colectiei respective permit crearea iteratorilor pentru parcurgere implementarea interfetelor este indirecta, n sensul ca au o Implementarea interfetelor este indirecta n sensul ca aceste clase au superclase abstracte care ofera implementari concrete pentru majoritatea metodelor definite de interfete. Cele mai importante superclase sunt AbstractCollection si AbstractMap, din care sunt apoi extinse clasele abstracte AbstractList si AbstractSet,respectiv AbstractMap. Clasele prezentate in tabelul de mai sus sunt extensii concrete ale claselor abstracte aminitite. Singura interfata care nu are nici o implementare este Collection.
96
colectiei in cazul unor modificari. Sa consideram urmatoarele exemple ce creeaza o lista folosind ArrayList, respectiv LinkedList si executa diverse operatii pe ea.
//exemplul 1 import java.util.*; public class List1 { public static void main(String(args[]) { List lst = new ArrayList(); //List lst = new LinkedList(); final int N = 25000; for(int i=0; i < N; i++) lst.add(new Integer(i)); //* } } //exemplul 2 - List2 Adaugam la exemplul 1 (*) urmatoarea secventa for(int i=0; i < N; i++) lst.get(i); //exemplul 3 - List3 Adaugam la exemplul 1 (*) urmatoarea secventa for(int i=0; i < N; i++) lst.remove(0);
Timpii aproximativi de rulare a acestor programe sunt dati in tabelul de mai jos:
Asadar, adaugarea elementelor este rapida pentru ambele tipuri de liste. ArrayList ofera acces in timp constant la elementele sale si din acest motiv folosirea lui "get" este rapida, n timp ce pentru LinkedList este extrem de lenta, deoarece intr-o lista inlantuita accesul la un element se face prin parcurgerea secventiala a listei pna la elementul respectiv. La eliminarea elementelor din lista folosirea lui ArrayList este lenta deoarece elementele ramase sufera un proces de reindexare (shift la stnga) in timp ce pentru LinkedList este rapida si se face prin simpla schimbare a unor legaturi. Deci, ArrayList se comporta bine pentru cazuri in care avem nevoie de regasirea unor elemente la pozitii diferite in lista, iar LinkedList functioneaza optim atunci cnd facem multe operatii de editare(stergeri, inserari) n corpul listei. De asemenea, ArrayList foloseste mai eficient spatiul de memerie dect LinkedList, deoarece aceasta din urma are nevoie de o structura de date auxiliara pentru memorare unui nod. Nodurile sunt reprezentate prin instante ale unei clase interne, avnd forma:
class Entry { Object element; Entry next; Entry previous; }
97
Concluzia nu este ca una din aceste clase este mai "buna" dect cealalta, ci ca exista diferente substantiale in reprezentarea si comportamentul diferitelor implementari ale colectiilor si ca alegerea unei clase pentru reprezentarea unei colectii trebuie sa se faca n functie de natura problemei ce trebuie rezolvata. Acest lucru este valabil pentru toate tipurile de colectii. De exemplu, HashSet si TreeSet sunt doua modalitati de reprezentare a multimilor. Prima se bazeaza pe folosirea unei tabele de dispersie, a doua pe folosirea unei structuri arborescente.
Algoritmi
Algorimtii polimorfici descrisi n aceasta sectiune sunt metode definite n clasa Collections care permit efectuarea unor operatii utile cum ar fi cautarea, sortarea,etc. Caracterisiticile principale ale algoritmilor sunt: sunt metode statice au un singur argument de tip colectie apelul lor va fi de forma Collections.algoritm([colectie]) majoritatea opereaza pe liste dar si pe colectii arbitrare Metodele mai importante din clasa Collections sunt date in tabelul de mai jos: sort shuffle reverse fill copy min max Sorteaza ascendent o lista referitor la ordinea sa naturala sau la ordinea data de un comparator Amesteca elementele unei liste - opusul lui sort Inverseaza ordinea elementelor dintr-o lista Populeaza o lista cu un element Copie elementele unei liste in alta Returneaza minimul dintr-o colectie Returneaza maximul dintr-o colectie
Iteratori si enumerari
Enumerarile si iteratorii descriu modalitati pentru parcurgerea secventiala a unei colectii. Ei sunt descrisi de obiecte ce implementeaza interfetele Enumeration, respectiv Iterator sau ListIterator. Toate clasele care implementeaza colectii au metode ce returneaza o enumerare sau un iterator pentru parcurgerea elementelor lor. Metodele acestor interfete sunt date in tabelul de mai jos, semnificatiile lor fiind evidente:
98
Enumeration
boolean hasMoreElements() Object nextElement()
Iterator
boolean hasNext() Object next() void remove()
ListIterator
boolean hasNext(),hasPrevious() Object next(), previous() void add(Object o) void remove() void set(Object o)
Iteratorii simpli permit eliminarea elementului curent din colectia pe care o parcurg, cei ordonati (de tip ListIterator) permit si inserarea unui element la pozitia curenta, respectiv modificarea elementului curent. Iteratorii sunt preferati enumerarilor datorita posibilitatii lor de a actiona asupra colectiei pe care o parcurg prin metode de tip remove, add, set dar si prin faptul ca denumirile metodelor sunt mai concise. In exemplul de mai jos punem ntr-un vector numerele de la 1 la 10, le amestecam, dupa care le parcurgem element cu element folosind un iterator.
import java.util.*; class TestIterator{ public static void main(String args[]) { ArrayList a = new ArrayList(); for(int i=0; i<10; i++) a.add(new Integer(i)); Collections.shuffle(a); //amestecam elementele colectiei System.out.println("Vectorul amestecat: " + a); System.out.println("Parcurgem vectorul element cu element:"); System.out.println("\nvarianta 1: cu while"); Iterator it = a.iterator(); while (it.hasNext()) System.out.print(it.next() + " "); System.out.println("\nvarianta 2: cu for"); for(it=a.iterator(); it.hasNext(); ) System.out.print(it.next() + " "); } }
Exemplu
In exemplul de mai jos vom folosi clasa HashMap pentru a tine evidenta angajatilor unei companii. Vom folosi mecanismul serializarii pentru salvarea informatiilor intr-un fisier, respectiv pentru restaurarea lor.
//clasa Angajat import java.io.Serializable; class Angajat implements Serializable { String cod; String nume; int salariu; public Angajat(String cod, String nume, int salariu) { this.cod=cod;
99
this.nume=nume; this.salariu=salariu; } public String toString() { return cod + "\t" + nume + "\t" + salariu; } } //clasa Personal import java.io.*; import java.util.*; class Personal implements Serializable { HashMap personal = new HashMap(); String fisier=null; boolean salvat=false; void load(String fis) throws IOException{ ObjectInputStream in=null; this.fisier=fis; try { in=new ObjectInputStream(new FileInputStream(fisier)); personal = (HashMap)in.readObject(); } catch(FileNotFoundException e) { System.out.println("Fisierul " + fisier + " nu exista!"); } catch(ClassNotFoundException e) { System.out.println("Eroare la incarcarea datelor!"); }finally { if (in != null) in.close(); } } void saveAs(String fis) throws IOException{ ObjectOutputStream out=null; try { out=new ObjectOutputStream(new FileOutputStream(fis)); out.writeObject(personal); salvat=true; System.out.println("Salvare reusita in fisierul " + fis); }catch(IOException e) { System.out.println("Salvarea nu a reusit!"); }finally { if (out != null) out.close(); } } void save() throws IOException { if (fisier == null) fisier="personal.txt"; saveAs(fisier); } Angajat getAngajat(String argumente) { String cod="", nume="";
100
int salariu=0; try { StringTokenizer st=new StringTokenizer(argumente); cod = st.nextToken(); nume = st.nextToken(); salariu = Integer.parseInt(st.nextToken()); }catch(NoSuchElementException e) { System.out.println("Argumente incomplete!"); }catch(NumberFormatException e) { System.out.println("salariul trebuie sa fie numeric!"); } return new Angajat(cod, nume, salariu); } boolean add(String argumente) { Angajat a=getAngajat(argumente); if (personal.containsKey(a.cod)) { System.out.println("Mai exista un angajat cu acest cod!"); return false; } personal.put(a.cod, a); salvat=false; return true; } boolean delete(String cod) { if (personal.remove(cod) == null) { System.out.println("Nu exista nici un angajat cu acest cod!"); return false; } salvat=false; return true; } void update(String argumente) { Angajat a=getAngajat(argumente); delete(a.cod); add(argumente); } void list() { Iterator it=personal.values().iterator(); while (it.hasNext()) System.out.println((Angajat) (it.next())); } void executaComenzi() { String linie, comanda, argumente; try { BufferedReader stdin=new BufferedReader(new InputStreamReader(System.in)); while (true) { linie = stdin.readLine().trim();
101
StringTokenizer(linie);
StringTokenizer st=new comanda=st.nextToken(); argumente=""; while (st.hasMoreTokens()) argumente += argumente=argumente.trim(); if (comanda.startsWith("exit")) break; else if (comanda.startsWith("add"))
else if (comanda.startsWith("del")) else if (comanda.startsWith("update")) else if (comanda.startsWith("list")) else if (comanda.startsWith("load")) else if (comanda.startsWith("saveAs")) else if (comanda.startsWith("save"))
} }
else System.out.println("what ?"); } if (!salvat) save(); System.out.println("bye..."); }catch (IOException e) { System.out.println("Eroare I/O:" + e); e.printStackTrace(); }
//clasa principala GestiuneAngajati public class GestiuneAngajati { public static void main(String args[]) { Personal p = new Personal(); p.executaComenzi(); } }
Privire de ansamblu asupra interfetei grafice Componente AWT Suprafete de afisare (Clasa Container) Gestionarea pozitionarii o Folosirea gestionarilor de pozitionare o Gruparea componentelor (Clasa Panel) Tratarea evenimentelor 102
Exemplu de tratare a evenimentelor Tipuri de evenimente si componentele care le genereaza Evenimente suportate de o componenta Metodele interfetelor de tip "Listener" Folosirea adaptorilor si a claselor interne n tratarea evenimentelor Folosirea ferestrelor o Clasa Window o Clasa Frame o Clasa Dialog (ferestre de dialog) o Clasa FileDialog Folosirea meniurilor o Tratarea evenimentelor generate de meniuri o Meniuri de context (popup) o Acceleratori (clasa MenuShortcut) Folosirea componentelor o Label o Button o Checkbox o CheckboxGroup o Choice o List o Scrollbar o ScrollPane o TextField o TextArea
o o o o o
103
Crearea unei suprafete de afisare (cum ar fi o fereastra) pe care vor fi asezate obiectele grafice care servesc la comunicarea cu utilizatorul (butoane, controale de editare, texte, etc); Crearea si asezarea obiectelor grafice pe suprafata de afisare n pozitiile corespunzatoare; Definirea unor actiuni care trebuie sa se execute n momentul cnd utilizatorul interactioneaza cu obiectele grafice ale aplicatiei; "Ascultarea" evenimentelor generate de obiecte n momentul interactiunii cu utilizatorul si executarea actiunilor corespunzatoare asa cum au fost ele definite.
Majoritatea obiectelor grafice sunt subclase ale clasei Component, clasa care defineste generic o componenta grafica care poate interactiona cu utilizatorul. Singura exceptie o constituie meniurile care descind din clasa MenuComponent. Asadar, print-o componenta sau componenta grafica vom ntelege n continuare orice obiect care are o reprezentare grafica ce poate fi afisata pe ecran si care poate interactiona cu utilizatorul. Exemple de componente sunt ferestrele, butoanele, bare de defilare, etc. In general, toate componentele sunt definte de clase proprii ce se gasesc n pachetul java.awt, clasa Component fiind superclasa abstracta a tuturor acestor clase. Crearea obiectelor grafice nu realizeaza automat si afisarea lor pe ecran. Mai nti ele trebuie asezate pe o suprafata de afisare, care poate fi o fereastra sau suprafata unui applet, si vor deveni vizibile n momentul n care suprafata pe care sunt afisate va fi vizibila. O astfel de suprafata pe care se aseaza obiectele grafice reprezinta o instanta a unei clase obtinuta prin extensia clasei Container; din acest motiv suprafetele de afisare vor mai fi numite si containere. Clasa Container este o subclasa aparte a clasei Component, fiind la rndul ei superclasa tuturor suprafetelor de afisare Java (ferestre, applet-uri, etc). (vezi "Suprafete de afisare") Asa cum am vazut, interfata grafica serveste interactiunii cu utilizatorul. De cele mai multe ori programul trebuie sa faca o anumita prelucrare n momentul n care utilizatorul a efectuat o actiune si, prin urmare, obiectele grafice trebuie sa genereze evenimente n functie de actiunea pe care au suferit-o (actiune transmisa de la tastatura, mouse, etc.). Incepnd cu versiunea 1.1 a limbajului Java evenimentele se implementeaza ca obiecte instanta ale clasei AWTEvent sau ale subclaselor ei. Un eveniment este produs de o actiune a utilizatorului asupra unui obiect grafic, deci evenimentele nu trebuie generate de programator. In schimb ntr-un program trebuie specificat codul care se executa la aparitia unui eveniment. Interceptarea evenimentelor se realizeaza prin intermediul unor clase de tip listener (ascultator, consumator de evenimente), clase care sunt definite n pachetul java.awt.event. In Java, orice componenta poate "consuma" evenimentele generate de o alta componenta grafica. (vezi "Tratarea evenimentelor")
104
//creez fereastra - un obiect de tip frame Frame f = new Frame("O fereastra"); //setez modul de dipunere a ob. pe suprafata ferestrei f.setLayout(new FlowLayout()); //creez cele doua butoane Button b1 = new Button("OK"); Button b2 = new Button("Cancel"); //adaug primul buton pe suprafata ferestrei f.add(b1); f.pack(); //adaug al doile buton pe suprafata ferestrei f.add(b2); f.pack(); //afisez fereastra (o fac vizibila) f.show(); } }
Dupa cum veti observa la executia acestui program, att butoanele adaugate de noi ct si butonul de nchidere a ferestrei sunt functionale, adica pot fi apasate, dar nu realizeaza nimic. Acest lucru se ntmpla deoarece nu am specificat nicaieri codul care trebuie sa se execute la apasarea acestor butoane. De asemenea mai trebuie remarcat ca nu am specificat nicaieri dimensiunile ferestrei sau ale butoanelor si nici pozitiile n acestea sa fie plasate. Cu toate acestea ele sunt plasate unul lnga celalalt, fara sa se suprapuna iar suprafata fereastrei este suficient de mare ct sa cuprinda ambele obiecte. Aceste "fenomene" sunt provocate de un obiect special de tip FlowLayout care se ocupa cu gestionarea ferestrei si cu plasarea componentelor ntr-o anumita ordine pe suprafata ei. Asadar, modul de aranjare nu este o caracteristica a suprafetei de afisare. Fiecare obiect de tip Container, sau o extensie a lui, are asociat un obiect care se ocupa cu dispunerea componentelor pe suprafata de afisare si care se numeste gestionar de pozitionare (Layout Manager). (vezi "Gestionarea pozitionarii")
Componente AWT
Prin componenta vom ntelege n continuare orice obiect care are o reprezentare grafica ce poate fi afisata pe ecran si care poate interactiona cu utilizatorul. Exemple de componente sunt ferestrele, butoanele, bare de defilare, etc. In general, toate componentele sunt definte de clase proprii ce se gasesc n pachetul java.awt, clasa Component fiind superclasa abstracta a tuturor acestor clase. Ierarhia acestor clase este sumarizata n diagrama de mai jos.
105
Din cauza unor diferente esentiale n implementarea meniurilor pe diferite platforme de operare acestea nu au putut fi integrate ca obiecte de tip Component. Superclasa care descrie meniuri este MenuComponent iar ierarhia subclaselor sale este data n diagrama de mai jos:
Asadar, majoritatea obiectelor grafice sunt subclase ale clasei Component, clasa care defineste generic o componenta grafica care poate interactiona cu utilizatorul. Singura exceptie o constituie meniurile care descind din clasa MenuComponent.
106
Asadar, un container este folosit pentru a adauga componente pe suprafata lui. Componentele adaugate sunt memorate ntr-o lista iar pozitiile lor din aceasta lista vor defini ordinea de traversare "front-to-back" a acestora n cadrul containerului. Daca nu este specificat nici un index la adaugarea unei componente atunci ea va fi adaugata pe ultima pozitie a listei.
Gestionarea pozitionarii
Sa consideram mai ntai un exemplu de program Java care afiseaza 5 butoane pe o fereastra:
import java.awt.*; public class TestLayout { public static void main(String args[]) { Frame f = new Frame("Grid Layout"); f.setLayout(new GridLayout(3, 2)); Button Button Button Button Button b1 b2 b3 b4 b5 = = = = = new new new new new
//*
Button("Button 1"); Button("2"); Button("Button 3"); Button("Long-Named Button 4"); Button("Button 5");
107
Sa modificam acum linia marcata cu '*' ca mai jos, lasnd neschimbat restul programului:
Frame f = new Frame("Flow Layout"); f.setLayout(new FlowLayout());
Fereastra afisata dupa aceasta modificare va avea o cu totul altfel de dispunere a componentelor sale:
Motivul pentru care cele doua ferestre arata att de diferit este ca folosesc gestionari de pozitionare diferiti: GridLayout, respectiv FlowLayout. Un gestionar de pozitionare (layout manager) este un obiect care controleaza dimensiunea si aranjarea (pozitia) componentelor unui container. Asadar, modul de aranjare a componentelor pe o suprafata de afisare nu este o caracteristica a clasei Container. Fiecare obiect de tip Container, sau o extensie a lui (Applet, Frame, Panel) are asociat un obiect care se ocupa cu dispunerea componentelor pe suprafata sa : gestionarul de pozitionare. Toate clasele care instantiaza obiecte pentru gestionarea pozitionarii implementeaza interfata LayoutManager. La instantierea unui container se creeaza implicit un gestionar de pozitionare asociat acestui container. De exemplu pentru o fereastra (un obiect de tip Window sau o subclasa a sa) gestionarul implict este de tip BorderLayout, n timp ce pentru un container de tip Panel este o instanta a clasei FlowLayout.
108
Atasarea explicita a unui gestionar de pozitionare la un container se face cu metoda setLayout a clasei Container. Metoda poate primi ca parametru orice instanta a unei clase care implementeaza interfata LayoutManager. Secventa de atasare a unui gestionar pentru un container este:
FlowLayout gestionar = new FlowLayout(); container.setLayout(gestionar); sau, mai uzual : container.setLayout(new FlowLayout());
Programele nu apeleaza n general metode ale gestionarilor de pozitionare iar n cazul cnd avem nevoie de obiectul gestionar l putem obtine cu metoda getLayout din clasa Container. Una din facilitatile cele mai utile oferite de gestionarii de pozitionare este rearanjarea componentele unui container atunci cnd acesta este redimesionat. Pozitiile si dimensiunile componentelor nu sunt fixe, ele fiind ajustate automat de catre gestionar la fiecare redimensionare astfel nct sa ocupe ct mai "estetic" suprafata de afisare. Sunt nsa situatii cnd dorim sa plasam componentele la anumite pozitii fixe iar acestea sa ramna acolo chiar daca redimensionam containerul. Folosind un gestionar de pozitionare aceasta pozitionare absoluta a componentelor nu este posibila si deci trebuie cumva sa renuntam la gestionarea automata a containerul. Acest lucru se realizeaza prin trimitera argumentului null metodei setLayout:
//pozitionare absoluta a componentelor in container container.setLayout(null); pozitionarea absoluta, nu va mai fi suficient sa adaugam cu metoda add
Folosind componentele n container ci va trebui sa specificam pozitia si dimensiunea lor - acest lucru era facut automat de gestionarul de pozitionare.
container.setLayout( null ); Button b = new Button("Buton"); b.setSize(10, 10); b.setLocation (0, 0); b.add();
In general, se recomanda folosirea gestionarilor de pozitionare n toate situatiile cnd acest lucru este posibil, deoarece permit programului sa aiba aceeasi "nfatisare" indiferent de platforma si rezolutia pe care este rulat. Pozitionarea fixa poate ridica diverse probleme n acest sens. Sa analizam n continuare pe fiecare din cei cinci gestionari amintiti anterior.
Gestionarul FlowLayout
Acest gestionar aseaza componentele pe suprafata de afisare n flux liniar, mai precis, componentele sunt adaugate una dupa alta pe linii, n limita spatiului disponibil. In momentul cnd o componenta nu mai ncape pe linia curenta se trece la urmatoarea linie, 109
de sus n jos. Adaugarea componentelor se face de la stnga la dreapta pe linie, iar alinierea obiectelor n cadrul unei linii poate fi de trei feluri : la stnga, la drepata, centrate. Implicit componentele sunt centrate pe fiecare linie iar distanta implicita ntre componente este de 5 unitati pe verticala si 5 pe orizontala. Este gestionarul implicit al containerelor derivate din clasa Panel deci si al applet-urilor. Dimeniunile componentelor afisate sunt preluate automat de catre gestionar prin intermediul metodei getPreferredSize, implementata de toate componentele standard.
//Exemplu import java.awt.*; public class TestLayout { public static void main(String args[]) { Frame f = new Frame("Flow Layout"); f.setLayout(new FlowLayout()); Button b1 = new Button("Button 1"); Button b2 = new Button("2"); Button b3 = new Button("Button 3"); Button b4 = new Button("Long-Named Button 4"); Button b5 = new Button("Button 5"); f.add(b1); f.add(b2); f.add(b3); f.add(b4); f.add(b5); f.pack(); f.show(); } }
Redimensionnd fereastra astfel nct cele cinci butoane sa nu mai ncapa pe o linie, ultimele dintre ele vor fi trecute pe linia urmatoare:
Gestionarul BorderLayout
Gestionarul BorderLayout mparte suprafata de afisare n cinci regiuni, corespunzatoare celor patru puncte cardinale si centrului. O componenta poate fi plasata n oricare din aceste regiuni, dimeniunea componentei fiind calculata astfel nct sa ocupe ntreg spatiul de afisare oferit de regiunea respectiva. Pentru a adauga mai multe obiecte grafice ntruna din cele cinci zone, ele trebuie grupate n prealabil ntr-un panel, care va fi amplasat 110
apoi n regiunea dorita.(vezi "Gruparea componentelor - clasa Panel") Asadar, la adaugarea unei componente pe o suprafata gestionata de BorderLayout, metoda add va mai primi pe lnga numele componentei si zona n care aceasta va fi amplasata, acesta fiind apecificata prin una din constantele clasei BorderLayout: NORTH, SOUTH, EAST, WEST, CENTER . Este gestionarul implicit pentru toate containerele care descind din clasa Window, deci este gestionarul implicit al ferestrelor Java.
//Exemplu import java.awt.*; public class TestBorderLayout { public static void main(String args[]) { Frame f = new Frame("Border Layout"); f.setLayout(new BorderLayout());//poate sa lipseasca f.add(new f.add(new f.add(new f.add(new f.add(new f.pack(); } } f.show(); Button("Nord"), BorderLayout.NORTH); Button("Sud"), BorderLayout.SOUTH); Button("Est"), BorderLayout.EAST); Button("Vest"), BorderLayout.WEST); Button("Centru"), BorderLayout.CENTER);
La redimensionarea ferestrei se pot observa urmatoarele lucruri: nordul si sudul se redimensioneaza doar pe orizontala, estul si vestul doar pe verticala, n timp ce centrul se redimensioneaza att pe orizontala ct si pe verticala. Redimensionarea componentelor se face astfel nct ele ocupa toata zona containerului din care fac parte.
Gestionarul GridLayout
Gestionarul GridLayout organizeaza containerul ca un tabel cu rnduri si coloane, componentele fiind plasate n casutele tabelului de la stnga la dreapta ncepnd cu primul rnd. Casutele tabelului au dimensiuni egale iar o componenta poate ocupa doar o singura casuta. Numarul de linii si coloane poate fi specificat n constructorul gestionarului dar poate fi modificat si ulterior prin metodele setRows si setCols. De asemenea, distanta ntre componente pe orizontala si distanta ntre rndurile tabelului pot fi specificate n constructor sau stabilite ulterior.
111
Acest tip de gestionar poate fi util n implementarea unor componente de tip calculator, n care numerele si operatiile sunt afisate prin intermediul unor butoane dispuse sub forma unei grile.
//Exemplu import java.awt.*; public class TestGridLayout { public static void main(String args[]) { Frame f = new Frame("Grid Layout"); f.setLayout(new GridLayout(3, 2)); f.add(new f.add(new f.add(new f.add(new f.add(new f.add(new f.pack(); f.show(); Button("1")); Button("2")); Button("3")); Button("4")); Button("5")); Button("6"));
} }
Cele sase butoane ale ferestrei vor fi pe trei rnduri si doua coloane astfel:
Gestionarul CardLayout
Gestionarul CardLayout trateaza componentele adaugate pe suprafata ntr-o maniera asemanatoare cu cea a dispunerii cartilor de joc nntr-un pachet. Suprafata de afisare poate fi asemanata cu pachetul de carti iar fiecare componenta este o carte din pachet. La un moment dat numai o singura componenta este vizibila ("cea de deasupra"). Clasa dispune de metode prin care sa poata fi afisata o anumita componenta din pachet, sau sa se poata parcurge secvential pachetul, ordinea n care componentele se gasesc n pachet fiind interna gestionarului. Acest gestionar este util pentru implementarea unor cutii de dialog de tip tab, n care pentru o gestionare mai eficienta a spatiului, componentele sunt grupate n pachete, la un moment dat utilizatorul interactionnd cu un singur pachet, celelate fiind ascunse.
Gestionarul GridBagLayout
Este cel mai complex si flexibil gestionar de pozitionare din Java. La fel ca n cazul gestionarului GridLayout, suprafata de afisare este considerata ca fiind un tabel, nsa, spre deosebire de acesta, numarul de linii si de coloane sunt determinate automat, n functie de componentele amplasate pe suprafata de afisare. De asemenea, n functie de componentele gestionate, dimensiunile casutelor pot fi diferite, cu singurele restrictii ca 112
pe aceeasi linie casutele trebuie sa aiba aceeasi naltime, iar pe coloana trebuie sa aiba aceeasi latime. Spre deosebire de GridLayout, o componenta poate ocupa mai multe celule adiacente, chiar de dimensiuni diferite, zona ocupata fiind referita prin "regiunea de afisare" a componentei respective. Pentru a specifica modul de afisare a unei componente, acesteia i este asociat un obiect de tip GridBagConstraints, n care se specifica diferite proprietati ale componentei referitoare la regiunea sa de afisare si la modul n care va fi plasata n aceasta regiune. Legatura dintre o componenta si un obiect GridBagConstraints se realizeaza prin metode setConstraints:
GridBagLayout gridBag = new GridBagLayout(); container.setLayout(gridBag); GridBagConstraints c = new GridBagConstraints(); . . . //se specifica proprietatile referitoare la afisarea gridBag.setConstraints(componenta, c); container.add(componenta);
unei componente
Asadar, nainte de a adauga o componenta pe suprafata unui container care are un gestionar de tip GridBagLayout, va trebui sa specificam anumiti parametri (constrngeri) referitori la cum va fi plasata componenta respectiva. Aceste constrngeri vor fi specificate prin intermediul unui obiect de tip GridBagConstraints, care poate fi folosit pentru mai multe componente care au aceleassi cosntrngeri de afisare:
gridBag.setConstraints(componenta1, c); gridBag.setConstraints(componenta2, c); . . .
gruparea componentelor "nfratite" (care nu trebuie sa fie despartie de gestionarul de pozitionare al ferestrei) n panel-uri; aranjarea componentelor unui panel, prin specificarea acestuia a unui gestionar de pozitionare corespunzator aranjarea panel-urilor pe suprafata ferestrei, prin specificarea gestionarului de pozitionare al ferestrei.
//Exemplu import java.awt.*; public class TestPanel { public static void main(String args[]) { Frame f = new Frame("Panel"); Panel panel = new Panel(); panel.setLayout(new FlowLayout()); panel.add(new Label("Text:")); panel.add(new TextField("", 20)); panel.add(new Button("Reset")); f.add(panel, BorderLayout.NORTH); f.add(new Button("OK"), BorderLayout.EAST); f.add(new Button("Cancel"), BorderLayout.WEST); f.pack(); } } f.show();
Tratarea evenimentelor
Un eveniment este produs de o actiune a utilizatorului asupra unei componente grafice si reprezinta mecanismul prin care utilizatorul comunica efectiv cu programul. Exemple de evenimente sunt: apasarea unui buton, modificarea textului ntr-un control de editare, nchiderea, redimensionarea unei ferestre, etc. Componentele care genereaza anumite evenimente se mai numesc si surse de evenimente. Interceptarea evenimentelor generate de componentele unui program se realizeaza prin intermediul unor clase de tip listener (ascultator, consumator de evenimente). In Java, orice obiect poate "consuma" evenimentele generate de o anumita componenta grafica.
Asadar, pentru a scrie cod care sa se execute n momentul n care utilizatorul interactioneaza cu o componenta grafica trebuie sa facem urmatoarele lucruri:
114
sa scriem o clasa de tip listener care sa "asculte" evenimentele produse de acea componenta si n cadrul acestei clase sa implementam metode specifice pentru tratarea lor; sa comunicam componentei sursa ca respectiva clasa i "asculta" evenimentele pe care le genereaza, cu alte cuvinte sa nregistram acea clasa drept "consumator" al evenimentelor produse de componenta respectiva.
Evenimentele sunt, ca orice altceva n Java, obiecte. Clasele care descriu aceste obiecte se mpart n mai multe tipuri n functie de componenta care le genereaza, mai precis n functie de actiunea utilizatorului asupra acesteia. Pentru fiecare tip de eveniment exista o clasa care instantiaza obiecte de acel tip; de exemplu: evenimentul generat de actionarea unui buton este implementat prin clasa ActionEvent, cel generat de modificarea unui text prin clasa TextEvent, etc. Toate aceste clase au ca superclasa comuna clasa AWTEvent. Lista completa a claselor care descriu evenimente va fi data ntr-un tabel n care vom specifica si modalitatile de utilizare ale acestora. O clasa consumatoare de evenimente (listener) poate fi orice clasa care specifica n declaratia sa ca doreste sa asculte evenimente de un anumit tip. Acest lucru se realizeaza prin implementarea unei interfete specifice fiecarui tip de eveniment. Astfel, pentru ascultarea evenimentelor de tip ActionEvent clasa respectiva trebuie sa implementeze interfata ActionListener, pentru TextEvent interfata care trebuie implementata este TextListener, etc. Toate aceste interfete au suprainterfata comuna EventListener.
class AscultaButoane implements ActionListener class AscultaTexte implements TextListener
Intruct o clasa poate implementa oricte interfete ea va putea sa asculte evenimente de mai multe tipuri:
class Ascultator implements ActionListener, TextListener
Vom vedea n continuare metodele fiecarei interfete pentru a sti ce trebuie sa implementeze o clasa consumatoare de evenimente. Asa cum spus mai devreme, pentru ca evenimentele unei componente sa fie interceptate de catre o instanta a unei clase ascultator, aceasta clasa trebuie nregistrata n lista ascultatorilor componentei respective. Am spus lista, deoarece evenimentele unei componente pot fi ascultate de oricte clase - cu conditia ca acestea sa fie nregistrate la componenta respectiva. Inregistrarea unei clase n lista ascultatorilor unei componente se face cu metode din clasa Component de tipul addXXXListener, iar eliminarea ei din aceasta lista cu removeXXXListenerm unde XXX reprezenta tipul evenimentului.
//Exemplu:Ascultarea evenimentelor de la doua butoane import java.awt.*; import java.awt.event.*; class Fereastra extends Frame { public Fereastra(String titlu) { super(titlu); } public void initializare() { setLayout(new FlowLayout()); gestionarul setSize(200, 100); fereastra Button b1 = new Button("OK"); add(b1); buton doilea buton Button b2 = new Button("Cancel"); add(b2);
Ascultator listener = new Ascultator(this); b1.addActionListener(listener); b2.addActionListener(listener); //ambele butoane sunt ascultate de obiectul "listener" //instanta a clasei Ascultator, definita mai jos } }
class Ascultator implements ActionListener { private Fereastra f; public Ascultator(Fereastra f) { this.f = f; } //metoda interfetei ActionListener public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); //numele comenzii este numele butonului apasat System.out.println(e.toString()); if (command.equals("OK")) f.setTitle("Ati apasat OK"); else if (command.equals("Cancel")) f.setTitle("Ati apasat Cancel"); } } public class TestEvent { //fereastra principala public static void main(String args[]) { Fereastra f = new Fereastra("ActionEvent"); f.initializare(); f.show(); } }
Nu este obligatoriu sa definim clase speciale pentru ascultarea evenimentelor. In exemplul de mai sus am definit o clasa speciala "Ascultator" pentru a intercepta
116
evenimentele produse de cele doua butoane si din acest motiv a trebuit sa trimitem ca parametru acestei clase instanta la fereastra noastra. Mai corect ar fi fost sa folosim chiar clasa "Fereastra" pentru a-si asculta evenimentele produse de componentele sale:
class Fereastra extends Frame implements ActionListener{ public Fereastra(String titlu) { super(titlu); } public void initializare() { . . . b1.addActionListener(this); b2.addActionListener(this); //ambele butoane sunt ascultate chiar din clasa Fereastra //deci ascultatorul este instanta curenta: this } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); //numele comenzii este numele butonului apasat System.out.println(e.toString()); if (command.equals("OK")) this.setTitle("Ati apasat OK"); else if (command.equals("Cancel")) this.setTitle("Ati apasat Cancel"); }
} . . .
Asadar, orice clasa poate asculta evenimnte de orice tip cu conditia sa implementeze interfetele specifice acelor evenimente.
Preluarea, pierderea focusului de catre o componenta Component si subclasele sale Apasarea, eliberarii unei taste cnd focusul este pe o anumita componenta. Component si subclasele sale Click, apasare, eliberare a mouse-ului pe o componenta, intrarea, iesirea mouse-ului pe/de pe suprafata unei componente
Component si subclasele sale MouseEvent Miscarea sau "trrea" (drag) mouse-ului pe suprafata unei MouseMotionListener componente WindowEvent WindowListener ItemEvent ItemListener TextEvent TextListener Window si subclasele sale Dialog, FileDialog, Frame Inchiderea, maximizarea, minimizarea, redimensionarea unei ferestre Checkbox, CheckboxMenuItem, Choice, List si orice clasa care implementeaza interfata ItemSelectable Selectia, deselecttia unui articol dintr-o lista sau meniu. Orice clasa derivata din TextComponent cum ar fi : TextArea, TextField Modificarea textului dintr-o componenta de editare a textului
118
Dialog FileDialog Frame Label List Menu MenuItem Panel PopupMenu Scrollbar ScrollPane TextArea TextComponent TextField Window
ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, FocusEvent, KeyEvent, MouseEvent, ItemEvent, ComponentEvent ActionEvent ActionEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent AdjustmentEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
119
componentResized(ComponentEvent)
componentAdded(ContainerEvent) componentRemoved(ContainerEvent) focusGained(FocusEvent) focusLost(FocusEvent) keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent) mouseDragged(MouseEvent) windowOpened(WindowEvent) windowClosing(WindowEvent) windowClosed(WindowEvent) windowActivated(WindowEvent) windowDeactivated(WindowEvent) windowIconified(WindowEvent) windowDeiconified(WindowEvent) itemStateChanged(ItemEvent) textValueChanged(TextEvent)
MouseListener
MouseMotionListener mouseMoved(MouseEvent)
WindowListener
ItemListener TextListener
120
public Fereastra(String titlu) { super(titlu); this.addWindowListener(this); } //metodele interfetei WindowListener public void windowOpened(WindowEvent e) {} public void windowClosing(WindowEvent e) { dispose(); //inchid fereastra System.exit(0); //termin programul } public void windowClosed(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} } public class TestWindowListener { public static void main(String args[]) { Fereastra f = new Fereastra("O fereastra"); f.show(); } }
Observati ca trebuie sa implementam toate metodele interfetei, chiar daca nu scriem nici un cod pentru ele. Singura metoda care ne intereseaza este windowClosing n care specificam ce trebuie facut atunci cnd utilizatorul doreste sa nchida fereastra. Pentru a evita scrierea inutila a acestor metode exista o serie de clase care implementeaza interfetele de tip "listener" fara a specifica nici un cod pentru metodele lor. Aceste clase se numesc adaptori.
Folosirea adaptorilor
Un adaptor este o clasa abstracta care implementeaza o interfata de tip "listener". Scopul unei astfel de clase este ca la crearea unui "ascultator" de evenimente, n loc sa implementa o anumita interfata si implicit toate metodele sale, sa extindem adaptorul corespunzator interfetei respective (daca are!) si sa supradefinim doar metodele care ne intereseaza (cele n care vrem sa scriem o anumita secventa de cod). De exemplu, adaptorul interfetei WindowListener este WindowAdapter iar folosirea acestuia este data n exemplul de mai jos:
//Crearea unei ferestre cu ascultarea evenimentelor sale //folosind extinderea clasei WindowAdapter import java.awt.*; import java.awt.event.*; class Fereastra extends Frame { public Fereastra(String titlu) { super(titlu); this.addWindowListener(new Ascultator()); } } class Ascultator extends WindowAdapter {
121
Avantajul clar al acestei modaitati de tratare a evenimentelor este reducerea codului programului, acesta devenind mult mai usor lizibil. Insa exista si doua dezavantaje majore. Dupa cum ati observat, fata de exemplul anterior clasa "Fereastra" nu poate extinde WindowAdapter deoarece ea extinde deja clasa Frame si din acest motiv am construi o noua clasa numita "Ascultator". Vom vedea nsa ca acest dezavantaj poate fi eliminat prin folosirea unei clase interne. Un alt dezavantaj este ca orice greseala de sintaxa n declararea unei metode a interfetei nu va produce o eroare de compilare dar nici nu va supradefini metoda interfetei ci, pur si simplu, va crea o metoda a clasei respective.
class Ascultator extends WindowAdapter { // in loc de windowClosing scriem WindowClosing // nu supradefinim vreo metoda a clasei WindowAdapter // nu da nici o eroare // nu face nimic ! public void WindowClosing(WindowEvent e) { System.exit(0); } }
In tabelul de mai jos sunt dati toti adaptorii interfetele de tip "listener" - se oberva ca o interfata XXXListener are un adaptor de tipul XXXAdapter. Interfetele care nu au un adaptor sunt cele care definesc o singura metoda si prin urmare crearea unei clase adaptor nu si are rostul. Interfata "listener" ActionListener AdjustmentListener ComponentListener ContainerListener FocusListener ItemListener KeyListener MouseListener TextListener WindowListener nu are nu are ComponentAdapter ContainerAdapter FocusAdapter nu are KeyAdapter Mouse nu are WindowAdapter Adaptor
MouseMotionListener MouseMotionAdapter
122
exemplu tipic de folosire a lor este instantierea adaptorilor direct n corpul unei clase care contine componente ale caror evenimente trebuie interceptate. Clasa "Fereastra" din exemplul anterior poate fi scrisa astfel:
class Fereastra extends Frame { public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { //corpul clasei anonime public void windowClosing(WindowEvent e) { }); } } } System.exit(0);
Observati cum codul programului a fost redus substantial prin folosirea unui adaptor si a unei clase anonime.
Folosirea ferestrelor
Dupa cum am vazut suprafetele de afisare ale componentelor grafice (containerele) sunt extensii ale clasei Container. O categorie aparte a acestor containere o reprezinta ferestrele. Spre deosebire de un applet care si poate plasa componentele direct pe suprafata de afisare a browser-ului n care ruleaza, o aplicatie intependenta are nevoie de propriile ferestre pe care sa faca afisarea componentelor sale grafice. Pentru dezvoltarea aplicatiilor care folosesc grafica se vor folosi clasele Window si subclasele sale directe Frame si Dialog.
Clasa Window
Clasa Window este rar utilizata n mod direct. Ea permite crearea unor ferestre top-level care nu au chenar si nici bara de meniuri. Pentru a crea ferestre mai complexe se utilizeaza clasele Frame si Dialog. Metodele mai importante ale clasei Window (mostenite de toate subclasele sale) sunt date n tabelul de mai jos:
void dispose() Component getFocusOwner() Window getOwnedWindows() Window getOwner() void hide()
Distruge (nchide) fereastra si si elibereaza toate resursele acesteia Returneaza componenta ferestrei care are focus-ul daca si numai daca fereastra este activa Returneaza un vector cu toate ferestrele subclase ale ferestrei respective Returneaza parintele (superclasa) ferestrei Face fereastra invizibila fara a o distruge nsa. Pentru a redeveni
123
Testeaza daca fereastra este vizibila sau nu Redimensioneaza automat fereastra la o suprafata optima care sa cuprinda toate componentele sale. Trebuie apelata, n general, dupa adaugarea tuturor componentelor pe suprafata ferestrei. Face vizibila o fereastra creata. Implicit o fereastra nou creata nu este vizibila. Trimite fereastra n spatele celorlalte ferestre deschise Aduce fereastra n fata celorlalte ferestre deschise
Clasa Frame
Este subclasa directa a clasei Window. si este folosita pentru crearea de ferestre independente si functionale, eventual continnd bare de meniuri. Orice aplicatie grafica independenta trebuie sa aiba cel putin o fereastra, numita si fereastra principala, care va fi afisata la pornirea programului. Constructorii clasei Frame sunt:
Frame () Construieste o fereastra, fara titlu, initial invizibila. Frame(String title) Construieste o fereastra, cu titlul specificat, initial invizibila.
Asadar, o fereastra nou creata este invizibila. Pentru a fi facuta vizibila se va apela metoda show definita n superclasa Window. In exemplul de mai jos este construita si afisata o fereasta cu titlul "O fereastra".
//Crearea unei ferestre import java.awt.*; public class TestFrame { public static void main(String args[]) { Frame f = new Frame("O fereastra"); f.show(); } } Crearea ferestrelor prin instantierea obiectelor de tip Frame este
mai putin uzuala. De obicei, ferestrele unui program vor fi definite n clase separate care extind clasa Frame, ca n exemplul de mai jos:
import java.awt.*; class Fereastra extends Frame{ //constructorul public Fereastra(String titlu) { super(titlu); } void initializare() { . . . //adaugam componentele ferestrei }
124
} public class TestFrame { public static void main(String args[]) { Fereastra f = new Fereastra("O fereastra"); f.initializare(); f.show(); } } Gestionarul de pozitionare implicit al clasei Window este BorderLayout. Din
acest motiv, n momentul n care fereastra este creata dar nici o componenta grafica nu este pusa pe suprafata ei, suprafata de afisare a feretrei va fi nula. Acelasi efect l vom obtine daca o redimenionam si apelam apoi metoda pack care determina dimeniunea suprafetei de afisare n functie de componentele grafice afisate pe ea. Se observa de asemenea ca butonul de nchidere a ferestrei nu este functional. Interceptarea evenimentelor se face prin implementarea interfetei WindowListener si prin adaugarea n lista ascultatorilor ferestrei (uzual) chiar a obiectului care implementeaza fereastra sau prin folosirea unor adaptori si clase anonime. Metodele mai folosite ale clasei Frame sunt date n tabelul de mai jos:
static Framest getFrames() Image getIconImage() void setIconImage(Image img) MenuBar getMenuBar() void setMenuBar(MenuBar mb) int getState() void setState(int s) String getTitle() void setTitle() boolean isResizable() void setResizable(boolean r)
Metoda statica ce returneaza lista tuturor ferestrelor deschise ale unei aplicatii Afla/seteaza imaginea(iconita) care sa fie afisata atunci cnd fereastra este minimizata Afla/seteaza bara de meniuri a ferestrei (vezi "Folosirea meniurilor") Returneaza/seteaza starea ferestrei. O fereastra se poat gasi n doua stari, descrise de constantele: Frame.ICONIFIED (daca este minimizata) Frame.NORMAL (daca nu este minimizata). Afla/seteaza titlul ferestrei Determina/stabileste daca fereastra poate fi redimenionata de utilizator.
Clasa Dialog
Toate interfetele grafice ofera un tip special de ferestre destinate preluarii datelor de la utilizator. Acestea se numesc ferestre de dialog sau casete de dialog si sunt implementate prin intermediul clasei Dialog, subclasa directa a clasei Window. Diferenta majora ntre ferestrele de dialog si ferestrele normale (obiecte de tip Frame) 125
consta n faptul ca o fereastra de dialog este dependenta de o alta fereastra (normala sau tot fereastra dialog), numita si fereastra parinte. Cu alte cuvinte, ferestrele de dialog nu au o existenta de sine statatoare. Cnd fereastra parinte este distrusa sunt distruse si ferestrele sale de dialog, cnd este minimizata ferestrele sale de dialog sunt facute invizibile iar cnd este maximizata acestea sunt aduse la starea n care se gaseau n momentul minimizarii ferestrei parinte. Ferestrele de dialog pot fi de doua tipuri:
modale: care blocheaza accesul la fereastra parinte n momentul deschiderii lor de exemplu, ferestre de introducere a unor date, de alegere a unui fisier n vederea deschideriii, de selectare a unei optiuni, mesaje de avertizare, etc; nemodale: care nu blocheaza fluxul de intrare catre fereastra parinte - de exemplu, ferestrele de cautare a unui cuvnt ntr-un fisier.
Implicit o fereastra de dialog este nemodala si invizibila. Constructorii clasei Dialog sunt:
Dialog(Frame parinte) Dialog(Frame parinte, String titlu) Dialog(Frame parinte, String titlu, boolean modala) Dialog(Frame parinte, boolean modala) Dialog(Dialog parinte) Dialog(Dialog parinte, String titlu) Dialog(Dialog parinte, String titlu, boolean modala)
unde "parinte" reprezina o instanta ferestrei parinte, "titlu" reprezinta titlul ferestrei iar prin argumentul "modala" specificam daca fereastra de dialog creata va fi modala (true) sau nemodala (false - valoarea implicita). Pe lnga metodele mostenite de la superclasa Window clasa Dialog mai contine metodele:
boolean isModal() void setModal(boolean modala)
Determina daca fereastra de dialog este modala sau nu. Specifica tipul ferestrei de dialog: modala (true) sau nemodala (false)
Crearea unei ferestre de dialog este relativ simpla si se realizeaza prin crearea unei clase care sa extinda clasa Dialog. Mai complicat este nsa modul n care se implementeaza comunicarea ntre fereastra de dialog si fereastra parinte, pentru ca aceasta din urma sa poata folosi datele introduse (sau optiunea specificata) n caseta de dialog. Exista doua abordari generale :
obiectul care reprezinta dialogul poate sa capteze evenimentele de la componentele de pe suprafata sa si sa sa seteze valorile unor variabile ale ferestrei parinte n momentul n care dialogul este ncheiat sau obiectul care creeaza dialogul (fereastra parinte) sa se nregistreze ca ascultator al evenimentelor de la butoanele care determina ncheierea dialogului, iar fereastra
126
de dialog sa ofere metode publice prin care datele introduse sa fie preluate din exterior. Sa cream, de exemplu, o fereastra de dialog modala pentru introducerea unui sir de caractere. Fereastra principala a aplicatiei va fi parintele casetei de dialog, va primi sirul de caractere introdus si si va modifica titlul ca fiind sirul primit. Deschiderea ferestrei de dialog se va face la apasarea unui buton al ferestrei principale numit "Schimba titlul". Dialogul va mai avea doua butoane OK si Cancel pentru terminarea sa cu confirmare, respectiv renuntare. Cele doua ferestre vor arata ca n imaginile de mai jos
import java.awt.*; import java.awt.event.*; //Fereastra principala a aplicatiei class FerPrinc extends Frame implements ActionListener{ public FerPrinc(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(new FlowLayout()); setSize(300, 100); Button b = new Button("Schimba titlul"); add(b); b.addActionListener(this); } //metoda interfetei ActionListener public void actionPerformed(ActionEvent e) { FerDialog d = new FerDialog(this, "Titlu", true); //astept sa se inchida fereastra modala de dialog if (d.raspuns == null) return; setTitle(d.raspuns); }
127
//fereastra de dialog class FerDialog extends Dialog implements ActionListener { public String raspuns = null; private TextField text; public FerDialog(Frame parinte, String titlu, boolean modala) { super(parinte, titlu, modala); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { raspuns = null; dispose(); } }); setLayout(new FlowLayout()); Button ok, cancel; ok = new Button("OK"); cancel = new Button("Cancel"); text = new TextField("", 50); add(cancel);add(text);add(ok);pack(); ok.addActionListener(this); cancel.addActionListener(this); show(); } //metoda interfetei ActionListener public void actionPerformed(ActionEvent e) { //vad ce buton a fost apasat String buton = e.getActionCommand(); if (buton.equals("OK")) raspuns = text.getText(); else if (buton.equals("Cancel")) raspuns = null; dispose(); } } //clasa principala public class TestDialog { public static void main(String args[]) { FerPrinc f = new FerPrinc("Fereastra principala"); f.initializare(); f.show(); } }
Clasa FileDialog
Pachetul java.awt pune la dispozitie si un tip de fereastra de dialog folosita pentru ncarcarea / salvarea fisierelor : clasa FileDialog, subclasa directa a clasei Dialog. Instantele acestei clase au un comportament comun dialogurilor de acest tip de pe majoritatea platformelor de lucru, dar forma n care vor fi afisate este specifica platformei
128
unde "parinte" reprezina o instanta ferestrei parinte, "titlu" reprezinta titlul ferestrei iar prin argumentul "mod" specificam daca ncarcam sau salvam un fisier; valorile pe care le poate lua acest argument sunt FileDialog.LOAD (pentru ncarcare), respectiv FileDialog.SAVE (pentru salvare).
//dialog pentru incarcarea unui fisier new FileDialog(mainWin, "Alegere fisier", FileDialog.LOAD);
//dialog pentru salvarea unui fisier new FileDialog(mainWin, "Salvare fisier", FileDialog.SAVE); La crearea unui obiect FileDialog acesta nu este implicit vizibil. Daca
afisarea sa se face cu show caseta de dialog va fi modala. Daca afisarea se face cu setVisible(true) va fi nemodala. Dupa selectarea unui fisier ea va fi facuta automat invizibila. Pe lnga metodele mostenite de la superclasa Dialog clasa FileDialog mai contine metodele: Afla/specifica directorul din care se va face selectia fisierului sau n care se va face salvare. Sunt permise si notatii specifice pentru directorul curent (.), directorul radacina (/), etc. Returneaza numele fisierului selectat. Stabileste numele implicit al fisierului care va aparea n caseta de dialog Afla/specifica filtrul care se va aplica fisierelor din directorul din care se va face selectia fisierului sau n care se va face salvare (vezi "Intrari si iesiri - Interfata FilenameFilter" ) Nu functioneaza pe platformele Windows ! Afla/specifica daca ncarcam sau salvam un fisier; FileDialog.LOAD (pentru ncarcare)
FileDialog.SAVE
(pentru salvare)
Sa consideram un exemplu n care vom alege, prin intermediul unui obiect FileDialog, un fisier cu extensia "java". Directorul initial este directorul curent, iar numele implicit este TestFileDialog.java. Numele fisierului ales va fi afisat la consola.
import java.awt.*; import java.awt.event.*;
129
import java.io.*; class FerPrinc extends Frame implements ActionListener{ public FerPrinc(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(new FlowLayout()); setSize(300, 100); Button b = new Button("Alege fisier"); add(b); b.addActionListener(this);
//metoda interfetei ActionListener public void actionPerformed(ActionEvent e) { FileDialog fd = new FileDialog(this, "Alegeti un fisier", FileDialog.LOAD); //stabilim directorul curent fd.setDirectory("."); //numele implicit fd.setFile("TestFileDialog.java"); //specificam filtrul fd.setFilenameFilter(new FilenameFilter() { public boolean accept(File dir, String numeFis) { return (numeFis.endsWith(".java")); } }); fd.show(); //facem vizibila fereastra de dialog System.out.println("Fisierul ales este:" + fd.getFile()); } } public class TestFileDialog { public static void main(String args[]) { FerPrinc f = new FerPrinc("Fereastra principala"); f.initializare(); f.show(); } }
Folosirea meniurilor
130
Spre deosebire de celelate obiecte grafice, care deriva din clasa Component, componentele unui meniu reprezinta instante ale unor clase derivate din superclasa abstracta MenuComponent. Aceasta exceptie este facuta deoarece multe platforme grafice limiteaza capabilitatile unui meniu. Meniurile sunt grupate n doua categorii: Meniuri fixe (vizibile permanent): sunt grupate ntr-o bara de meniuri ce contine cte un meniu pentru fiecare intrare a sa; la rndul lor aceste meniuri contin articole ce pot fi selectate, comutatoare - care au doua stari (checkbox) sau alte meniuri (submeniuri). O fereastra poate avea un singur meniu fix. Meniuri de context (popup): sunt meniuri invizbile asociate unei ferestre si care se activeaza prin apasarea butonul drept al mouse-ului. Diferenta fata de meniurile fixe consta n faptul ca meniurile de context nu au bara de meniuri. In figura de mai jos este pusa n evidenta alcatuirea unui meniu fix:
Exemplul de mai sus contine o bara de meniuri, doua meniuri principale File si Edit. Meniul Edit contine la rndul lui alt meniu (submeniu) Options , articolul Undo si doua comutatoare Bold si Italic. In Java AWT meniurile sunt reprezentate ca instante al clasei MenuBar, aceasta fiind clasa care descrie barele de meniuri. Un obiect de tip MenuBar contine obiecte de tip Menu, care sunt de fapt meniurile derulante propriu-zise. La rndul lor acestea pot contine obiecte de tip MenuItem, CheckBoxMenuItem, dar si alte obiecte de tip Menu (submeniuri). Sa vedem n continuare care este ierarhia claselor folosite n lucrul cu meniuri si sa analizam pe rnd aceste clase:
131
Pentru a putea contine un meniu o componenta trebuie sa implementez interfata MenuContainer. Cel mai adesea meniurile sunt atasate ferestrelor, mai precis obiectelor de tip Frame, aceste implementnd interfata MenuContainer. Atasarea unei bare de meniuri la o fereastra se face prin metoda addMenuBar a clasei Frame. Sa vedem cum ar arata un program care construieste un meniu ca cel din figura de mai sus:
import java.awt.*; import java.awt.event.*;
public class TestMenu { public static void main(String args[]) { Frame f = new Frame("Meniu"); MenuBar mb = new MenuBar(); Menu fisier = new Menu("File"); fisier.add(new MenuItem("Open")); fisier.add(new MenuItem("Close")); fisier.addSeparator(); fisier.add(new MenuItem("Exit")); Menu optiuni = new Menu("Options"); optiuni.add(new MenuItem("Copy")); optiuni.add(new MenuItem("Cut")); optiuni.add(new MenuItem("Paste")); Menu editare = new Menu("Edit"); editare.add(new MenuItem("Undo")); editare.add(optiuni); editare.addSeparator(); editare.add(new CheckboxMenuItem("Bold")); editare.add(new CheckboxMenuItem("Italic")); mb.add(fisier); mb.add(editare); f.setMenuBar(mb); f.show();
132
} }
Clasa MenuComponent
Este o clasa abstracta, din care sunt extinse toate celelalte clase folosite pentru lucrul cu meniuri, fiind analoaga celeilalte superclase abstracte Component. Clasa MenuComponent contine metode de ordin general, dintre care amintim getName, setName, getFont, setFont, cu sintaxa si semnificatiile uzuale.
//adaug meniurile derulante la bara de meniuri . . . //atasez unei ferestre bara de meniuri Frame f = new Frame("Fereastra cu meniu"); f.addMenuBar(mb);
Clasa MenuItem
Orice articol al unui meniu trebuie sa fie o instanta a clasei MenuItem. Instantele acestei clase descriu asadar articolele (optiunile individuale) ale meniurilor derulante, cum sunt "Open", "Close", "Exit", etc. O instanta a clasei MenuItem reprezinta de fapt o eticheta ce descrie numele cu care va aparea articolul n meniu, nsotita eventual de un accelerator (obiect de tip MenuShortcut) ce reprezinta combinatia de taste cu care articolul poate fi apelat rapid (vezi "Acceleratori").
Clasa Menu
Este clasa care permite crearea unui meniu derulant ntr-o bara de meniuri. Optional, un meniu poate fi declarat ca fiind tear-off, ceea ce nseamna ca poate fi deschis si deplasat cu mouse-ul (dragged) ntr-o alta pozitie dect cea originala ("rupt" din pozitia sa). Acest mecanism este dependent de platforma si poate fi ignorat pe unele dintr ele. Fiecare meniu are o eticheta, care este de fapt numele sau ce va fi afisat pe bara de meniuri. Articolele dintr-un meniu trebuie sa apartina clasei MenuItem, ceea ce nseamna ca pot fi instante ale uneia din clasele MenuItem, Menu sau CheckboxMenuItem.
//Exemplu MenuBar mb = new MenuBar(); bara de meniuri meniu Menu optiuni = new Menu("Options"); optiuni.add(new MenuItem("Copy")); //creez //creez un
133
articole
optiuni.add("Cut");
//adaug
optiuni.add("Paste); optiuni.addSeparator(); optiuni.add("Help"); mb.add(optiuni); //adaug meniul la bara Frame f = new Frame("Fereastra cu meniu"); f.addMenuBar( mb ); bara unei ferestre
//atasez
Clasa CheckboxMenuItem
Implementeaza ntr-un meniu articole de tip comutator - articole care au doua stari logice (validat/nevalidat), actionarea asupra articolului determinnd trecerea sa dintr-o stare n alta. La validarea unui comutator n dreptul etichetei sale va aparea un simbol grafic care indica acest lucru; la invalidarea sa, simbolul grafic va dispare. Clasa CheckboxMenuItem are aceeasi functionalitate cu cea a casetelor de validare, implementnd interfata ItemSelectable.
134
Tipul de operatie selectare / deselectare este codificat de cmpurile statice ItemEvent.SELECTED si ItemEvent.DESELECTED.
} }
135
Au fost introduse ncepnd cu AWT 1.1 si sunt implementate prin intermediul clasei PopupMenu, subclasa directa a clasei Menu. Sunt meniuri invizibile care sunt activate uzual prin apasarea butonului drept al mouse-ului, fiind afisate la pozitia la care se gasea mouse-ul n momentul apasarii butonului sau drept. Metodele de adaugare a articolelor unui meniu popup sunt identice cu cele de la meniurile fixe, PopupMenu fiind subclasa directa a clasei Menu.
popup = new PopupMenu("Options"); popup.add(new MenuItem("New")); popup.add(new MenuItem("Edit")); popup.addSeparator(); popup.add(new MenuItem("Exit"));
si este, de obicei, rezultatul apasarii unui buton al mouse-ului, pentru a avea acces rapid la meniu. Argumentul "origin" reprezinta componenta fata de originile careia se va calcula pozitia de afisare a meniului popup. De obicei, reprezinta instanta ferestrei n care se va afisa meniul. Meniurile de context nu se adauga la un alt meniu (bara sau sub-meniu) ci se ataseaza la o componenta (de obicei la o fereastra) prin metoda add: fereastra.add(pm) . In cazul cnd avem mai multe meniuri popup pe care vrem sa le folosim ntr-o fereastra, trebuie sa le definim pe toate si, la un moment dat, vom adauga ferestrei meniul corespunzator. Dupa nchiderea acestuia vom "rupe" legatura ntre fereastra si meniu prin instructiunea remove:
fereastra.add(popup1); . . . fereastra.remove(popup1); fereastra.add(popup2);
In exemplul de mai jos, vom crea un meniu de contexe ca n imaginea de mai jos, pe care l vom activa la apasarea butonului drept al mouse-ului pe suprafata ferestrei principale:
La alegerea optiunii "Exit" din meniu vom termina programul. Interceptarea evenimentelor generate de un meniu popup se realizeaza identic ca pentru meniurile fixe (vezi "Tratarea evenimentelor generate de meniuri").
136
//pozitia ferestrei
this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); this.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if ( (e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK ) popup.show(origin, e.getX(), e.getY()); //BUTTON3 reprezinta butonul din dreapta mouse} }); setSize(300, 300); //cream meniul popup popup = new PopupMenu("Options"); popup.add(new MenuItem("New")); popup.add(new MenuItem("Edit")); popup.addSeparator(); popup.add(new MenuItem("Exit")); add(popup); //atasam meniul popup ferestrei popup.addActionListener(this); } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); //La alegerea optiunii "Exit" din meniu parasim aplicatia if (command.equals("Exit")) System.exit(0); } } public class TestPopupMenu { public static void main(String args[]) { Fereastra f = new Fereastra("PopupMenu"); f.show(); } }
ului
accelerator, definit prin intermediul clasei MenuShortcut. Singurele combinatii de taste care pot juca rolul acceleratorilor sunt: + sau + + . Atribuirea unui accelerator la un articol al unui meniu poate fi realizata prin constructorul obiectelor de tip MenuItem n forma:
MenuItem(String eticheta, MenuShortcut accelerator) //Exemplu new MenuItem("Open", new MenuShortcut(KeyEvent.VK_O));
import java.awt.*; public class TestLabel { public static void main(String args[]) { Frame f = new Frame("TestLabel"); f.setLayout(new BorderLayout()); Label nord, sud, est, vest, centru; nord = new Label("Nord", Label.CENTER); sud = new Label("Sud", Label.CENTER); est = new Label("Est", Label.RIGHT); vest = new Label("Vest", Label.LEFT); centru = new Label("Centru", Label.CENTER); centru.setBackground(Color.yellow); centru.setFont(new Font("Arial", Font.BOLD, 14)); f.add(nord, BorderLayout.NORTH); f.add(sud, BorderLayout.SOUTH); f.add(est, BorderLayout.EAST); f.add(vest, BorderLayout.WEST); f.add(centru, BorderLayout.CENTER); f.pack();
138
f.show(); } }
Clasa Button
Un obiect de tip Button (buton) reprezinta o componenta pentru plasarea unui buton etichetat pe o suprafata de afisare. Exemplu: definim doua butoane si le plasam pe o fereastra; la apasarea butonului "OK" titlul ferestrei va fi "Confirmare", iar la apasarea butonului "Cancel" titlul ferestrei va fi "Renuntare".
import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements ActionListener{ public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(null); setSize(200, 200); Button b1 = new Button("OK"); b1.setBounds(30, 30, 50, 70); b1.setFont(new Font("Arial", Font.BOLD, 14)); b1.setBackground(java.awt.Color.orange); add(b1); Button b2 = new Button("Cancel"); b2.setBounds(100, 30, 70, 50); b2.setForeground(java.awt.Color.blue); add(b2); b1.addActionListener(this); b2.addActionListener(this);
139
} //metoda interfetei ActionListener public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); System.out.println(e.toString()); if (command.equals("OK")) setTitle("Confirmare!"); if (command.equals("Cancel")) setTitle("Anulare!"); }
public class TestButton { public static void main(String args[]) { Fereastra f = new Fereastra("Button"); f.initializare(); f.show(); } }
Clasa Checkbox
Un obiect de tip Checkbox (comutator) reprezinta o componenta care se poate gasi n doua stari : "selectata" sau "neselectata" (on/off). Actiunea utilizatorului asupra unui comutator l trece pe acesta n starea complementara celei n care se gasea. Este folosit pentru a prelua o anumita optiune de la utilizator.
import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements ItemListener { private Label label1, label2; private Checkbox cbx1, cbx2, cbx3; public Fereastra(String titlu) {
140
super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(new GridLayout(5, 1)); label1 = new Label("Ingrediente Pizza:", Label.CENTER); label1.setBackground(Color.orange); label2 = new Label(""); label2.setBackground(Color.lightGray); cbx1 = new Checkbox("cascaval"); cbx2 = new Checkbox("sunca"); cbx3 = new Checkbox("ardei"); add(label1); add(label2); add(cbx1); add(cbx2); add(cbx3); pack(); setSize(200, 200); cbx1.addItemListener(this); cbx2.addItemListener(this); cbx3.addItemListener(this);
//metoda interfetei ItemListener public void itemStateChanged(ItemEvent e) { StringBuffer ingrediente = new StringBuffer(); if (cbx1.getState() == true) ingrediente.append(" cascaval "); if (cbx2.getState() == true) ingrediente.append(" sunca "); if (cbx3.getState() == true) ingrediente.append(" ardei "); label2.setText(ingrediente.toString()); } } public class TestCheckbox { public static void main(String args[]) { Fereastra f = new Fereastra("Checkbox"); f.initializare(); f.show(); } }
Clasa CheckboxGroup
141
Un obiect de tip CheckboxGroup defineste un grup de comutatoare din care doar unul poate fi selectat. Uzual, aceste componente se mai numesc butoane radio.
import java.awt.*; import java.awt.event.*; class Fereastra private private private extends Frame implements ItemListener { Label label1, label2; Checkbox cbx1, cbx2, cbx3; CheckboxGroup cbg;
public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(new GridLayout(5, 1)); label1 = new Label("Alegeti postul TV", Label.CENTER); label1.setBackground(Color.orange); label2 = new Label("", Label.CENTER); label2.setBackground(Color.lightGray); cbg = new CheckboxGroup(); cbx1 = new Checkbox("Pro TV", cbg, false); cbx2 = new Checkbox("Antena 1", cbg, false); cbx3 = new Checkbox("Prima", cbg, false); add(label1); add(label2); add(cbx1); add(cbx2); add(cbx3); pack(); setSize(200, 200); cbx1.addItemListener(this); cbx2.addItemListener(this);
142
cbx3.addItemListener(this);
//metoda interfetei ItemListener public void itemStateChanged(ItemEvent e) { Checkbox cbx = cbg.getSelectedCheckbox(); if (cbx != null) label2.setText(cbx.getLabel()); }
public class TestCheckboxGroup { public static void main(String args[]) { Fereastra f = new Fereastra("CheckboxGroup"); f.initializare(); f.show(); } }
Clasa Choice
Un obiect de tip Choice defineste o lista de optiuni din care utilizatorul poate selecta una singura. La un moment dat, din ntreaga lista doar o singura optiune este vizibila, cea selectata n momentul curent. O componenta Choice este nsotita de un buton etichetat cu o sageata verticala la apasarea caruia este afisata ntreaga sa lista, pentru ca utilizatorul sa poata selecta o anumita optiune.
import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements ItemListener { private Label label; private Choice culori; public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); }
143
public void initializare() { setLayout(new GridLayout(4, 1)); label = new Label("Alegeti culoarea"); label.setBackground(Color.red); culori = new Choice(); culori.add("Rosu"); culori.add("Verde"); culori.add("Albastru"); culori.select("Rosu"); add(label); add(culori); pack(); setSize(200, 100); } culori.addItemListener(this);
//metoda interfetei ItemListener public void itemStateChanged(ItemEvent e) { switch (culori.getSelectedIndex()) { case 0: label.setBackground(Color.red); break; case 1: label.setBackground(Color.green); break; case 2: label.setBackground(Color.blue); } } } public class TestChoice { public static void main(String args[]) { Fereastra f = new Fereastra("Choice"); f.initializare(); f.show(); } }
Clasa List
Un obiect de tip List defineste o lista de optiuni care poate fi setata astfel nct utilizatorul sa poata selecta o singura optiune sau mai multe. Toate optiunile listei sunt vizibile n limita dimensiunilor grafice ale componentei.
144
import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements ItemListener { private Label label; private List culori; public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(new GridLayout(2, 1)); label = new Label("Alegeti culoarea", Label.CENTER); label.setBackground(Color.red); culori = new List(3); culori.add("Rosu"); culori.add("Verde"); culori.add("Albastru"); culori.select(3); add(label); add(culori); pack(); setSize(200, 200); } culori.addItemListener(this);
//metoda interfetei ItemListener public void itemStateChanged(ItemEvent e) { switch (culori.getSelectedIndex()) { case 0: label.setBackground(Color.red); break; case 1: label.setBackground(Color.green); break;
145
case 2: } } }
label.setBackground(Color.blue);
public class TestList { public static void main(String args[]) { Fereastra f = new Fereastra("List"); f.initializare(); f.show(); } }
Clasa Scrollbar
Un obiect de tip Scrollbar defineste o bara de defilare verticala sau orizontala. Este utila pentru punerea la dispozitie a utilizatorului a unei modalitati sugestive de a alege o anumita valoare dintr-un interval.
import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements AdjustmentListener { private Scrollbar scroll; private Label valoare; public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(new GridLayout(2, 1)); valoare = new Label("", Label.CENTER); valoare.setBackground(Color.lightGray); scroll = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 101); add(valoare);
146
public class TestScrollbar { public static void main(String args[]) { Fereastra f = new Fereastra("Scrollbar"); f.initializare(); f.show(); } }
Clasa ScrollPane
Un obiect de tip ScrollPane permite atasarea unor bare de defilare (orizontala si/sau verticala) oricarei componente grafice. Acest lucru este util pentru acele componente care nu au implementata functionalitatea de defilare automata, cum ar fi listele (obiecte din clasa List).
import java.awt.*; import java.awt.event.*; class Fereastra extends Frame { private ScrollPane sp; private List list; public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0);
147
}); }
public void initializare() { setLayout(new FlowLayout()); list = new List(7); list.add("Luni"); list.add("Marti"); list.add("Miercuri"); list.add("Joi"); list.add("Vineri"); list.add("Sambata"); list.add("Duminica"); list.select(1); sp = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS); sp.add(list); add(sp); pack(); setSize(200, 200); } } public class TestScrollPane { public static void main(String args[]) { Fereastra f = new Fereastra("ScrollPane"); f.initializare(); f.show(); } }
Clasa TextField
Un obiect de tip TextField defineste un control de editare a textului pe o singura linie. Este util pentru interogarea utilizatorului asupra unor valori.
148
extends Frame implements TextListener { TextField nume, parola; Label acces; static final String UID="Ion", PWD="java" ;
public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setLayout(new GridLayout(3, 1)); setBackground(Color.lightGray); nume = new TextField("", 30); parola = new TextField("", 10); parola.setEchoChar('*'); Panel p1 = new Panel(); p1.setLayout(new FlowLayout(FlowLayout.LEFT)); p1.add(new Label("Nume:")); p1.add(nume); Panel p2 = new Panel(); p2.setLayout(new FlowLayout(FlowLayout.LEFT)); p2.add(new Label("Parola:")); p2.add(parola); Label.CENTER); acces = new Label("Introduceti numele si parola!", add(p1); add(p2); add(acces); pack(); setSize(350, 100); nume.addTextListener(this); parola.addTextListener(this);
//metoda interfetei TextListener public void textValueChanged(TextEvent e) { if ((nume.getText().length() == 0) || (parola.getText().length() == 0)) { acces.setText(""); return; } if (nume.getText().equals(UID) && parola.getText().equals(PWD)) acces.setText("Acces permis!"); else acces.setText("Acces interzis!");
149
} } public class TestTextField { public static void main(String args[]) { Fereastra f = new Fereastra("TextField"); f.initializare(); f.show(); } }
Clasa TextArea
Un obiect de tip TextArea defineste un control de editare a textului pe mai multe linii. Este util pentru editarea de texte, introducerea unor comentarii, etc .
import java.awt.*; import java.awt.event.*; import java.io.*; class Fereastra private private private extends Frame implements TextListener, ActionListener { TextArea text; TextField nume; Button save;
public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { setBackground(Color.lightGray);
150
text = new TextArea("", 30, 10, TextArea.SCROLLBARS_VERTICAL_ONLY); nume = new TextField("", 12); save = new Button("Salveaza text"); save.setActionCommand("save"); save.setEnabled(false); Panel fisier = new Panel(); fisier.add(new Label("Fisier:")); fisier.add(nume); add(fisier, BorderLayout.NORTH); add(text, BorderLayout.CENTER); add(save, BorderLayout.SOUTH); pack(); setSize(300, 200); text.addTextListener(this); save.addActionListener(this); } //metoda interfetei TextListener public void textValueChanged(TextEvent e) { if ((text.getText().length() == 0) || (nume.getText().length() == 0)) save.setEnabled(false); else save.setEnabled(true); } //metoda interfetei ActionListener public void actionPerformed(ActionEvent e) { String continut = text.getText(); int len = continut.length(); char buffer[] = new char[len]; try { FileWriter out = new FileWriter(nume.getText()); continut.getChars(0, len-1, buffer, 0); out.write(buffer); out.close(); text.requestFocus();
} }
public class TestTextArea { public static void main(String args[]) { Fereastra f = new Fereastra("TextArea"); f.initializare(); f.show(); } }
151
Curs 7 Desenarea
Conceptul de desenare Desenarea obiectelor - metoda paint() Suprafete de desenare - clasa Canvas Contextul grafic de desenare - clasa Graphics o Proprietatile unui context grafic o Primitive grafice Desenarea textelor Desenarea figurilor geometrice Folosirea fonturilor o Clasa Font o Clasa FontMetrics Folosirea culorilor Folosirea imaginilor o Incarcarea unei imagini dintr-un fisier o Afisarea imaginilor o Monitorizarea ncarcarii imaginilor - interfata ImageObserver o Crearea imaginilor n memorie - clasa MemoryImageSource Tiparirea
Conceptul de desenare
Un program Java care are interfata grafica cu utilizatorul trebui sa deseneze pe ecran toate componentele sale care au o reprezentarea grafica vizuala. Aceasta desenare include componentele vizuale standard folosite n program precum si obiectele grafice definite de catre programator. Desenarea componentelor se face automat si este un proces care se executa n urmatoarele situatii: la afisarea pentru prima data a unei componente ca raspuns al unei solicitari explicite a programului la operatii de minimizare, maximizare, redimensionare a suprafetei de afisare pe care este plasata o componenta Metodele care controleaza procesul de desenare se gasesc n clasa Component si sunt prezentate n tabelul de mai jos:
void
152
paint(Graphics g)
componenta n parte pentru a furniza reprezentarea sa grafica specifica. Metoda este apelata de fiecare data cnd continutul componentei trebuie desenat (redesenat) - la afisarea pentru prima data a componentei, la operatii de redimensionare, etc. Nu se apeleaza explicit. Actualizeaza starea grafica a unei componente. Actiunea acestei metode se realizeaza n trei pasi: sterge componenta prin supradesenarea ei cu culoarea fundalului stabileste culoarea (foreground) a componentei apeleaza metoda paint pentru a redesena complet componenta Nu se apeleaza explicit.
void update(Graphics g)
void repaint()
Executa explicit un apel al metodei update pentru a actualiza reprezentarea grafica a unei componente. Dupa cum se observa singurul argument al metodelor paint si update este un obiect de tip Graphics. Acesta obiect reprezinta contextul grafic n care se executa desenarea componentelor (vezi "Contextul grafic de desenare - clasa Graphics"). Toate desenele care trebuie sa apara pe o suprafata de desenare se realizeaza n metoda paint a unei componente, n general apelata intern sau explicit cu metoda repaint, ori de cte ori componenta respectiva trebuie redesenata.
153
super.paint(g);//apelez metoda paint a clasei Frame g.setFont(new Font("Arial", Font.BOLD, 11)); g.setColor(Color.red); g.drawString("Aplicatie DEMO", 5, 35); } }
public class TestPaint { public static void main(String args[]) { Fereastra f = new Fereastra("Test Paint"); f.show(); } }
Observati ca la orice redimensionare a ferestrei textul "Aplicatie DEMO" va fi redesenat. Daca desenarea acestui text ar fi fost facuta oriunde n alta parte dect n metoda paint, la prima redimensionare a ferestrei acesta s-ar pierde. Asadar, desenarea n Java trebuie sa se faca doar n cadrul metodelor paint ale componentelor grafice.
crearea unei planse de desenare, adica o subclasa a clasei Canvas redefinirea metodei paint din clasa respectiva redefinirea metodelor getMinimumSize, getMaximumSize, getPreferredSize desenarea efectiva a componentei n cadrul metodei paint adaugarea plansei la un container cu metoda add. 154
interceptarea evenimentelor de tip FocusEvent, KeyEvent, MouseEvent, ComponentEvent si tratarea lor (daca este cazul).
Exemplu: Sa definim o plansa pe care desenam un patrat si cercul sau circumscris. Plansa o vom afisa apoi pe o fereastra.
import java.awt.*; class Plansa extends Canvas { Dimension canvasSize = new Dimension(100, 100); public void paint(Graphics g) { g.setColor(Color.red); g.drawRect(0, 0, 100, 100); g.setColor(Color.blue); g.drawOval(0, 0, 100, 100); } public Dimension getMinimumSize() { return canvasSize; } public Dimension getPreferredSize() { return canvasSize; } } class Fereastra extends Frame { public Fereastra(String titlu) { super(titlu); setSize(200, 200); add(new Plansa(), BorderLayout.CENTER); } } public class TestCanvas { public static void main(String args[]) { Fereastra f = new Fereastra("Test Paint"); f.show(); } }
155
primitive grafice : desenarea de figuri geometrice, texte si imagini stabilirea proprietatilor unui context grafic, adica: o stabilirea culorii si fontului curente cu care se face desenarea o stabilirea originii coordonatelor suprafetei de desenare o stabilirea suprafetei n care sunt vizibile componentelor desenate o stabilirea modului de desenare.
modul de desenare
void setXorMode(Color c1) - desenare "sau exclusiv" void setPaintMode(Color c1) - supradesenare
156
Primitive grafice
Prin primitive grafice ne vom referi n continuare la metodele clasei Graphics care permit desenarea de figuri geometrice si texte.
Desenarea textelor
Desenarea textelor de face cu metodele drawString, drawBytes, drawChars n urmatoarele formate:
drawString(String str, int x, int y) drawBytes(bytest data, int offset, int length, int x, int y) drawChars(charst data, int offset, int length, int x, int y)
unde x si y reprezinta coltul din stnga-jos al textului. Textul desenat va avea culoarea curenta a contextului grafic.
dreptunghiuri simple
drawRect(int x, int y, int width, int height) fillRect(int x, int y, int width, int height) clearRect(int x, int y, int width, int height)
ovaluri
drawOval(int x, int y, int width, int height) fillOval(int x, int y, int width, int height)
poligoane
drawPolygon(intst xPoints, intst yPoints, int nPoints) drawPolygon(Polygon p) fillPolygon(intst xPoints, intst yPoints, int nPoints) fillPolygon(Polygon p)
157
Metodele care ncep cu "fill" vor desena figuri geometrice care au interiorul colorat, adica "umplut" cu culoarea curenta a contextului de desenare.
Folosirea fonturilor
Dupa cum vazut, pentru a scrie un text pe ecran avem doua posibilitati. Prima dintre acestea este sa folosim o componenta orientata-text cum ar fi Label, TextField sau TextArea, iar a doua sa apelam la metodele clasei Graphics de desenare a textelor: drawString, drawChars, drawBytes. Indiferent de modalitatea aleasa, putem specifica prin intermediul fonturilor cum sa arate textul respectiv, acest lucru realiznduse prin metoda clasei Component, respectiv Graphics: setFont(Font f). Cei mai importanti parametri ce caracterizeaza un font sunt:
numele fontului: Helvetica Bold, Arial Bold Italic, etc familia din care face parte fontul: Helvetica, Arial, etc dimensiunea fontului: naltimea sa stilul fontului: ngrosat (bold), nclinat (italic) metrica fontului
Clasele care ofera suport pentru lucrul cu fonturi sunt Font si FontMetrics. In continuare sunt prezentate modalitatile de lucru cu aceste doua clase.
Clasa Font
Un obiect de tip Font ncapsuleaza informatii despre toti parametrii unui font, mai putin despre metrica acestuia. Constructorul uzual al clasei este cel care primeste ca argumene numele fontului, dimensiunea si stilul acestuia.
Font(String name, int style, int size)
//pentru componente etichetate Label label = new Label("Text Java"); label.setFont(new Font("Arial", Font.BOLD, 12)); //in metoda paint(Graphics g) g.setFont(new Font("Times New Roman", Font.ITALIC, 14)); g.drawString("Text Java", 0, 0);
158
O platforma de lucru are instalate, la un moment dat, o serie ntreaga de fonturi care sunt disponibile pentru scrierea textelor. Lista acestor fonturi se poate obtine cu metoda getAllFonts a clasei GraphicsEnvironment astfel:
Font[] fonturi = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
Exemplul urmator afiseaza lista primelor 20 de fonturi disponibile pe platforma curenta de lucru. Textul fiecarui nume de font va fi scris cu fontul sau corespunzator.
import java.awt.*; class Fonturi extends Canvas { private Font[] fonturi; Dimension canvasSize = new Dimension(400, 400); public Fonturi() { setSize(canvasSize); fonturi = GraphicsEnvironment. getLocalGraphicsEnvironment().getAllFonts(); } public void paint(Graphics g) { String nume; for(int i=0; i < 20; i++) { nume = fonturi[i].getFontName(); g.setFont(new Font(nume, Font.PLAIN, 14)); g.drawString(nume, 20, (i + 1) * 20); } } public Dimension getMinimumSize() { return canvasSize; } public Dimension getPreferredSize() { return canvasSize; }
class Fereastra extends Frame { public Fereastra(String titlu) { super(titlu); add(new Fonturi(),BorderLayout.CENTER); pack(); } } public class TestAllFonts { public static void main(String args[]) { Fereastra f = new Fereastra("All fonts"); f.show(); } }
Clasa FontMetrics
159
La afisarea unui sir cu metoda drawString trebuie sa specificam pozitia la care sa apara sirul pe ecran. In momentul n care avem de afisat mai multe siruri trebuie sa calculam pozitiile lor de afisare n functie de lungimea si naltimea n pixeli a textului fiecarui sir. Pentru aceasta este folosita clasa FontMetrics. Un obiect din aceasta clasa se construieste pornind de la un obiect de tip Font si pune la dispozitie informatii despre dimensiunile n pixeli pe care le au caracterele fontului respectiv. Asadar, un obiect de tip FontMetrics ncapsuleaza informatii despre metrica unui font, cu alte cuvinte despre dimensiunile n pixeli ale caracterelor sale. Utilitatea principala a acestei clase consta n faptul ca permite pozitionarea precisa a textelor pe o suprafata de desenare, indiferent de fontul folosit de acestea. Metrica unui font consta n urmatoarele atribute pe care le au caracterele unui font:
linia de baza : este linia dupa care sunt aliniate caracterele unui font linia de ascendenta : linia superioara pe care nu o depaseste nici un caracter din font linia de descendenta : linia inferioara sub care nu coboara nici un caracter din font ascendentul: distanta ntre linia de baza si linia de ascendenta descendentul:distanta ntre linia de baza si linia de descendenta latimea: latimea unui anumit caracter din font naltimea: distanta ntre liniile de baza distanta ntre linii ("leading"):distanta optima ntre doua linii de text scrise cu acelasi font
Figura de mai jos prezinta o imagine grafica asupra metricii unui font:
Reamintim ca la metoda drawString(String s, int x, int y) argumentele x si y semnifica coltul din stnga-jos al textului. Ca sa fim mai precisi, y reprezinta pozitia liniei de baza a textului care va fi scris. Constructorul clasei FontMetrics creeaza un obiect ce ncapsuleaza informatii despre un anumit font: FontMetrics(Font f).
Font f = new Font("Arial", Font.BOLD, 11); FontMetrics fm = new FontMetrics(f); Un context grafic pune la dispozitie o metoda speciala getFontMetrics de creare obiect de tip FontMetrics, pornind de la fontul curent al contextului grafic: public void paint(Graphics g) { Font f = new Font("Arial", Font.BOLD, 11);
a unui
160
Cele mai uzuale metode ale clasei FontMetrics sunt cele pentru: aflarea naltimii unei linii pe care vor fi scrise caractere ale unui font: getHeight, aflarea latimii totale n pixeli a unui sir de caractere specificat: stringWidth aflarea latimii unui anumit caracter din font: charWidth Exemplu: afisarea unor texte pe ecran (zilele saptamnii, lunile anului si mesajul "Hello FontMetrics!") folosind clasa FontMetrics.
import java.awt.*; class Texte extends Canvas { Dimension canvasSize = new Dimension(700, 300); private Stringst zile={"Luni", "Marti", "Miercuri", "Joi", "Vineri", "Sambata", "Duminica"}; private Stringst luni = { "Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"}; public Texte() { setSize(canvasSize); } public void paint(Graphics g) { FontMetrics fm; int x,y; String enum_zile = "Zilele saptamanii:", enum_luni="Lunile anului:", text; //alegem un font si aflam metrica sa g.setFont(new Font("Arial", Font.BOLD, 20)); fm = g.getFontMetrics(); x = 0; y = fm.getHeight(); g.drawString(enum_zile, x, y); x += fm.stringWidth(enum_zile); for(int i=0; i < zile.length; i++) { text = zilesit; if (i < zile.length - 1) text += ", "; g.drawString(text, x, y); x += fm.stringWidth(text); } //schimbam fontul g.setFont(new Font("Times New Roman", Font.PLAIN, 14)); fm = g.getFontMetrics(); x = 0; y += fm.getHeight(); g.drawString(enum_luni, x, y); x += fm.stringWidth(enum_luni); for(int i=0; i < luni.length; i++) {
161
text = lunisit; if (i < luni.length - 1) text += ", "; g.drawString(text, x, y); x += fm.stringWidth(text); } //schimbam fontul curent g.setFont(new Font("Courier New", Font.BOLD, 60)); fm = g.getFontMetrics(); x = 0; y += fm.getHeight(); g.drawString("Hello FontMetrics!", x, y); } public Dimension getMinimumSize() { return canvasSize; } public Dimension getPreferredSize() { return canvasSize; }
} class Fereastra extends Frame { public Fereastra(String titlu) { super(titlu); add(new Texte(),BorderLayout.CENTER); pack(); } } public class TestFontMetrics { public static void main(String args[]) { Fereastra f = new Fereastra("FontMetrics"); f.show(); } }
Folosirea culorilor
Orice culoare este formata prin combinatia culorilor standard rosu (Red), verde (Green) si albastru (Blue), la care se adauga un anumit grad de transparenta (Alpha). Fiecare din acesti patru parametri poate varia ntr-un interval cuprins fie ntre 0 si 255 (daca dorim sa specificam valorile prin numere ntregi), fie ntre 0.0 si 1.0 (daca dorim sa specificam valorile prin numere reale). O culoare este reprezentata printr-o instanta a clasei Color sau a subclasei sale SystemColor. Pentru a crea o culoare avem doua posibilitati: sa folosim una din constantele definite ntr-un din cele doua clase sa folosim unul din constructorii clasei Color. Sa vedem mai nti care sunt constantele definite n aceste clase: Color SystemColor
162
black blue cyan darkGray gray green lightGray magenta orange pink red white yellow
activeCaption activeCaptionBorder activeCaptionText control controlHighlight controlShadow contolText desktop menu text textHighlight window . . .
Observati ca n clasa Color sunt definite culori uzuale din paleta standard de culori, n timp ce n clasa SystemColor sunt definite culorile componentelor standard (ferestre, texte, meniuri, etc) ale platformei curente de lucru. Folosirea acestor constante se face ca n exemplele de mai jos:
Color rosu = Color.red; Color galben = Color.yellow; Color fundal = SystemColor.desktop;
Daca nici una din aceste culori predefinite nu corespunde preferintelor noastre atunci putem crea noi culori prin intermediul constructorilor clasei Color:
Color(float r, float Color(float r, float Color(int r, int g, Color(int r, int g, Color(int rgb) g, g, int int float b) float b, float a) b) b, int a)
unde r, g, b, a sunt valorile pentru rosu, verde, albastru si transparenta (alpha) iar parametrul "rgb" de la ultimul constructor reprezinta un ntreg format din: bitii 16-23 rosu, 8-15 verde, 0-7 albastru. Valorile argumentelor variaza ntre 0-255 pentru tipul int, respectiv 0.0-1.0 pentru tipul float. Valoarea 255 (sau 1.0) pentru transparenta specifica faptul ca respectiva culoare este complet opaca, iar valoarea 0 (sau 0.0) specifica transparenta totala. Implicit, culorile sunt complet opace.
//Exemple de folosire a constructorilor: Color alb = new Color(255, 255, 255); Color negru = new Color(0, 0, 0); Color rosu = new Color(255, 0, 0); Color rosuTransparent = new Color(255, 0, 0, 128); Metodele cele mai folosite ale clasei Color sunt: Color brighter() Color darker() int getAlpha() int getRed() int getGreen() int getBlue()
163
int getRGB()
Determina valoarea ce reprezinta culoarea respectiva (bitii 16-23 rosu, 8-15 verde, 0-7 albastru)
Sa consideram o aplicatie cu ajutorul careia putem vizualiza dinamic culorile obtinute prin diferite combinatii ale parametrilor ce formeaza o culoare. Aplicatia va arata astfel:
import java.awt.*; import java.awt.event.*; class Culoare extends Canvas { public Color color = new Color(0, 0, 0, 255); Dimension canvasSize = new Dimension(150, 50); public Culoare() { setSize(canvasSize); } public void paint(Graphics g) { g.setColor(color); g.fillRect(0, 0, canvasSize.width, canvasSize.height); String text = ""; text += " R=" + color.getRed(); text += " G=" + color.getGreen(); text += " B=" + color.getBlue(); text += " A=" + color.getAlpha(); g.drawString(text, 0, 30);
} }
public Dimension getPreferredSize() {return canvasSize; } //fereastra principala class Fereastra extends Frame implements AdjustmentListener { private Scrollbar rValue, gValue, bValue, aValue; private Culoare culoare; public Fereastra(String titlu) { super(titlu); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void initializare() { Panel rgbValues = new Panel();
164
rgbValues.setLayout(new GridLayout(4, 1)); rValue = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 256); rValue.setBackground(Color.red); gValue = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 256); gValue.setBackground(Color.green); bValue = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 256); bValue.setBackground(Color.blue); aValue = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 256); aValue.setValue(255); aValue.setBackground(Color.lightGray); rgbValues.add(rValue); rgbValues.add(gValue); rgbValues.add(bValue); rgbValues.add(aValue); rgbValues.setSize(200, 100); add(rgbValues, BorderLayout.CENTER); culoare = new Culoare(); add(culoare, BorderLayout.NORTH); pack(); rValue.addAdjustmentListener(this); gValue.addAdjustmentListener(this); bValue.addAdjustmentListener(this); aValue.addAdjustmentListener(this); } public void adjustmentValueChanged(AdjustmentEvent e) { int r = rValue.getValue(); int g = gValue.getValue(); int b = bValue.getValue(); int a = aValue.getValue(); Color c = new Color(r, g, b, a); culoare.color = c; culoare.repaint(); }
//clasa principala public class TestColor{ public static void main(String args[]) { Fereastra f = new Fereastra("Color"); f.initializare(); f.show(); } }
165
Folosirea imaginilor
Aceasta este o imagine:
In Java AWT este posibila folosirea imaginilor create extern n format gif sau jpeg. Orice imagine este o instanta a clasei Image. Aceasta nu este o clasa de componente (nu extinde clasa Component) ci implementeaza obiecte care pot fi desenate pe suprafata unor componente cu metode specifice unui context grafic pentru componenta respectiva (similar modului cum se deseneaza o linie sau un cerc).
Toolkit
public Image getImage(URL url) public Image getImage(String fisier)
Pentru a obtine un obiect de tip Toolkit se va folosi metoda getDefaultToolkit, ca n exemplul de mai jos:
Toolkit toolkit = Toolkit.getDefaultToolkit(); Image image1 = toolkit.getImage("imageFile.gif"); Image image2 = toolkit.getImage( new URL("http://java.sun.com/graphics/people.gif")); Metoda getImage nu verifica daca fisierul sau adresa specificata reprezinta o imagine
valida si nici nu ncarca efectiv imaginea n memorie, aceste operatiuni fiind facute abia n momentul n care se va realiza afisarea imaginii pentru prima data. Metoda nu face dect sa creeze un obiect de tip Image care face referinta la o anumita imagine externa.
166
Dintre metodele clasei Image cele mai des folosite sunt cele pentru determinarea dimensiunilor unei imagini: unde parametrul discuta ulterior)
int getHeight(ImageObserver observer) int getWidth(ImageObserver observer) observer este uzual this. (despre interfata ImageObserver
se va
Afisarea imaginilor
Afisarea unei imagini ntr-un context grafic se realizeaza prin intermediul metodei drawImage din clasa Graphics si, n general, se realizeaza n metoda paint a unui obiect de tip Canvas. Cele mai uzuale formate ale metodei sunt:
boolean drawImage(Image img, int x, int y, ImageObserver observer) boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)
unde:
img este obiectul ce reprezinta imaginea x si y sunt coordonatele stnga-sus la care
va fi afisata imaginea, relative la spatiul de coordonate al contextului grafic observer este un obiect care "observa" ncaracarea imaginii si va fi informat pe masura derularii acesteia; de obicei se specifica this. width, heigth reprezinta naltimea si latimea la care trebuie scalata imaginea bgColor reprezinta culoarea cu care vor fi colorati pixelii transparenti ai imaginii
Image img = Toolkit.getDefaultToolkit().getImage("taz.gif"); g.drawImage(img, 0, 0, this); g.drawImage(img, 0, 200, 100, 100, this); g.drawImage(img, 200, 0, 200, 400, Color.yellow, this); drawImage returneaza true daca imaginea a fost afisata n ntregime si false
Metoda n caz contrar, cu alte cuvinte metoda nu astepta ca o imagine sa fie complet afisata ci se termina imediat ce procesul de afisare a nceput. Sa detaliem putin acest aspect. In cazul n care se afiseaza o imagine care se gaseste pe Internet sau imaginea afisata este de dimensiuni mari se va observa ca aceasta nu apare complet de la nceput ci este desenata treptat fara interventia programatorului. Acest lucru se ntmpla deoarece metoda drawImage nu face dect sa declanseze procesul de ncarcare/afisare a imaginii, dupa care reda imediat controlul apelantului, lucru deosebit de util ntruct procesul de ncarcare a unei imagini poate dura mult si nu este de dorit ca n acest interval de timp (pna la ncarcarea completa a imaginii) aplicatia sa fie blocata. Ca urmare, la apelul metodei drawImage va fi desenata numai portiunea de imagine care este disponibila la un moment dat si care poate fi incompleta. De aceea trebuie sa existe un mecanism prin care componenta sa fie redesenata n momentul n care au mai sosit 167
informatii legate de imagine. Acest mecanism este realizat prin intermediul interfetei ImageObserver, implementata de clasa Component si deci de toate componentele. Aceasta interfata specifica obiecte care au nceput sa utilizeze o imagine incompleta si care trebuie anuntate de noile date obtinute n legatura cu imaginea respectiva.
Implementarea implicita consta ntr-un apel la metoda repaint pentru dreptunghiul specificat la apel si care reprezinta zona din imagine pentru care se cunosc noi informatii. Intregul flags furnizeaza informatii despre starea transferului. Aceste informatii pot fi aflate prin intermediul constantelor definite de interfata. Acestea sunt : ABORT Incarcarea imaginii a fost ntrerupta, nainte de completarea ei. ALLBITS Imaginea a fost ncarcata complet ERROR A aparut o eroare n timpul ncarcarii imaginii FRAMEBITS Totii bitii cadrului curent sunt disponibili HEIGHT Inaltimea imaginii este disponibila PROPERTIES Proprietatile imaginii sunt disponibile SOMEBITS Au fost receptionati noi pixeli ai imaginii WIDTH Latimea imaginii este disponibila Prezenta n flags a unui bit de valoare 1 pe pozitia reprezentata de o constanta nseamna ca respectiva conditie este ndeplinita.
//Exemple (flags & ALLBITS) != 0 imaginea este completa (flags & ERROR | ABORT ) != 0 a aparut o erorare sau transferul imaginii a fost ntrerupt Metoda imageUpdate poate fi redefinta pentru a personaliza afisarea imaginii. Pentru aceasta implementam clasei de tip Canvas, folosita pentru afisarea imaginii, metoda imageUpdate, care va fi apelata asincron de fiecare data cnd sunt disponibili noi pixeli. public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) { //se deseneaza imaginea numai daca toti bitii sunt disponibili if (( flags & ALLBITS) != 0) { repaint(); } //daca am toti bitii nu mai sunt necesare noi update-uri return ( (flags & (ALLBITS | ABORT)) == 0); }
168
In cazul n care dorim sa folosim o anumita imagine creata direct din program si nu ncarcata dintr-un fisier vom folosi clasa MemoryImageSource, aflata in pachetul java.awt.image. Pentru aceasta va trebui sa definim un vector de numere ntregi n care vom scrie valorile ntregi (RGB) ale culorilor pixelilor ce definesc imaginea noastra. Dimensiunea vectorului va fi naltimea nmultita cu latimea n pixeli a imaginii. Constructorul clasei MemoryImageSource este:
MemoryImageSource(int w, int h, intst pixeli, int off, int scan)
unde:
w, h reprezinta dimensiunile imaginii (latimea si naltimea) pixeli[] este vectorul cu culorile imaginii off, scan reprezinta modalitatea de construire a matricii imaginii
pornind de la
vectorul cu pixeli, normal aceste valori sunt off = 0, scan = w In exemplul urmator vom crea o imagine cu pixeli de culori aleatorii si o vom afisa pe ecran:
int w = 100; int h = 100; intst pix = new intsw * ht; int index = 0; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { int red = (int) (Math.random() * 255); int green = (int) (Math.random() * 255); int blue = (int) (Math.random() * 255); pixsindex++t = new Color(red, green, blue).getRGB(); } } img = createImage(new MemoryImageSource(w, h, pix, 0, w)); g.drawImage(img, 0, 0, this); //g este un context grafic
Tiparirea
Tiparirea in Java este tratata in aceeasi maniera ca si desenarea, singurul lucru diferit fiind contextul grafic in care se executa operatiile. Pachetul care ofera suport pentru tiparire este java.awt.print, iar clasa principala care controleaza tiparirea este PrinterJob. O aplicatie va apela metode ale acestei clase pentru: crearea unei sesiuni de tiparire (job) invocarea dialogului cu utilizatorul pentru specificarea unor parametri legati de tiparire tiparirea efectiva Orice componenta care poate fi afisata pe ecran poate fi si tiparita. In general, orice informatii care trebuie att afisate ct si tiparite, vor fi incapsulate ntr-un obiect grafic componenta, care are o reprezentare vizuala descrisa de metoda paint si care va specifica si modalitatea de reprezentare a sa la imprimanta. Un obiect care va fi tiparit trebuie sa implementeze interfata Printable care contine o 169
singura metoda print responsabila cu descrierea modalitatii de tiparire a obiectului. In cazul cnd imaginea de pe ecran coincide cu imaginea de la imprimanta, codurile metodelor paint si print pot fi identice. In general, metoda print are urmatorul format:
public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { //descrirea imaginii obiectului ce va fi afisata la imprimanta //poate fi un apel la metoda paint: paint(g) if (ceva nu este in regula) { return Printable.NO_SUCH_PAGE; } return Printable.PAGE_EXISTS; } }
Pasii care trebuie efectuati pentru tiparirea unui obiect sunt: 1. Crearea unei sesiuni de tiparire 2. Specificarea obiectului care va fi tiparit; acesta trebuie sa implementeze interfata Printable 3. Optional, initierea unui dialog cu utilizatorul pentru precizarea unor parametri legati de tiparire 4. Tiparirea efectiva
PrinterJob.getPrinterJob setPrintable printDialog print
In exemplul urmator vom defini un obiect care are aceeasi reprezentare pe ecran ct si la imprimanta (un cerc circumscris unui patrat, nsotit de un text) si vom tipari obiectul respectiv.
import import import import java.io.*; java.awt.*; java.awt.event.*; java.awt.print.*;
class Plansa extends Canvas implements Printable { Dimension d = new Dimension(400, 400); public Dimension getPreferredSize() { return d; } public void paint(Graphics g) { g.drawRect(200, 200, 100, 100); g.drawOval(200, 200, 100, 100); g.drawString("Hello", 200, 200); } public int print(Graphics g, PageFormat pf, int pi) throws PrinterException { if (pi >= 1) { return Printable.NO_SUCH_PAGE;
170
class Fereastra extends Frame implements ActionListener { private Plansa plansa = new Plansa(); private Button print = new Button("Print"); public Fereastra(String titlu) { super(titlu); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); add(plansa, BorderLayout.CENTER); Panel south = new Panel(); south.setLayout(new FlowLayout(FlowLayout.CENTER)); south.add(print); add(south, BorderLayout.SOUTH); print.addActionListener(this); pack(); } public void actionPerformed(ActionEvent e) { //1.crearea unei sesiuni de tiparire PrinterJob printJob = PrinterJob.getPrinterJob(); //2.stabilirea obiectului ce va fi tiparit printJob.setPrintable(plansa); //3.initierea dialogului cu utilizatorul if (printJob.printDialog()) { try { //4.tiparirea efectiva printJob.print(); } catch (PrinterException e) { System.out.println("Exceptie la tiparire!"); } } } public class TestPrint { public static void main(String args[]) throws Exception { Fereastra f = new Fer("Test Print"); } e.printStackTrace();
171
} }
f.show();
Tiparirea textelor
O alta varianta pentru tiparirea de texte este deschiderea unui flux catre dispozitivul special reprezentat de imprimanta si scrierea informatiilor, linie cu linie, pe acest flux. In Windows, imprimanta poate fi referita prin "lpt1", iar n Unix prin "/dev/lp". Observati ca aceasta abordare nu este portabila, deoarece necesita tratare speciala n functie de sistemul de operare folosit.
import java.io.*; import java.awt.*;
class TestPrint { public static void main(String args[]) throws Exception { //pentru Windows PrintWriter imp = new PrintWriter(new FileWriter("lpt1")); //pentru UNIX //PrintWriter imp = new PrintWriter(new FileWriter("/dev/lp")); imp.println("Test imprimanta"); imp.println("ABCDE"); imp.close();
} }
Ce este un fir de executie ? Crearea unui fir de executie o Extinderea clasei Thread o Implementarea interfetei Runnable Ciclul de viata al unui fir de executie Stabilirea prioritatilor de executie Sincronizarea mai multor fire de executie o Scenariul producator / consumator o Blocarea unui obiect (cuvntul cheie synchronized) o Metodele wait, notify si notifyAll Gruparea firelor de executie Comunicarea prin fluxuri de tip "pipe"
172
Un program si poate defini nsa nu doar un fir de executie ci oricte, ceea ce nseamna ca n cadrul unui proces se pot executa simultan mai multe fire de executie, permitnd executia concurenta a sarcinilor independente ale acelui program. Un fir de executie poate fi asemanat cu o versiune redusa a unui proces, ambele rulnd simultan si independent pe o structura secventiala de executie a instructiunilor lor. De asemenea executia simultana a firelor de executie n cadrul unui proces este similara cu executia concurenta a proceselor: sistemul de operare va aloca ciclic cuante din timpul procesorului fiecarui fir de executie pna la terminarea lor. Din acest motiv firele de executie mai sunt numite si procese usoare. Care ar fi nsa deosebirile ntre un fir de executie si un proces ? In primul rnd deosebirea majora consta n faptul ca firele de executie nu pot rula dect n cadrul unui proces. O alta deosebire rezulta din faptul ca fiecare proces are propria sa memorie (propriul sau spatiu de adrese) iar la crearea unui nou proces (fork) este realizata o copie exacta a procesului parinte : cod + date; la crearea unui fir de executie nu este copiat dect codul procesului parinte; toate firele de executie au deci acces la aceleasi date, datele procesului original. Asadar un fir de executie mai poate fi privit si ca un context de executie n cadrul unui proces parinte.
173
Firele de executie sunt utile n multe privinte, nsa uzual ele sunt folosite pentru executarea unor operatii consumatoare de timp fara a bloca procesul principal : calcule matematice, asteptarea eliberarii unei resurse, acestea realizndu-se de obicei n fundal.
Prima metoda a clasei este constructorul, care primeste ca argument un sir ce va reprezenta numele firului de executie creat n momentul cnd constructorul este apelat.
SimpleThread t = new SimpleThread("Java") //creeaza un fir de executie cu numele Java
In cazul n care nu vrem sa dam nume firelor de executie pe care le cream atunci putem renunta la definirea acestui constructor si sa ramnem doar cu constructorul implicit, fara argumente, care creeaza un fir de executie fara nici un nume. Ulterior acesta poate primi 174
un nume cu metoda setName(String). Evident, se pot defini si alti constructori, acestia fiinde utili cnd vrem sa trimitem diversi parametri firului de executie. A doua metoda este metoda run, "inima" oricarui fir de executie n care scriem efectiv codul pe care trebuie sa-l execute firul de executie. Un fir de executie creat nu este automat pornit, lansarea sa n executie se realizeaza prin metoda start, definita de asemenea n clasa Thread.
SimpleThread t = new SimpleThread("Java") t.start() //creeaza si lanseaza un fir de executie
Sa consideram n continuare un exemplu n care definim un fir de executie ce afiseaza numerele ntregi dintr-un interval cu un anumit pas. Firul de executie este implementat de clasa Counter.
class Counter extends Thread { //clasa care defineste firul de executie private int from, to, step; public Counter(int from, int to, int step) { this.from = from; this.to = to; this.step = step; } public void run() { for(int i = from; i <= to; i += step) System.out.print(i + " " ); } } public class TestCounter { //clasa principala public static void main(String args[]) { Counter cnt1, cnt2; cnt1 = new Counter(0, 10, 2); //numara de la 0 la 100 cu pasul 5 cnt2 = new Counter(100, 200, 10); //numara de la 100 la 200 cu pasul 10 cnt1.start(); cnt2.start(); //pornim firele de executie //ele vor fi distruse automat la terminarea lor } }
Gndind secvential, s-ar crede ca acest program va afisa prima data numerele de la 0 la 100 cu pasul 5, apoi numerele de la 100 la 200 cu pasul 10, ntruct primul apel este catre contorul cnt1, deci rezultatul afisat pe ecran ar trbui sa fie: 0 5 10 15 20 25 30 35 40
45 50 55 60 65 70 75 80 85 90 95 100 100 110 120 130 140 150 160 170 180 190 200 .
In realitate nsa, rezultatul obtinut va fi o intercalare de valori produse de cele doua fire de executie ce ruleaza simultan. La rulari diferite se pot obtine rezultate diferite deoarece timpul alocat fiecarui fir de executie poate sa nu fie acelasi, el fiind controlat de procesor
175
ntr-o maniera "aparent" aleatoare: 0 100 5 110 10 120 15 130 20 140 25 150 160
170 180 190 200 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100
In acest caz nu mai putem extinde clasa Thread ci trebuie sa implementam direct n clasa noastra interfata Runnable. Clasa Thread implementeaza ea nsasi interfata Runnable si, din acest motiv, la extinderea ei obtineam o implementare implicita a interfetei. Asadar, interfata Runnable permite unei clase sa fie active, fara a extinde clasa Thread. Interfata Runnable se gaseste n pachetul java.lang si este definita astfel:
public interface Runnable { pulic abstract void run( ); }
Prin urmare, o clasa care instantiaza fire de executie prin implementarea interfetei Runnable trebuie obligatoriu sa implementeze metoda run. Formatul general al unei clase care implementeaza interfata Runnable este:
public class SimpleThread implements Runnable { private Thread simpleThread = null; public SimpleThread() { if (simpleThread == null) { simpleThread = new Thread(this); simpleThread.start(); } public void run() { //codul executat de firul de executie } }
Spre deosebire de modalitatea anterioara, se pierde nsa tot suportul oferit de clasa Thread pentu crearea unui fir de executie. Simpla instantiere a unei clase care implemeneaza interfata Runnable nu creeaza nici un fir de executie. Din acest motiv crearea firelor de executie prin instantierea unei astfel de clase trebuie facuta explicit. Cum se realizeaza acest lucru ? In primul rnd trebuie declarat un obiect de tip Thread ca variabila membra a clasei respective. Acest obiect va reprezenta firul de executie propriu zis al carui cod se gaseste n clasa noastra.
private Thread simpleThread = null;
Urmatorul pas este instantierea si initializarea firului de executie. Acest lucru se realizeaza ca pentru orice alt obiect prin instructiunea new, urmata de un apel la un constructor al clasei Thread, nsa nu la oricare dintre acestia. Trebuie apelat constructorul care sa primeasca drept argument o instanta a clasei noastre. Dupa creare, firul de executie poate fi lansat printr-un apel la metoda start.(Aceste operatiuni sunt scrise de obicei n constructorul clasei noastre pentru a fi executate la initializarea unei instante, dar pot fi scrise oriunde n corpul clasei sau chiar n afara ei) 176
Specificarea determina crearea unui fir de executie care la lansarea sa va cauta n clasa noastra metoda run si o va executa. Acest constructor accepta ca argument orice instanta a unei clase "Runnable". Asadar metoda run nu trebuie apelata explicit, acest lucru realizndu-se automat la apelul metodei start. Apelul explicit al metodei run nu va furniza nici o eroare, nsa aceasta va fi executata ca orice alta metoda, deci nu ntr-un fir de executie. Sa rescriem acum exemplul anterior (afisarea numerele ntregi dintr-un interval cu un anumit pas), folosind interfata Runnable. Vom vedea ca implementarea interfetei Runnable permite o flexibilitate sporita n lucrul cu fire de executie.
simpleThread = new Thread( this ); simpleThread.start(); argumentului this n constructorul clasei Thread
Varianta 1 (standard)
Crearea firului de executie se realizeaza n constructorul clasei Counter
class Counter implements Runnable { private Thread counterThread = null; private int from, to, step; public Counter(int from, int to, int step) { this.from = from; this.to = to; this.step = step; if (counterThread == null) { counterThread = new Thread(this); counterThread.start(); } } public void run() { for(int i = from; i <= to; i += step) System.out.print(i + " " ); } } public class TestThread2 { public static void main(String args[]) { Counter cnt1, cnt2; //lansez primul fir de executie (prin constructor) cnt1 = new Counter(0, 100, 5); //lansez al doilea fir de executie (prin constructor) cnt2 = new Counter(100, 200, 10); } }
Varianta 2
Crearea firului de executie se realizeaza n afara clasei Counter:
class Counter implements Runnable { private int from, to, step; public Counter(int from, int to, int step) { this.from = from; this.to = to; this.step = step; }
177
public void run() { for(int i = from; i <= to; i += step) System.out.print(i + " " ); } } public class TestThread2 { public static void main(String args[]) { Counter cnt1, cnt2; cnt1 = new Counter(0, 100, 5); cnt2 = new Counter(100, 200, 10); new Thread( cnt1 ).start(); //lansez primul fir de executie new Thread( cnt2 ).start(); //lansez al doilea fir de executie } }
Asadar, un fir de executie se poate gasi n una din urmatoarele patru stari: 1. New Thread 2. Runnable 3. Not Runnable 4. Dead
178
In aceasta stare firul de executie este "vid", el nu are alocate nici un fel de resurse sistem si singura operatiune pe care o putem executa asupra lui este lansarea n executie, prin metoda start. Apelul oricarei alte metode n afara de start nu are nici un sens si va provoca o exceptie de tipul IllegalThreadStateException.
Starea "Runnable"
Dupa apelul metodei start un fir de executie va trece n starea "Runnable", adica se gaseste n executie.
counterThread.start(); //counterThread se gaseste in starea Runnable
Metoda start realizea urmatoarele operatiuni necesare rularii firului de executie: aloca resursele sistem necesare planifica firul de executie la CPU pentru a fi lansat apeleaza metoda run a obiectului reprezentat de firul de executie Un fir de executie aflat n starea Runnable nu nseamna neaparat ca acesta se gaseste efectiv n executie, adica instructiunile sale sunt interpretate de procesor. Acest lucru se ntmpla din cauza ca majoritatea calculatoarelor au un singur procesor iar acesta nu poate rula simultan toate firele de executie care se gasesc n starea Runnable. Pentru a rezolva aceasta problema interpretorul Java implementeaza o planificare care sa partajeze dinamic si corect procesorul ntre toate firele de executie care sunt n starea Runnable. Asadar, un fir de executie care "ruleaza" poate sa-si astepte de fapt rndul la procesor.
metode se
179
Observati ca metoda fiind statica apelul ei nu se face pentru o instanta anume a clasei Thread. Acest lucru este foarte normal deoarece, la un moment dat, un singur fir este n executie si doar pentru acesta are sens "adormirea" sa. In intervalul n care un fir de executie "doarme", acesta nu va fi execut chiar daca procesorul devine disponibil. Dupa expirarea acestui interval firul revine n starea Runnable, iar daca procesourul este n continuare disponibil si contiunua executia. Pentru fiecare tip de intrare n starea "Not Runnable", exista o secventa specifica de iesire din starea repectiva, care readuce firul de executie n starea Runnable. Acestea sunt: Daca un fir de executie a fost "adormit", atunci el devine Runnable doar dupa scurgerea intervalului de timp specificat de instructiunea sleep. Daca un fir de executie asteapta o anumita conditie, atunci un alt obiect trebuie sa l informeze daca acea conditie este ndeplinita sau nu; acest lucru se realizeaza prin instructiunile notify sau notifyAll (vezi "Sincronizarea firelor de executie"). Daca un fir de executie este blocat ntr-o operatiune de intrare/iesire atunci el redevine Runnable atunci cnd acea operatiune s-a terminat.
Starea "Dead"
Este starea n care ajunge un fir de executie la terminarea sa. Un fir de executie nu poate fi oprit din program printr-o anumita metoda, ci trebuie sa se termine n mod natural la terminarea metodei run pe care o executa. Spre deosebire de versiunile curente ale limbajului Java, n versiunea 1.0 exista metoda stop a clasei Thread care termina fortat un fir de executie, nsa ea a fost eliminata din motive de securitate. Asadar, un fir de executie trebuie sa-si "aranjeze" singur propria sa "moarte".
Dupa afisarea numerelor din intervalul specificat metoda se termina si odata cu ea si firul de executie repsectiv.
Prin folosirea unei variabile de terminare. In cazul cnd metoda run trebuie sa execute o bucla infinita atunci aceasta trebuie controlata si printr-o variabila care sa opreasca aceasta bucla atunci cnd dorim ca firul de executie sa se termine. Uzual, aceasta este o variabila membra a clasei care descrie firul de executie care fie este publica, fie este asociata cu o metoda care i schimba valoarea.
180
Sa consideram exemplul unui fir de executie care trebuie sa numere secundele scurse pna la apasarea tastei Enter. Vom scrie mai nti programul folosind metoda stop:
"nvechita" (deprecated) iar la compilarea programului vom obtine un mesaj de avertizare n acest sens. Putem evita metoda stop prin folosirea unei variabile de terminare.
181
try {
Metoda isAlive
Aceasta metoda este folosita pentru a vedea daca un fir de executie a fost pornit si nu s-a terminat nca. Metoda returneaza: true - daca firul este n una din starile Runnable sau Not Runnable false - daca firul este n una din starile New Thread sau Dead Intre starile Runnable sau Not Runnable, repectiv New Thread sau Dead nu se poate face nici o diferentiere.
WaitKey thread = new WaitKey(); // isAlive retuneaza false (starea este New Thread) thread.start(); // isAlive retuneaza true (starea este Runnable) System.in.read(); thread.running = false; // isAlive retuneaza false (starea este Dead)
Nu este necesara distrugerea explicita a unui fir de executie. Sistemul Java de colectare a gunoiului se ocupa de acest lucru. El poate fi fortat sa dezaloce resuresele alocate unui thread prin atribuirea cu null a variabilei care referea instanta firului de executie: myThread = null .
Schimbarea ulterioara a prioritatii unui fir de executie se realizeaza cu metoda setPriority a clasei Thread. Planificatorul Java lucreaza n modul urmator : daca la un moment dat sunt mai multe fire
182
de executie n starea Runnable, adica sunt pregatite pentru a fi executate, planificatorul l va alege pe cel cu prioritatea cea mai mare pentru a-l executa. Doar cnd firul de executie cu prioritate maxima se termina sau este suspendat din diverse motive va fi ales un fir de executie cu o prioritate mai mica. In cazul n care toate firele au aceeasi prioritate ele sunt alese dupa un algoritm simplu de tip "round-robin". De asemenea, planificarea este complet preemptiva : daca un fir cu prioritate mai mare dect firul care se executa la un moment dat solicita procesorul, atunci firul cu prioritate mai mare este imediat trecut n executie iar celalalt trecut n asteptare. Planificatorul Java nu va ntrerupe nsa un fir de executie n favoarea altuia de aceeasi prioritate, nsa acest lucru l poate face sistemul de operare n cazul n care acesta aloca procesorul n cuante de timp (un astfel de SO este Windows 95/NT). Asadar, un fir de executie Java cedeaza procesorul n una din situatiile : un fir de executie cu o prioritate mai mare solicita procesorul metoda sa run se termina vrea sa faca explicit acest lucru apelnd metoda yield timpul alocat pentru executia sa a expirat (pe SO cu cuante de timp) In nici un caz corectitudinea unui program nu trebuie sa se bazeze pe mecansimul de planificare a firelor de executie, deoarece acesta poate fi imprevizibil si depinde de la un sistem de operare la altul. Un fir de executie de lunga durata si care nu cedeaza explicit procesorul la anumite intervale de timp astfel nct sa poata fi executate si celelalte fire de executie se numeste fir de executie egoist si trebuie evitata scrierea lor, ntruct acapareaza pe termen nedefinit procesorul, blocnd efectiv executia celorlalte fire de executie pna la terminarea sa. Unele sistemele de operare combat acest tip de comportament prin metoda alocarii procesorului n cuante de timp fiecarui fir de executie, nsa nu trebuie sa ne bazam pe acest lucru la scrierea unui program. Un fir de executie trebuie sa fie "corect" fata de celelalte fire si sa cedeze periodic procesorul astfel nct toate sa aiba posibilitatea de a se executa.
183
public static void main(String args[]) { Selfish s1, s2; s1 = new Selfish("Firul 1"); s1.setPriority (Thread.MAX_PRIORITY); s2 = new Selfish("Firul 2"); s2.setPriority (Thread.MAX_PRIORITY); s1.start(); s2.start(); }
Firul de executie s1 are prioritate maxima si pna nu-si va termina executia nu-i va permite firului s2 sa execute nici o instructiune, acaparnd efectiv procesorul. Rezultatul va arata astfel:
Firul Firul Firul . . . Firul Firul Firul Firul . . . Firul Firul 1 a ajuns la 100 1 a ajuns la 200 1 a ajuns la 300 1 1 2 2 a a a a ajuns ajuns ajuns ajuns la la la la 99900 100000 100 200
Rezolvarea acestei probleme se face fie prin intermediul metodei statice yield a clasei Thread care determina firul de executie curent sa se opreasca temporar, dnd ocazia si altor fire sa se execute, fie prin "adormirea" temporara a firului curent cu ajutorul metodei sleep. Metoda run a clasei Selfish ar trebui rescrisa astfel:
public void run() { int i = 0; while (i < 100000) { i ++; if (i % 100 == 0) System.out.println(getName()+" a ajuns la } yield(); //cedez temporar procesorul
"+i);
Prin metoda yield un fir de executie nu cedeaza procesorul dect firelor de executie care au aceeasi prioritate cu a sa si nu celor cu prioritati mai mici.
184
Sa consideram de exemplu o aplicatie Java n care un fir de executie (producatorul) scrie date ntr-un fisier n timp ce alt fir de executie (consumatorul) citeste date din acelasi fisier pentru a le prelucra. Sau, sa presupunem ca producatorul genereaza niste numere si le plaseaza, pe rnd, ntr-un buffer iar consumatorul citeste numerele din acel buffer pentru a le interpreta. In ambele cazuri avem de-a face cu fire de executie concurente care folosesc o resursa comuna : un fisier, respectiv un vector si, din acest motiv, ele trebuie sincronizate ntr-o maniera care sa permita decurgerea normala a activitatii lor.
Vom implementa acum clasele Producator si Consumator care vor descrie cele doua fire de executie. Ambele vor avea o referinta comuna la un obiect de tip Buffer prin intermediul caruia si comunica valorile.
class Producator extends Thread { private Buffer buffer; public Producator(Buffer b) { buffer = b; } public void run() { for (int i = 0; i < 10; i++) { buffer.put(i); System.out.println("Producatorul a pus:\t" + i); try { sleep((int)(Math.random() * 100));
185
} } }
} catch (InterruptedException e) { }
class Consumator extends Thread { private Buffer buffer; public Consumator(Buffer b) { buffer = b; } public void run() { int value = 0; for (int i = 0; i < 10; i++) { value = buffer.get(); System.out.println("Consumatorul a primit:\t" + value); } } } //Clasa principala public class TestSincronizare1 { public static void main(String[] args) { Buffer b = new Buffer(); Producator p1 = new Producator(b); Consumator c1 = new Consumator(b); p1.start(); c1.start(); } }
Dupa cum ne asteptam rezultatul rularii acestui program nu va rezolva fi nici pe departe problema propusa de noi, motivul fiind lipsa oricarei sincronizari ntre cele doua fire de executie. Mai precis, rezultatul va fi ceva de forma:
Consumatorul Consumatorul Producatorul Consumatorul Consumatorul Consumatorul Consumatorul Consumatorul Consumatorul Consumatorul Consumatorul Producatorul Producatorul Producatorul Producatorul Producatorul Producatorul Producatorul Producatorul Producatorul a a a a a a a a a a a a a a a a a a a a primit: primit: pus: primit: primit: primit: primit: primit: primit: primit: primit: pus: pus: pus: pus: pus: pus: pus: pus: pus: -1 -1 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9
Ambele fire de executie acceseaza resursa comuna, adica obiectul de tip Buffer, ntr-o maniera haotica si acest lucru se ntmpla din dou\ motive :
186
consumatorul nu asteapta nainte de a citi ca producatorul sa genereze un numar si va prelua de mai multe ori acelasi numar. producatorul nu asteapta consumatorul sa preia numarul generat nainte de a produce un altul, n felul acesta consumatorul va "rata" cu siguranta unele numere (n cazul nostru aproape pe toate).
Problema care se ridica n acest moment este : cine trebuie sa se ocupe de sincronizarea celor doua fire de executie : clasele Producator si Consumator sau resursa comuna Buffer ? Raspunsul este: resursa comuna Buffer, deoarece ea trebuie sa permita sau nu accesul la continutul sau si nu firele de executie care o folosesc. In felul acesta efortul sincronizarii este transferat de la producator/consumator la un nivel mai jos, cel al resursei critice. Activitatile producatorului si consumatorului trebuie sincronizate la nivelul resursei comune n doua privinte: 1. Cele doua fire de executie nu trebuie sa acceseze simultan buffer-ul ; acest lucru se realizeaza prin blocarea obiectului Buffer atunci cnd este accesat de un fir de executie, astfel nct nici nu alt fir de executie sa nu-l mai poate accesa. (vezi "Blocarea unui obiect"). 2. Cele doua fire de executie trebuie sa se coordoneze, adica producatorul trebuie sa gaseasca o modalitate de a "spune" consumatorului ca a plasat o valoare n buffer, iar consumatorul trebuie sa comunice producatorului ca a preluat aceasta valoare, pentru ca acesta sa poata genera o alta. Pentru a realiza aceasta comunicare, clasa Thread pune la dispozitie metodele wait, notify, notifyAll. (vezi "Metodele wait, notify, notifyAll"). Folosind sincronizarea clasa Buffer va arata astfel:
class Buffer { private int number = -1; private boolean available = false; public synchronized int get() { while (!available) { try { wait(); //asteapta producatorul sa } catch (InterruptedException e) { } available = false; notifyAll(); return number; } public synchronized void put(int number) { while (available) { try { wait(); //asteapta consumatorul sa } catch (InterruptedException e) { } this.number = number; available = true; notifyAll(); }
puna o valoare }
preia valoarea }
187
a a a a
0 0 1 1
a pus: 9 a primit: 9
Sa observam ca ambele metode au fost declarate cu modificatorul synchronized. Cu toate acestea sistemul asociaza un monitor unei instante a clasei Buffer si nu unei metode anume. In momentul n este apelata o metoda sincrona firul de executie care a facut apelul va bloca obiectul a carei metoda o acceseaza , ceea ce nseamna ca celelalte fire de executie nu vor mai putea accesa resursele critice, adica nu vor putea apela nici o metoda sincrona din acel obiect. Acesta este un lucru logic, deoarece mai multe sectiuni critice (metode sincrone) ale unui obiect gestioneaza de fapt o singura resursa critica. In exemplul nostru, atunci cnd producatorul apeleaza metoda put pentru a scrie un numar, va bloca tot obiectul de tip Buffer, astfel ca firul de executie consumator nu va avea acces la cealalta metoda sincrona get, si reciproc.
public synchronized void put(int number) { // buffer blocat de producator ... // buffer deblocat de producator }
188
public synchronized int get() { // buffer blocat de consumator ... // buffer deblocat de consumator }
Implementate ca mai sus cele doua metode nu vor functiona corect Acest lucru se ntmpla deoarece firele de executie, desi si sincronizeaza accesul la buffer, nu se "asteapta" unul pe celalalt. Situatiile n care metodele get si put nu fac nimic vor duce la "ratarea" unor numere de catre consumator. Asadar, cele doua fire de executie trebuie sa se astepte unul pe celalalt.
public synchronized int get() { while (!available) { //nimic - astept ca variabila sa devina true } available = false; return number; } public synchronized int put(int number) { while (available) { //nimic - astept ca variabila sa devina false } available = true; this.number = number; }
Varianta de mai sus, desi pare corecta, nu este. Aceasta deoarece implementarea metodelor este "selfish" - cele doua metode si asteapta in mod egoist conditia de terminare. Ca urmare, corectitudinea functionarii va depinde de sistemul de operare, ceea ce trprezinta o greseala de programare.
189
Punerea corecta a unui fir de executie n asteptare se realizeaza cu metoda wait a clasei Thread, care are trei forme:
void wait( ) void wait( long timeout ) void wait( long timeout, long nanos ) metodei wait, firul de executie curent elibereaza monitorul
Dupa apelul asociat obiectului respectiv si asteapta ca una din urmatoarele conditii sa fie ndeplinita: un alt fir de executie informeaza pe cei care "asteapta" la un anumit monitor sa se trezeasca; acest lucru se realizeaza printr-un apel al metodei notifyAll sau notify. perioada de astepatare specificata a expirat. Metoda wait poate produce exceptii de tipul InterruptedException, atunci cnd firul de executie care asteapta (este deci n starea Not Runnable) este ntrerupt din asteptare si trecut fortat n starea Runnable, desi conditia asteptata nu era nca ndeplinita. Metoda notifyAll informeaza toate firele de executie care sunt n asteptare la monitorul obiectului curent ndeplinirea conditiei pe care o asteptatu. Metoda notify informeaza doar un singur fir de executie. Iata variantele corecte ale metodelor get si put:
public synchronized int get() { while (!available) { try { wait(); //asteapta producatorul sa puna o } catch (InterruptedException e) { } } available = false; notifyAll(); return number;
valoare
} public synchronized void put(int number) { while (available) { try { wait(); //asteapta consumatorul sa preia valoarea } catch (InterruptedException e) { } } this.number = number; available = true; notifyAll(); } }
190
Gruparea firelor de executie pune la dispozitie un mecanism pentru manipularea acestora ca un tot si nu individual. De exemplu, putem sa pornim sau sa suspendam toate firele dintr-un grup cu un singur apel de metoda. Gruparea firelor de executie se realizeaza prin intermediul clasei ThreadGroup. Fiecare fir de executie Java este mebmru al unui grup, indiferent daca specificam explicit acest lucru. Afilierea unui fir de executie la un anumit grup se realizeaza la crearea sa si devine permanenta, n sensul ca nu vom putea muta un fir de executie dintr-un grup n altul, dupa ce acesta a fost creat. In cazul n care cream un fir de executie fara a specifica n constructor din ce grup face parte, el va fi plasat automat n acelasi grup cu firul de executie care l-a creat. La pornirea unui program Java se creeaza automat un obiect de tip ThreadGroup cu numele main, care va reprezenta grupul tuturor firelor de executie create direct din program si care nu au fost atasate explicit altui grup. Cu alte cuvinte, putem sa ignoram complet plasarea firelor de executie n grupuri si sa lasam sistemul sa se ocupe cu aceasta, adunndu-le pe toate n grupul main. Exista situatii cnd programul creeaza multe fire de executie iar gruparea lor poate usura substantial manevrarea lor. Crearea unui fir de executie si plasarea lui ntr-un grup (altul dect cel implicit) se realizeaa prin urmatorii constructori ai clasei Thread:
public Thread(ThreadGroup group, Runnable target) public Thread(ThreadGroup group, String name) public Thread(ThreadGroup group, Runnable target, String name)
Fiecare din acesti costructori creeaza un fir de executie, l initializeaza si l plaseaza ntrun grup specificat ca argument. In exemplul urmator vor fi create doua grupuri, primul cu doua fire de executie iar al doile cu trei:
ThreadGroup grup1 = new ThreadGroup("Producatori"); Thread p1 = new Thread(grup, "Producator 1"); Thread p2 = new Thread(grup, "Producator 2"); ThreadGroup Thread c1 = Thread c2 = Thread c3 = grup2 = new ThreadGroup("Consumatori"); new Thread(grup, "Consumator 1"); new Thread(grup, "Consumator 2"); new Thread(grup, "Consumator 3");
Pentru a afla carui grup apartine un anumit fir de executie putem folosi metoda getThreadGroup a clasei Thread. Un grup poate avea ca parinte un alt grup, ceea ce nseamna ca firele de executie pot fi plasate ntr-o ierarhie de grupuri, n care radacina este grupul implicit main, ca n figura de mai jos:
191
192
In cazul n care este folosit constructorul fara argument conectarea unui flux de intrare cu un flux de iesire se face prin metoda connect:
public void connect( PipedWriter pw ) throws IOException public void connect( PipedReader pr ) throws IOException,
Intruct fluxurile care sunt conectate printr-un pipe trebuie sa execute simultan operatii de scriere/citire folosirea lor se va face n cadrul unor fire de executie. Functionarea obicetelor care instantiaza PipedWriter si PipedReader este asemanatoare cu a canalelor UNIX (pipes). Fiecare capat al unui canal este utilizat dintr-un fir de executie separat. La un capat al pipeline-ului se scriu caractere, la celalalt se citesc. La citire, daca nu sunt date disponibile firul de executie se va bloca. Se observa ca acesta este un comportament tipic producator-consumator, firele de executie comunicnd printrun canal. Realizarea conexiunii se face astfel:
PipedWriter pw1 = new PipedWriter(); PipedReader pr1 = new PipedReader(pw1); sau sau PipedReader pr2 = new PipedReader(); PipedWriter pw2 = new PipedWriter(pr2); PipedReader pr = new PipedReader(); PipedWriter pw = new PipedWirter(); pr.connect(pw) //echivalent cu pw.connect(pr);
Scrierea si citirea pe/de pe canale se realizeaza prin metodele uzuale read si write n toate formele lor. Sa reconsideram acum exemplul producator/consumator folosind canale de comunicatie. Producatorul trimite datele printr-un flux de iesire de tip DataOutputStream catre consumator care le primeste printr-un flux de intrare de tip DataInputStream. Aceste doua fluxuri sunt interconectate prin intermediul unor fluxuri de tip "pipe".
import java.io.*; //clasa principala public class TestPipes { public static void main(String[] args) throws IOException { PipedOutputStream pipeOut = new PipedOutputStream(); PipedInputStream pipeIn = new PipedInputStream(pipeOut); DataOutputStream out = new DataOutputStream( pipeOut); DataInputStream in = new DataInputStream( pipeIn ); Producator p1 = new Producator(out); Consumator c1 = new Consumator(in); p1.start(); c1.start();
} }
193
public Producator(DataOutputStream out) { this.out = out; } public void run() { for (int i = 0; i < 10; i++) { try { out.writeInt(i); } catch (IOException e) {} System.out.println("Producatorul a pus:\t" + i); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } } } class Consumator extends Thread { private DataInputStream in; public Consumator(DataInputStream in) { this.in = in; } public void run() { int value = 0; for (int i = 0; i < 10; i++) { try { value = in.readInt(); } catch (IOException e) {} System.out.println("Consumatorul a primit:\t" + value); } } }
Notiuni generale despre retele Lucrul cu URL-uri Socket-uri Comunicarea prin conexiuni Comunicarea prin datagrame Trimiterea de mesaje catre mai multi clienti
194
Programarea n retea implica trimiterea de mesaje si date ntre aplicatii ce ruleaza pe calculatoare aflate ntr-o retea locala sau conectate la Internet. Pachetul care ofera suport pentru scrierea aplicatiilor de retea este java.net. Clasele din acest pachet ofera o modalitate facila de programare n retea, fara a fi nevoie de cunostine prealabile referitoare la comunicarea efectiva ntre calculatoare. Cu toate acestea sunt necesare cteva notiuni fundamentale referitoare la retele, cum ar fi protocol, adresa IP, port, socket.
Ce este un protocol ?
Un protocol reprezinta o conventie de reprezentare a datelor folosita n comunicarea ntre doua calculatoare. Avnd n vedere faptul ca orice informatie care trebuie trimisa prin retea trebuie serializata astfel nct sa poata fi transmisa secvential, octet cu octet, catre destinatie, era nevoie de stabilirea unor conventii (protocoale) care sa fie folosite att de calculatorul care trimite datele ct si de cel care le primeste. Cele mai utilizate protocoale sunt TCP si UDP. Definitii TCP (Transport Control Protocol) este un protocol ce furnizeaza un flux sigur de date ntre doua calculatoare. Acest protocol asigura stabilirea unei conexiuni permanente ntre cele doua calculatoare pe parcursul comunicatiei. UDP (User Datagram Protocol) este un protocol ce trimite pachete independente de date, numite datagrame, de la un calculator catre altul fara a garanta n vreun fel ajungerea acestora la destinatie. Acest protocol nu stabileste o conexiune permanta ntre cele doua calculatoare.
Ce este un port ?
Un calculator are n general o singura legatura fizica la retea. Orice informatie destinata unei anumite masini trebuie deci sa specifice obligatoriu adresa IP a acelei masini. Insa pe un calculator pot exista concurent mai multe procese care au stabilite conexiuni n retea, asteptnd diverse informatii. Prin urmare datele trimise catre o destinatie trebuie sa specifice pe lnga adresa IP a calculatorului si procesul catre care se ndreapta informatiile respective. Identificarea proceselor se realizeaza prin intermdiul porturilor. Un port este un numar de 16 biti care identifica n mod unic procesle care ruleaza pe o anumita masina. Orice aplicatie care realizeaza o conexiune n retea va trebui sa ataseze un numar de port acelei
195
conexiuni. Valorile pe care le poate lua un numar de port sunt cuprinse ntre 0 si 65535 (deoarece sunt numere reprezentate pe 16 biti), numerele cuprinse ntre 0 si 1023 fiind nsa rezervate unor servicii sistem si, din acest motiv, nu trebuie folosite n aplicatii.
TCP
URL URLConnection Socket ServerSocket
UDP
DatagramPacket DatagramSocket MulticastSocket
Aceste clase permit programarea de retea la nivel de aplicatie. Cele 7 nivele ale comunicarii n retea sunt :
Application Layer Application-level layers Presentation Layer Session Layer (Sockets) Transport Layer (TCP, UDP) Data communication-level layers Network Layer (IP) Data Layer Physical Layer
Lucrul cu URL-uri
Definitie URL este acronimul pentru Uniform Resource Locator si reprezinta o referinta (adresa) la o resursa aflata pe Internet. Aceasta este n general un fisier reprezentnd o pagina Web sau o imagine, nsa un URL poat referi si interogari la baze de date, rezultate ale unor comenzi (programe), etc. Exemple de URL-uri sunt: http://java.sun.com http://students.infoiasi.ro/index.html http://www.infoiasi.ro/~acf/imgs/taz.gif http://www.infoiasi.ro/~acf/java/curs/9/prog_retea.html#url Dupa cum se observa din exemplele de mai sus, un URL are doua componente principale: 1. Identificatorul protocolului folosit (http, ftp, etc) 2. Numele resursei referite. Acesta are urmatoarele componente:
196
o o
o o
numele calculatorului gazda (www.infoiasi.ro) calea completa spre resursa referita (~acf/java/curs/9/prog_retea.html) Notatia ~user semnifica uzual subdirectorul html al directorului rezervat pe server utilizatorului specificat (HOME). In cazul n care este specificat doar un director, fisierul ce reprezinta resursa va fi considerat implicit index.html. optional, o referinta de tip anchor n cadrul fisierului referit (#url) optional, portul la care sa se realizeze conexiunea
Clasa care permite lucrul cu URL-uri este java.net.URL. Aceasta are mai multi constructori pentru creearea de obiecte ce reprezinta referinte catre resurse aflate n retea, cel mai uzual fiind cel care primeste ca parametru un sir de caractere. In cazul n care sirul nu reprezinta un URL valid va fi aruncata o exceptie de tipul MalformedURLException. try { URL myURL = new URL("http://java.sun.com"); } catch (MalformedURLException e) { . . . } Odata creat, un obiect de tip URL poate fi folosit pentru o o o aflarea informatiilor despre resursa referita (numele calculatorului gazda, numele fisierului, protocolul folosit. etc), citirea printr-un flux a continutului fisierului respectiv conectarea la acel URL pentru citirea si scrierea de informatii
197
Conectarea la un URL
Se realizeaza prin metoda openConnection ce realizeaza stabilirea unei conexiuni bidirectionale cu resursa specificata. Aceasta conexiune este reprezentata de un obiect de tip URLConeection ce permite crearea att a unui flux de intrare pentru citirea informatiilor de la URL-ul specificat ct si a unui flux de iesire pentru scrierea de date catre acel URL. Operatiunea de trimitere de date dintr-un program catre un URL este similara cu trimiterea de date dintr-un FORM aflat ntro pagina HTML. Metoda folosita pentru trimitere este POST. In cazul trimiterii de date, obiectul URL este de fapt un program (comanda) ce ruleaza pe serverul Web referit prin URL-ul respectiv (servlet, cgi-bin, php, etc).
Socket-uri
Definitie Un socket (soclu) este o abstractiune software folosita pentru a reprezenta fiecare din cele doua "capete" ale unei conexiuni ntre doua procese ce ruleaza ntr-o retea. Fiecare socket este atasat unui port astfel nct sa poata identifica unic programul caruia i sunt destinate datele. Socket-urile sunt de doua tipuri:
o o
O aplicatie de retea ce foloseste socket-uri se ncadreaza n modelul client/server de concepere a unei aplicatii. In acest model aplicatia este formata din doua categorii distincte de programe numite servere, respectiv clienti. Programele de tip server sunt cele care ofera diverse servicii eventualilor clienti, fiind n stare de asteptare atta vreme ct nici un client nu le solicita serviciile Programele de tip client sunt cele care initiaza conversatia cu un server, solicitnd un anumit serviciu. Uzual, un server trebuie sa fie capabil sa trateze mai multi clienti simultan si, din acest motiv, fiecare cerere adresata serverului va fi tratata ntr-un fir de executie separat.
In acest model se stabileste o conexiune TCP ntre un program client si un server care furnizeaza un anumit serviciu.
199
} public SimpleServer() throws IOException { serverSocket = new ServerSocket(PORT); try { //Asteapta un client clientSocket = serverSocket.accept(); executie //Executa solicitarea clientului intr-un fir de new Thread(this).start(); } finally { serverSocket.close(); }
public static void main(String[] args) throws IOException { SimpleServer server = new SimpleServer(); } }
200
} catch (UnknownHostException e) { System.err.println("Serverul nu poate fi gasit \n" + e); System.exit(1); } finally { if (out != null) out.close(); if (in != null) in.close(); if (clientSocket!= null) clientSocket.close(); }
201
//Afla adresa si portul de la care vine cererea InetAddress adresa = cerere.getAddress(); int port = cerere.getPort();
//Construieste raspunsul buf = ("Hello " + new String(cerere.getData())).getBytes(); client //Trimite un pachet cu raspunsul catre
raspuns = new DatagramPacket(buf, buf.length, adresa, port); socket.send(raspuns); } } finally { socket.close(); } } public static void main(String[] args) throws IOException { new DatagramServer(); } }
202
packet = new DatagramPacket(buf, buf.length); socket.receive(packet); //Afiseaza raspunsul System.out.println(new String(packet.getData())); } finally { socket.close(); }
} }
203
//Trimite un pachet catre toti clientii din grup buf = (new String("Salut grup")).getBytes(); packet = new DatagramPacket(buf, buf.length, socket.send(packet);
group, port);
} finally { socket.close(); } } }
Notiuni generale despre retele Lucrul cu URL-uri Socket-uri Comunicarea prin conexiuni Comunicarea prin datagrame Trimiterea de mesaje catre mai multi clienti
204
Ce este un protocol ?
Un protocol reprezinta o conventie de reprezentare a datelor folosita n comunicarea ntre doua calculatoare. Avnd n vedere faptul ca orice informatie care trebuie trimisa prin retea trebuie serializata astfel nct sa poata fi transmisa secvential, octet cu octet, catre destinatie, era nevoie de stabilirea unor conventii (protocoale) care sa fie folosite att de calculatorul care trimite datele ct si de cel care le primeste. Cele mai utilizate protocoale sunt TCP si UDP. Definitii TCP (Transport Control Protocol) este un protocol ce furnizeaza un flux sigur de date ntre doua calculatoare. Acest protocol asigura stabilirea unei conexiuni permanente ntre cele doua calculatoare pe parcursul comunicatiei. UDP (User Datagram Protocol) este un protocol ce trimite pachete independente de date, numite datagrame, de la un calculator catre altul fara a garanta n vreun fel ajungerea acestora la destinatie. Acest protocol nu stabileste o conexiune permanta ntre cele doua calculatoare.
Ce este un port ?
Un calculator are n general o singura legatura fizica la retea. Orice informatie destinata unei anumite masini trebuie deci sa specifice obligatoriu adresa IP a acelei masini. Insa pe un calculator pot exista concurent mai multe procese care au stabilite conexiuni n retea, asteptnd diverse informatii. Prin urmare datele trimise catre o
205
destinatie trebuie sa specifice pe lnga adresa IP a calculatorului si procesul catre care se ndreapta informatiile respective. Identificarea proceselor se realizeaza prin intermdiul porturilor. Un port este un numar de 16 biti care identifica n mod unic procesle care ruleaza pe o anumita masina. Orice aplicatie care realizeaza o conexiune n retea va trebui sa ataseze un numar de port acelei conexiuni. Valorile pe care le poate lua un numar de port sunt cuprinse ntre 0 si 65535 (deoarece sunt numere reprezentate pe 16 biti), numerele cuprinse ntre 0 si 1023 fiind nsa rezervate unor servicii sistem si, din acest motiv, nu trebuie folosite n aplicatii.
TCP
URL URLConnection Socket ServerSocket
UDP
DatagramPacket DatagramSocket MulticastSocket
Aceste clase permit programarea de retea la nivel de aplicatie. Cele 7 nivele ale comunicarii n retea sunt :
Application Layer Application-level layers Presentation Layer Session Layer (Sockets) Transport Layer (TCP, UDP) Data communication-level layers Network Layer (IP) Data Layer Physical Layer
Lucrul cu URL-uri
Definitie URL este acronimul pentru Uniform Resource Locator si reprezinta o referinta (adresa) la o resursa aflata pe Internet. Aceasta este n general un fisier reprezentnd o pagina Web sau o imagine, nsa un URL poat referi si interogari la baze de date, rezultate ale unor comenzi (programe), etc. Exemple de URL-uri sunt: http://java.sun.com http://students.infoiasi.ro/index.html
206
http://www.infoiasi.ro/~acf/imgs/taz.gif http://www.infoiasi.ro/~acf/java/curs/9/prog_retea.html#url Dupa cum se observa din exemplele de mai sus, un URL are doua componente principale: 1. Identificatorul protocolului folosit (http, ftp, etc) 2. Numele resursei referite. Acesta are urmatoarele componente: o numele calculatorului gazda (www.infoiasi.ro) o calea completa spre resursa referita (~acf/java/curs/9/prog_retea.html) Notatia ~user semnifica uzual subdirectorul html al directorului rezervat pe server utilizatorului specificat (HOME). In cazul n care este specificat doar un director, fisierul ce reprezinta resursa va fi considerat implicit index.html. o optional, o referinta de tip anchor n cadrul fisierului referit (#url) o optional, portul la care sa se realizeze conexiunea Clasa care permite lucrul cu URL-uri este java.net.URL. Aceasta are mai multi constructori pentru creearea de obiecte ce reprezinta referinte catre resurse aflate n retea, cel mai uzual fiind cel care primeste ca parametru un sir de caractere. In cazul n care sirul nu reprezinta un URL valid va fi aruncata o exceptie de tipul MalformedURLException. try {
URL myURL = new URL("http://java.sun.com"); } catch (MalformedURLException e) { . . . } Odata creat, un obiect de tip URL poate fi folosit pentru o o o aflarea informatiilor despre resursa referita (numele calculatorului gazda, numele fisierului, protocolul folosit. etc), citirea printr-un flux a continutului fisierului respectiv conectarea la acel URL pentru citirea si scrierea de informatii
207
br = new BufferedReader(new InputStreamReader(in)); String linie; while ((linie = br.readLine()) != null) { //proceseaza linia citita System.out.println(linie); } } catch(MalformedURLException e) { System.err.println("URL incorect: " + e); } finally { br.close(); } } }
Conectarea la un URL
Se realizeaza prin metoda openConnection ce realizeaza stabilirea unei conexiuni bidirectionale cu resursa specificata. Aceasta conexiune este reprezentata de un obiect de tip URLConeection ce permite crearea att a unui flux de intrare pentru citirea informatiilor de la URL-ul specificat ct si a unui flux de iesire pentru scrierea de date catre acel URL. Operatiunea de trimitere de date dintr-un program catre un URL este similara cu trimiterea de date dintr-un FORM aflat ntro pagina HTML. Metoda folosita pentru trimitere este POST. In cazul trimiterii de date, obiectul URL este de fapt un program (comanda) ce ruleaza pe serverul Web referit prin URL-ul respectiv (servlet, cgi-bin, php, etc).
Socket-uri
Definitie Un socket (soclu) este o abstractiune software folosita pentru a reprezenta fiecare din cele doua "capete" ale unei conexiuni ntre doua procese ce ruleaza ntr-o retea. Fiecare socket este atasat unui port astfel nct sa poata identifica unic programul caruia i sunt destinate datele. Socket-urile sunt de doua tipuri:
o o
O aplicatie de retea ce foloseste socket-uri se ncadreaza n modelul client/server de concepere a unei aplicatii. In acest model aplicatia este formata din doua categorii distincte de programe numite servere, respectiv clienti. Programele de tip server sunt cele care ofera diverse servicii eventualilor clienti, fiind n stare de asteptare atta vreme ct nici un client nu le solicita serviciile Programele de tip client sunt cele care initiaza conversatia cu un server, solicitnd
208
un anumit serviciu. Uzual, un server trebuie sa fie capabil sa trateze mai multi clienti simultan si, din acest motiv, fiecare cerere adresata serverului va fi tratata ntr-un fir de executie separat.
209
// Inchid socketul deschis pentru clientul curent try { clientSocket.close(); } catch (IOException e) { System.err.println("Socketul nu poate fi }
public SimpleServer() throws IOException { serverSocket = new ServerSocket(PORT); try { //Asteapta un client clientSocket = serverSocket.accept(); //Executa solicitarea clientului intr-un fir de executie new Thread(this).start(); } finally { serverSocket.close(); } } public static void main(String[] args) throws IOException { SimpleServer server = new SimpleServer(); }
210
//se trimite o cerere la server cerere = "duke"; out.println(cerere); //se asteapta raspuns de la server raspuns = in.readLine(); System.out.println(raspuns); } catch (UnknownHostException e) { System.err.println("Serverul nu poate fi gasit System.exit(1); } finally { if (out != null) out.close(); if (in != null) in.close(); if (clientSocket!= null) clientSocket.close(); }
\n" + e);
} }
211
//Declara pachetul in care va fi receptionata cererea buf.length); byte[] buf = new byte[256]; cerere = new DatagramPacket(buf, //Astepta aparitia unui pachet cu cererea socket.receive(cerere); cererea //Afla adresa si portul de la care vine InetAddress adresa = cerere.getAddress(); int port = cerere.getPort(); //Construieste raspunsul buf = ("Hello " + new String(cerere.getData())).getBytes(); //Trimite un pachet cu raspunsul catre client raspuns = new DatagramPacket(buf, buf.length, adresa, port); socket.send(raspuns); } } finally { socket.close(); } } public static void main(String[] args) throws IOException { new DatagramServer(); }
212
//Construieste si trimite pachetul cu cerere buf = "Duke".getBytes(); packet = new DatagramPacket(buf, buf.length, socket.send(packet); //Asteapta pachetul cu raspunsul de la server buf = new byte[256]; packet = new DatagramPacket(buf, buf.length); socket.receive(packet); //Afiseaza raspunsul System.out.println(new String(packet.getData()));
} finally { socket.close(); } } }
213
specificate
//Se alatura grupului aflat la adresa si portul socket = new MulticastSocket(port); socket.joinGroup(group); //asteapta un pachet venit pe adresa grupului buf = new byte[256]; DatagramPacket packet = new DatagramPacket(buf,
buf.length);
} }
group, port);
} }
Curs 10 Applet-uri
214
Ce este un applet ? Crearea unui applet simplu Ciclul de viata al unui applet Interfata grafica cu utilizatorul Definirea si folosirea parametrilor Tag-ul <APPLET> Folosirea firelor de executie n appleturi Alte metode oferite de clasa Applet Probleme de securitate Programe care sunt att appleturi ct si aplicatii Exemple de applet-uri
Ce este un applet ?
Definitie Un applet reprezinta o suprafata de afisare (container) ce poate fi inclusa ntr-o pagina Web si gestionata printr-un program Java. Un astfel de program se mai numeste miniaplicatie sau, prin abuz de limbaj, applet. Codul unui applet poate fi format din una sau mai multe clase. Una dintre acestea este principala si extinde clasa Applet, fiind clasa ce trebuie specificata n documentul HTML ce descrie pagina de Web n care dorim sa includem appletul. Diferenta fundamentala dintre un applet si o aplicatie consta n faptul ca, un applet nu poate fi executat independent, ci va fi executat de browserul n care este ncarcata pagina Web ce contine appletul respectiv. O aplicatie independenta este executata prin apelul interpretorului java, avnd ca parametru numele clasei principale a aplicatiei, clasa principala fiind cea care contine metoda main. Ciclul de viata al unui applet este complet diferit, fiind dictat de evenimentele generate de catre browser la vizualizarea documentului HTML ce contine appletul. Pachetul care ofera suport pentru creearea de appleturi este java.applet.
215
Fiind derivata din clasa Container, clasa Applet descrie de fapt suprafete de afisare, asemenea claselor Frame sau Panel.
Un applet simplu
import java.applet.Applet; import java.awt.*; public class AppletSimplu extends Applet { public void paint(Graphics g) { g.setFont(new Font("Arial", Font.BOLD, 16)); g.drawString("Hello", 0, 30); } } Uzual, clasa principala va fi salvata ntr-un fisier cu acelasi nume si extensia .java. Asadar, vo salva clasa de mai sus ntr-un fisier AppletSimplu.java.
Compilarea
Compilarea se face la fel ca si la aplicatiile independente, apelnd compilatorul javac pentru clasa principala a appletului (cea care extinde Applet).
javac AppletSimplu.java
Executia (vizualizarea)
Pentru a vizualiza acest applet trebuie sa cream un document HTML, sa-i spunem demo.html, n care sa specificam cel putin urmatoarele informatii clasa ce contine codul appletului latimea si naltimea suprafetei alocate pe pagina Web
<HTML> <HEAD> <TITLE> Un applet simplu </TITLE> </HEAD> <APPLET CODE="AppletSimplu.class" WIDTH=100 HEIGHT=50></APPLET> </HTML>
Vizualizarea acestui document se poate face cu orice browser (Internet Explorer, Netscape, etc), sau cu utilitarul appletviewer ce vine n pachetul JDK.
appletviewer demo.html
216
217
principala a appletului. Aceste metode sunt apelate automat de browser si nu trebuie apelate explicit din program !
218
Ca si n cazul argumentelor trimise aplicatiilor de la linia de comanda, tipul parametrilor este sir de caractere, indiferent daca valoarea este ntre ghilimele sau nu. Fiecare applet are si un set de parametri prestabiliti ale caror nume nu vor putea fi folosite pentru definirea de noi parametri folosind metoda de mai sus. Acestia apar direct n corpul tagului APPLET si definesc informatii generale despre applet. Exemple de astfel de parametri sun CODE, WIDTH sau HEIGHT. Lista lor completa va fi prezentata la descrierea tagului APPLET . fi pre Folosirea parametrilor primiti de catre un applet se face prin intermediul metodei getParameter care primeste ca argument numele unui parametru si returneaza valoarea acestuia. In cazul n care nu exista nici un parametru cu numele specificat, metoda ntoarce null, caz n care programul trebuie sa atribuie o valaore implicita variabilei n care se dorea citirea respectivului parametru. Sa rescriem apletul considerat initial (AppletSimplu) astfel nct acesta sa afiseze textul primit ca parametru, folosind un font cu numele si dimeniunea specificate de asemenea ca parametri.
import java.applet.Applet; import java.awt.*; public class AppletSimplu extends Applet String text, numeFont; int dimFont; public void init() { text = getParameter("textAfisat"); if (text==null) text="Hello"; // valoare implicita {
219
numeFont = getParameter("numeFont"); if (numeFont==null) numeFont="Arial"; try { dimFont = Integer.parseInt(getParameter("dimFont")); } catch(NumberFormatException e) { dimFont = 16; } } public void paint(Graphics g) { g.setFont(new Font(numeFont, Font.BOLD, dimFont)); g.drawString(text, 20, 20); }
Orice applet poate pune la dispozitie o "documentatie" referitoare la parametrii pe care i suporta, pentru a veni n ajutorul utilizatorilor care doresc sa includa appletul ntr-o pagina Web. Aceasta se realizeaza prin supradefinirea metodei getParameterInfo, care returneaza un vector format din triplete de siruri. Fiecare element al vectorului este de fapt un vector cu trei elemente de tip String, cele trei siruri reprezentnd numele parametrului, tipul sau si o descriere a sa.
public String[][] getParameterInfo() { String[][] info = { //Nume Tip {"textAfisat", "String", afisat"}, {"numeFont", "String", fontului"}, {"dimFont", "int", fontului"} }; return info; } Descriere "Sirul ce va fi "Numele "Dimensiunea
Informatiile furnizate de un applet pot fi citite din browserul folosit pentru vizualizare prin metode specifice acestuia. De exemplu, n Netscape se foloseste optiunea Page info din meniul View.
Tag-ul <APPLET>
< APPLET [CODEBASE = directorApplet] CODE = clasaApplet [ALT = textAlternativ] [NAME = numeInstantaApplet] WIDTH = latimeInPixeli HEIGHT = naltimeInPixeli [ALIGN = aliniere] [VSPACE = spatiuVertical] [HSPACE = spatiuOrizontal] > [< PARAM NAME = numeParametru1 VALUE = valoare1 >]
220
[< PARAM NAME = numeParametru2 VALUE = valoare2 >] . . . [text HTML alternativ] </APPLET>
Atributele puse ntre paranteze patrate sun optionale. CODEBASE = directorApplet Specifica URL-ul n care se gaseste clasa appletului. Uzual se exprima relativ la directorul documentului HTML. In cazul n care lipseste, se considera implicit URL-ul documentului. CODE = clasaApplet Numele fisierului ce contine clasa principala a appletului. Acesta va fi cautat n directorul specificat de CODEBASE. Nu poate fi absolut. ALT = textAlternativ Specifica textul ce trebuie afisat daca browserul ntelege tagul APPLET dar nu poate rula appleturi Java. NAME = numeInstantaApplet Ofera posibilitatea de a da un nume respectivei instante a appletului, astfel nct mai multe appleturi aflate pe aceeasi pagina sa comunice ntre ele folosindu-se de numele lor. WIDTH = latimeInPixeli HEIGHT = naltimeInPixeli Specifica latimea si naltimea suprafetei n care va fi afisat appletul. ALIGN = aliniere Semnifica modalitatea de aliniere a appletului n pagina Web. Acest atribut poate primi una din urmatoarele valori: left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom , seminificatiile lor fiind aceleasi ca si la tagul IMG. VSPACE = spatiuVertical HSPACE = spatiuOrizontal Specifica numarul de pixeli dintre applet si marginile suprafetei de afisare. < PARAM NAME = numeParametru1 VALUE = valoare1 > Tag-urile <PARAM> sunt folosite pentru specificarea parametrilor unui applet. vezi "Definirea si folosirea parametrilor" text HTML alternativ Este textul ce va fi afisat n cazul n care browserul nu ntelege tagul APPLET. Browserele care nteleg Java vor ignora acest text.
221
recomandat sa le realizam ntr-un alt fir de executie, pentru a nu bloca interactiunea utilizatorului cu appletul sau redesenarea acestuia. Structura unui applet care doreste sa lanseze un fir de executie poate avea doua forme. In prima situatie appletul porneste un fir de executie la initialzarea sa iar acesta va rula, indiferent daca appletul mai este sau nu vizibil, pna la oprirea sa naturala (terminarea metodei run)
import java.applet.Applet; class AppletThread1 extends Applet implements Runnable { Thread appletThread = null; public void init() { if (appletThread == null) { appletThread = new Thread(this); appletThread.start(); } } public void run() { //codul firului de executie } }
In cazul n care firul de executie pornit de applet efectueaza operatii ce au sens doar daca appletul este vizibil, cum ar fi animatie, ar fi de dorit ca acesta sa se opreasca atunci cnd appletul nu mai este vizibil (la apelul metodei stop) si sa reporneasca atunci cnd appletul redevine vizibil (la apelul metodei start).
import java.applet.Applet; public class StructuraApplet extends Applet implements Runnable { Thread appletThread = null; boolean running = false; public void start() { //reporneste firul de executie if (appletThread == null) { appletThread = new Thread(this); running = true; appletThread.start(); } } public void stop() { //opreste firul de executie running = false; appletThread = null; } public void run() { while (running) { //codul firului de executie
222
} }
Un applet este considerat activ imediat dupa apelul metodei start si devine inactiv la apelul metodei stop.Pentru a afla daca un applet este activ se foloseste metoda isActive.
Afisarea imaginilor
Afisarea imaginilor ntr-un applet se face fie prin intermediul unei componente ce permite acest lucru, cum ar fi o suprafata de desenare de tip Canvas, fie direct n metoda paint a applet-ului, folosind metoda drawImage a clasei Graphics. In ambele cazuri, ncarcarea imaginii n memorie se va face cu ajutorul metodei getImage din clasa Applet. Aceasta poate primi ca argument fie adresa URL absoluta a fisierului ce contine imaginea, fie calea sa relativa la o anumita adresa URL, cum ar fi cea a directorului n care se gaseste documentul HTML ce contine appletul (getDocumentBase) sau a directorului n care se gaseste clasa appletului (getCodeBase).
import java.applet.Applet; import java.awt.*; public class AppletImagine extends Applet Image img = null; public void init() { {
223
224
Probleme de securitate
Un applet nu poate sa: Citeasca sau scrie fisiere pe calculatorul pe care a fost ncarcat (client) Deschida conexiuni cu alte masini n afara de cea de pe care provine (host) Porneasca programe pe masina client Citeasca diverse proprietati ale sistemului de operare al clientului Ferestrele folosite de un applet, altele dect cea a browserului, vor arata altfel dect ntr-o aplicatie obisnuita.
public void init() { add(new Label("Applet si aplicatie")); } public static void main(String args[]) { AppletApp applet = new AppletApp(); Frame f = new Frame("Aplicatie si applet"); f.setSize(200, 200); f.add(applet, BorderLayout.CENTER); applet.init(); applet.start(); } } f.show();
Generalitati despre baze de date Ce este JDBC ? Conectarea la o baza de date Efectuarea de secvente SQL Obtinerea si prelucrarea rezultatelor
225
Exemplu
Ce este JDBC ?
Definitie JDBC (Java Database Connectivity) este o interfata standard SQL de acces la baze de date. JDBC este constituita dintr-un set de clase si interfete scrise n Java, furniznd mecanisme standard pentru proiectantii aplicatiilor de baze de date. Pachetul care ofera suport pentru lucrul cu baze de date este java.sql. Folosind JDBC este usor sa transmitem secvente SQL catre baze de date relationale. Cu alte cuvinte, nu este necesar sa scriem un program pentru a accesa o baza de date Oracle, alt program pentru a accesa o baza de date Sybase si asa mai departe. Este de ajuns sa scriem un singur program folosind API-ul JDBC si acesta va fi capabil sa trimita secvente SQL bazei de date dorite. Binenteles, scriind codul sursa n Java, ne este asigurata
226
portabilitatea programului. Deci, iata doua motive puternice care fac combinatia Java JDBC demna de luat n seama. Fiind robust, sigur, usor de folosit, usor de nteles, Java este un excelent limbaj pentru a dezvolta aplicatii de baze de date. Tot ceea ce-i lipseste este modalitatea prin care aplicatiile Java pot comunica cu bazele de date. Aici vine nsa JDBC-ul care ofera acest mecanism.
Ce face JDBC-ul?
In linii mari, JDBC face trei lucruri: stabileste o conexiune cu o baza de date trimite secvente SQL prelucreaza rezultatele
227
Cmpul sub-protocol denumeste tipul de driver ce trebuie folosit pentru realizarea conexiunii si poate fi odbc, oracle, sybase, db2 si asa mai departe. Identificatorul bazei de date este un indicator specific fiecarui driver care specifica baza de date cu care aplicatia doreste sa interactioneze. In functie de tipul driver-ului acest identificator poate include numele unei masini gazda, un numar de port, numele unui fisier sau al unui director, etc.
jdbc:odbc:testdb jdbc:oracle:thin@persistentjava.com:1521:testdb jdbc:sybase:testdb jdbc:db2:testdb
La primirea unui JDBC URL, DriverManager-ul va parcurge lista driver-elor nregistrate n memorie, pna cnd unul dintre ele va recunoaste URL-ul respectiv. Daca nu exista nici unul potrivit, atunci va fi lansata o exceptie de tipul SQLException, cu mesajul no suitable driver.
O conexiune va fi folosita pentru: crearea de secvente SQL ce vor fi folosite pentru interogarea sau actualizarea bazei aflarea unor informatii legate de baza de date (meta-date) Clasa Connection asigura suport pentru controlul tranzactiilor din memorie catre baza de date prin metodele commit, rollback, setAutoCommit .
putem trimite secvente SQL catre baza de date. Cele mai uzuale comenzi SQL sunt cele folosite pentru: 1. interogarea bazei de date (SELECT) 2. actualizarea bazei de date (INSERT, UPDATE, DELETE)
Connection c = DriverManager.getConnection(url); Statement s = c.createStatement(); ResultSet r = s.executeQuery("SELECT * FROM un_tabel ORDER BY o_coloana"); s.executeUpdate("DELETE * FROM un_tabel"); Metoda executeQuery trimite interogari SQL catre baza de date si primeste rasuns ntrun obiect de tip ResultSet.
Interfata ResultSetMetaData
ResultSet r = s.executeQuery(" SELECT * FROM localitati" ); ResultSetMetaData rsmd = r.getMetaData(); System.out.println("Coloane: " + rsmd.getColumnCount());
Exemplu
import java.sql.*; import java.io.*; public class TestJDBC { public static void main (String[] args) { String dbUrl = "jdbc:odbc:test"; String user = "dba"; String password = "sql"; try{ Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(ClassNotFoundException e) { e.printStackTrace(); System.out.println("Eroare incarcare driver!\n" + e); } try{
229
user, password);
Connection c=DriverManager.getConnection(dbUrl, Statement s= c.createStatement(); ResultSet r = s.executeQuery( " SELECT cod, nume FROM
localitati"+
" ORDER BY nume"); while (r.next()) { System.out.println ( r.getString ("cod") + "," + r.getString ("nume") ); } s.close();
} }
} catch(SQLException e) { e.printStackTrace(); }
Organizarea fisierelor sursa (.java) Organizarea fisierelor cuextensia .class Necesitatea organizarii fisierelor Setarea caii de cautare (CLASSPATH) Arhive JAR
230
este obligatoriu ci doar recomandat. Intr-un fisier sursa pot exista oricte clase care nu sunt publice. 2. Fisierele sursa trebuie sa se gaseasca n directoare care sa reflecte numele pachetelor n care se gasesc clasele si interfetele din acele fisiere sursa. Cu alte cuvinte un director va contine surse pentru clase si interfete din acelassi pachet iar numele directorului va fi chiar numele pachetului. Daca numele pachetelor sunt formate din mai multe unitati lexicale separate prin punct, atunci acestea trebuie de asemenea sa corespunda unor directoare ce vor descrie calea spre fisierele sursa ale caror clase/interfete fac parte din pachetele respective. Vom clarifica modalitatea de organizare a fisierelor sursa ale unei aplicatii printr-un exemplu concret. Sa presupunem ca dorim crearea unui program java care sa reprezinte diverse notiuni matematice din domenii diferite cum ar fi geometrie, algebra, analiza, etc. Pentru a simplifica lucrurile sa presupunem ca dorim sa cream, clase care sa descrie urmatoarele notiuni: poligon, cerc, poliedru, sfera, grup, functie. O prima varianta ar fi sa construim cte o clasa java pentru fiecare si sa le plasam n acelasi director mpreuna cu un program care sa le foloseasca, nsa, avnd n vedere posibila extindere a aplicatiei cu noi reprezentari de notiuni matematice, aceasta abordare ar fi ineficienta. O abordare eleganta ar fi aceea n care clasele care descriu notiuni din acelasi domeniu sa se gaseasca n pachete separate si directoare separate. Ierarhia fisierelor sursa ar fi:
/matematica /surse /geometrie /plan Poligon.java Cerc.java /spatiu Poliedru.java Sfera.java
Clasele descrise n fisierele de mai sus trebuie declarate n pachete denumite corespunzator cu numele directoarelor n care se gasesc: Poligon.java public class Poligon { . . . } Cerc.java
package geometrie.plan; public class Cerc { . . . } package geometrie.spatiu; package geometrie.plan;
231
Functie.java public class Functie { . . . } este clasa principala a aplicatiei. Numele lung al unei clase trebuie sa descrie calea spre acea clasa n cadrul fisierelor sursa ale unei aplicatii.
Matematica.java
package analiza;
Crearea acestei structuri ierarhice poate fi facuta automat de catre compilator. In directorul aplicatiei (matematica) cream subdirectorul clase si dam comanda:
sau javac -sourcepath surse surse/Matematica.java -d clase javac -classpath surse surse/Matematica.java -d clase
232
gaseasca o anumita clasa n timpul executiei programului. Insa aceasta organizare nu este suficienta deoarece specifica numai partea finala din calea catre fisierele .java si .class : /matematica/clase/geometrie/plan/Poligon.class. Pentru aceasta, att la compilare ct si la interpretare trebui specificata lista de directoare n care se gasesc fisierele aplicatiei. Aceasta lista se numeste cale de cautare (classpath). Definitie O cale de cautare este o lista de directoare sau arhive n care vor fi cautate fisierele necesare unei aplicatii. Fiecare director din calea de cautare este directorul imediat superior structurii de directoare formate de organizarea claselor n directoare corespunzatoare pachetelor, astfel nct compilatorul si interpretorul sa poata construi calea completa spre clasele aplicatiei. Implicit calea de cautare este formata doar din directorul curent. Sa consideram clasa principala a aplicatiei Matematica.java:
import geometrie.plan.*; import algebra.Grup; import analiza.Functie; public class Matematica { public static void main(String args[]) { Poligon a = new Poligon(); geometrie.spatiu.Sfera = new geometrie.spatiu.Sfera(); //... } }
Identificarea unei clase referite n program se face n felul urmator: 1. La directoarele aflate n calea de cautare se adauga subdirectoarele specificate n import sau n numele lung al clasei 2. In directoarele formate este cautat un fisier cu numele clasei. In cazul n care nu este gasit nici unul sau sunt gasite mai multe va fi semnalata o eroare.
9. javac - classpath <cale de cautare> <fisier.java> 10. java - classpath <cale de cautare> <fisier.class>
Lansarea n executie a aplicatiei noastre, din directorul aplicatiei, s-ar putea face astfel:
java -classpath clase Matematica.java
233
/matematica /surse /clase compile.bat (javac -sourcepath surse surse/Matematica.java -d clase) run.bat (java -classpath clase Matematica.java)
Beneficii
portabilitate - este singurul format de arhivare independent de platforma compresarea fisierelor est optimizata pentru fisiere de tip class minimizarea timpului de incarcare a unui applet : daca appletul (fisiere class, resurse, etc) este compresat intr-o arhiva jar, el poate fi incarcat intr-o singura tranzactie HTTP, fara a fi deci nevoie de a se deschide o conexiune noua pt. fiecare fisier. securitate - arhivele JAR pot fi "semnate" electronic mecanismul pentru lucrul cu fisiere JAR este parte integrata a platformei Java.
234
Exemple:
arhivarea a doua fisiere class: jar cf classes.jar A.class B.class arhivarea tuturor fisierelor din directorul curent: jar cvf allfiles.jar *
In urma acestei comenzi vom obtine arhiva editor.jar. Daca vom ncerca sa lansam n executie aceasta arhiva prin comanda java -jar editor.jar vom obtine urmatoarea eroare:
"Failed to load Main-Class manifest from editor.jar" . Aceasta nseamna ca n fiserul Manifest.mf ce se gaseste n directorul META-INF
trebuie sa nregistram clasa principala a aplicatiei. Acest lucru l vom face n doi pasi: 1. se creeaza un fisier cu un nume oarecare (ex: mymanifest ) n care vom scrie:
Main-Class : GraphEditor.java
235