Sunteți pe pagina 1din 192

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 "crape 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 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.

Java - un limbaj compilat si interpretat In functie de modul de executie al programelor, limbajele de programare se mpart n doua categorii : interpretate - instructiunile sunt citite linie cu linie de un program numit interpretor si traduse n instructiuni masina; avantaj - simplitate; dezavantaj - 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 pot 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) Crearea unei aplicatii simple 1. Scriererea codului sursa class FirstApp { public static void main( String args[]) { System.out.println("Hello world"); } } 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. 2. 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 3. 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

4. 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.

Crearea unui applet Crearea structurii de fisere si compilarea applet-urilor sunt identice ca n cazul aplicatiilor. Difera n schimb structura programului si modul de rulare al acestuia. 1. Scrierea codului sursa si salvarea n fisier import java.awt.* ; import java.applet.* ; public class FirstApplet extends Applet { Image img; public void init() { img = getImage(getCodeBase(), "taz.gif"); } public void paint (Graphics g) { g.drawImage(img, 0, 0, this); g.drawOval(100,0,150,50); g.drawString("Hello! My name is Taz!", 110, 25); } } Salvarea se va face n fisierul FirstApplet.java 2. Compilarea applet-ului javac FirstApplet.java -> FirstApplet.class

3. 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. Crearea unui fisier HTML pentru miniaplicatie (exemplu.html) <html> <head> <title>First Java Applet</title> </head> <body> <applet code=FirstApplet.class width=400 height=400> </applet> </body> </html>

Vizualizarea appletului appletviewer exemplu.html

Structura lexicala a limbajului Setul de caractere Limbajului Java lucreaza n mod nativ folosind setul de caractere Unicode. Acesta este un standard international care nlocuieste vechiul set de caractere ASCII si care foloseste pentru reprezentarea caracterelor 2 octeti, ceea ce nseamna ca se pot reprezenta 65536 de semne, spre deosebire de ASCII, unde era posibila reprezentarea a 256 de caractere. Primele 256 caractere Unicode corespund celor din ASCII, referirea la celelate facndu-se prin \uxxxx, unde xxxx reprezinta codul caracterului. 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 \u0008 '\b' \u0009 '\t' \u000a '\n' \u000c '\f' \u000d '\r' \u0022 '\"' \u0027 '\'' \u005c '\\' Caracter Backspace(BS) Tab orizontal (HT) Linie noua - linefeed (LF) Pagina noua - formfeed (FF) Inceput de rand (CR) Ghilimele Apostrof 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; operatorul + pentru concatenarea sirurilor: String s="abcd"

int x=100; System.out.println(s + " - " + x);

operatori pentru conversii (cast) : (tip_de_data) int i = 200; long l = (long)i; //widening conversion - conversie prin extensie long l2 = (long)200; int i2 = (int)l2; //narrowing conversion - conversie prin contractie

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 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: variabilele finale (constante) se scriu cu majuscule 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: 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: variabile membre - declarate n interiorul unei clase, vizibile pentru toate metodele clasei respective si pentru alte clase n functie de nivelul lor de acces variabile locale - declarate ntr-o metoda sau ntr-un bloc de cod, vizibile doar n metoda/blocul respectiv Parametri metodelor - vizibili doar n metoda respectiva 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++ Obs: Spre deosebire de C++ nu este permisa ascunderea unei variabile : int x=12; { int x=96; //ilegal

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 if (exp_booleana) { /*...*/} if (exp_booleana) { /*...*/} else { /*...*/} switch (variabila) { case val1 : /* ... */ break; case val2 : /* ... */ break; /*...*/ default : /*...*/ } if-else, switch-case for, while, do-while break, continue, label: , return

Instructiuni tratare exceptii try-catch-finally, throw

switch-case

Instructiuni de salt for(initializare; exp_booleana; pas_iteratie) Ex: for(int i=0, j=100 ; i<100 && j>0; i++, j--) {/* ... /*} Obs: att la initializare ct si n pasul de iteratie pot fi mai multe instructiuni despartite prin virgula. while (exp_booleana) { /*...*/ } do { do-while /*...*/ } while (exp_booleana) ;

for

while

Instructiuni pentru tratarea exceptiilor try-catch-finally, -utilizate pentru tratarea exceptiilor throw Alte instructiuni break continue return label:

paraseste fortat corpul iteratiei curente termina fortat iteratia curenta return [valoare]; Termina o metoda Defineste o eticheta

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++; } Vectori Crearea unui vector 1. Declararea vectorului

Tip[] numeVector; Tip numeVector[]; Ex: int[] intregi; String adrese[];

sau

2. 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; numeVector = new Tip[dimensiune]; Ex: v = new int[10]; //se aloca spatiu pentru 10 intregi adrese = new String[100]; Obs: declararea si instantierea unui vector pot fi facute simultan astfel: Tip[] numeVector = new Tip[dimensiune]; 3. 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. Ex: 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

Vectori multidimensionali In Java tablourile multidimensionale sunt de fapt vectori de vectori. int m[][]; //declararea unei matrici m = new int[5][10]; //cu 5 linii, 10 coloane Obs: m[0], m[1], ..., m[5] sunt vectori de intregi cu 10 elemente Dimensiunea unui vector - cu ajutorul cuvntului cheie length se poate afla dimensiunea unui vector. int []a = new int[5]; Ex:

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. 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"); Concatenarea sirurilor Concatenarea sirurilor de caractere se face prin intermediul operatorului +. String str1 = "abc" + "xyz"; String str2 = "123"; String str3 = str1 + str2; 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") Folosirea argumentelor de la linia de comanda O aplicatie Java poate primi oricte argumente de la linia de comanda n momentul lansarii ei. Aceste argumente sunt utile pentru a permite utilizatorului sa specifice diverse 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. Reamintesc 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 rezultat: Drink Hot Java

(aplicatia Echo a primit 3 argumente). Una apel de genul java Echo "Drink Hot Java" va produce urmatorul rezultat: Drink Hot Java (aplicatia Echo a primit un singur argument). 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, 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 Conversia celor doua argumente n numere se va face astfel: 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 numar de tipul respectiv.

Obiecte si clase in Java Ciclul de viata al unui obiect 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 NumeClasa numeObiect; Ex: Rectangle patrat; 2. Instantierea Se realizeaza prin intermediul operatorului new si are ca efect crearea efectiva a obiectului cu alocarea spatiului de memorie corespunzator. patrat = new Rectangle(); 3. 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; 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()

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! Alocarea memoriei se face doar la apelul instructiunii new ! Folosirea obiectelor 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. 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 sa //valideze sau nu schimbarea lor 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 :

natural, atunci cnd variabila respectiva iese din domeniul sau, de exemplu la terminarea unei metode explicit, daca atribuim variabilei respective valoare null.

Cum functioneaza colectorul de gunoaie ? GC este un proces de prioritate scazuta care se executa periodic si care scaneaza dinamic memoria ocupata de programul Java aflat n executie si marcheaza acele obiecte care au referinte directe sau indirecte. Dupa ce toate obiectele au fost parcurse cele care au ramas nemarcate sunt eliminate din memorie. Procesul gc poate fi fortat sa se execute prin metoda gc a clasei System. 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). 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 Implicit, o clasa poate fi folosita doar de clasele aflate n acelasi pachet cu clasa 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.

public

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; Declara ca respectiva clasa nu poate avea subclase. Declarare claselor finale are doua scopuri:

final

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"). 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

Corpul unei clase Urmeaza declararea clasei si este cuprins ntre acolade. Contine:

declararea variabilelor de instanta si de clasa (cunoscute mpreuna ca variabile membre) declararea si implementarea metodelor de instanta si de clasa (cunoscute mpreuna ca metode membre)

Spre deosebire de C++, nu este permisa doar declararea metodei n corpul clasei, urmnd ca implementare sa fie facuta n afara ei. Implementarea metodelor unei clase trebuie sa se faca "inline" in corpul clasei. C++ Java

class A { void metoda1(); int metoda2() { class A { //implementare void metoda(){ } //implementare } } A::metoda1() { } //implementare } Obs: variabilele unei clase pot avea acelasi nume cu metodele clasei.

Constructorii unei clase Constructorii unei clase sunt metode speciale care au acelasi nume cu cel al clasei, nu returneaza nici o valoare si sunt folositi pentru initializarea obiectelor acelei clase n momentul instantierii lor. class Dreptunghi { Dreptunghi() { //constructor } } 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; } } 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.

protected Doar subclasele pot crea obiecte de tipul clasei respective. public Orice clasa poate crea instante ale clasei respective implicit Doar clasele din acelasi pachet pot crea instante ale clasei respective

Declararea variabilelor membre Variabilele se delara de obicei naintea metodelor, desi acest lucru nu este impus de compilator. class NumeClasa { //declararea variabilelor //declararea metodelor } 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 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 Generic, o variabila se declara astfel: Declararea variabilelor membre [modificatori] TipDeDate numeVariabila [ = valoareInitiala ] ; unde un modificator poate fi :

un specificator de acces : public, protected, private unul din cuvintele rezervate: static, final, transient, volatile double x; protected static int n; public String s = "abcd"; private Point p = new Point(10, 10); final long MAX = 100000L; Prezenta lui declara ca o variabila este variabila de clasa si nu de instanta: int x ; //variabila de instanta static int nrDeObiecteCreate; //variabila de 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 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;

Ex:

static Declararea variabilelor de clasa

final Declararea constantelor

Test() { MAX = 100; MAX = 200; compilare } } transient volatile

// legal // ilegal -> eroare la

Este folosit la serializarea obiectelor, pentru a specifica ce variabile membre ale unui obiect nu participa la serializare Este folosit pentru a semnala compilatorului sa nu execute anumite optimizari asupra membrilor unei clase. Este o facilitate avansata a 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 unul din cuvintele rezervate: static, abstract, final, native, synchronized Prezenta lui declara ca o metoda este metoda de clasa si nu de instanta: void metoda1() ; //metoda de instanta static void metoda2(); //metoda de clasa O metoda abstracta este o metoda care nu are implementare si trebuie sa faca parte dintr-o clasa abstracta. Specifica faptul ca acea metoda nu mai poate fi supradefinita n subclasele clasei n care ea este definita ca fiind finala. 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.

static Declararea metodelor de clasa abstract Declararea metodelor abstracte final native

synchronized

Tipul returnat de o metoda

Metodele pot sau nu sa returneze o valoare (un obiect) la terminarea lor. Tipul returnat poate fi att un tip primitiv de date (int, double, etc.) sau o referinta la un obiect al unei clase. In cazul n care o metoda nu returneaza nimic atunci la rubrica TipReturnat trebuie obligatoriu sa apara cuvntul cheie void. (Ex: void afisareRezultat() ) Daca o metoda trebuie sa returneze o valoare acest lucru se realizeaza prin intermediul instructiunii return, care trebuie sa apara n toate situatiile de terminare a functiei. double radical(double x) { if (radical >= 0) return Math.sqrt(x); else System.out.println("Eroare - numar negativ!"); //Eroare la compilare - lipseste return pe aceasta ramura } 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 } Trimiterea parametrilor catre o metoda Signatura unei metode este data de numarul si tipul argumentelor primite de acea metoda: metoda([tip1 argument1] [, tip2 argument2] ... )

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. 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 acest 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 propus 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: 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); Suprancarcarea si supradefinirea metodelor Sunt doua concepte extrem de utile ale POO si se refera la:

suprancarcarea (overloading) - n cadrul unei clase pot exista metode cu acelasi nume cu conditia ca signaturile lor sa fie diferite (lista de argumente primite sa difere fie prin numarul argumentelor, fie prin tipul lor) astfel nct la apelul functiei cu acel nume sa se poata stabili n mod unic care dintre ele se executa. Fenomenul de supradefinire a metodelor se mai numeste si polimorfism . supradefinirea (overriding) - o subclasa a unei clase poate rescrie o metoda a clasei parinte, prin implementarea unei metode cu acelasi nume si aceeasi signatura ca ale superclasei.

Exemplificare: class A { void metoda() { System.out.println("A: metoda fara parametru"); } //supraincarcare - polimorfism void metoda(int arg) { System.out.println("A: metoda cu un parametru"); }

} class B extends A { //supradefinire void metoda() { System.out.println("B: metoda fara parametru"); } } 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"

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!

Specificatori de acces pentru membrii unei clase Sunt cuvinte rezervate ce controleaza accesul celorlate clase la membrii unei clasei. Specificatorii de acces pentru variabilele si metodele unei clase sunt: public, protected, private si cel implicit (package), iar nivelul lor de acces este dat n tabelul de mai jos: Specificator Clasa Subclasa Pachet Toti private protected public package* Exemple de declaratii: private int secretPersonal; proteceted String secretDeFamilie; public Vector elemente; long doarIntrePrieteni ; -> package private void metodaInterna(); public double calculeazaRezultat(); X X X X X** X X X X X

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.

Membri de instanta si membri de clasa O clasa Java poate contine doua tipuri de variabile si metode :

de instanta: declarate fara modificatorul static, specifice fiecarei instante si de clasa: declarate cu modificatorul static, specifice clasei

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 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(); //legal, echivalent cu MyClass obj = new MyClass(); obj.metodaStatica(); //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(); Utilitatea membrilor de clasa (statici) Sunt folositi pentru a pune la dispozitie valori si metode independente de starea obiectelor dintr-o anumita clasa. 1. Declararea constantelor class MyClass { final double PI = 3.14; //variabila finala de instanta } 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 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 } //ilegal //legal

2. Numararea obiectelor unei clase class MyClass { static long nrInstante = 0; MyClass() { //constructorul este apelat la fiecare instantiere nrInstante ++; } }

Folosind variabile statice putem controla diversi parametri legati de crearea obiectelor unei clase

3. 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):

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) . 4. Initializarea variabilelor de clasa Se poate face n momentul declararii lor : class MyClass static static static } { final double PI = 3.14; long nrInstante = 0; final double EPS = 0.01;

sau prin intermediul unui bloc static de initializare: class MyClass { 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); } } 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 { . . . } } Diferentierea acestor denumiri se face deoarece: 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. Identificarea claselor imbricate Dupa cum stim orice clasa produce la compilare asa numitele "unitati de compilare", care sunt fisiere avnd numele clasei respective si extensia .class, si care contin toate informatiile despre clasa respectiva.

Pentru clasele imbricate aceste unitati de compilare sunt denumite astfel: numele clasei de acoperire, urmat de simbolul '$' apoi de numele clasei imbricate. class ClasaDeAcoperire{ class ClasaInterna1 {} class ClasaInterna2 {} } Pentru exemplul de mai sus vor fi generate trei fisiere: ClasaDeAcoperire.class ClasaDeAcoperire$ClasaInterna1.class ClasaDeAcoperire$ClasaInterna2.class 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.

Clase si metode abstracte Uneori n proiectarea unei aplicatii este necesar sa reprezentam cu ajutorul claselor concepte abstracte care sa nu poata fi instantiate si care sa foloseasca doar la dezvoltarea ulterioara a unor clase ce descriu obiecte concrete. De exemplu n pachetul java.lang exista clasa abstracta Number care modeleaza conceptul generic de "numar". Intr-un program nu avem nsa nevoie de numere generice ci de numere ntregi, reale. Clasa Number serveste ca superclasa pentru clase cum ar fi Byte, Double, Float, Integer, Long si Short ce implementeaza obiecte pentru descrierea numerelor de un anumit tip. Asadar clasa Number reprezinta un concept abstract si nu vom putea instantia obiecte de acest tip: Number numarGeneric = new Number(); //eroare Declararea unei clase abstracte Se face folosind cuvntul rezervat abstract n declaratia clasei: abstract class ClasaAbstracta { . . . } 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(); //corect void metoda(); //incorect (Method metoda requires a method body; otherwisw declare it abstract) 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, Stari comune - pozitia(originea), dimensiunea, culoarea, Comportament - mutare, redimensionare, desenare, colorare,

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) { //metoda normala . . . } abstract void draw(); //metoda abstracta } //nici 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 } } class Rectangle extends GraphicObject {

void draw() { . . . //obligatoriu implementarea } } 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: 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 doar o parte din equals, 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. 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(); toString System.ot.println("Obiect=" + obj); //echivalent cu System.ot.println("Obiect=" + obj.toString()); Exemplu de supradefinire a metodelor clasei Object class MyObject extends Object { //clasa in care este supradefinita metoda equals public int x; public boolean equals(Object obj) { if (obj == null) return false; if (!(obj instanceof MyObject)) return false; if ( ((MyObject) obj).x == x) return true; else return false; } 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()); //Se tipareste: Obiectul clonat: [1] } }

Fluxuri (Intrari / Iesiri) 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. 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: Dupa "directia" canalului de comunicatie deschis fluxurile se mpart n:
o o

fluxuri de intrare (pentru citirea datelor) fluxuri de iesire (pentru scrierea datelor)

Dupa tipul de date pe care opereaza:


o o

fluxuri de octeti (comunicare seriala se realizeaza pe 8 biti) fluxuri de caractere (comunicare seriala se realizeaza pe 16 biti)

Dupa actiunea lor:


o o

fluxuri primare de citire/scriere a datelor (se ocupa efectiv cu citirea/scrierea datelor) fluxuri pentru procesarea datelor

Ierarhia claselor pentru lucrul cu fluxuri Fluxuri de caractere Clasele radacina pentru ierarhia claselor ce se ocupa cu fluxurile de caractere sunt Reader (pentru fluxuri de intrare) si Writer (pentru fluxuri de iesire). Acestea sunt superclase abstracte pentru clase ce implementeaza fluxuri specializate pentru citirea/scrierea datelor pe 16 biti. Ierarhia claselor pentru fluxuri de intrare pe caractere:

Ierarhia claselor pentru fluxuri de iesire pe caractere:

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:

Ierarhia claselor pentru fluxuri de iesire pe octeti:

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.

Metode comune fluxurilor Superclasele abstracte Reader si InputStream definesc metode similare pentru citirea datelor. Reader int read() int read(char buf[]) int read(char buf[], int offset,int length) InputStream int read() int read(byte buf[]) int read(byte buf[], int offset,int length)

De asemenea ambele clase pun la dispozitie metode pentru marcarea unei locatii ntr-un flux, saltul peste un numar de pozitii, resetarea pozitiei curente, 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 pentru citirea/scrierea efectiva a datelor Clasele ce descriu fluxuri pentru citirea/scrierea efectiva a datelor pot fi mpartite n functie de tipul sursei datelor astfel: Tip sursa Fluxuri caractere Fluxuri octeti

CharArrayReader, CharArrayWriter

ByteArrayInputStream, ByteArrayOutputStream

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. Memorie StringReader, StringBufferInputStream StringWriter 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. PipedReader, PipedInputStream, PipedWriter PipedOutputStream Pipe Implementeaza componentele de intrare/iesire ale unei conducte de date (pipe). Pipe-urile sunt folosite pentru a canaliza iesirea unui program sau fir de executie catre intrarea altui program sau fir de executie FileInputStream, FileReader, FileWriter FileOutputStream Numite si fluxuri fisier, acestea sunt folosite pentru citirea datelor dintr-un fisier, respectiv scrierea datelor ntr-un fisier

Fisier

Fluxuri pentru procesarea datelor Clasele ce descriu fluxuri pentru procesarea datelor pot fi mpartite n functie de tipul de procesare pe care l efectueaza: Tip procesare Fluxuri caractere Fluxuri octeti BufferedReader,Buffere BufferedInputStream,BufferedOutp dWriter utStream 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 eset posibil FilterReader, FilterInputStream, FilterWriter FilterOutputStream Sunt clase abstracte ce definesc o interfata pentru fluxuri care filtreaza automat datele citite sau scrise InputStreamReader, OutputStreamWriter Formeaza o punte de legatura ntre fluxurile de caractere si fluxurile de octeti. Un flux InputStreamReader citeste octeti dintr-un flux InputStream ai i converteate la 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 Concateneaza mai multe fluxuri de intrare ntr-unul singur ObjectInputStream, ObjectOutputStream

"Bufferizare"

Filtrare

Conversie octeticaractere

Concatenare Serializare

Conversie tipuri de date

Folosite pentru serializarea obiectelor DataInputStream, DataOutputStream Folosite la scrierea/citirea datelor de tip primitiv ntr-un format independent de masina pe care se lucreaza LineNumberReader LineNumberInputStream Numara liniile citite de la un flux de intrare. PushbackReader PushbackInputStream

Numarare

Citire n avans Fluxuri de intrare care au un buffer de 1-caracter(octet) n care este citit n avans si caracterul (octetul) care urmeaza celui curent citit. PrintWriter PrintStream Afisare Metode convenabile pentru afisarea informatiilor. Crearea unui flux Orice flux este un obiect al clasei ce implementeaza fluxul respectiv. Crearea unui flux se realizeaza asadar similar cu crearea obiectelor prin instructiunea new(). Exemple: //crearea unui flux de intrare pe caractere FileReader in = new FileReader("fisier_intrare.txt"); //crearea unui flux de iesire pe caractere FileWriter out = new FileWriter("fisier_iesire.txt"); //crearea unui flux de intrare pe octeti FileInputStream in = new FileInputStream("fisier_intrare.txt"); //crearea unui flux de iesire pe octeti FileOutputStrem out = new FileOutputStream("fisier_iesire.txt"); 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"); BufferedWriter out = new BufferedWriter(fo); Asadar, crearea unui flux pentru procesarea datelor are formatul general: FluxProcesare numeFlux = new FluxProcesare( referintaFluxPrimitiv ) In general, fluxurile pot fi grupate n succesiuni orict de lungi: DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream("fisier.in"))); Fluxuri pentru lucrul cu fisiere (fluxuri de tip "File") Fluxurile pentru lucrul cu fisiere sunt cele mai usor de nteles. Clasele care implementeaza aceste fluxuri sunt urmatoarele: FileReader, FileWriter FileInputStream, FileOutputStream - caractere - octeti

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 Constructorii clasei FileReader: public FileReader( String fileName ) throws FileNotFoundException public FileReader( File file ) throws FileNotFoundException public FileReader( FileDescriptor fd ) Constructorii clasei FileWriter: public FileWriter( public FileWriter( public FileWriter( public FileWriter( IOException String fileName ) throws IOException File file ) throws IOException FileDescriptor fd ) String fileName, boolean append ) throws

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 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. Citirea si scrierea cu zona tampon Clasele pentru citirea/scrierea cu zona tampon sunt: BufferedReader, BufferedWriter caractere BufferedInputStream, BufferedOutputStream - octeti 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. Exemplu: BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream("out.dat"), 1024) Constructorii acestor clase sunt urmatorii: BufferedReader( Reader in ) BufferedReader BufferedReader( Reader in, int dim_buffer ) BufferedWriter( Writer out ) BufferedWriter( Writer out, int dim_buffer

BufferedWriter

) BufferedInputStream( InputStream in ) BufferedInputStream BufferedInputStream( InputStream in, int dim_buffer )

BufferedOutputStream( OutputStream out ) BufferedOutputStream BufferedOutputStream( OutputStream out, int dim_buffer ) 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 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 } Metoda readLine permite citirea unui flux linie cu linie. Concatenarea fisierelor 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 de fluxuri de intrare. Fiecare obiect n enumerarea primita ca parametru trebuie sa fie de tipul InputStream. Construieste un flux de intrare care combina doua

SequenceInputStream( Enumeration e ) SequenceInputStream(

InputStream s1, InputStream fluxuri s1 si s2. Primul flux citit va fi s1. s2 ) 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 Pentru concatenarea mai multor fisiere exista doua variante o folosirea unei enumerari - primul constructor o 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( f1, new SequenceInputStream(f2, f3)); Fluxuri pentru filtrarea datelor Un flux de filtrare se ataseaza altui flux pentru a filtra datele care sunt citite/scrise de catre acel flux. Clasele pentru fluxuri de filtrare au ca superclase clasele abstracte FilterInputStream (pentru filtrarea fluxurilor de intrare) si FilterOutputStream (pentru filtrarea fluxurilor de iesire). Clasele pentru filtrarea datelor sunt: DataInputStream, DataOutputStream BufferedInputStream, BufferedOutputStream LineNumberInputStream PushbackInputStream PrintStream (flux de iesire) Observati ca toate aceste clase descriu fluxuri de octeti. Filtrarea datelor nu trebuie vazuta ca o metoda de a elimina anumiti octeti dintr-un 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. 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 Clasele DataInputStream si DataOutputStream Aceste clase ofera metode prin care un flux nu mai este vazut ca o nsiruire de octeti, ci ca o sursa de date primitive. Prin urmare vor furniza metode pentru citirea si scrierea datelor la nivel de tip de data si nu la nivel de octet. Constructorii si metodele cele mai importante (altele dect read/write) sunt date n tabelul de mai jos: DataInputStream //Constructor DataInputStream(InputStream in) readBoolean( ) readByte( ) readChar( ) readDouble( ) readFloat( ) readInt( ) readLong( ) readShort( ) readUnsignedByte( ) readUnsignedShort( ) String readUTF( ) DataOuputStream //Constructor DataOutputStream(OutputStream out) 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. Fluxuri standard de intrare/iesire Mergnd pe linia introdusa de sistemul de operare UNIX orice program Java are : o o intrare standard o o iesire standard o o iesire standard pentru erori 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

Semnificatie flux standard de intrare

Tip flux InputStream

System.out flux standard de iesire PrintStream System.err flux standard pentru afisarea erorilor PrintStream Afisarea informatiilor pe ecran Am vazut deja numeroase exemple de folosire a fluxului standard de iesire, el fiind folosit la afisarea oricaror rezultate pe ecran (n modul consola):System.out.println("mesaj"). Fluxul standard pentru afisarea erorilor se foloseste similar: catch (IOException e) { System.err.println("Eroare de intrare/iesire!") } 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. Citirea datelor de la tastatura Uzual vom dori sa folosim metoda readLine pentru citirea datelor de la tastatura si din acest motiv vom folosi intrarea standard mpreuna cu o clasa de procesare care implementeaza metoda readLine. Exemplul tipic este: BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Introduceti o linie:"); String linie = stdin.readLine() System.out.println(linie); Exemplu: un program care afiseaza liniile introduse de la tastatura import java.io.*; public class Echo { public static void main(String[] args) { BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); String s; try { while((s = stdin.readLine()).length() != 0) System.out.println(s); //Programul se opreste cu o linie vida } catch(IOException e) {e.printStackTrace();} } } Observatie: Metoda readLine poate provoca exceptii de tipul IOException. Redirectarea intrarii/iesirii standard

Incepnd cu Java 1.1 au fost introduse n clasa System metode statice pentru a redirecta fluxurile de intrare si iesire standard. Aceste metode sunt: setIn(InputStream) setOut(PrintStream) setErr(PrintStream) - redirectare intrare - redirectare iesire - redirectare erori

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 erorilor ntr-un fisier poate fi de asemenea utila. PrintStream err = new PrintStream(new BufferedOutputStream( new FileOutputStream("erori.err"))); System.setErr (err); 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) { e.printStackTrace(); } } }

Analiza lexicala pe fluxuri (clasa StreamTokenizer) Clasa StreamTokenizer parcurge un flux de intrare de orice tip si l mparte n "atomi lexicali". Rezultatul va consta n faptul ca n loc sa se citeasca octeti sau caractere se vor citi, pe rnd, atomii lexicali ai fluxului respectiv. Printr-un atom lexical se ntelege n general: o un identificator (un sir care nu este ntre ghilimele) o un numar o un sir de caractere o un comentariu o 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 TT_EOL - atom TT_NUMBER TT_WORD nval sval 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.*; 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 while (tip != StreamTokenizer.TT_EOF) { switch (tip) { case StreamTokenizer.TT_WORD : //cuvant System.out.println(st.sval); break; //citesc primul atom ce marcheaz sfrsitul fluxului ce marcheaz sfrsitul unei linii - atom de tip numar - atom de tip cuvnt - valoarea unui atom numeric - sirul continut de un atom de tip

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) {} } } Alte clase pentru lucrul cu fisiere Clasa RandomAccesFile (fisiere cu acces direct) 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, si altele, desi sunt foarte utile si pentru dispozitive n care informatia poate fi accesata direct. Clasa RandomAccesFile: o permite accesul nesecvential (direct) la continutul unui fisier. o este o clasa de sine statatoare, subclasa directa a clasei Object. o se gaseste n pachetul java.io. o implementeaza interfetele DataInput si DataOutput, ceea ce nseamna ca sunt disponibile metode de tipul readXXX, writeXXX o permite att citirea ct si scriere din/in fisiere cu acces direct o permite specificarea modului de acces al unui fisier (read-only, read-write) Constructorii acestei clase sunt: RandomAccessFile(String numeFisier,String mod_acces) throws IOException

RandomAccessFile(String fisier,String mod_acces) throws IOException unde mod_acces poate fi: "r" - fisierul este deschis numai pentru citire (read-only) "rw" - fisierul este deschis pentru citire si scriere (readwrite) 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.

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

dintr-un

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 Metodele mai importante ale clasei File sunt: boolean isDirectory( ) boolean isFile( ) String String String String getName( ) getPath( ) getAbsolutePath() getParent() exists( ) delete( ) mkdir( ) mkdirs( ) renameTo(File File( String fisier)

Testeaza daca un obiect File>/tt> reprezinta un fisier sau un director Afla numele (fara cale), calea fisierului sau directorului reprezentat de obiectul respectiv Testeaza daca exista un anumit fisier/director 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 Creeaza o lista cu numele fisierelor dintr-un director filtrate dupa un anumit criteriu specificat. Testeaza daca un anumit fisier poate fi folosit pentru citire, respectiv scriere Afla lungimea si data ultimei modificari a unui fisier.

boolean boolean boolean boolean boolean dest)

String[] list( ) String[] list (FilenameFilter filter ) boolean canRead( ) boolean canWrite( ) long length( ) long lastModified( )

Exemplu1: listarea fisierelor din directorul curent //Listarea fisierelor din directorul curent import java.io.*; public class DirList { public static void main(String[] args) { try { File director = new File("."); String[] list; list = director.list(); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } catch(Exception e) { e.printStackTrace(); } } }

Exemplu2: aflarea informatiilor despre un fisier //Afiseaza informatii despre fisierele primite la linia de comanda import java.io.*; public class FileInfo { private static void fileInfo(File f) { System.out.println( "Cale absoluta: " + f.getAbsolutePath() + "\n Poate citi: " + f.canRead() + "\n Poate scrie: " + f.canWrite() + "\n Nume: " + f.getName() + "\n Parinte: " + f.getParent() + "\n Cale: " + f.getPath() + "\n Lungime: " + f.length() + "\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]) ); } } Interfete 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 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 Definirea unei interfete Definirea unei interfete se face prin intermediul cuvntului cheie interface: [public] interface NumeInterfata [extends SuperInterfata1 [,extends SuperInterfata2...]] { //corpul interfetei:constane si metode abstracte }

O interfata poate avea un singur modificator: public. O interfata publica este accesibila 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. 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. 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. Implementarea unei interfete Se face prin intermediul cuvntului cheie implements: class NumeClasa implements NumeInterfata sau class NumeClasa implements Interfata1, Interfata2... O clasa poate implementa oricte interfete. 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()"); } } 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. Diferente ntre o interfata si o clasa abstracta La prima vedere o interfata nu este altceva dect o clasa abstacta n care toate metodele sunt abstracte (nu au nici o implementare). Asadar o clasa abstracta nu ar putea nlocui o interfata? Raspunsul la intrebare este Nu. Deosebirea consta n faptul ca unele clase sunt fortate sa extinda o anumita clasa (de exemplu orice applet trebuie sa fie subclasa a clasei Applet) si nu ar mai putea sa extinda o clasa abstracta deoarece n Java nu exista dect mostenire simpla. Fara folosirea interfetelor nu am putea forta clasa respectiva sa respecte un anumit protocol. La nivel conceptual diferenta consta n:
o o

extinderea unei clase abstracte forteaza o relatie ntre clase implementarea unei interfete specifica doar necesitatea implementarii unor anumie metode

Mostenire multipla prin intermediul interfetelor Interfetele nu au nici o implementare si nu ocupa spatiu de memorie la instantierea lor. Din acest motiv nu reprezinta nici o problema ca anumite clase sa implementeze mai multe interfete sau ca o interfata sa extinda mai multe interfete (sa aiba mai multe superinterfete) class NumeClasa implements Interfata1, Interfata2, ... interface NumeInterfata extends Interfata1, Interfata2, ... 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() {} } Exemplu de interfata care extinde mai multe interfete : interface Monstru { void ameninta(); } interface MonstruPericulos extends Monstru { void distruge(); } interface Mortal {

void omoara(); } interface Vampir extends MonstruPericulos, Mortal { void beaSange(); } class Dracula implements Vampir { public void ameninta() {} public void distruge() {} public void omoara(); public void beaSange() {} } 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: 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) Crearea grupurilor de constante Deoarece orice variabila a unei interfete este implicit declarata cu public, static si final interfetele reprezinta o metoda convenabila de creare a unor grupuri de constante, similar cu enum din C++. public interface Luni { int IAN=1, FEB=2, ..., DEC=12; } Folosirea acestor constante se face prin expresii de genul NumeInterfata.constanta : if (luna < Luni.DEC) luna ++ else luna = Luni.IAN; Transmiterea metodelor ca parametri (call-back)

Transmiterea metodelor ca parametri se face n C++ cu ajutorul pointerilor. In Java aceasta tehnica este implementata prin intermediul interfetelor. Vom ilustra acest lucru prin intermediul unui exemplu. Explorarea unui graf In fiecare nod trebuie sa se execute prelucrarea informatiei din el prin intermediul unei functii primite ca parametru. interface functie { public int executie(int arg); } class Graf { //... void explorare(functie f) { //... if explorarea a ajuns in nodul v f.executie(v.valoare); //... } } //Definim doua functii class f1 implements functie { public int executie(int arg) { return arg+1; } } class f2 implements functie { public int executie(int arg) { return arg*arg; } } public class TestCallBack { public static void main(String args[]) { Graf G = new Graf(); G.explorare(new f1()); G.explorare(new f2()); } } 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 s //returneaza 0=nu este subsir, >0=pozitia subsirului 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; //folosim o clasa anonima pentru specificarea filtrului list = director.list(new FilenameFilter() { public boolean accept (File dir,String nume) return ( nume.endsWith(".java")); } } ); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } catch(Exception e) { e.printStackTrace(); } } } Pachete Crearea unui pachet Definitie - Un pachet este o colectie de clase si interfete nrudite. Sunt folosite pentru gasirea si utilizarea mai usoara a claselor, pentru a evita conflictele de nume si pentru a controla accesul la anumite clase. In alte limbaje de programare pachetele se numesc librarii.

Toate clasele si interfetele Java apartin la diverse pachete, grupate dupa functionalitatea lor: clasele de baza se gasesc n pachetul java.lang, clasele pentru intrari/iesiri sunt n java.io, clasele pentru grafica n java.awt, cele pentru construirea applet-urile n java.applet, etc. Crearea unui pachet se realizeaza prin scriere la nceputul fisierelor sursa ce contin clasele si interfetele pe care dorim sa le grupam ntr-un pachet a instructiunii: package NumePachet; Sa consideram un exemplu: presupunem ca avem doua fisiere sursa Graf.java si Arbore.java Graf.java Arbore.java

package grafuri; package grafuri; class Graf {...} class Arbore {...} class GrafPerfect extends Graf {...} 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.

Denumirea unui pachet Exista posibilitatea ca doi programatori care lucreaza la un proiect comun sa foloseasca acelasi nume pentru unele din clasele lor. De asemenea, se poate ca una din clasele unei aplicatii sa aiba acelasi nume cu o clasa a mediului Java. Acest lucru este posibil att timp ct clasele cu acelasi nume se gasesc n pachte diferite, ele fiind diferentiate prin prefixarea lor cu numele pachetelor. Asadar numele complet al unei clase este format din numele pachetului la care apartine + numele sau: numePachet.NumeClasa Ex: java.lang.String (java.lang=pachet, String=clasa) De exemplu sa presupunem ca n aplicatia noastra folosim o clasa numita Stack: package my_package; class Stack { ... } Clasa Stack exista deja n pachetul java.util. Diferentierea ntre cele doua clase se va face prin specificarea numelui complet al clasei, adica numelePachetului.NumeleClasei: java.util.Stack s1 = new java.util.Stack(); my_package.Stack s2 = new my_package.Stack(); 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.

Folosirea membrilor unui pachet 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. 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. Importul unei clase sau interfete Se face printr-o instructiune import n care specificam numele clasei (interfetei) pe care dorim sa o folosim dintr-un pachet: import java.awt.Rectangle; 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 java.awt.Rectangle; import java.awt.Circle; import java.awt.Line; import java.awt.Point; import java.awt.Polygon;

In aceasta situatie ar fi recomandat importul ntregului pachet si nu al fiecarei clase n parte.

Importul unui pachet (importul la cerere) Se face printr-o instructiune import n care specificam numele pachetului ale carui clase si interfete dorim sa le folosim dintr-un pachet, urmat de simbolul '*'. Se mai numeste 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)

java.security mecanisme de securitate : criptare, autentificare java.sql java.text java.util 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. Serializarea 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 ntro 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 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. 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 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: private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException; private void writeObject(ObjectOutputStream stream) throws IOException; //salvare obiect

//refacere obiect

Aceste metode contin apeluri catre metodele implicite de seializare a obiectelor: final void defaultWriteObject() throws IOException final void defaultReadObject() throws IOException,ClassNotFoundException,NotActiveException

Clasele care necesita o serializare speciala trebuie sa supradefineasca metodele writeObject si readObject (obligatoriu pe amndoua!) pentru a implementa metode specifice de serializare. 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();

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. Implementarea interfetei Serializable Definitia completa a interfetei Serializable este: package java.io; public interface Serializable { // 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. Personalizarea serializarii obiectelor Personalizarea serializarii se realizeaza prin supradefinirea (ntr-o clasa serializabila!) a metodelor writeObject si readObject, modificnd astfel actiunea lor implicita. Metoda writeObject controleaza ce date sunt salvate si este uzual folosita pentru a adauga informatii suplimentare la cele scrise implicit de metoda defaultWriteObject.

Metoda readObject controleaza modul n care sunt restaurate obiectele, citind informatiile salvate si, eventual, modifcnd starea obiectelor citite astfel nct ele sa corespunda anumitor cerinte. Aceste metode trebuie obligatoriu sa aiba urmatorul format: private void writeObject(ObjectOutputStream stream) throws IOException private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException 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) } 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. Implementarea interfetei Externalizable Pentru un control complet, explicit, al procesului de serializare, o clasa trebuie sa implementeze interfata Externalizable. Pentru instante ale acestor clase doar numele clasei este salvat automat pe un flux de obiecte, clasa fiind responsabila cu scrierea si citirea membrilor sai si trebuie sa se coordoneze cu superclasele ei. Definitia interfetei Externalizable este: package java.io; public interface Externalizable extends Serializable { public void writeExternal(ObjectOutput out) throws IOException; public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; } Asadar, aceste clase trebuie sa implementeze obligatoriu metodele writeExternal si readExternal n care se va face serializarea completa a obiectelor si coordonarea cu 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 Atentie Modificatorul static anuleaza efectul modificatorului transient; static transient private String parola; //participa la serializare

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) class A { ... } class B implements Serializable { ... } public class Test implements Serializable { private int x; // DA transient public int y; // NU static int var1; // DA transient static var2; // DA A a; // NU B b1; // DA transient B b2; // 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. Exemplu de folosire a serializarii import java.io.*; public class TestSerializare { public static void main(String args[]) throws IOException { MyObject obj = new MyObject(10, 20, 30); //salvam obiectul in fisierul "fisier.tmp" FileOutputStream fout = new FileOutputStream("fisier.tmp"); ObjectOutputStream sout = new ObjectOutputStream(fout); sout.writeObject(obj); sout.flush(); sout.close(); fout.close(); System.out.println("A fost salvat obiectul " + obj); System.out.println("Restauram..."); FileInputStream fin = new FileInputStream("fisier.tmp"); ObjectInputStream sin = new ObjectInputStream(fin); try { 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); } }

Rezultatul acestui program va fi : A fost salvat obiectul x=10, y=20, z=30 Restauram... A fost restaurat obiectul x=10, y=0, z=30

Folosirea serializarii pentru copierea obiectelor Se stie ca nu putem copia un obiect prin instructiunea de atribuire. O secventa de forma: MyObject o1 = new MyObject(10, 20, 30); MyObject o2 = o1; 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();

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

Colectii 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.

Interfete ce descriu colectii Interfetele ce descriu colectii reprezinta nucleul mecanismului de lucru cu colectii. Scopul acestor interfete este de a permite utilizarea colectiilor independent de modul lor de implementare. Mai jos sunt descrise structurile de date modelate de aceste interfete: 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 { // 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); // Optional

boolean removeAll(Collection c); // Optional boolean retainAll(Collection c); // Optional void clear(); // Optional // 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. 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 abstract boolean addAll(int index, Collection c); // Optional // Cautare int indexOf(Object o); int lastIndexOf(Object o); // Iterare ListIterator listIterator(); ListIterator listIterator(int index); // Extragere sublista List subList(int from, int to); }

Doua clase care implementeaza interfata List sunt ArrayList si Vector.

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); } } Clase care implementeaza interfata Map sunt HashMap, TreeMap si Hashtable.

Interfata SortedSet 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(); } Implementari ale colectiilor Clasele de baza care implementeaza interfete ce descriu colectii sunt prezentate in tabelul de mai jos. Numele lor este de forma <Implementare><Interfata>, unde 'implementare' se refera la structura de date folosita. Implementari Set Interfete List Map HashMap HashSet ArrayList TreeMap TreeSet LinkedList

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

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. Folosirea eficienta a colectiilor Dupa cum am vazut, fiecare interfata ce descrie o colectie are cte doua implementari, dintre care una este de baza, fiind folosita in 90% din cazuri. De exemplu, interfata List este implementata de clasele ArrayList si LinkedList, prima fiind cea mai folosita. De ce exista atunci si clasa LinkedList ? Raspunsul consta in faptul ca implementari diferite ale interfetelor pot oferi performante mai bune in functie de situatie, prin realizarea unor compromisuri ntre spatiul necesar pentru reprezentarea datelor, rapiditatea regasirii acestora si timpul necesar actualizarii 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:

ArrayList LinkedList List1 (add) List2 (get) List3 (remove) 0.4 0.4 6.9 0.4 21.3 0.4

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

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 Sorteaza ascendent o lista referitor la ordinea sa naturala sau la ordinea data de un comparator Amesteca elementele unei liste - opusul lui sort

binarySearch Efectueaza o cautare binara a unui element ntr-o lista ordonata reverse fill copy min max 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

enumeration Returneaza o enumerare a elementelor 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: Enumeration Iterator ListIterator

boolean hasNext(),hasPrevious() boolean hasNext() boolean hasMoreElements() Object next(), previous() Object next() Object nextElement() void add(Object o) void remove() 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 se utilizeaza 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; 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=""; 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(); StringTokenizer st=new StringTokenizer(linie); comanda=st.nextToken(); argumente=""; while (st.hasMoreTokens()) argumente += st.nextToken() + " "; argumente=argumente.trim(); if (comanda.startsWith("exit")) break; else if (comanda.startsWith("add")) add(argumente); else if (comanda.startsWith("del")) delete(argumente); else if (comanda.startsWith("update")) update(argumente); else if (comanda.startsWith("list")) list(); else if (comanda.startsWith("load")) load(argumente); else if (comanda.startsWith("saveAs")) saveAs(argumente); else if (comanda.startsWith("save")) 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(); } }

Interfata grafica Privire de ansamblu asupra interfetei grafice Interfata grafica sau, mai bine zis, interfata grafica cu utilizatorul (GUI), este un termen cu nteles larg care se refera la toate tipurile de comunicare vizuala ntre un program si utilizatorii sai. Aceasta este o particularizare a interfetei cu utilizatorul (UI), prin care vom ntelege conceptul generic de interactiune ntre un program si utilizatorii sai. Asadar, UI se refera nu numai la ceea ce utilizatorul vede pe ecran ci la toate mecanismele de comunicare ntre acesta si program. Limbajul Java pune la dispozitie numeroase clase pentru implementarea diverselor functionalitati UI, nsa ne vom ocupa n continuare de acelea care permit realizarea unei intefete grafice cu utilizatorul (GUI). Biblioteca de clase care ofera servicii grafice se numeste java.awt, AWT fiind prescurtarea de la Abstract Window Toolkit si este pachetul care care a suferit cele mai multe modificari n trecerea de la o versiune JDK la alta. In principiu, crearea unei aplicatii grafice presupune urmatoarele lucruri:

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; 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). 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 suferito (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. Exemplu: crearea unei ferestre ce contine doua butoane import java.awt.*; public class TestAWT1 { public static void main(String args[]) { //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). 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.

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. Suprafete de afisare (Clasa Container) Crearea obiectelor grafice nu realizeaza automat si afisarea lor pe ecran. Mai nti ele trebuie asezate pe o suprafata, 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 se numeste suprafata de afisare sau container si reprezinta o instanta a unei clase obtinuta prin extensia superclasei Container. O parte din ierarhia a carei radacina este Container este prezentata n figura de mai jos:

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. Adaugarea unei componente Clasa Container pune la dispozitie metoda add pentru adaugarea unei componente pe o suprafata de afisare. O componenta nu poate apartine dect unui singur container, ceea ce nseamna ca pentru a muta un obiect dintr-un container n altul trebuie sa-l eliminam mai nti de pe containerul initial. Eliminarea unei componente de pe un container se face cu metoda remove. Frame f = new Frame("O fereastra"); Button b = new Button("OK"); f.add(b); //adauga butonul pe suprafata ferestrei f.show(); 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");

f.add(b1); f.add(b2); f.add(b3); f.add(b4); f.add(b5); f.pack(); f.show(); } } Fereastra afisata de acest program va arata astfel:

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. Folosirea gestionarilor de pozitionare Asa cum am vazut, orice container are un gestionar implicit de pozitionare - un obiect care implemeneaza interfata LayoutManager, acesta fiindu-i atasat automat la crearea sa. In cazul n care acesta nu corespunde necesittailor noastre el poate fi schimbat cu usurinta. Cei mai utilizati gestionari din pachetul java.awt sunt:

FlowLayout BorderLayout GridLayout CardLayout GridBagLayout

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); Folosind pozitionarea absoluta, nu va mai fi suficient sa adaugam cu metoda add 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, 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(); } }

Componentele ferestrei vor fi afisate astfel:

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 ntr-una din cele cinci zone, ele trebuie grupate n prealabil ntr-un panel, care va fi amplasat apoi n regiunea dorita. 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(); } } Cele cinci butoane ale ferestrei vor fi afisate astfel: 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. 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:

Redimensionarea ferestrei va determina redimensionarea tuturor componentelor.

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 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 unei componente gridBag.setConstraints(componenta, c); container.add(componenta); 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 (Clasa Panel) Plasarea componentelor direct pe suprafata de afisare poate deveni incomoda n cazul n care avem multe obiecte grafice. Din acest motiv se recomanda gruparea obiectelor grafice nrudite ca functii astfel nct sa putem fi siguri ca, indiferent de gestionarul de pozitionare al suprafetei de afisare, ele se vor gasi mpreuna. Gruparea componentelor se face n panel-uri.

Un panel este cel mai simplu model de container. El nu are o reprezentare vizibila, rolul sau fiind de a oferi o suprafata de afisare pentru componente grafice, inclusiv pentru alte panel-uri. Clasa care instantiaza aceste obiecte este Panel, extensie a superclasei Container. Pentru a aranja corespunzator componentele grupate ntr-un panel, acestuia i se poate specifica un gestionar de pozitionare anume, folosind metoda setLayout. Gestionarul implicit pentru containerele de tip Panel este FlowLayout. Asadar, o aranjare eficienta a componentelor unei ferestre nseamna:

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:

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 de tratare a evenimentelor Inainte de a detalia aspectele prezentate mai sus, sa consideram un exemplu de tratare a evenimentelor. Vom crea o fereastra care sa contina doua butoane cu numele "OK", repectiv "Cancel". La apasarea fiecarui buton vom scrie pe bara titlu a ferestrei mesajul " Ati apasat butonul ...". //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()); //se stabileste gestionarul setSize(200, 100); //se dimensioneaza fereastra Button b1 = new Button("OK"); add(b1); buton Button b2 = new Button("Cancel"); add(b2); doilea buton 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 evenimentele produse de cele doua butoane si din acest motiv a trebuit sa trimitem ca parametru acestei clase instanta la fereastra noastra. //se adauga al //se adauga primul

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. Tipuri de evenimente si componentele care le genereaza In tabelul de mai jos sunt prezentate n stnga tipurile de evenimente si interfetele iar n dreapta lista componentelor ce pot genera evenimente de acel tip precum si o scurta explicatie despre motivul care le provoaca. Eveniment/Interfata ActionEvent ActionListener AdjustmentEvent AdjustmentListener ComponentEvent ComponentListener ContainerEvent ContainerListener FocusEvent FocusListener KeyEvent KeyListener MouseEvent MouseListener Componente care genereaza acest eveniment Button, List, TextField, MenuItem, CheckboxMenuItem, Menu, PopupMenu Actiuni asupra unui control Scrollbar si orice clasa care implementeaza interfata Adjustable Modificarea unei valori variind ntre doua limite Component si subclasele sale Redimensionari, deplasari, ascunderi ale componentelor Container si subclasele sale Adaugarea, stergerea componentelor la un container Component si subclasele sale 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 MouseEvent Component si subclasele sale MouseMotionListener Miscarea sau "trrea" (drag) mouse-ului pe suprafata unei 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

Evenimente suportate de o componenta Urmatorul tabel prezinta o clasificare a evenimentelor n functie de componentele care le suporta: Componenta Adjustable Applet Button Canvas Checkbox Choice Component Container Dialog FileDialog Frame Label List Menu MenuItem Panel PopupMenu Scrollbar ScrollPane TextArea TextComponent AdjustmentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent FocusEvent, KeyEvent, MouseEvent, ComponentEvent ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent 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 Evenimente suportate de componenta

CheckboxMenuItem ActionEvent, ItemEvent

TextField Window

ActionEvent, TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Metodele interfetelor de tip "listener" Orice clasa care trateaza evenimente trebuie sa implementeze obligatoriu metodele interfetelor corespunzatoare evenimentelor pe care le trateaza. Tabelul de mai jos prezinta, pentru fiecare interfata, metodele puse la dispozitie si care trebuie implementate de clasa ascultator. Interfata ActionListener AdjustmentListener ComponentListener Metodele interfetei actionPerformed(ActionEvent) adjustmentValueChanged(AdjustmentEvent) componentHidden(ComponentEvent) componentShown(ComponentEvent) componentMoved(ComponentEvent) 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) mouseMoved(MouseEvent) windowOpened(WindowEvent) windowClosing(WindowEvent) windowClosed(WindowEvent) windowActivated(WindowEvent) windowDeactivated(WindowEvent) windowIconified(WindowEvent) windowDeiconified(WindowEvent) itemStateChanged(ItemEvent) textValueChanged(TextEvent)

ContainerListener FocusListener KeyListener

MouseListener

MouseMotionListener

WindowListener

ItemListener TextListener

Folosirea adaptorilor si a claselor interne n tratarea evenimentelor Am vazut ca o clasa care trateaza evenimente de un anumit tip trebuie sa implementeze interfata corespunzatoare acelui tip. Aceasta nseamna ca trebuie sa implementeze obligatoriu toate metodele definite de acea interfata, chiar daca nu specifica nici un cod pentru unele dintre ele. Sunt nsa situatii

cnd acest lucru este suparator, mai ales atunci cnd nu ne intereseaza dect o singura metoda a interfetei. Un exemplu sugestiv este urmatorul: o fereastra care nu are specificat cod pentru tratarea evenimentelor sale nu poate fi nchisa cu butonul standard marcat cu 'x' din coltul dreapta sus si nici cu combinatia de taste Alt+F4. Pentru a realiza acest lucru trebuie interceptat evenimentul de nchidere a ferestrei n metoda windoClosing si apelata apoi metoda dispose de nchidere a ferestrei, eventual umata de iesirea din program, n cazul cnd este vorba de fereastra principala a aplicatiei. Aceasta nseamna ca trebuie sa implementam interfata WindowListener care are nu mai putin de sapte metode. //Crearea unei ferestre cu ascultarea evenimentelor sale //folosind implementarea directa a interfetei WindowListener import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements WindowListener { 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 { //suprdefinim metodele care ne intereseaza public void windowClosing(WindowEvent e) { System.exit(0); } } 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 ce definesc o singura metoda deci crearea unei clase adaptor nu si are rostul. Interfata "listener" ActionListener AdjustmentListener nu are nu are Adaptor

ComponentListener ContainerListener FocusListener ItemListener KeyListener MouseListener TextListener WindowListener Folosirea claselor interne (anonime)

ComponentAdapter ContainerAdapter FocusAdapter nu are KeyAdapter Mouse nu are WindowAdapter

MouseMotionListener MouseMotionAdapter

Stim ca o clasa interna este o clasa declarata n cadrul altei clase iar clasele anonime sunt acele clase interne folosite doar pentru instantierea unui singur obiect de acel tip. Un 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 vizibila apelati metoda show 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

boolean isShowing() Testeaza daca fereastra este vizibila sau nu void pack() void show() void toBack() void toFront()

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 } } 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) Metoda statica ce returneaza lista tuturor ferestrelor deschise ale unei aplicatii Afla/seteaza imaginea(iconita) care sa fie afisata atunci cnd fereastra este minimizata

MenuBar getMenuBar() void Afla/seteaza bara de meniuri a ferestrei setMenuBar(MenuBar mb) int getState() void setState(int s) String getTitle() void setTitle() boolean isResizable() void setResizable(boolean r) 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) 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 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); } } //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 pe care ruleaza aplicatia. Constructorii clasei sunt: FileDialog(Frame parinte) FileDialog(Frame parinte, String titlu) FileDialog(Frame parinte, String titlu, boolean mod)

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

String getDirectory() void setDirectory(String dir) String getFile() void setFile(String f)

FilenameFilter Afla/specifica filtrul care se va aplica fisierelor din getFilenameFilter() directorul din care se va face selectia fisierului sau void n care se va face salvare setFilenameFilter(FilenameFilter Nu functioneaza pe platformele Windows ! f) Afla/specifica daca ncarcam sau salvam un fisier; int getMode() void setMode(int mod)

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.*; 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 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:

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(); } } 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. Clasa Clasa MenuBar Permite crearea barelor de meniuri asociate unei ferestre cadru (Frame). Aceasta clasa adapteaza conceptul de bara de meniuri la platforma curenta de lucru. Pentru a lega bara de meniuri la o anumita fereastra trebuie apelata metoda setMenuBar din clasa Frame. Crearea unei bare de meniuri si legarea ei de o fereastra se realizeaza astfel: //creez bara de meniuri MenuBar mb = new MenuBar(); //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. 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(); //creat bara de meniuri Menu optiuni = new Menu("Options"); //creat un meniu optiuni.add(new MenuItem("Copy")); optiuni.add("Cut"); //adaug articole 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 ); //atasez bara unei ferestre 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. Tratarea evenimentelor generate de meniuri La alegerea unei optiuni dintr-un meniu se genereaza un eveniment de tip ActionEvent si comanda este reprezentata de numele optiunii alese. Asadar pentru a activa optiunile unui meniu trebuie implementat un obiect receptor care sa implementeze interfata ActionListener si care n metoda actionPerformed sa specifice codul ce trebuie executat la alegerea unei optiuni. Fiecarui meniu n putem asocia un obiect receptor diferit, ceea ce usureaza munca n cazul n care ierarhia de meniuri este complexa. Pentru a realiza legatura ntre obiectul meniu si obiectul receptor trebuie sa adaugam receptorul n lista de ascultatori a meniului respectiv prin commanda: meniu.addActionListener(listener).

Asadar, tratarea evenimentelor unui meniu este asemanatoare cu tratarea butoanelor, ceea ce face posibil ca unui buton de pe suprafata de afisare sa i corespunda o optiune ntr-un meniu, ambele cu acelasi nume, tratarea evenimentului corespunzator apasarii butonului, sau alegerii optiunii facndu-se o singura data ntr-o clasa care este nregistrata ca receptor att la buton ct si la meniu. Un caz special l constituie optiunile de tip CheckboxMenuItem. Obiectele de acest tip se gasesc ntr-o categorie comuna cu List, Choice, CheckBox, implementeaza o interfata comuna ItemSelectable si toate genereaza evenimente de tip ItemEvent. Din aceasta cauza actionarea unei optiuni de tip CheckboxMenuItem nu va determina generarea unui eveniment de tip ActionEvent de catre meniul din care face parte, ci va genera un eveniment ItemEvent chiar de catre articolul respectiv. Pentru a intercepta un asemenea eveniment avem nevoie de un obiect receptor care sa implementeze interfata ItemListener si sa specifice n metoda acesteia itemStateChanged codul ce trebuie executat la validarea/invalidarea optiunii din meniu. De asemenea receptorul trebuie nregistrat cu metoda addItemListener. Tipul de operatie selectare / deselectare este codificat de cmpurile statice ItemEvent.SELECTED si ItemEvent.DESELECTED. Exemplu de tratare a evenimentelor unui meniu import java.awt.*; import java.awt.event.*; public class TestMenuEvent extends Frame implements ActionListener, ItemListener{ public TestMenuEvent(String titlu) { super(titlu); } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); if (command.equals("Exit")) System.exit(0); //valabila si pentru meniu si pentru buton } public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) setTitle("Checked!"); else setTitle("Not checked!"); } public static void main(String args[]) { MenuBar mb = new MenuBar(); Menu test = new Menu("Test"); CheckboxMenuItem check = new CheckboxMenuItem("Check me"); test.add(check); test.addSeparator(); test.add(new MenuItem("Exit")); mb.add(test); TestMenuEvent f = new TestMenuEvent("Test Meniu");

Button btnExit = new Button("Exit"); f.setMenuBar(mb); f.add(btnExit, BorderLayout.SOUTH); f.setSize(300, 200); f.show(); test.addActionListener(f); check.addItemListener(f); btnExit.addActionListener(f); } }

Meniuri de context (popup) 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")); Afisarea meniului de context se face prin metoda show: popup.show(Component origin, int x, int y) 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. Exemplu de folosire a unui meniu popup import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements ActionListener{ private PopupMenu popup; //definim meniul popup al ferestrei private Component origin; //pozitia meniului va fi relativa la //pozitia ferestrei public Fereastra(String titlu) { super(titlu); origin = this; 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 mouseului } }); 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(); } } Acceleratori (Clasa MenuShortcut) Incepnd cu Java AWT 1.1 este posibila specificarea unor combinatii de taste (acceleratori shortcuts) pentru accesarea directa, prin intermediul tastaturii, a optiunilor dintr-un meniu. Astfel, oricarui obiect de tip MenuItem i poate fi asociat un obiect de tip 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)); Folosirea componentelor Clasa Label Un obiect de tip Label (eticheta) reprezinta o componenta pentru plasarea unui text pe o suprafata de afisare. O eticheta este formata dintr-o singura linie de text static ce nu poate fi modificat de catre utilizator, dar poate fi modificat din program. Exemplu: definim cinci etichete si le plasam ntr-un container.

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(); 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); } //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) {

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 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 extends Frame implements ItemListener { private Label label1, label2; private Checkbox cbx1, cbx2, cbx3; private 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); 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); } }); }

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.

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;

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); add(scroll);

pack(); setSize(200, 80); scroll.addAdjustmentListener(this); } //metoda interfetei ItemListener public void adjustmentValueChanged(AdjustmentEvent e) { valoare.setText(scroll.getValue() + " %"); } } 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); } }); } 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.

import java.awt.*; import java.awt.event.*; class Fereastra extends Frame implements TextListener { private TextField nume, parola; private Label acces; private 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); 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) || acces.setText(""); return; } if (nume.getText().equals(UID) && acces.setText("Acces permis!"); else acces.setText("Acces interzis!"); } } 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 extends Frame implements TextListener, ActionListener { private TextArea text; private TextField nume; private 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); text = new TextArea("", 30, 10, 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) || 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(); } catch(IOException ex) { ex.printStackTrace(); } } } public class TestTextArea { public static void main(String args[]) { Fereastra f = new Fereastra("TextArea"); f.initializare(); f.show(); } }

Desenarea 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: Deseneaza o componenta. Este o metoda supradefinita de fiecare 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: void update(Graphics g)

void paint(Graphics g)

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

Desenarea obiectelor - metoda paint Toate desenele care trebuie sa apara pe o suprafata de desenare se realizeaza n metoda paint a unei componente. Metoda paint este definita n superclasa Component nsa nu are nici o implementare si, din acest motiv, orice obiect grafic care doreste sa se deseneze trebuie sa o supradefineasca pentru a-si crea propria sa reprezentare. Componentele standard AWT au deja supradefinita aceasta metoda deci nu trebuie sa ne preocupe desenarea lor, nsa putem modifica reprezentarea lor grafica prin crearea unei subclase si supradefinirea metodei paint, avnd nsa grija sa apelam si metoda superclasei care se ocupa cu desenarea efectiva a componentei. In exemplul de mai jos, redefinim metoda paint pentru un obiect de tip Frame, pentru a crea o clasa ce instantiaza ferestre pentru o aplicatie demonstrativa (n coltul stnga sus este afisat textul "Aplicatie DEMO").

import java.awt.*; class Fereastra extends Frame { public Fereastra(String titlu) { super(titlu); setSize(200, 100); } public void paint(Graphics g) { 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. Suprafete de desenare - clasa Canvas In afara posibilitatii de a utiliza componente grafice standard, Java ofera si posibilitatea controlului la nivel de punct (pixel) pe dispozitivul grafic, respectiv desenarea a diferite forme grafice direct pe suprafata unei componente. Desi este posibil, n general nu se deseneaza la nivel de pixel direct pe suprafata ferestrelor sau a altor suprafete de afisare. In Java a fost definit un tip special de componenta numita Canvas (pnza de pictor), a carui scop este de a fi extinsa pentru a implementa obiecte grafice cu o anumita nfatisare. Asadar clasa Canvas este o clasa generica din care se deriveaza subclase pentru crearea suprafetelor de desenare (planse). Plansele nu pot contine alte componente grafice, ele fiind utilizate doar ca suprafete de desenat sau ca fundal pentru animatie. Desenarea pe o plansa se face prin supradefinirea metodei paint. Concret, o plansa este suprafata dreptunghiulara de culoare alba pe care se poate desena. Implicit dimensiunile plansei sunt 0 si, din acest motiv, gestionarii de pozitionare nu vor avea la dispozitie dimensiuni implcite pentru afisarea unui obiect de tip Canvas. Pentru a evita acest neajuns este recomandat ca o plansa sa redefineasca si metodele getMinimumSize, getMaximumSize, getPreferredSize pentru a-si specifica dimensiunile implicite. Etapele care trebuie parcurse pentru crearea unui desen, sau mai bine zis, a unui obiect grafic cu o anumita nfatisare sunt:

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.

interceptarea evenimentelor de tip FocusEvent, KeyEvent, MouseEvent, ComponentEvent si tratarea lor (daca este cazul).

Definirea generica a unei planse are urmatorul format: class Plansa extends Canvas { public void paint(Graphics g) { . . . //desenarea } public Dimension getMinimumSize() { return . . . } public Dimension getMaximumSize() { return . . . } public Dimension getPreferredSize() { return . . .; } 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(); }

Contextul grafic de desenare - clasa Graphics Inainte ca utilizatorul sa poata desena el trebuie sa obtina un context grafic de desenare pentru suprafata careia i apartine regiunea pe care se va desena. Un context grafic este, de fapt, un obiect prin intermediul caruia putem controla procesul de desenare a unui obiect. In general desenarea se poate face:

pe o portiune de ecran, la imprimanta sau ntr-o zona virtuala de memorie.

Un context grafic este specificat prin intermediul obiectelor de tip Graphics primite ca parametru n metodele paint si update. In functie de dispozitivul fizic pe care se face afisarea (ecran, imprimanta, plotter, etc) metodele de desenare au implementari interne diferite, transparente utilizatorului. Clasa Graphics pune la dispozitie metode pentru:

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.

Proprietatile contextului grafic La orice tip de desenare parametrii legati de culoare, font, etc. sunt specificati de contextul grafic n care se face desenarea. In continuare, enumeram aceste proprietati si metodele asociate lor n clasa Graphics.

culoarea curenta de desenare Color getColor() void setColor(Color c)

fontul curent cu care vor fi scrise textele Font getFont() void setFont(Font f)

originea coordonatelor - poate fi modificata prin : translate(int x, int y)

zona de decupare: zona n care sunt vizibile desenele Shape getClip() void setClip(Shape s)

void setClip(int x, int y, int width, int height)

modul de desenare void setXorMode(Color c1) - desenare "sau exclusiv" void setPaintMode(Color c1) - supradesenare

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. Desenarea figurilor geometrice Enumeram n continuare figurile geometrice ce pot fi desenate n Java si metodele folosite pentru aceasta:

linii drawLine(int x1, int y1, int x2, int y2) drawPolyline(intst xPoints, intst yPoints, int nPoints)

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)

dreptunghiuri cu chenar "ridicat" sau "adncit"

draw3DRect(int x, int y, int width, int height, boolean raised) fill3DRect(int x, int y, int width, int height, boolean raised)

dreptunghiuri cu colturi rotunjite

drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight

ovaluri

drawOval(int x, int y, int width, int height) fillOval(int x, int y, int width, int height)

arce circulare sau eliptice

drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)

poligoane

drawPolygon(intst xPoints, intst yPoints, int nPoints) drawPolygon(Polygon p) fillPolygon(intst xPoints, intst yPoints, int nPoints) fillPolygon(Polygon p) 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 realizndu-se 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) Stilul unui font este specificat prin intermediul constantelor : Font.PLAIN Font.BOLD Font.ITALIC - normal - ngrosat - nclinat

Exemple: new Font("Arial", Font.BOLD, 12); new Font("Times New Roman", Font.ITALIC, 14); new Font("Courier New", Font.PLAIN, 10); Folosirea unui obiect de tip Font se realizeaza uzual astfel: //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); 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 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 a unui 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); FontMetrics fm = g.getFontMetrics();//echivalent cu FontMetrics fm = new FontMetrics(f); } 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++) { 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 black blue cyan darkGray gray green lightGray magenta orange pink red white yellow SystemColor 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() Creeaza o noua versiune a culorii curent mai deschisa / nchisa Color darker() int int int int getAlpha() getRed() Determina parametrii din care este alcatuita culoarea getGreen() getBlue() Determina valoarea ce reprezinta culoarea respectiva (bitii 16-23 rosu, 8-15 verde, 0-7 albastru)

int getRGB()

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(); 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(); } } 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). Incarcarea unei imagini dintr-un fisier Crearea unui obiect de tip Image se face folosind o imagine dintr-un fisier fie aflat pe masina pe care se lucreaza, fie aflat la o anumita adresa (URL) pe Internet. Metodele pentru ncarcarea unei imagini dintr-un fisier se gasesc n clasele Applet si Toolkit, avnd nsa aceeasi denumire getImage si urmatoarele formate: Applet public Image getImage(URL url) public Image getImage(URL url, String fisier) 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. Dintre metodele clasei Image cele mai des folosite sunt cele pentru determinarea dimensiunilor unei imagini: int getHeight(ImageObserver observer) int getWidth(ImageObserver observer) unde parametrul observer este uzual this. (despre interfata ImageObserver se va discuta ulterior) 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, observer) boolean drawImage(Image img, int x, ImageObserver observer) boolean drawImage(Image img, int x, height, ImageObserver observer) boolean drawImage(Image img, int x, height, Color bgcolor, ImageObserver unde:

int y, ImageObserver int y, Color bgcolor, int y, int width, int int y, int width, int observer)

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

In exemplul urmator afisam aceeasi imagine de trei ori 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); Metoda drawImage returneaza true daca imaginea a fost afisata n ntregime si false 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. 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 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. Monitorizarea ncarcarii imaginilor - interfata ImageObserver Interfata are o singura metoda imageUpdate apelata periodic de firul de executie (creat automat) care se ocupa cu ncarcarea imaginii. Formatul acestei metode este: boolean imageUpdate (Image img, int flags, int x, int y, int w, int h ) 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 : Incarcarea imaginii a fost ntrerupta, nainte de completarea ei. ABORT 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); }

Crearea imaginilor n memorie - clasa MemoryImageSource 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 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; } paint(g); g.drawString("Numai la imprimanta", 200, 300); return Printable.PAGE_EXISTS; } } 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!"); e.printStackTrace(); }

} } } public class TestPrint { public static void main(String args[]) throws Exception { Fereastra f = new Fer("Test Print"); 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(); } }

Fire de executie Ce este un fir de executie ? Firele de executie fac trecerea de la programarea secventiala la programarea concurenta. Un program secvential reprezinta modelul clasic de program : are un nceput, o secventa de executie a instructiunilor sale si un sfrsit. Cu alte cuvinte, la un moment dat programul are un singur punct de executie. Un program aflat n executie se numeste proces. Un sistem de operare monotasking (MS-DOS) nu este capabil sa execute dect un singur proces la un moment dat n timp ce un sistem de operare multitasking (UNIX, Windows) poate rula oricte procese n acelasi timp (concurent), alocnd periodic cuante din timpul de lucru al CPU fiecarui proces. Am

reamintit acest lucru deoarece notiunea de fir de executie nu are sens dect n cadrul unui sistem de operare multitasking. Un fir de executie este similar unui proces secvential n sensul ca are un nceput, o secventa de executie si un sfrsit. Diferenta ntre un fir de executie si un proces consta n faptul ca un fir de executie nu poate rula independent ci trebuie sa ruleze n cadrul unui proces. Definitie Un fir de executie este o succesiune sceventiala de instructiuni care se executa n cadrul unui proces.

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. 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. Crearea unui fir de executie Ca orice alt obiect Java, un fir de executie este o instanta a unei clase. Firele de executie definite de o clasa vor avea acelasi cod si, prin urmare, aceeasi secventa de instructiuni. Crearea unei clase care sa defineasca fire de excutie poate fi facuta prin doua modalitati: prin extinderea clasei Thread prin implementarea interfetei Runnable Orice clasa ale carei instante vor fi executate ntr-un fir de executie trebuie declarata ca fiind Runnable. Aceasta este o interfata care contine o singura metoda, si anume metoda run. Asadar,

orice clasa ce descrie fire de executie va contine o metoda run n care este implementat codul ce va fi executat de firul de executie. Interfata Runnable este conceputa ca fiind un protocol comun pentru obiectele care doresc sa execute un cod pe durata existentei lor (care reprezinta fire de executie). Cea mai importanta clasa care implementeaza interfata Runnable este clasa Thread. Clasa Thread implementeaza un fir de executie generic care, implicit, nu face nimic. Cu alte cuvinte metoda run nu contine nici un cod. Orice fir de executie este o instanta a clasei Thread sau a unei subclase a sa. A. Extinderea clasei Thread Cea mai simpla metoda de a crea un fir de executie care sa realizeze ceva este prin extinderea clasei Thread si supradefinirea metodei run a acesteia. Formatul general al unei astfel de clase este: public class SimpleThread extends Thread { public SimpleThread(String nume) { super(nume); //apelez constructorul superclasei Thread } public void run() { //codul executat de firul de executie } } 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 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. B. Implementare interfata Runnable 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 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 Implementarea interfetei Runnable Ce facem nsa cnd dorim sa cream o clasa care instantiaza fire de executie dar aceasta are deja o superclasa, stiind ca n Java nu este permisa mostenirea multipla ? class FirExecutie extends Parinte, Thread // ilegal ! 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) simpleThread = new Thread( this ); simpleThread.start(); Specificarea argumentului this n constructorul clasei Thread executie care la lansarea sa va cauta n clasa noastra metoda run si accepta ca argument orice instanta a unei clase "Runnable". Asadar explicit, acest lucru realizndu-se automat la determina crearea unui fir de o va executa. Acest constructor metoda run nu trebuie apelata 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.

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; } 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 } } Ciclul de viata al unui fir de executie Fiecare fir de executie are propriul sau ciclu de viata : este creat, devine activ prin lansarea sa n executie si, la un moment dat, se termina. In continuare vom vedea mai ndeaproape starile n care se poate gasi un fir de executie. Diagrama de mai jos ilustreaza generic aceste stari precum si metodele care provoaca tranzitia dintr-o stare n alta:

Asadar, un fir de executie se poate gasi n una din urmatoarele patru stari: 1. 2. 3. 4. New Thread Runnable Not Runnable Dead

Starea "New Thread" Un fir de executie se gaseste n aceasta stare imediat dupa crearea sa, cu alte cuvinte dupa instantierea unui obiect din clasa Thread sau dintr-o subclasa a sa. Thread counterThread = new Thread ( this ); //counterThread se gaseste in starea New Thread 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 realizeaza 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. Starea "Not Runnable" Un fir de executie ajunge n acesata stare n una din urmatoarele situatii:

este "adormit" prin apelul metodei sleep. a apelat metoda wait, asteaptnd ca o anumita conditie sa fie satisfacuta este blocat ntr-o operatie de intrare/iesire

"Adormirea" unui fir de executie Metoda sleep este o metoda statica a clasei Thread care provoaca o pauza n timpul rularii firului curent aflat n executie, cu alte cuvinte l "adoarme" pentru un timp specificat. Lungimea acestei pauze este specificata n milisecunde si chiar nanosecunde. public static throws public static throws void sleep( long millis ) InterruptedException void sleep( long millis, int nanos ) InterruptedException

Intruct poate provoca exceptii de tipul InterruptedException apelul acestei metode se face ntr-un bloc de tip try-cacth: try { Thread.sleep(1000); //face pauza de o secunda } catch (InterruptedException e) { . . . }

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 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". Terminarea unui fir de executie Dupa cum am vazut, un fir de executie nu poate fi terminat fortat de catre program ci trebuie sa-si "aranjeze" singur terminarea sa. Acest lucru poate fi realizat n doua modalitati:

A - prin scrierea unor metode run care sa-si termine executia n mod natural; la terminarea metodei run se va termina automat si firul de executie, acesta intrnd n starea Dead.

Acesta este cazul din exemplul considerat anterior: public void run() { for(int i = from; i <= to; i += step) System.out.print(i + " " ); } Dupa afisarea numerelor din intervalul specificat metoda se termina si odata cu ea si firul de executie repsectiv.

B - 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.

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: Terminarea unui fir de executie folosind metoda "nvechita" stop import java.io.*; public class TestThread { public static void main(String args[]) throws IOException { WaitKey thread = new WaitKey(); thread.start(); System.in.read(); //astept apasarea tastei Enter thread.stop(); //opresc firul de executie System.out.println("S-au scurs " + thread.sec + " secunde"); } } class WaitKey extends Thread { public int sec = 0; public void run() { while (true) { try { Thread.sleep(1000); //pauze de o secunda sec ++; //s-a mai scurs o secunda } catch(InterruptedException e){} } } } Observam ca metoda run nu se termina natural, ea ruleaza la infinit asteptnd sa fie terminata fortat. Acest lucru l-am realizat aici cu metoda stop. Aceasta metoda este nsa "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. Terminarea unui fir de executie folosind o variabila de terminare import java.io.*; public class TestThread { public static void main(String args[]) throws IOException { WaitKey thread = new WaitKey(); thread.start(); System.in.read(); //astept apasarea tastei Enter thread.running = false; System.out.println("S-au scurs " + thread.sec + " secunde"); } } class WaitKey extends Thread { public int sec = 0;

public boolean running = true; //variabila de terminare public void run() { while ( running ) { try { Thread.sleep(1000); sec ++; } catch(InterruptedException e){} } } } 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 .

Stabilirea prioritatilor de executie Majoritatea calculatoarelor au un sigur procesor, ceea ce nseamna ca firele de executie trebuie sa-si mparta accesul la acel procesor. Executia ntr-o anumita ordine a mai multor fire de executie pe un singur procesor se numeste planificare (scheduling). Sistemul Java de executie a programelor implementeaza un algoritm simplu, determinist de planificare, cunoscut sub numele de planificare cu prioritati fixate. Fiecare fir de executie Java primeste la crearea sa o anumita prioritate. O prioritate este de fapt un numar ntreg cu valori cuprinse ntre MIN_PRIORITY si MAX_PRIORITY. Implicit prioritatea unui fir de executie nou creat are valoarea NORM_PRIORITY. Aceste trei constante sunt definite n clasa Thread: public static final int MIN_PRIORITY minima - prioritatea

public static final int NORM_PRIORITY implicita public static final int MAX_PRIORITY maxima

- prioritatea - prioritatea

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

Observatii !! 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. Exemplu de fir de executie "egoist" //un fir de executie care numara pana la 100.000 din 100 n 100 class Selfish extends Thread { public Selfish(String name) { super(name); } public void run() {

int i = 0; while (i < 100000) { //bucla stransa care acapareaza procesorul i ++; if (i % 100 == 0) System.out.println(getName()+" a ajuns la "+i); } } } //clasa principala public class TestSelfishThread { 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

2 a ajuns la 99900 2 a ajuns la 100000

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 "+i); yield(); //cedez temporar procesorul }

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. Sincronizarea firelor de executie Pna acum am vazut cum putem crea fire de executie independente si asincrone, cu alte cuvinte care nu depind n nici un fel de executia sau de rezultatele altor fire de executie. Exista nsa numeroase situatii cnd fire de executie separate, dar care ruleaza concurent, trebuie sa comunice ntre ele pentru a accesa diferite resurse comune sau pentru a-si transmite dinamic rezultatele "muncii" lor. Cel mai elocvent scenariu n care firele de executie trebuie sa se comunice ntre ele este cunoscut sub numele de problema producatorului/consumatorului, n care producatorul genereaza un flux de date care este preluat si prelucrat de catre consumator. 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. Scenariul producator / consumator Pentru a ntelege mai bine modalitatea de sincronizare a doua fire de executie sa implementam efectiv o problema de tip producator/consumator. Sa consideram urmatoarea situatie:

Producatorul genereaza numerele ntregi de la 1 la 10, fiecare la un interval neregulat cuprins ntre 0 si 100 de milisecunde. Pe masura ce le genereaza ncearca sa le plaseze ntr-o zona de memorie (o variabila ntreaga) de unde sa fie citite de catre consumator. Consumatorul va prelua, pe rnd, numerele generate de catre producator si va afisa valoarea lor pe ecran.

Pentru a fi accesibila ambelor fire de executie, vom ncapsula variabila ce va contine numerele generate ntr-un obiect descris de clasa Buffer si care va avea doua metode put (pentru punerea unui numar n buffer) si get (pentru obtinerea numarului din buffer). Fara a folosi nici un mecanism de sincronizare clasa Buffer arata astfel: class Buffer { private int number = -1; public int get() { return number; } public void put(int number) { this.number = number; } }

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)); } 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 a a a a primit: primit: pus: 0 primit: -1 -1 0

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

primit: primit: primit: primit: primit: primit: primit: pus: 1 pus: 2 pus: 3 pus: 4 pus: 5 pus: 6 pus: 7 pus: 8 pus: 9

0 0 0 0 0 0 0

Ambele fire de executie acceseaza resursa comuna, adica obiectul de tip Buffer, ntr-o maniera haotica si acest lucru se ntmpla din doua motive :

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: 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.

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. 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 puna o valoare } catch (InterruptedException e) { } } available = false; notifyAll(); return number; } public synchronized void put(int number) { while (available) { try { wait(); //asteapta consumatorul sa preia valoarea } catch (InterruptedException e) { } } this.number = number; available = true; notifyAll(); } } Rezultatul obtinut va fi cel scontat: Producatorul Consumatorul Producatorul Consumatorul . . . Producatorul Consumatorul a a a a pus: 0 primit: pus: 1 primit:

0 1 9

a pus: 9 a primit:

Blocarea unui obiect (cuvntul cheie synchronized) Definitie Un segment de cod ce gestioneaza o resursa comuna mai multor de fire de executie separate si concurente se numeste sectiune critica. In Java o sectiune critica poate fi un bloc de instructiuni sau o metoda. Controlul accesului ntr-o sectiune critica se face prin cuvntul cheie synchronized. Platforma Java asociaza un monitor fiecarui obiect al unui program ce contine sectiuni critice care necesita sincronizare. Acest monitor va indica daca resursa critica este accesata de vreun fir de executie sau este libera, cu alte cuvinte "monitorizeaza" o resursa critica. In cazul n care este accesata, va "pune un lacat" pe aceasta, astfel nct sa mpiedice accesul altor fire de executie la ea. In momentul cnd resursa este eliberata "lacatul" va fi eliminat pentru a permite accesul altor fire de executie. In exemplul tip producator/consumator de mai sus, sectiunile critice sunt metodele put si get iar resursa citica comuna este obiectul buffer. Consumatorul nu trebuie sa acceseze buffer-ul cnd producatorul tocmai pune o valoare n el, iar producatorul nu trebuie sa modifice valoarea din buffer n momentul cnd aceasta este citita de catre consumator. public synchronized int get() {

... } public synchronized void put(int number) { ... } 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 } public synchronized int get() { // buffer blocat de consumator ... // buffer deblocat de consumator } Metodele wait, notify si notifyAll Obiectul de tip Buffer din exemplul are o variabila membra privata numita number, n care este memorat numarul pe care l comunica producatorul si din care l preia consumatorul. De asemenea, mai are o variabila privata logica available care ne da starea buffer-ului: daca are valoarea true nseamna ca producatorul a pus o valoare n buffer si consumatorul nu a preluat-o nca; daca este false, consumatorul a preluat valoarea din buffer dar producatorul nu a pus deocamdata alta la loc. Deci, la prima vedere metodele clasei Buffer ar trebui sa arate astfel: public synchronized int get() { if (available) { available = false; return number; } } public synchronized int put(int number) { if (!available) { available = true; this.number = number; } } 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. 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 ) Dupa apelul metodei wait, firul de executie curent elibereaza monitorul 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 valoare } catch (InterruptedException e) { } } available = false;

notifyAll(); return number; } public synchronized void put(int number) { while (available) { try { wait(); //asteapta consumatorul sa preia valoarea } catch (InterruptedException e) { } } this.number = number; available = true; notifyAll(); } }

Gruparea firelor de executie 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 realizeaza 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 ntr-un 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:

Exemplu: listarea firelor de executie active public class EnumerateTest { public void listCurrentThreads() { ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); //aflu numarul firelor de executie active int numThreads = currentGroup.activeCount(); //pun intr-un vector referinte la firele de exec. active Thread[] listOfThreads = new Thread[numThreads]; currentGroup.enumerate(listOfThreads); //le afisez pe ecran for (int i = 0; i < numThreads; i++) System.out.println("Thread #" + i + " = " + listOfThreads[i].getName()); } } Comunicarea prin fluxuri de tip "pipe" O modalitate deosebit de utila prin care doua fire de executie pot comunica este realizata prin intermediul canalelor de comunicatii (pipes). Acestea sunt implementate prin fluxuri descrise de clasele PipedReader, PipedWriter - pentru caractere, respectiv

PipedOutputStream, PipedInputStream - pentru octeti Constructorii acestor clase sunt : public PipedReader( ) public PipedReader( PipedWriter pw ) throws IOException public PipedWriter( ) public PipedWriter( PipedReader pr ) throws IOException 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 printr-un canal. Realizarea conexiunii se face astfel: PipedWriter pw1 = new PipedWriter(); PipedReader pr1 = new PipedReader(pw1); sau PipedReader pr2 = new PipedReader(); PipedWriter pw2 = new PipedWriter(pr2); sau 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(); } } class Producator extends Thread { private DataOutputStream out; 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); } } }

Programarea n retea Notiuni generale despre retele

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. Cum este identificat un calculator n retea ? Orice calculator gazda conectat la Internet este identificat n mod unic de adresa sa IP (IP este acronimul de la Internet Protocol). Aceasta reprezinta un numar reprezentat pe 32 de biti, uzual sub forma a 4 octeti, cum ar fi de exemplu: 193.226.26.231 si este numit adresa IP numerica. Corespunzatoare unei adrese numerice exista si o adresa IP simbolica, cum ar fi fenrir.infoiasi.ro. De asemenea fiecare calculator aflat ntr-o retea locala are un nume unic ce poat fi folosit la identificarea locala a acestuia. Clasa Java care reprezinta notiunea de adresa IP este InetAddress. 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 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. Clase de baza din java.net

Clase din java.net permit comunicare ntre procese folosind protocoalele TCP si UDP si sunt prezentate n tabelul de mai jos. 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: 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

Citirea continutului unui URL Orice obiect de tip URL poate returna un flux de intrare de tip InputStream pentru citirea continutului sau. Secventa clasica pentru aceasta operatiune este: //Afisarea paginii index.html de la adresa www.infoiasi.ro public class CitireURL { public static void main(String[] args) throws IOException{ BufferedReader br = null; try { URL resursa = new URL("http://www.infoiasi.ro"); InputStream in = resursa.openStream(); 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 ntr-o 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

TCP - implementate de clasele Socket si ServerSocket UDP - implementate de clasa DatagramSocket

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. Comunicarea prin conexiuni In acest model se stabileste o conexiune TCP ntre un program client si un server care furnizeaza un anumit serviciu. Structura generala a unui server bazat pe conexiuni while (true) { accept a connection ; create a thread to deal with the client ; end while import java.net.*; import java.io.*; public class SimpleServer extends Thread { // Definesc portul pe care se gaseste serverul in afara intervalului 1-1024: public static final int PORT = 8100; private static ServerSocket serverSocket = null; private Socket clientSocket = null; public void run() { //Executa solicitarea clientului String cerere, raspuns; try { //in este fluxul de intrare de la client

BufferedReader in = new BufferedReader(new InputStreamReader( clientSocket.getInputStream() )); //out este flux de iesire catre client PrintWriter out = new PrintWriter( clientSocket.getOutputStream() ); //primesc cerere de la client cerere = in.readLine(); //trimit raspuns clientului raspuns = "hello " + cerere; out.println(raspuns); out.flush(); } catch (IOException e) { System.err.println("Eroare de citire/scriere \n" + e); } finally { // Inchid socketul deschis pentru clientul curent try { clientSocket.close(); } catch (IOException e) { System.err.println("Socketul nu poate fi inchis \n" + e); } } } 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(); } }

Structura generala a unui client bazat pe conexiuni import java.net.*; import java.io.*; public class SimpleClient { public static void main(String[] args) throws IOException { //adresa IP a serverului String serverAddress = "127.0.0.1"; //portul la care serverul ofera serviciul int PORT = 8100; Socket clientSocket = null; PrintWriter out = null; BufferedReader in = null; String cerere, raspuns; try { clientSocket = new Socket(serverAddress, PORT); out = new PrintWriter( clientSocket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader( clientSocket.getInputStream())); //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 \n" + e); System.exit(1); } finally { if (out != null) out.close(); if (in != null) in.close(); if (clientSocket!= null) clientSocket.close(); } } } Comunicarea prin datagrame In acest model clientul trimite un pachet cu cererea catre server, acesta primeste pachetul si returneaza raspunsul tot prin intermediul unui pachet. Un astfel de pachet se numeste datagrama si este reprezentat printr-un obiect din clasa DatagramPacket.

Primirea si trimiterea datagramelor se realizeaza tot prin intermediul unui socket, acesta fiind modelat preintr-un obiect al clasei DatagramSocket. Structura generala a unui server bazat pe datagrame import java.net.*; import java.io.*; public class DatagramServer { public static final int PORT = 8200; private DatagramSocket socket = null; DatagramPacket cerere, raspuns = null; public DatagramServer() throws IOException { Socket = new DatagramSocket(PORT); try { while (true) { //Declara pachetul in care va fi receptionata cererea byte[] buf = new byte[256]; cerere = new DatagramPacket(buf, buf.length); //Astepta aparitia unui pachet cu cererea socket.receive(cerere); //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(); //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();

} }

Structura generala a unui client bazat pe datagrame import java.net.*; import java.io.*; public class DatagramClient { public static void main(String[] args) throws IOException { //adresa IP si portul la care ruleaza serverul InetAddress address = InetAddress.getByName("127.0.0.1"); int port=8200; DatagramSocket socket = null; DatagramPacket packet = null; byte buf[]; try { //Construieste un socket pentru comunicare socket = new DatagramSocket(); //Construieste si trimite pachetul cu cerere catre server buf = "Duke".getBytes(); packet = new DatagramPacket(buf, buf.length, address, port); 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(); } } } Trimiterea de mesaje catre mai multi clienti Diverse situatii impun gruparea mai multor clienti astfel nct un mesaj (pachet) trimis pe adresa grupului sa fie receptionat de fiecare dintre acestia. Gruparea mai multor programe n vederea trimiterii multiple de mesaje se realizeaza prin intermediul unui socket special, descris de clasa MulticastSocket, extensie a clasei DatagramSocket.

Un grup de clienti abonati pentru trimitere multipla este specificat printr-o adresa IP din intervalul 224.0.0.1 - 239.255.255.255 si un port UDP. Adresa 224.0.0.0 este rezervata si nu trebuie folosita. Inregistrarea unui client ntr-un grup import java.net.*; import java.io.*; public class MulticastClient { public static void main(String[] args) throws IOException { //adresa IP si portul care reprezinta grupul de clienti InetAddress group = InetAddress.getByName("230.0.0.1"); int port=4444; MulticastSocket socket = null; byte buf[]; try { //Se alatura grupului aflat la adresa si portul specificate 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); socket.receive(packet); System.out.println(new String(packet.getData())); } finally { socket.leaveGroup(group); socket.close(); } } }

Transmiterea unui mesaj catre un grup import java.net.*; import java.io.*; public class MulticastSend { public static void main(String[] args) throws Exception {

InetAddress group = InetAddress.getByName("230.0.0.1"); int port = 4444; byte[] buf; DatagramPacket packet = null; //Creeaza un socket cu un numar oarecare DatagramSocket socket = new DatagramSocket(0); try { //Trimite un pachet catre toti clientii din grup buf = (new String("Salut grup")).getBytes(); packet = new DatagramPacket(buf, buf.length, group, port); socket.send(packet); } finally { socket.close(); } } } 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.

Crearea unui applet Orice applet este implementat prin crearea unei subclase a clasei Applet. Ierarhia claselor din care deriva Applet este prezentata n figura de mai jos:

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, se va 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 In cazul n care compilarea a reusit va fi generat fisierul AppletSimplu.class. 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, ), sau cu utilitarul appletviewer ce vine n pachetul JDK. appletviewer demo.html

Ciclul de viata al unui applet Executia unui applet ncepe n momentul n care un browser afiseaza o pagina Web n care este inclus appletul respectiv si poate trece prin mai multe etape. Fiecare etapa este strns legata de un eveniment generat de catre browser si determina apelarea unei metode specifice din clasa ce implementeaza appletul. 1. Incarcarea in memorie Este creata o instanta a clasei principale a appletului si ncarcata n memorie. 2. Initializarea Este apelata metoda init ce permite initializarea diverselor variabile, citirea unor parametri de intrare, etc. 3. Pornirea Este apelata metoda start 4. Executia propriu-zisa Consta n interactiunea dintre utilizator si componentele afisate pe suprafata appletului sau n executarea unui anumit cod ntr-un fir de executie. In unele situatii ntreaga executie a appletului se consuma la etapele de initializare si pornire. 5. Oprirea temporara In cazul n care utilizatorul paraseste pagina Web n care ruleaza appletul este apelata metoda stop a acestuia, dndu-i astfel posibilitatea sa se opreasca temporar ct timp nu este vizibil, pentru a nu consuma inutil din timpul procesorului. Acelasi lucru se ntmpla daca fereastra browserului este minimizata. In momentul cnd pagina Web ce contine appletul devine din nou activa, va fi reapelata metoda start. 6. Oprirea definitiva La nchiderea tuturor instantelor browserului folosit pentru vizualizare, appletul va fi eliminat din memorie si va fi apelata metoda destroy a acestuia, pentru a-i permite sa elibereze resursele detinute. Apelul metodei destroy este ntotdeauna precedat de apelul lui stop. Metodele specifice appleturilor Asadar, exista metode specifice appletului ce sunt apelate automat la diverse evenimente generate de catre browser. Acestea sunt date n tabelul de mai jos: Metoda init start stop Situatia n care este apelata La initializarea appletului; teoretic, aceasta metoda ar trebui sa se apeleze o singura data, la prima afisare a appletului n pagina, nsa, la unele browsere, este posibil ca ea sa se apeleze de mai multe ori. Imediat dupa initializare si de fiecare data cnd appletul redevine activ, dupa o oprire temporara. De fiecare data cnd appletul nu mai este vizibil (pagina Web nu mai este vizibila, fereastra

browserului este minimizata, etc) si nainte de destroy. destroy La nchiderea ultimei instante a browserului care a ncarcat n memorie clasa principala a appletului.

Aceste metode sunt apelate automat de browser si nu trebuie apelate explicit din program ! Structura generala a unui applet import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class StructuraApplet extends Applet { public void init() { } public void start() { } public void stop() { } public void destroy() { } public void paint(Graphics g) { } } Interfata grafica cu utilizatorul Dupa cum am vazut, clasa Applet este o extensie a superclasei Container, ceea ce nseamna ca appleturile sunt, nainte de toate, suprafete de afisare. Plasarea componentelor, gestionarea pozitionarii lor si tratarea evenimentelor generate se realizeaza la fel ca si n cazul aplicatiilor. Uzual, adaugarea componentelor pe suprafata appletului precum si stabilirea obiectelor responsabile cu tratarea evenimentelor generate sunt operatiuni ce vor fi realizate n metoda init. Gestionarul de pozitionare implicit este FlowLayout, nsa acesta poate fi schimbat prin metoda setLayout. Desenarea pe suprafata unui applet Exista o categorie ntreaga de appleturi ce nu comunica cu utilizatorul prin intermediul componentelor ci, executia lor se rezuma la diverse operatiuni de desenare executate n metoda paint. Reamintim ca metoda paint este responsabila cu definirea aspectului grafic al oricarei componente. Implicit, metoda paint din clasa Applet nu realizeaza nimic, deci, n cazul n care dorim sa desenam direct pe suprafata unui applet va fi nevoie sa supradefinim aceasta metoda. In cazul n care este aleasa aceasta solutie, evenimentele tratate uzual vor fi cele generate de mouse sau tastatura. Definirea si folosirea parametrilor

Parametrii sunt pentru appleturi ceea ce argumentele de la linia de comanda sunt pentru aplicatiile independente. Ei permit utilizatorului sa personalizeze aspectul sau comportarea unui applet fara a-i schimba codul si recompila clasele. Definirea parametrilor se face n cadrul tagului APPLET din documentul HTML ce contine appletul si sunt identificati prin atributul PARAM. Fiecare parametru are un nume, specificat prin NAME si o valoare, specificata prin VALUE, ca n exemplul de mai jos: <APPLET CODE="AppletSimplu.class" WIDTH=100 HEIGHT=50 <PARAM NAME=textAfisat VALUE="Salut"> <PARAM NAME=numeFont VALUE="Times New Roman"> <PARAM NAME=dimFont VALUE=20> </APPLET> 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 descriere tag APPLET. 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 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 Descriere {"textAfisat", "String","Sirul ce va fi afisat"}, {"numeFont", "String","Numele fontului"}, {"dimFont", "int", "Dimensiunea fontului"} }; return info; } 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 >] [< 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. 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.

Folosirea firelor de executie n appleturi Fiecare applet aflat pe o pagina Web se executa ntr-un fir de executie propriu. Acesta este creat de catre browser si este responsabil cu desenarea appletului (apelul metodelor update si paint) precum si cu transmiterea mesajelor generate de catre componentele appletului. In cazul n care dorim sa realizam si alte operatiuni consumatoare de timp este 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 } } } 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.

Alte metode oferite de clasa Applet Clasa Applet ofera metode specifice applet-urilor pentru: Punerea la dispozitie a unor informatii despre applet

Similara cu metoda getParameterInfo ce oferea o "documentatie" despre parametrii pe care i suporta un applet, exista metoda getAppletInfo ce permite specificarea unor informatii legate de applet cum ar fi numele, autorul, versiunea, etc. Metoda returneaza un sir de caractere continnd informatii despre applet. public String getAppletInfo() { return "Cel mai simplu applet, autor necunoscut, ver 1.0"; } Aflarea unor adrese URL referitoare la applet Se realizeaza cu metodele:

getCodeBase - ce returneaza URL-ul directorului ce contine clasa appletului getDocumentBase - returneaza URL-ul directorului ce contine documentul HTML n care este inclus appletul respectiv.

Sunt foarte utile deoarece permit specificarea relativa a fisierelor folosite de un applet. 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() { img = getImage(getCodeBase(), "taz.gif"); } public void paint(Graphics g) { g.drawImage(img, 0, 0, this); } } Afisarea unor mesaje n bara de stare a browserului Acest lucru se realizeaza cu metoda showStatus public void init() { showStatus("Initializare applet..."); } Aflarea contextului de executie

Contextul de executie al unui applet se refera la pagina n care acesta ruleaza si este descris de interfata AppletContext. Crearea unui obiect ce implementeaza aceasta interfata se realizeaza de catre browser, la apelul metodei getAppletContext a clasei Applet. Prin intermediul acestei interfete un applet poate "vedea" n jurul sau, putnd comunica cu alte appleturi aflate pe aceeasi pagina sau cere browser-ului sa deschida diverse documente. AppletContext env = getAppletContext(); Afisarea unor documente n browser Se face cu metoda showDocument ce primeste adresa URL a fisierului ce contine documentul dorit (text, html, imagine, etc). Aceasta metoda se gaseste n interfata AppletContext. try { URL doc = new URL("http://www.infoiasi.ro"); getAppletContext().showDocument(doc); } catch(MalformedURLException e) {} Comunicarea cu alte applet-uri aflate pe aceeasi pagina Aceasta comunicare implica de fapt identificarea unui applet aflat pe aceeasi pagina si apelarea unei metode sau setare unei variabile publice a acestuia. Identificarea se face prin intermediu numelui pe care orice instanta a unui applet l poate specifica prin atributul NAME. Obtinerea unei instante a unui applet al carui nume l cunoastem sau obtinerea unei enumerari a tuturor applet-urilor din pagina se fac cu metodele definite de interfata AppletContext getApplet si getApplets. 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.

Programe care sunt att applet-uri ct si aplicatii import java.applet.Applet; import java.awt.*; public class AppletApp extends Applet {

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

Lucrul cu baze de date n Java Generalitati despre baze de date Aplicatiile care folosesc baze de date sunt, n general, aplicatii complexe folosite pentru gestionarea unor informatii de dimensiuni mai mari ntr-o maniera sigura si eficienta. Ce este o baza de date ? O baza de date reprezinta o modalitate de stocare a unor informatii (date) pe un suport extern, cu posibilitatea regasirii acestora. Uzual, o baza de date este memorata ntr-unul sau mai multe fisiere. Modelul clasic de baza de date este cel relational, n care datele sunt memorate n tabele. Pe lnga tabele, o baza de date mai poate contine: proceduri si functii, utilizatori si grupuri de utilizatori, tipuri de date, obiecte, . Dintre producatorii cei mai importanti de baze de date amintim Oracle, Sybase, IBM, Informix, Microsoft, . Crearea unei baze de date Se face cu aplicatii specializate oferite de producatorul tipului respectiv de baza de date. Accesul la o baza de date Se face prin intermediul unui driver specific tipului respectiv de baza de date. Acesta este responsabil cu accesul efectiv la datele stocate, fiind legatura ntre aplicatie si baza de date.

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

Conectarea la o baza de date Procesul de conectare la o baza de date implica doua operatii: 1. ncarcarea n memorie a unui driver corespunzator 2. realizarea unei conexiuni propriu-zise Definitie O conexiune (sesiune) la o baza de date reprezinta un context prin care sunt trimise secvente SQL si primite rezultate. Intr-o aplicatie pot exista mai multe conexiuni simultan la baze de date diferite sau la aceeasi baza. Clasele si interfetele responsabile cu realizarea unei conexiuni sunt:

clasa DriverManager - se ocupa cu nregistrarea driverelor ce vor fi folosite n aplicatie interfata Driver pe care trebuie sa o implementeze orice clasa ce descrie un driver clasa DriverPropertyInfo interfata Connection - descrie obiectele ce modeleaza o conexiune propriu-zisa cu baza de date

Incarcarea n memorie a unui driver Primul lucru pe care trebuie sa-l faca o aplicatie n procesul de conectare la o baza de date este sa ncarce n memorie clasa ce implementeaza driver-ul necesar comunicarii cu respectiva baza de date. Acest lucru poate fi realizat prin mai multe modalitati: DriverManager.registerDriver(new sun.jdbc.odbc.JdbcOdbcDriver()); Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); System.setProperty("jdbc.drivers", "sun.jdbc.odbc.JdbcOdbcDriver");

java -Djdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver Specificarea unei baze de date O data ce un driver JDBC a fost ncarcat n memorie cu DriverManager, acesta poate fi folosit la stabilirea unei conexiuni cu o baza de date. Avnd n vedere faptul ca pot exista mai multe drivere nregistrate n memorie, trebuie sa avem posibilitea de a specifica pe lnga identificatorul bazei de date si driverul ce trebuie folosit. Aceasta se realizeaza prin intermediul unei adrese specifice, numita JDBC URL, ce are urmatorul format: jdbc:sub-protocol:identificator_baza_de_date 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. Realizarea unei conexiuni Metoda folosita pentru realizarea unei conexiuni este getConnection din clasa DriverManager si poate avea mai multe forme: Connection c = DriverManager.getConnection(url); Connection c = DriverManager.getConnection(url, username, password); Connection c = DriverManager.getConnection(url, dbproperies); 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 . Efectuarea de secvente SQL

O data facuta conectarea cu DriverManager.getConection(), se poate folosi obiectul Connection rezultat pentru a se crea un obiect de tip Statements, cu ajutorul caruia putem trimite secvente SQL catre baza de date. Cele mai uzuale comenzi SQL sunt cele folosite pentru: interogarea bazei de date (SELECT) 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 ntr-un obiect de tip ResultSet.

Obtinerea si prelucrarea rezultatelor Interfata ResultSet String query = "SELECT cod, nume FROM localitati ORDER BY nume"; ResultSet r = s.executeQuery( query ); while (r.next()) { System.out.println ( r.getString ("cod") + "," + r.getString ("nume") ); 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{ Connection c=DriverManager.getConnection(dbUrl, user, password); 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(); } } }