programare in java

310
PROGRAMARE IN JAVA - Note de curs 1 Curs 1 Introducere Incepand cu anul 1977 incepe utilizarea pe scara larga a calculatoarelor personale, pretul acestora facandu-le accesibile tuturor. In 1981 IBM, cel mai mare producator de computere, lanseaza pe piata modelul sau de calculator personal – IBM PC (XT). Aproape peste noapte calculatoarele personale patrund in intreprinderi, organizatii si chiar in casele oamenilor. Initial aceste calculatoare erau folosite ca unitati independente, transferul informatiilor de la un calculator la altul se facea prin intermediul dischetelor. Pentru a eficientiza schimbul informatiilor intre calculatoarele personale acestea au fost interconectate fie prin legaturi telefonice fie in retele locale in cadrul aceleiasi cladiri (LANs – local area networks). Aceasta a dus la raspandirea aplicatiilor de calcul distribuit (distributed computing). In loc de a fi adunate si prelucrate centralizat, intr-un centru de calcul dotat cu calculatoare foarte performante, datele se procesau distribuit, pe calculatoare personale conectate in retea, amplasate in locurile in care informatia era obtinuta. Puterea de calcul a unui PC era suficienta pentru a asigura cerintele unui utilizator individual si comunicatia in retea. La ora actuala un PC de 1000$ are o putere de calcul impresionanta, fiind la fel de performant ca un calculator mare din anii 70, avand pretul de un milion de dolari. Informatia este distribuita prin retea unde unele calculatoare numite file servere au sarcina principala de a stoca programe si date iar altele numite clienti apeleaza la serviciile serverelor pentru a accesa si procesa distribuit aceste date. Prelucrarea distribuita a datelor presupune “cooperarea” intre aplicatiile executate pe calculatoarele client si cele executate de servere. Aplicatia client solicita un anumit serviciu aplicatiei server, aceasta din urma efectueaza prelucrarile necesare satisfacerii cererii clientului si ii transmite rezultatele solicitate. O aplicatie data de prelucrare distribuita consta nu dintr-un singur program executat pe un singur calculator ci din mai multe programe executate pe doua sau mai multe calculatoare interconectate in retea. O astfel de aplicatie se numeste aplicatie client/server. In anii 70 si 80 limbajele de programare uzuale in elaborarea sistemelor de operare si a aplicatiilor client /server de prelucrare distribuita in retea erau C si C++. Odata cu interconectarea retelelor locale LAN in retele WAN (wide area network) si dezvoltarea tehnologiilor Internet, limbajul de programare Java castiga rapid teren oferind perspective noi pentru dezvoltarea aplicatiilor bazate pe aceste tehnologii. Limbaje masina, limbaje de asamblare, limbaje de nivel inalt Fiecare calculator “intelege” doar limbajul de programare specific procesorului cu care este prevazut. Acest limbaj “natural’ care consta dintr-un “vocabular” restrans de comenzi si un set de reguli sintactice de utilizare a comenzilor se numeste limbaj

Upload: stefureac-silviu

Post on 02-Jul-2015

1.026 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: programare in Java

PROGRAMARE IN JAVA - Note de curs 1

Curs 1 Introducere

Incepand cu anul 1977 incepe utilizarea pe scara larga a calculatoarelor personale, pretul acestora facandu-le accesibile tuturor. In 1981 IBM, cel mai mare producator de computere, lanseaza pe piata modelul sau de calculator personal – IBM PC (XT). Aproape peste noapte calculatoarele personale patrund in intreprinderi, organizatii si chiar in casele oamenilor. Initial aceste calculatoare erau folosite ca unitati independente, transferul informatiilor de la un calculator la altul se facea prin intermediul dischetelor. Pentru a eficientiza schimbul informatiilor intre calculatoarele personale acestea au fost interconectate fie prin legaturi telefonice fie in retele locale in cadrul aceleiasi cladiri (LANs – local area networks). Aceasta a dus la raspandirea aplicatiilor de calcul distribuit (distributed computing). In loc de a fi adunate si prelucrate centralizat, intr-un centru de calcul dotat cu calculatoare foarte performante, datele se procesau distribuit, pe calculatoare personale conectate in retea, amplasate in locurile in care informatia era obtinuta. Puterea de calcul a unui PC era suficienta pentru a asigura cerintele unui utilizator individual si comunicatia in retea. La ora actuala un PC de 1000$ are o putere de calcul impresionanta, fiind la fel de performant ca un calculator mare din anii 70, avand pretul de un milion de dolari. Informatia este distribuita prin retea unde unele calculatoare numite file servere au sarcina principala de a stoca programe si date iar altele numite clienti apeleaza la serviciile serverelor pentru a accesa si procesa distribuit aceste date. Prelucrarea distribuita a datelor presupune “cooperarea” intre aplicatiile executate pe calculatoarele client si cele executate de servere. Aplicatia client solicita un anumit serviciu aplicatiei server, aceasta din urma efectueaza prelucrarile necesare satisfacerii cererii clientului si ii transmite rezultatele solicitate. O aplicatie data de prelucrare distribuita consta nu dintr-un singur program executat pe un singur calculator ci din mai multe programe executate pe doua sau mai multe calculatoare interconectate in retea. O astfel de aplicatie se numeste aplicatie client/server. In anii 70 si 80 limbajele de programare uzuale in elaborarea sistemelor de operare si a aplicatiilor client /server de prelucrare distribuita in retea erau C si C++. Odata cu interconectarea retelelor locale LAN in retele WAN (wide area network) si dezvoltarea tehnologiilor Internet, limbajul de programare Java castiga rapid teren oferind perspective noi pentru dezvoltarea aplicatiilor bazate pe aceste tehnologii.

Limbaje masina, limbaje de asamblare, limbaje de nivel inalt

Fiecare calculator “intelege” doar limbajul de programare specific procesorului cu care este prevazut. Acest limbaj “natural’ care consta dintr-un “vocabular” restrans de comenzi si un set de reguli sintactice de utilizare a comenzilor se numeste limbaj

Page 2: programare in Java

2 CURS 1

masina. Fiecare comanda este reprezentata printr-un numar. Operanzii comenzii sunt si ele numere reprezentand fie valori ale datelor prelucrate de comanda fie registrul sau adresa locatiei de memorie in care datele prelucrate se afla sau unde va fi depus rezultatul. O instructiune masina ce poate fi executata de procesor consta deci din numarul asociat comenzii urmat eventual de numere desemnand operanzii comenzii.

Programul masina consta dintr-o succesiune de instructiuni masina a caror executie duce la prelucrarea dorita a datelor initiale si obtinerea rezultatelor. Acest program masina, constand deci dintr-un sir de numere – cod si date, se poate incarca sub forma binara in memoria calculatorului si executat. Este evident ca un program masina nu poate fi executat decat pe un calculator dotat cu procesorul al carui set de comenzi a fost folosit la scrierea programului. Spunem deci ca programul masina nu este portabil fiind dependent de masina. Odata cu raspandirea calculatoarelor cererea tot mai mare de aplicatii programarea direct in limbaj masina a devenit ineficienta fiind prea greoaie. Din acest motiv, pentru a facilita elaborarea programelor, fiecarei comenzi elementare (numar) a procesorului i s-a asociat o succesiune de litere reprezentand abrevierea din limba engleza a comenzii respective. De exemplu pentru comanda de adunare s-a folosit mnemonica ADD (abreviere de la addition). Folosind mnemonicile programele puteau fi concepute, intelese, corectate si modificate mai usor. Setul de mnemonici ale comenzilor si regulile de constructie a instructiunilor constitue asa numitul limbaj de asamblare. Un program scris in limbaj de asamblare nu poate fi executat direct de calculator. Desi inteligibil pentru om, el nu poate fi “inteles” de calculator care asa cum am vazut “intelege” numai limbajul masina al procesorului cu care este echipat. In aceste conditii programul scris in limbaj de asamblare, pentru a putea fi executat, trebuie in prealabil “tradus” in limbaj masina. Aceasta “traducere” este o operatie de rutina prin care fiecare mnemonica a unei comenzi este substituita cu codul masina asociat. Fiind o operatie de rutina, aceasta “traducere” poate fi realizata automat de un program de calculator numit asamblor. Ca si programele masina, programele scrise in limbaj de asamblare se adreseaza unui anumit procesor. Fiecare tip de procesor are propriul sau set de comenzi masina si deci si propriul sau limbaj de asamblare. Mai mult, daca programul apeleaza functii ale sistemului de operare executia sa pe un calculator va fi conditionata nu numai de tipul procesorului ci si de prezenta sistemului de operare ale carui functii le apeleaza programul. Spunem in acest caz ca programul este dependent de platforma (procesor + sistem de operare). Pentru a nu rescrie programele pentru fiecare tip de calculator in parte si pentru a usura elaborarea, depanarea si modificarea lor s-au conceput asa numitele limbaje de nivel inalt. Acestea sunt limbaje artificiale, avand un vocabular si o sintaxa care permit descrierea unor operatii complexe. Primul limbaj de acest tip a fost FORTRAN (FORmula TRANslator). Acesta permitea in principal formularea unor instructiuni de calcul a expresiilor algebrice (formule) continand paranteze, variabile, constante, operatori, functii, intro forma apropiata de cea folosita in algebra. Datele puteau fi scalari – numere reale sau intregi - sau structuri similare vectorilor si matricilor. De asemenea limbajul prevedea instructiuni puternice de introducere si afisare a datelor de la si spre diferite echipamente periferice si pentru lucrul cu fisiere.

Programele scrise intr-un limbaj de nivel inalt nu sunt dependente de platforma si din acest motiv pot fi usor insusite de nespecialisti in tehnica de calcul (ingineri, fizicieni, economisti, medici, etc.). Pentru a scrie un program intr-un limbaj de nivel inalt nu este necesar sa cunosti detalii despre calculatorul sau sistemul de operare sub care va fi executat acesta. Ca si programele in limbaj de asamblare, programele scrise in limbaj de nivel inalt nu pot fi “intelese” de calculator trebuind sa fie “traduse” in cod masina pentru a fi executate. Aceasta “traducere” se face prin substituirea fiecarei

Page 3: programare in Java

PROGRAMARE IN JAVA - Note de curs 3

instructiuni cu codul masina a carui executie duce la realizarea prelucrarii descrise de instructiune. Spre deosebire de instructiunile in limbaj de asamblare instructiunile in limbaj de nivel definesc operatii de calcul complexe fiind substituite la traducere nu cu una ci cu mai multe instructiuni masina. Operatia aceasta de “traducere” a unui program scris in limbaj de nivel inalt in cod masina se numeste compilare si este realizata de programe specializate numite compilatoare. Codul masina generat de compilator se adreseaza evident unui anumit tip de procesor si unui anumit sistem de operare. Pentru a putea fi executat pe o alta platforma, programul sursa trebuie recompilat cu un alt compilator care genereaza cod masina pentru procesorul si sistemul de operare date. Cu alte cuvinte programul in cod masina obtinut prin compilare nu este independent de platforma. O alta varianta de executie a unui program scris in limbaj de nivel inalt consta in utilizarea unui program numit interpretor. Interpretorul preia succesiv cate o singura instructiune a programului sursa, o “interpreteaza” si o executa. Programele interpretate se executa mai lent decat programele compilate in cod masina. Pe de alta parte compilarea este un proces care dureaza si atunci in faza de elaborare a programului este preferabil sa folosim daca este posibil un interpretor si abia cand programul a fost definitivat sa il compilam. Primul limbaj interpretat a fost BASIC.

Limbajul C si C++ In anul 1970 la Bell Laboratories s-a inceput dezvoltarea unui nou sistem de

operare pentru minicalculatoare, numit UNIX. O prima versiune a acestui sistem a fost scrisa pentru minicalculatorul DEC – PDP7 in limbajul B (creat de Martin Richards) orientat pentru programare de sistem. UNIX se dorea sa fie un sistem de operare portabil, scris in limbaj de nivel inalt, putand astfel sa fie compilat si instalat pe orice tip de minicalculator, cu conditia sa fie disponibil un compilator pentru limbajul de nivel inalt folosit. In 1972 pentru elaborarea unei noi versiuni UNIX, Dennis Ritchie pornind de la limbajul B il dezvolta creand limbajul C si compilatorul pentru DEC - PDP11. Odata cu raspandirea in mediul universitar a sistemului de operare UNIX si limbajul C devine din ce in ce mai cunoscut si folosit ca instrument de dezvoltare a UNIX. In prezent compilatorul limbajului C este disponibil pe toate tipurile de calculatoare ceea ce permite scrierea de aplicatii portabile mai ales dupa ce in 1983 limbajul a fost standardizat de American National Standards Committee on Computers and Information Processing. In 1989 standardul este aprobat de ANSI in cooperare cu ISO (International Standards Organization)(ANSI C).

In 1980, tot la Bell Laboratories, Bjarne Stroustrup extinde limbajului C adaugandu-i facilitati necesare programarii orientate pe obiecte. Noua versiune a limbajului este numita C++.

Conceptul de programare orientata pe obiecte revolutioneaza tehnologia de elaborare aprogramelor. Obiectele sunt componente software refolosibile, modeland obiectele din lumea reala. In aceasta abordare programele se construesc cu ajutorul acestor componente ca intr-un joc cu cuburi. Lucrul in echipa la proiectarea si dezvoltarea programelor folosind aceste componente modulare este mult mai productiva decat in cazul folosirii tehnologiilor utilizate anterior (modularitate, programare structurata). C++ permite scrierea programelor atat in stilul C clasic cat si in stilul orientat pe obiecte. Ulterior au aparut si alte limbaje de programare orientata pe obiecte cum ar fi Smaltalk elaborat de Xerox Palo Alto Research Center (PARC) si limbajul Java elaborat de Sun. Practic toate sistemele de operare aparute dupa 1972 au fost scrise in C/C++.

Page 4: programare in Java

4 CURS 1

Limbajul Java Numarul de calculatoare personale este de sute de milioane in toata lumea si

numarul acesta este in crestere. Totodata interconectarea acestora intr-o retea globala (Internet) este tot mai accentuata. Descoperirea microprocesorului a revolutionat si productia de sisteme numerice de conducere cu utilizarea lor in numeroase aplicatii industriale, comerciale sau casnice. Utilizarea microprocesoarelor face posibila aparitia unor utilaje si aparate inteligente – casa inteligenta nu mai este o fantezie sci-fi. Ca si calculatoarele personale si aceste aparate inteligente pentru a functiona eficient vor trebui sa fie interconectate in retea. De exemplu nu este greu de imaginat un sistem inteligent care sa – mentina constanta umiditatea pamantului in ghivecele cu flori si sa schimbe apa si sa mentina temperatura la pestisori in timp ce noi suntem plecati in vacanta. Mai mult, prin Internet vom putea de la orice distanta sa intram in legatura cu sistemul si sa aducem corectii la valorile prescrise pentru umiditate si temperatura, sa vedem imaginea acvariului cu pestisori pe displayul PC-ului si chiar sa le vorbim.

Intrucat diversele aparate inteligente care vor fi comercializate nu vor fi echipate cu acelasi tip de procesor apare problema elaborarii unor aplicatii independente de platforma.

Prevazand aceste evolutii, firma Sun a lansat in anul 1991 un proiect de cercetare numit Green. Rezultatul a fost un limbaj de programare derivat din C si C++, botezat de autorul sau James Gosling Oak dupa numele varietatii unui arbore ce crestea in fata geamului biroului sau. S-a descoperit mai tarziu ca un limbaj de programare cu acest nume exista deja si atunci, dupa ce au fost la o cafeanea la o cafea, cercetatorii au ales pentru noul limbaj de programare numele Java.

Deoarece piata aparatelor inteligente nu se dezvolta asa de repede si datorita pierderii unui contract pentru care Sun concurase, programul Green a inceput sa aibe dificultati de finantare fiind pe cale sa fie abandonat. Din fericire pentru proiect si Java, in anul 1993 a aparut si a capatat o evolutie exploziva in Internet serviciul WorldWideWeb (WWW). Cercetatorii Sun implicati in proiect au sesizat imediat potentialul oferit de utilizarea limbajului Java in crearea asa numitelor pagini Web cu continut dinamic. Noile perspective au revitalizat proiectul Green astfel ca in Mai 1995 firma Sun face cunoscute rezultatele cercetarilor la o conferinta SunWorld.

Aparitia java este imediat sesizata si apreciata de cercurile de afaceri interesate de aspectele utilizarii comerciala a serviciului WorldWideWeb. In aceste conditii Java nu se prezinta ca un limbaj academic cum a fost Pascal si nici ca un limbaj destinat uzului individual sau al unui grup de programatori caum este C. Java este un limbaj promovat de interesul cercurilor de afaceri pentru utilizarea comerciala a Internet in conexiune cu WWW.

Tehnologii de programare La inceputurile sale programarea se facea “dupa ureche” in functie de talentul,

experienta si de capacitatea de analiza si sinteza a programatorului. La proiectarea programului se pornea de la reprezentare grafica (numita schema logica) a algoritmului de rezolvare a problemei. O astfel de schema logica este de fapt un graf orientat ale carui noduri sunt operatiile de prelucrare a datelor reprezentate prin simboluri grafice adecvate. Nodurile sunt conectate prin arce orientate care stabilesc succesiunea de efectuare a operatiilor. O astfel de abordare ofera prea multa libertate in proiectarea algoritmului. Nu intamplator, in acea vreme cea mai importanta instructiune discutata in manualele de programare era instructiunea de salt GOTO.

Page 5: programare in Java

PROGRAMARE IN JAVA - Note de curs 5

Limbajul FORTRAN avea o multime de variante a acestei instructiuni. Datorita acestei lipse de restrictii in proiectarea fluxului de calcul adeseori codul sursa devenea foarte “incalcit” greu de inteles, de modificat si mai ales nesigur, in special in cazul programelor cu complexitate mare. Activitatea de cercetare a dus la aparitia in anii 60 a conceptelor programarii structurate care introducea o anumita disciplina in elaborarea programelor. Respectand principiile programarii structurate programele capata o structura precisa, sunt mai usor de inteles, de testat, depanat si de modificat. In aceasta abordare, la proiectarea programului nu mai este necesara elaborarea schemelor logice. Deoarece in acea perioada nu existau limbaje de programare care sa ofere suport pentru programarea structurata, Nicklaus Wirth a elaborat in 1971 limbajul de programare Pascal (dupa numele matematicianului Blaise Pascal – creator al primei masini mecanice de calcul) pentru a fi folosit la predarea programarii in mediile academice. Cu acest limbaj, Wirth a introdus si conceptul de structura de date –indisolubil legat de algoritmul ce sta la baza programului. Pascal a devenit in scurt timp limbajul preferat predat in universitati. Conceput in scopuri didactice limbajul avea o serie de slabiciuni care au facut sa nu fie agreeat pentru dezvoltarea aplicatiilor comerciale, industriale si guvernamentale. In anii 70-80 Departamentul Apararii al S.U.A. (DOD) a lansat o cerere pentru elaborarea unui limbaj de programare ce urma sa inlocuiasca sutele de limbaje de programare folosite la acea data in sistemele sale. Noul limbaj urma sa devina unicul limbaj de programare folosit in sistemele sale de calcul. Limbajul astfel obtinut s-a numit Ada (dupa numele primului programator al unei masini de calcul – construita de matematicianul Charls Babbage – Lady Ada Lovelace, fica poetului englez Lord Byron). Chiar daca este un urmas al Pascalului, limbajul Ada este mult diferit de acesta. El introduce printe altele mecanismul de multitasking care permite programatorului sa defineasca procesari ale datelor care decurg in paralel. C++ si Java prevad si ele acest mecanism sub denumirea de multithreading. Am vazut ca C++ si Java introduc in plus conceptul de programare orientata pe obiecte oferind si suportul necesar implementarii acestuia. Conceptul de obiect “incapsuleaza” la un loc datele si procedurile de prelucrare a acestora intr-o singura entitate, extinzand astfel conceptul de structura de date din Pascal.

Mediul de dezvoltare a aplicatiilor Java Programele Java parcurg cinci faze de dezvoltare: editare, compilare, incarcare,

verificare si executie. Schematic aceste etape pot fi reprezentate ca in figura 1.1. Prima faza consta in

editarea programului folosind un editor de text (cum ar fi Notepad-ul din Windows sau vi –ul din UNIX). Programul sursa va fi salvat intr-un fisier cu extensia java (de exemplu prg1.java)

In a doua faza programul java este compilat folosind comanda javac (de exemplu javac prg1.java) pentru a lansa compilatorul. acesta “traduce” programul sursa in cod binar (byte code) – limbaj inteles de interpretor. Acest cod intermediar este salvat intr-un fisier cu extensia class (de exemplu prg1.class).

Faza a treia consta in amplasarea codul binar din fisierul class in memorie de catre incarcator. Incarcarea se poate face de pe discul local sau prin retea. Exista doua tipuri de programe java – aplicatii si applet-uri. in cazul aplicatiilor java aceasta operatie este initiata de comanda java (de exemplu java prg1). Appleturile sunt destinate executiei de catre o masina virtuala java implementata de un browser (de exemplu Internet Explorer sau Netscape Navigator) in cadrul unui document HTML (

Page 6: programare in Java

6 CURS 1

Figura 1.1 – mediul de dezvoltare al programelor Java

HyperText Markup Language). Un applet poate fi executat si local cu ajutorul programului appletviewer.

Clase si Obiecte Java In Java un program este constituit dintr-o comunitate de obiecte care

interactioneaza intre ele in timpul executiei programului. Fiecare obiect component al programului are propriile sale proprietati si un anumit comportament specific.

Page 7: programare in Java

PROGRAMARE IN JAVA - Note de curs 7

Un program este un sistem de procesare a datelor. Putem spune ca este similar unui storcator de fructe. Asa cum introducand portocale si energie electrica in storcator obtinem la iesire suc, tot asa introducand in program datele initiale obtinem la iesire rezultatele. Un program de calculator este o masina de prelucrare a informatiei asemanator storcatorului de fructe dar cu deosebirea ca este mult mai versatil. Un storcator de fructe, oricat de perfectionat, nu stie sa faca decat un singur lucru. Un program de calculator poate sa fie proiectat astfel incat sa modeleze nu numai procese si sisteme existente in lumea reala dar si lucruri care nu au existat si nu ar putea exista in realitate.

La fel ca si majoritatea masinilor, un program de calculator este facut din diferite repere (parti) componente care interactioneaza intr-un mod definit cu precizie. O astfel de colectie de componente constitue un sistem motiv pentru care programele de calculator sunt numite uneori si sisteme informatice. In mod traditional, la elaborarea unui astfel de sistem de procesare a informatiei, proiectantul se concentreaza in special pe ce prelucrari trebuie sa faca programul. In cazul programelor complexe, problema este descompusa in subprobleme ce urmeaza a fi abordate separat. Daca unele din aceste subprobleme au inca un grad ridicat de complexitate, ele sunt la randul lor descompuse in subprobleme mai simple. O astfel de abordare de la complex la simplu prin detalieri succesive se numeste metodologie top-down de dezvoltare a programelor. Detalierea continua pana cand fiecare subproblema poate fi rezolvata de un subprogram numit subrutina, procedura sau functie in functie de limbajul de programare folosit. Programul obtinut are drept componente aceste proceduri care la executie interactioneaza pentru obtinerea rezultatului. In cazul unui program procedural datele sunt prelucrate similar cu prelucrarea unor piese intr-o linie de productie fiind transmise ca in figura 1.2 de la un punct de prelucrare la altul (de la o procedura la procedura) pana la procesarea completa.

Figura 1.2 – prelucrarea procedurala a datelor

In cazul programelor orientate pe obiecte lucrurile stau oarecum diferit. Si aceste programe sunt sisteme constituite din componente care interactioneaza intre ele dar aceste componente nu implementeaza algoritmi de rezolvare a unor subprobleme ca procedurile. Aceste componente reunesc datele cu procedurile de prelucrare ale acestora intr-o singura entitate denumita obiect. La fel ca si obiectele din lumea reala, un astfel de obiect are o serie de proprietati – de exemplu culoare, dimensiune, coordonate. Totodata obiectul are un anumit comportament specific fiind capabil de a efectua in anumite conditii o serie de actiuni care ii modifica starea. Asa cum in cazul unei masini comanda de accelerare ii modifica parametrii de stare viteza si coordonate tot asa actiunile obiectului ii modifica valorile proprietatilor. Putem reprezenta un obiect ca in Figura 1.3.

Obiectul executa actiunile 1, 2 si 3 asupra proprietatilor la solicitari venite din exterior de la alte obiecte. Spunem ca aceste actiuni sunt publice, fiind accesibile din afara obiectului.

Page 8: programare in Java

8 CURS 1

Figura 1.3 Reprezentarea unui obiect

Actiunea 4 este “interna” obiectului si nu poate fi solicitata din exterior.

Spunem despre ea ca este o actiune privata. Proprietatile obiectului sunt si ele private, valorile lor neputand fi modificat direct din exterior ci numai prin actiunile obiectului insusi.

Proprietatile obiectului se numesc variabile reprezentand de fapt datele supuse prelucrarii. Actiunile de care obiectul este capabil si care definesc comportamentul sau sunt de fapt proceduri ce se executa la apel asupra datelor interne ale obiectului. Aceste proceduri se numesc metode. Variabilele si metodele se numesc membri ai obiectului. Mai multe obiecte pot “coopera” intre ele pentru rezolvarea unei probleme asa cum motorul coopereaza cu carburatorul, pompa de benzina si rezervorul de combustibil pentru a deplasa un autoturism. In alta ordine de idei obiecte simple pot fi “asamblate” impreuna pentru a crea un obiect mai complex la fel cum obiectul autoturism se obtine din asamblarea impreuna a caroseriei, motorului, carburatorului, pompelor de apa si benzina, rezervorului si a altor repere. Motorul este la randul sau constituit din mai multe repere mai simple.

Un program java este el insusi un obiect complex care poate fi construit folosind o serie de obiecte de complexitate mai redusa compuse la randul lor din obiecte mai simple, care la randul lor … Toate aceste obiecte componente coopereaza la rezolvarea problemei. In aceasta abordare proiectarea unui program orientat pe obiecte difera fundamental de proiectarea unui program procedural. Se porneste de la realizarea sau refolosirea unor obiecte simple pentru a construi obiecte din ce in ce mai complexe, obiectul rezultat in final fiind capabil sa prelucreze datele de intrare in modul dorit. Astfel proiectarea unui program orientat pe obiecte se aseamana cu proiectarea hardware. Circuitele integrate sunt obiecte cu un anumit comportament care se asambleaza pe placi de textolit cu cablaj imprimat rezultand un modul care el insusi este un obiect cu comportamentul dorit. Mai multe astfel de module se pot

Page 9: programare in Java

PROGRAMARE IN JAVA - Note de curs 9

asambla impreuna pe un sasiu (sertar cu sloturi) pentru a forma un obiect mai complex. Mai multe sertare pot fi asamblate impreuna intr-un dulap cablat corespunzator rezultand un obiect si mai complex. La randul sau mai multe dulapuri de acest tip pot fi asamblate impreuna intr-o camera de comanda si interconectate intre ele rezultand un sistem extrem de complex care in esenta este si el un obiect. O astfel de abordare de la simplu la complex se numeste metodologie down-top de proiectare software (total diferita de metodologia top-down folosita la proiectarea programelor procedurale).

La creare obiectele sunt personalizate printr-un nume (identificator ) cu ajutorul caruia ne putem referi la obiectul respectiv. Pentru a crea un obiect trebuie sa definim care sunt proprietatile si comportamentul acestuia. Aceasta definire se numeste clasa. Obiectele sunt instantieri ale clasei date. Asa cum toate autoturismele pot fi incadrate intr-o clase de obiecte numita Autovehicule caracterizate prin proprietatile model, capacitate cilindrica, culoare, numar locuri etc. si capabile de actiunile acelerare, franare, schimbare a directiei, aprindere faruri, semnalizare, etc. si obiectele software se incadreaza intr-o clasa.

Crearea obiectului se face pe baza definitiei clasei careia apartine obiectul prin alocarea unui bloc de memorie si completarea acestuia cu datele si codul corespunzatoare. Zona de date a blocului de memorie se initializeaza cu valorile specificate la crearea obiectului. Avantajele programarii orientata pe obiecte sunt multiple:

- obiectele construite pot fi testate usor fiecare in parte astfel ca la folosirea lor la realizarea unor obiecte mai complexe putem fi siguri de buna lor functionare. Aceasta duce la o mai mare siguranta a programelor.

- obiectele odata realizate, pot fi refolosite si in alte aplicatii. - munca in echipa este evident mai eficienta. - Programele orientate obiect sunt mai usor de modificat prin simpla inlocuire

a unora din obiectele componente. Asa cum pe placa de baza a unui PC putem inlocui un procesor mai lent cu altul mai rapid fara a modifica celalalte componente, tot asa putem inlocui un obiect cu un altul avand acelasi comportament dar la realizarea caruia s-au folosit algoritmi mai performanti. Noul obiect fiind compatibil la nivel de “interfata” cu obiectul inlocuit, celalalte obiecte componente nu trebuiesc modificate. Programul insa va fi mai eficient beneficiind de performantele imbunatatite ale noului obiect introdus.

- Programele orientate obiect complexe sunt mult mai usor de proiectat si de inteles.

Mostenirea Unul din avantajele majore al POO il constituie mostenirea. Aceasta ii permite

programatorului sa nu reinventeze roata de fiecare data de cate ori incepe sa lucreze la un nou program. De ce sa fii nevoit sa rezolvi probleme pe care le-ai rezolvat odata, anterior sau pe care alti programatori le-au rezolvat deja inaintea ta. In cazul programarii procedurale aceasta problema era solutionata prin existenta unor biblioteci de functii. Functiile si procedurile de biblioteca pot fi apelate din programul nou creat de noi, codul acestora fiind cautat si extras din fisierul de biblioteca corespunzator si adaugat programului in cod masina obtinut la compilare prin operatia de editare a legaturilor (linkeditare). Utilizarea bibliotecilor de functii are insa o mare limitare – ea nu ofera un mecanism de extindere a capabilitatilor functiilor continute, acestea fiind deja in cod masina si neputand fi modificate. POO ofera un mecanism

Page 10: programare in Java

10 CURS 1

elegant si revolutionar, numit mostenire, de a depasi aceasta limitare. Locul bibliotecilor de functii este luat in cazul POO de bibliotecile de clase. Clasele pot fi extinse prin adaugare de noi proprietati si metode fara a modifica codul lor initial. Aceasta se bazeaza prin definirea unei clase derivate din clasa de baza continuta eventual de biblioteca. Noua clasa “mosteneste” proprietatile si metodele clasei de baza carora li se adauga proprietatile s metodele specificate la definirea clasei de baza. O astfel de clasa derivata din clasa de baza se numeste subclasa a acesteia. O parte din bibliotecile de clase java sunt standard acompaniand compilatorul si interpretorul Java, altele putand fi furnizate de alti producatori de software independenti. De asemenea, programatorul poate sa creeze propriile sale biblioteci clase pe care sa le foloseasca in diferite aplicatii.

Crearea unui program Java Vom incepe prin a dezvolta o aplicatie Java extrem de simpla (traditionalul

(HalloWorld) pentru a exemplifica structura unui astfel de program si etapele ce trebuiesc parcurse pentru a-l executa.

Vom incepe prin a edita cu un program editor de text codul sursa al aplicatiei. Asa cum am am spus anterior, un program java este el insusi un obiect. Vom incepe prin a defini clasa a carei instantiere este aplicatia noastra: class HalloWorld { //Aici se vor adauga membrii clasei }

Pentru ca aceasta clasa sa defineasca o aplicatie, ea trebuie obligatoriu sa contina metoda main care este apelata la lansarea in executie a programului. Metoda main este de fapt o functie (similara cu functiile C) care primeste ca parametri un vector de siruri de caractere reprezentand argumentele din linia de comanda cu care a fost lansat programul. In Java sirurile de caractere sunt (ca toate datele de altfel) obiecte apartinand clasei String. Un vector de astfel de obiecte este definit ca String[]. Aceasta functie trebuie sa fie accesibila din exteriorul obiectului pentru ca interpretorul sa poata cere executia acesteia, sa fie publica, motiv pentru care este declarata de tip public. Codul acestei functii este comun tuturor instantelor clasei metoda fiind declarata din acest motiv static. De asemenea main nu intoarce nimic fiind deci declarata void.

Vom avea deci: class HalloWorld { public static void main(String[] arguments){

//Aici se va adauga codul functiei main } }

In cazul exemplului nostru, dorim ca functia noastra sa afiseze mesajul Hallo world!. Pentru afisare vom solicita serviciile obiectului System care gestioneaza relatiile cu sistemul. Acesta are in componenta un obiect out care gestioneaza afisarea. Accesul la membrul out al lui System cu operatorul de acces “.” ( la fel cum se accesau membrii unei structuri in C): System.out . Acestei componente ii vom solicita sa afiseze mesajul nostru prin apelul metodei sale println. Vom obtine deci:

Page 11: programare in Java

PROGRAMARE IN JAVA - Note de curs 11

class HalloWorld { public static void main(String[] arguments){

System.out.println(“Hallo world!”); } }

Se observa ca instructiunea adaugata de noi la corpul functiei trebuie terminata ca si in limbajul C cu caracterul ";".

Textul programului il vom salva intr-un fisier avand acelasi nume cu classa si extensia java (HalloWorld.java). Vom compila programul cu comanda javac HelloWorld.java rezultand fisierul HelloWorld.class continand byte-codul programului. Acest byte-cod il vom executa cu interpretorul java prin comanda java HelloWorld. Programul va afisa in fereastra DOS in care a fost executat mesajul Hello world! si se va termina (figura 1.4).

Figura 1.4 – Compilarea si executia unui program java

Sumar Pe parcursul acestui curs am discutat aspectele introductive legate de limbajul

Java si POO. Am elaborat de asemenea un prim program java si am parcurs etapele necesare pentru executia acestuia:

1. Se editeaza programul cu un editor de text; 2. Se compileaza programul; 3. Se apeleaza interpretorul pentru executarea byte-codului generat de

compilator. Parcurgand aceste etape am vazut cum se defineste o clasa si cum se declara in

cadrul acesteia o metoda. De asemenea am vazut cum se apeleaza la serviciile unui alt obiect existent – in speta pentru afisarea unui mesaj am apelat metoda System.out.println(<mesaj>). Unde <mesaj> este un sir de caractere incadrat intre ghilimele. Aceasta metoda afiseaza sirul de caractere la consola sistemului (in fereastra DOS in care am rulat aplicatia) si trece cursorul pe randul urmator.

Trebuiesc de asemenea remarcate urmatoarele detalii de sintaxa: • clasa se declara cu instructiunea class; • Programul Java este instantierea unei clase care contine metoda main; • Metoda main este de tip public static void si are ca parametru String[]; • Numele clasei trebuie sa fie acelasi cu numele fisierului in care se salveaza codul

acesteia; • In textul programului pot fi introduse comentarii precedate de perechea de

caractere “//” (la fel ca in C), compilatorul ignorand textul ce urmeaza acestor caractere pana la sfarsitul randului.

• Instructiunile programului se termina cu caracterul “;”. Mai multe detalii privind cele de mai sus vor prezentate in cursurile urmatoare.

Page 12: programare in Java

12 CURS 1

Exercitii 1. Cand compilati un program, ce faceti de fapt?

a. Il salvati pe disc b. Il convertiti intr-o forma pe care calculatorul poate sa o inteleaga c. Il adaugati la colectia voastra de programe

2. Ce este o variabila?

a. O valoare necunoscuta ce este precizata in timpul executiei programului

b. Un text intr-un program ignorat de compilator c. Un loc unde vor fi pastrate de catre program informatii

3. Modificati programul HalloWorld introducand in codul sau diverse erori. De

exemplu stergeti terminatorul “;” de la sfarsitul instructiunii de afisare sau modificati numele clasei din class HalloWorld{...} in class halloWorld{...}. Salvati programul si incercati sa il compilati si executati. Comparati mesajele de eroare obtinute cu erorile introduse.

Page 13: programare in Java

PROGRAMARE IN JAVA - Note de curs 13

Curs 2

Algoritmi si structuri de control Inainte de a incepe scrierea unui program care sa rezolve o anumita problema,

programatorul trebuie sa analizeze si sa inteleaga pe deplin in ce consta problema care sunt datele initiale, care sunt rezultatele ce trebuiesc obtinute si ce prelucrari trebuie sa sufere datele initiale pentru a se obtine rezultatele cerute. La proiectarea programului este esential atat sa determinam ce blocuri componente vom folosi pentru a construi programul cat si ce metodologie de elaborare vom utiliza pentru aceste blocuri. Metodologia prezentata in continuare de proiectare a structurii programelor este aplicabila nu numai limbajului java ci si in cazul majoritatii limbajelor de programare de nivel inalt. orice problema de calcul poate fi rezolvata prin efectuarea unor anumite operatii asupra datelor intr-o succesiune data. O astfel de succesiune de efectuare a operatiilor asupra datelor initiale care duce la solutionarea unei probleme date ( prin obtinerea rezultatelor dorite) se numeste algoritm. Sa analizam pe un exemplu importanta succesiunii corecte a efectuarii a operatiilor in rezolvarea corecta a problemei. Fie algoritmul de desteptare si plecare la cursuri a unui student:

1. Trezirea si jos din pat 2. Dezbraca-ti pijamaua 3. Fa un dus 4. Imbraca-te 5. Ia micul dejun 6. Pleaca la cursuri Executarea in aceasta ordine a operatiilor descrise mai sus face ca studentul

nostru sa ajunga la cursuri intr-o forma corespunzatoare. Acum sa presupunem ca studentul ar executa aceleasi operatii intr-o ordine putin modificata:

1. Trezirea si jos din pat 2. Dezbraca-ti pijamaua 3. Imbraca-te 4. Ia micul dejun 5. Fa un dus 6. Pleaca la cursuri Aplicand acest algoritm, studentul nostru va ajunge la cursuri ud fleasca.

Specificarea ordinii in care se executa diferitele instructiuni ale programului se face prin instructiuni de control.

Inainte de atrece la scrierea programului este necesar deci sa determinam operatiile ce trebuiesc efectuate de acesta si succesiunea acestora adica sa determinam algoritmul de rezolvare a problemei care urmeaza sa fie implementat in final in limbajul de programare ales. Inainte de a fi transpus in program, algoritmul trebuie reprezentat intr-o forma sau alta pe hartie. Folosirea limbajului natural nu este cea mai buna solutie pentru descrierea algoritmului. O solutie o constitue utilizarea pseudocodului – un limbaj artificial neformal apropiat de un limbaj de programare dar

Page 14: programare in Java

14 CURS 2 mai putin rigid in ce priveste respectarea regulilor sintactice. Pseudocodul nu este un limbaj de programare propriuzis, servind numai pentru reprezentarea algoritmului ceea ce reprezinta o etapa intermediara in elaborarea programului. El permite programatorului sa analizeze si sa proiecteze structura si componentele viitorului program. Implementarea algoritmului reprezentat astfel in program se face foarte usor prin inlocuirea instructiunilor scrise in pseudocod cu instructiunile echivalente ale limbajului de programare ales. Pseudocodul contine doar instructiuni executabile. Instructiuni declarative de forma int i; nu apar in aceasta etapa de proiectare a programului. O astfel de instructiune declarativa este de fapt o directiva data compilatorului pentru a aloca memorie variabilei i in care sa se pastreze in timpul executiei valorile luate de aceasta. Deoarece programul in pseudocod nu va fi compilat, nu va fi necesara in aceasta faza de conceptie declararea variabilelor ce intervin in program. Cu toate acestea unii programatori prefera sa specifice la inceputul pseudocodului programului lista variabilelor folosite si destinatia acestora.

In mod normal, instructiunile sunt executate succesiv, una dupa alta in ordinea in care apar in program. O astfel de executie se numeste secventiala. Multe instructiuni Java pe care le vom discuta curand permit programatorului sa determine executarea (sa transfere controlul) unei alte instructiuni decat instructiunea imediat urmatoare din secventa. Abuzul in ce folosirea acestor instructiuni poate sa altereze grav structura programului ridicand mari dificultati in intelegerea, depanarea si modificarea acestuia. Am vorbit in cursul precedent de blamata instructiune goto care permitand transferul controlului in orice punct al programului, utilizata fara discriminare poate complica inutil programul. Astfel in anii 60, dupa cum am mai aratat, s-a formulat conceptul programarii structurate care duce la eliminarea folosirii acestei instructiuni. Cercetarile lui Bohm si Jacopini au dovedit prin enuntarea si demonstrarea in 1966 a teoremei de structura. Aceasta teorema afirma ca orice algoritm poate fi construit fara utilizarea lui goto folosind numai trei structuri de control: secventa, selectia si repetitia. Renuntarea la instructiunea goto a fost o adevarata provocare adresata producatorilor de software, programatorii trebuind sa–si modifice fundamental modul de gandire si stilul de programare. Analizele facute au demonstrat ca aplicarea principiilor programarii structurate au dus la o crestere impresionanta a eficientei productiei de software ( reducerea timpului de elaborare a software-ului si implicit a cheltuielilor). Explicatia acestui fenomen consta in imbunatatirea substantiala a structurii programelor ceea ce facea ca acestea sa fie mai clare, mai usor de depanat, testat si modificat. Desi limbajele C, C++ si Pascal mai pastreaza instructiunea goto, limbajul Java nu o mai contine in setul sau de instructiuni.

Secventa Structura secventa este implicita in Java. Daca nu se specifica altfel,

instructiunile sunt executate secvential, una dupa alta in ordinea in care apar in program. Fragmentul de schema logica din figura 2.1 exemplifica o astfel de structura de control de tip secventa tipica, in care doua operatii de calcul sunt executate succesiv.

O schema logica este o reprezentare grafica a unui algoritm folosind diferite simboluri grafice pentru reprezentarea operatiilor (dreptunghiuri, romburi, cercuri, elipse). In schema logica din figura 2.1 dreptunghiurile reprezinta blocuri de calcul ( specificand o actiune/operatie executata de program) iar cercurile noduri de conectare la restul schemei logice. Sagetile indica ordinea in care operatiile sunt efectuate.

Page 15: programare in Java

PROGRAMARE IN JAVA - Note de curs 15

Figura 2.1 – secventa Intai valoarea variabilei unghi este adaugata la variabila total iar apoi variabila

contor este incrementata cu 1. Java ne permite sa prevedem oricate instructiuni succesive intr-o secventa.

Selectia Selectia este o instructiune care introduce o ramificatie in fluxul de executie al

instructiunilor programului. Executia instructiunilor se va desfasura pe o ramura sau alta in functie de indeplinirea unei conditii specificate.

Java prevede trei tipuri de instructiuni de tip selectie: structura de selectie if , selectia dubla if/else si selectia multipla switch. Selectia simpla if determina executia unei operatii daca este este adevarata o conditie data. In cazul in care conditia nu este indeplinita (este falsa) se sare peste operatia conditionata trecandu-se la executia operatiei imediat urmatoare. O astfel de structura se numeste selectie simpla si poate fi reprezentata grafic prin schema logica din figura 2.2.

Aici simbolul romb este un bloc de decizie. Daca valoarea de adevar a expresiei relationala inscrisa in acest bloc este adevarat ( variabila unghi are valoarea mai mare sau egala cu 60), se executa actiunea din blocul urmator (afisarea mesajului “Mai mare”). Daca valoarea de adevar este fals, atunci se sare peste aces bloc. de remarcat ca si aceasta structura de control if ca si secventa are un singur punct de intrare si un singur punct de iesire. Astfel de structuri cu un singur punct de intrare si un singur punct de iesire prezinta avantajul ca programatorul le poate conecta unul la altul folosindu-le ca elemente de constructie a programului la fel ca intr-un joc de cuburi. Aceasta confera algoritmului o structura clara, usor de inteles si modificat.

Page 16: programare in Java

16 CURS 2

Figura 2.2 – selectia simpla

Mai exista o singura metoda de combibnare a structurilor de control de acest tip

si anume incuibarea. Structura descrisa mai sus poate transpusa in java astfel: ... if(unghi >= 60) System.out.println(“Mai mare”); ...

Se observa ca limbajul java corespunde foarte bine pseudocodului folosit in descrierea algoritmului ceea ce face ca utilizarea pseudocodului la proiectarea algoritmilor sa fie un instrument extrem de util. Ca argument al instructiunii de selectie if poate fi prevazuta orice expresie care returneaza la evaluare o valoare de tip boolean.

Selectia dubla Structura de selectie dubla if/else permite programatorului sa specifice o actiune

alternativa care se executa in cazul conditia nu este indeplinita. Aceasta structura se scrie in pseudocod astfel ... if(unghi >= 60) print “Mai mare” else print “Mai mic” ...

Schema logica corespunzatoare acestei structuri este prezentata in figura 2.3.

Page 17: programare in Java

PROGRAMARE IN JAVA - Note de curs 17

Figura 2.3 – selectia dubla

Structura descrisa mai sus poate transpusa in java astfel:

... if(unghi >= 60) System.out.println(“Mai mare”); else System.out.println(“Mai mic”); ...

Pe langa instructiunea if/else limbajul java mai prevede operatorul conditional ?: similar celui din limbajul C, avand sintaxa:

<expresie booleana>?<expresie1>:<expresie2> Daca la evaluarea expresiei booleene rezulta valoarea adevarat, operatorul evalueaza si intoarce valoarea expresiei 1 iar in caz contrar valoarea expresiei 2. Astfel instructiunea java: ... System.out.println(unghi>=60? “Mai mare”:”Mai mic”); ... va avea acelasi efect cu instructiunea if/else din exemplul anterior.Putem incuiba structurile de selectie duble ca in exemplul urmator: ... if(unghi < 90) System.out.println(“Ascutit”); else if (unghi == 90) System.out.println(“Drept”); else System.out.println(“Obtuz”); ...

Page 18: programare in Java

18 CURS 2

Schema logica corespunzatoare acestei secvente de instructiuni este cea din figura 2.4

Figura 2.4 – Incuibarea selectiilor duble

Este important de subliniat ca componenta else a structurii este intotdeauna

asociata de compilator cu ultimul if, indiferent de indentarea textului. Indentarea are doar rolul de a imbunatati claritatea programului.

Daca dorim sa asociem o componenta else cu un alt if decat cel precedent trebuie sa folosim acoladele ca in exemplul urmator.

Secventa de mai jos:

... if(x > 10) if(y > 10) System.out.println(“x si y sunt mai mari ca 10”); else System.out.println(“x este mai mic sau egal cu 10”); ... va functiona incorect afisand mesajul x este mai mic sau egal cu 10 chiar daca x are valoarea 12 dar y este 5. Aceasta se datoreste faptului ca else este asociat cu if( y > 10) si nu cu if(x > 10), secventa corespunzand schemei logice din figura 2.5. Versiunea corecta se obtine folosind acoladele:

Page 19: programare in Java

PROGRAMARE IN JAVA - Note de curs 19

... if(x > 10){ if(y > 10) System.out.println(“x si y sunt mai mari ca 10”); }else System.out.println(“x este mai mic sau egal cu 10”); ...

Acoladele {} indica compilatorului ca cel de al doilea if este incuibat in corpul primului si deci componenta else apartine primului if.

Instructiuni compuse In mod normal instructiunea if accepta in corpul sau o singura instructiune.

Daca prelucrarea ce trebuie executata este mai complexa si necesita mai multe instructiuni, acestea pentru a fi incluse in corpul lui if trebuiesc grupate intr-un bloc de instructiuni prin incadrarea intre acolade. Un astfel de bloc de instructiuni se numeste instructiune compusa. Exemplul urmator prezinta utilizarea instruciunilor compuse in corpul unei selectii duble: ... if(unghi >= 60) System.out.println(“Mai mare”); else{ System.out.println(“Mai mic”); System.out.println(“Mai mariti unghiul!”); } ... In acest exemplu daca variabila unghi are valoarea mai mica decat 60 este executat blocul de doua instructiuni incadrate de acolade, afisandu-se pe doua randuri mesajele Mai mic si Mai mariti unghiul! .

Daca nu s-ar fi folosit acoladele mesajul Mai mariti unghiul! ar fi fost afisat in oricare din situatii.

Selectia multipla Fie urmatoarea instructiune formata din mai multe instructiuni de selectie

incuibate: ... if(unghi == 30) System.out.println(“Unghi=30”); else if (unghi == 45) System.out.println(“Unghi=45”); else if (unghi == 60) System.out.println(“Unghi=60”); else if (unghi == 90) System.out.println(“Unghi=90”); else System.out.println(“Alte valori”); ...

Pentru astfel de cazuri in care, in functie de valoarea intreaga pe care o ia o variabila, trebuie selectata si executata o anumita actiune din mai multe posibile (selectie multipla) limbajul java prevede instructiunea switch avand sintaxa:

Page 20: programare in Java

20 CURS 2

switch ( <expresie intreaga> ){ case <val 1>: <actiune 1>; break; case <val 2>: <actiune 2>; break; ... case <val n>: <actiune n>; break; default:<actiune implicita>; break; }

Aici daca valoarea intreaga obtinuta prin evaluarea <expresie intreaga> este

<val 1> se va efectua <actiune 1>, daca este <val 2> se va efectua <actiune 2>, etc.Daca valoarea obtinuta nu este egala cu nici una din valorile <val1>, <val2>,...,<val n> se va efectua <actiune implicita> specificata cu eticheta default.

Folosind aceasta instructiune, instructiunea compusa din exemplul precedent poate fi inlocuita cu:

... switch(unghi){ case 30: System.out.println(“Unghi=30”); break; case 45: System.out.println(“Unghi=45”); break; case 60: System.out.println(“Unghi=60”); break; case 90: System.out.println(“Unghi=90”); break; default: System.out.println(“Alte valori”); break; } ...

Structura de control repetitiva while O structura de control repetitiva determina calculatorul sa repete ciclic o actiune

atat timp cat o anumita conditie este indeplinita (este adevarata). Un exemplu de astfel de operatie repetata ar fi descris de :

Cat timp mai sunt obiecte de cumparat pe lista mea de cumparaturi Cumpara urmatorul obiect din lista Instructiunea while corespunde lui “Cat timp”. Actiunea este descrisa de

directiva “Cumpara urmatorul obiect din lista”. Conditia verificata de fiecare data inaintea efectuarii acestei actiuni este “mai sunt obiecte de cumparat pe lista mea de cumparaturi?”. Daca aceasta conditie este satisfacuta se executa o noua cumparatura. Repetarea continua pana la epuizarea listei de cumparaturi, caz in care evaluarea conditiei intoarce un rezultat fals. O astfel de structura repetitiva se numeste ciclu cu testul la inceput. Actiunea constituie corpul ciclului iar conditia se numeste invariantul ciclului (denumire justificata de faptul ca acest ciclu se executa atat timp

Page 21: programare in Java

PROGRAMARE IN JAVA - Note de curs 21

valoarea rezultata din evaluarea conditiei este invariant adevarat). Ciclul se numeste ciclu cu testul la inceput deoarece evaluarea conditiei se face inainte de a se executa actiunea din corpul ciclului. Astfel este posibil ca aceasta actiune sa nu se execute nici o data (daca conditia are din start valoarea fals). Sintaxa instructiunii while este:

while (<expresie booleana>) <instructiune>

Instructiunea <instructiune> se repeta cat timp valoarea booleana obtinuta din evaluarea expresiei <expresie booleana> este adevarat. Instructiunea <instructiune> poate fi simpla sau compusa (bloc de instructiuni).

Exemplul urmator prezinta utilizarea instructiunii while: ... while(unghi < 360)

unghi = unghi + 10; ... In acest exemplu variabila unghi este marita ciclic cu 10 pana cand valoarea acesteia depaseste 360. Daca valoarea initiala a acestei variabile a fost 5, valoarea la iesirea din ciclu va fi 365. Daca valoarea initiala este un multiplu de zece dar mai mica de 360, valoarea finala obtinuta este 360. Daca insa valoarea initiala este egala sau mai mare ca 360, actiunea de incrementare a sa cu 10 nu are loc, ciclul terminandu-se fara a se mai executa corpul sau deoarece conditia unghi < 360 este evaluata la valoarea fals de la inceput.

Schema logica din figura 2.5 reprezinta structura repetitiva while din exemplul de mai sus.

Figura 2.5 Structura repetitiva while

Page 22: programare in Java

22 CURS 2

Structura de control repetitiva while

Figura 2.6 – structura repetitiva do/while

Sumar

Exercitii

Page 23: programare in Java

PROGRAMARE IN JAVA - Note de curs 25

Curs 3

Date, variabile, expresii Asa cum am aratat, programele sunt sisteme care prelucreaza date. Desi in

esenta aceste date sunt valori numere stocate in memoria calculatorului sub forma de numere intregi codificate in binar, la nivelul programului scris in limbaj de nivel inalt ele modeleaza obiecte ale lumii reale. Fiecare limbaj de nivel inalt pune la dispozitia programatorului o colectie mai mult sau mai putin bogata de tipuri de date uzuale prin care acesta sa poata reprezenta in program informatia supusa procesarii. In mod traditional calculatoarele sunt destinate sa prelucreze informatie numerica efectuand calcule cu numere intregi si reale. Din acest motiv din nici un limbaj de programare nu lipsesc aceste tipuri de date. De asemenea calculatoarele sunt frecvent folosite pentru prelucrarea informatiei de tip text numita informatie alfanumerica – caractere. Caracterele, sunt simboluri grafice cum ar fi litere, cifre, semne speciale, sunt codificate numeric prin asocierea fiecarui astfel de simbol a unui cod numeric standardizat (de exemplu codul ASCII – American Standard Code for Information Interchange). Si pentru acest gen de informatie limbajele de programare de nivel inalt prevad tipuri de date corespunzatoare. De asemenea, asa cum am constatat in cursul trecut, programele trebuiesc adesea sa opereze cu valori logice de tip adevarat si fals, motiv pentru care si pentru astfel de valori, numite booleene dupa matematicianul englez Bool, unele limbaje de programare (Pascal, Java) prevad un tip de date specific. Tipurile de date discutate sunt tipuri elementare, numite tipuri de date scalare. Pe langa tipurile de date scalare toate limbajele de nivel inalt permit definirea unor structuri omogene de date cum ar fi vectorii si matricile, formate din mai multe elemente de acelsi tip. In aceasta categorie ar putea intra si sirurile de caractere – privite ca un vector ale carui elemente sunt date de tip caracter. Totusi, unele limbaje (Pascal, Java) trateaza sirurile de caractere ca un tip distinct de date. Limbajele de nivel inalt moderne permit programatorului sa defineasca propriile tipuri de date sub forma de structuri eterogene (compuse din elemente de tipuri diferite) prin care acesta sa poata reprezenta si alte informatii decat cele uzuale.

Asa cum in algebra pe fiecare multime de numere exista definit un set de operatii si limbajele de nivel inalt prevad pentru fiecare tip de date un set de operatori.

Astfel avem operatori ce implementeaza operatiile cu numere intregi, alti operatori pentru operatii cu date de tip real, operatori ce opereaza cu date de tip boolean, cu caractere sau cu siruri de caractere. Exista de asemenea operatori relationali care permit compararea a doua date, evident ca de acelasi tip. Similar operatiilor din algebra, operatorii pot fi unari actionand asupra unui singur operand sau binari avand doi operanzi.

Limbajele de nivel inalt permit construirea cu ajutorul operatorilor a unor expresii asemanatoare expresiilor algebrice. Termenii expresiei sunt date de acelasi tip sau de tipuri compatibile. La executia programului expresia este evaluata prin aplicarea operatorilor asupra termenilor dupa niste reguli precise. De exemplu

Page 24: programare in Java

26 CURS 3 expresiile aritmetice se evalueaza ca si in matematica prin efectuarea calculelor de la stanga la dreapta, efectuandu-se intai operatiile multiplicative (inmultiri, impartiri) si apoi cele aditive (adunari, scaderi). Ordinea de efectuare a operatiilor poate fi controlata de programator prin folosirea parantezelor “( )”. Ca si in algebra, o expresie poate sa contina ca termeni si variabile. In programare variabila este o locatie de memorie destinata stocarii pe parcursul executiei programului a unui anumit tip de date. In programare variabila este caracterizata prin nume si tip. Numele permite accesul la datele memorate in acea variabila. Tipul variabilei este acelasi cu tipul datelor pe care este prevazut sa le stocheze aceasta. Daca numele unei variabile apare ca termen intr-o exprsie, la evaluarea acesteia, el este inlocuit cu data memorata in variabila. Pentru a memora o data intr-o variabila (operatie numita de atribuire) unele limbajele de programare de nivel inalt prevad operatorul de atribuire iar altele instructiunea de atribuire cu sintaxa:

<variabila> <op.atr><expresie> Desi sintaxa este identica, exista o diferenta intre instructiunea de atribuire si

operatia de atribuire. In primul caz, valoarea rezultata din evaluarea expresiei <expresie> este stocata in locatia de memorie desemnata de <variabila>.

In cel de al doilea caz <variabila> <op.atr><expresie> este ea insasi o expresie a carei valoare este chiar valoarea ce a fost memorata in variabila. o astfel de expresie, incadrata de paranteze rotunde poate sa fie termen intr-o alta expresie.

De remarcat este faptul ca prin <expresie> se intelege chiar si o expresie cu un singur termen si fara nici un operator.

Numere intregi si reale Inainte de a fi folosita intr-un program java, o variabila trebuie sa fie declarata.

Declaratia trebuie sa specifice numele si tipul variabilei pentru ca la compilare sa i se aloce un spatiu de memorie suficient pentru a putea stoca date de tipul declarat. De exemplu instructiunea:

int scorMaxim; declara o variabila cu numele scorMaxim de tipul int.

Denumirea tipulu int provine de la cuvintul englezesc Integer – intreg. Compilatorul va aloca pentru aceasta variabila o locatie de memorie dimensionata sa poata stoca numere intregi cu valori cuprinse intre -2,14 miliarde si +2,14 miliarde. Deoarece inca nu i s-a atribuit acestei variabile o valoare, continutul acestei locatii de memorie este neinitializat, continand o valoare arbitrara. Putem chiar la declarare sa atribuim variabilei o valoare intreaga prin operatorul de atribuire “=”: int scorMaxim = 40000;

Instructiunea:

float punctajMediu; declara o variabila cu numele punctajMediu de tipul float.

Denumirea tipulu float provine de la termenul englezesc Floating point – virgula mobila – care desemneaza modalitatea de codificare a numerelor reale in memoria calculatorului. Deoarece in memorie nu pot fi memorate decat numere

Page 25: programare in Java

PROGRAMARE IN JAVA - Note de curs 27

intregi, numerele reale sunt memorate ca o pereche de numere intregi. De exemplu modul de codificare in memorie a numarului real 123,456 poate fi prezentat simplificat astfel: numarul este transformat prin deplasarea virgulei in numarul real echivalent 0,123456 103 si memorat sub forma perechii de numere intregi (123456;3). Compilatorul va aloca pentru aceasta variabila o locatie de memorie suficient de incapatoare incat sa permita memorarea unor numere reale reprezentate in virgula mobila. Deoarece inca nu i s-a atribuit acestei variabile o valoare, continutul acestei locatii de memorie este neinitializat, continand o valoare arbitrara. Ca si in cazul variabilelor intregi putem atribui la declararea variabilei de tip float o valoare reala: float punctajMediu = 123.456; Numele variabilelor este un identificator. Un identificator poate fi orice combinatie de litere sau cifre cu conditia sa inceapa cu o litera. Caracterul “_” este asimilat literelor. De exemplu combinatiile Variabila1 sau Variabila_1 sau _X reprezinta identificatori valizi iar combinatiile 1X sau Variabila-1 nu sunt acceptati de compilator fie pentru ca incep cu o cifra ca in primul caz, fie datorita unui caracter care nu este nici litera, nici cifra (-), ca in cel de al doilea caz.

Alte tipuri numerice de variabile Pe langa tipul int limbajul Java mai prevede tipuri derivate pentru variabile care

stocheaza date de tip intreg. Tipul byte determina dimensionarea locatiei de memorie pentru a pastra date

cuprinse intre –128 si 127. Pentru o variabila de tip byte, compilatorul va aloca o locatie de memorie de un octet in timp ce pentru una de tip int memoria alocata este de 4 octeti.

Tipul short determina dimensionarea locatiei de memorie la doi octeti pentru a pastra date cuprinse intre –32768 si 32767 iar tipul long 8 octeti pentrru a stoca numere intregi foarte mari cuprinse intre –9.223.372.036.854.775.808 si 9.223.372.036.854.775.807.

Pentru cazul in care se doreste memorarea numerelor reale cu o precizie mai mare (cu mai multe zecimale) spatiul de memorie alocat trebuie dimensionat corespunzator. Pentru astfel de cazuri in locul tipului float se poate folosi tipul double (de la englezescul double precision – precizie dubla).

Caractere si siruri de caractere Asa cum am aratat mai inainte, caracterele desemneaza simboluri grafice cum ar

fi litere, cifre, semne de punctuatie, alte semne speciale care pot sa apara intr-un text. Pentru codificarea in memorie a datelor de tip caracter, fiecarui simbol grafic i

se asociaza un cod numeric unic – un numar intreg cuprins intre 0 si 255. Exista mai multe standarde pentru codificarea caracterelor, unul dintre acestea fiind codul ASCII. In tabelul de mai jos sunt extrase codurile numerice asociate literelor si cifrelor:

Caracter Cod ASCII

A-Z 65-90 a-z 97-122 0-9 48-57

Page 26: programare in Java

28 CURS 3

Datele de tip caracter se reprezinta incadrate de apostroafe. De exemplu ‘C’ reprezinta caracterul C cu codul ASCII 67.

O variabila de tip caracter (char) se declara si se initializeaza cu o instructiune de forma; char key = ‘C’;

Sirurile de caractere se reprezinta incadrate cu ghilimele, ca de exemplu sirul

“Hallo world!” afisat de primul nostru program java. Variabilele de tipul sir de caractere (String) se declara si se initializeaza ca in

instructiunea de mai jos:

String mesaj = “Hallo world!”;

Daca vrem ca String-ul sa contina ghilimele ca in sirul de mai jos: Sunt student la Universitatea “Politehnica” din Bucuresti trebuie sa folosesc in locul ghilimelelor o secventa speciala de caractere \” ,

numita secventa escape:

String mesaj = “Sunt student la Universitatea \“Politehnica\” din Bucuresti”; Acelasi lucru este valabil si pentru caracterul apostrof care se va specifica cu

secventa escape \’ ca si pentru caracterul backslash \ folosind \\ . Secventele escape se folosesc si pentru introducerea in sir a unor caractere speciale de control al afisarii specificate in tabelul de mai jos:

secventa escape Caracter de control

\t tab \b backspace \r carriage return \f formfeed \n new line

De exemplu folosind secventa escape \n instructiunea:

System.out.println(“Aceasta melodie este interpretata de \n formatia RoMania”) Va afisa pe display doua randuri de text:

Aceasta melodie este interpretata de formatia RoMania

Tipul de date boolean Java prevede si tipul boolean de date care pot avea doar doua valori, adevarat –

true si fals - false. Pentru a memora astfel de date, pot fi declarate variabile de tipul boolean ca in

exemplul de mai jos:

Page 27: programare in Java

PROGRAMARE IN JAVA - Note de curs 29

boolean sfirsitJoc = false;

Exemplu de declarare a variabilelor in program Vom aplica cele discutate mai sus intr-un program concret declarand diverse

tipuri de variabile, initializandu-le si afisandule valorile. Aplicatia noastra va consta in definirea clasei Variabile: class Variabile{ public static void main(String[] arguments){ // Declararea si initializarea variabilelor int scorMaxim=40000; double punctajMediu = 123.456; char key = ‘C’; String mesaj = “Hallo world!”; boolean sfirsitJoc = false; // Afisarea valorii variabilelor System.out.println(scorMaxim); System.out.println(punctajMediu); System.out.println(key); System.out.println(mesaj); System.out.println(sfirsitJoc); } }

Variabilele de tipurile discutate au fost declarate in cadrul metodei main care realizeaza si procesarea acestora. Spunem ca aceste variabile sunt locale metodei main.

Programul de mai sus va fi editat si salvat in fisierul Variabile.java. Comenzile de compilare si executie a programului continut de fisierul

Variabile.java precum si rezultatele afisate sunt redate in figura 3.1.

Figura 3.1 – Compilarea si executia aplicatiei Variabile.java

Exemplu de utilizare a expresiilor Scopul aplicatiei prezentate in continuare este de a face o introducere in modul

de utilizare a expresiilor in cadrul unui program Java.

class MotanulPacepa{ public static void main(String[] arguments){ int greutate = 3; System.out.println(“Motanul Pacepa cintareste “ + greutate); System.out.println(“Motanul Pacepa viziteaza ulcica cu smintana“); greutate = greutate + 1; System.out.println(“Motanul Pacepa cintareste acum “ + greutate); System.out.println(“Motanul Pacepa descopera gimnastica aerobica“);

Page 28: programare in Java

30 CURS 3 greutate = greutate - 2; System.out.println(“Motanul Pacepa cintareste acum “ + greutate); System.out.println(“Motanul Pacepa cade in masina automata de ” +

“spalat rufe“); greutate = greutate/2; System.out.println(“Motanul Pacepa cintareste acum “ + greutate); System.out.println(“Motanul Pacepa este clonat de 12 ori“); greutate = greutate + (greutate*12); System.out.println(“Cei 13 motani Pacepa cintaresc acum “ +

greutate); } }

Programul de mai sus va fi editat si salvat in fisierul MotanulPacepa.java. Comenzile de compilare si executie a programului continut de fisierul

Variabile.java precum si rezultatele afisate sunt redate in figura 3.2.

Figura 3.2 Compilarea si executia aplicatiei MotanulPacepa.java

In programul de mai sus am utilizat cateva expresii numerice cu rezultat intreg

pentru a calcula valori noi pentru variabila greutate atribuite acesteia prin operatorul de atribuire reprezentat cu semnul =. In cadrul acestor expresii au fost folositi operatori aritmetici binari care se aplica la operanzi de tip intreg. Acestia au fost reprezentati prin simbolurile + pentru adunare, - pentru scadere, * pentru si / pentru impartire. In una din expresii au fost folosite parantezele pentru a explicita ordinea de efectuare a operatiilor – ca si in algebra, la calculul expresiilor intai se executa operatiile din paranteze. De fapt o astfel de explicitare nu era necesara deoarece, asa cum am mai spus, implicit, la calculul expresiilor intai se efectueaza operatiile multiplicative si apoi cele aditive.

De asemenea in cadrul programului apar frecvent ca argument al metodei System.out.println expresii cu rezultat de tip String cum ar fi de exemplu:

“Motanul Pacepa cade in masina automata de ” + “spalat rufe“; Aici simbolul + desemneaza operatia de concatenare a sirurilor de caractere,

avand ca rezultat un String format din inlantuirea celor doi operanzi de tip String. Data de tip String rezultata este tramnsmisa ca argument metodei System.out.println.

Prezinta interes si expresiile de forma String + int in care cei doi operanzi sunt de tipuri diferite. In acest caz, in baza unor reguli precise de conversie automata si de compatibilitate a tipurilor de date pe care le vom discuta in detaliu la momentul

Page 29: programare in Java

PROGRAMARE IN JAVA - Note de curs 31

potrivit, termenul de tip int este convertit la tipul String dupa care se aplica operatorul de concatenare. Astfel in secventa: ... int greutate = 3; System.out.println(“Motanul Pacepa cintareste “ + greutate); ... expresia

“Motanul Pacepa cintareste “ + greutate este evaluata astfel: 1. termenul greutate care este o variabila de tip int este inlocuit cu valoarea continuta

adica 3. 2. Deoarece celalalt termen al expresiei este de tip String, aceasta valoare intreaga

este convertita automat la valoarea de tipul String “3”. 3. Cei doi termeni de tip String, “Motanul Pacepa cintareste “ si “3” sunt

concatenati rezultand sirul “Motanul Pacepa cintareste 3” care este transmis pentru afisare metodei println a obiectului System.out.

Operatori pentru date de tip intreg In exemplul precedent am vazut o serie de operatori care actioneaza asupra

datelor de tip intreg (+, -, *, /). Pe langa acestia, limbajul Java ofera programatorului urmatorii operatori:

operatorul modulo reprezentat in expresii prin simbolul %. Acest operator binar calculeaza restul impartirii intregi a primului operand la cel de al doilea operand. Astfel expresia 14 % 3 are ca rezultat valoarea 2 (restul impartirii lui 14 la 3).

operatorii unari de incrementare reprezentati prin ++ si decrementare reprezentati prin -- au ca efect incrementarea respectiv decrementarea operandului cu 1. Acesti operatori accepta ca operanzi numai variabile de tip intreg. Actiunea acestor operatori depinde de locul pe care il ocupa in raport cu operandul. Daca operatorul de incrementare/decrementare se afla inaintea operandului (il prefixeaza) operandul este incrementat/decrementat iar noua valoare este folosita la evaluarea expresiei. In acest caz operatia efectuata asupra operandului se numeste preincrementare/predecrementare. Astfel daca variabila x contine valoarea 3, continutul lui x dupa aplicarea operatorului ++ este 4 iar valoarea rezultata din evaluarea expresiei ++x este 4, aceasta expresie fiind echivalenta cu expresia x = x + 1. Similar stau lucrurile si in cazul predecrementarii lui x. Expresia --x are valoarea 2, iar x va contine valoarea 2 dupa aplicarea operatorului de predecrementare. Expresia –x este echivalenta cu expresia x = x – 1. Cu totul altfel stau lucrurile in cazul in care operatorul de incrementare/decrementare se afla amplasat dupa operand (il postfixeaza). In acest caz operatia se numeste postincrementare/postdecrementare. In acest caz la evaluarea expresiei se foloseste valoarea continuta de operand. Abia dupa ce expresia a fost evaluata continutul variabilei careia i s-a aplicat operatia de postincrementare/postdecrementare este marit/micsorat cu 1. Astfel daca variabila x contine valoarea 3, continutul lui x dupa aplicarea operatorului ++ este 4 iar valoarea rezultata din evaluarea expresiei x++ este 3 deoarece la evaluarea ei s-a folosit valoarea continuta de x. Abia dupa ce expresia a fost evaluata se incrementeaza x. Similar, dupa evaluarea expresiei x—valoarea rezultata este 3 iar variabila x va contine valoarea 2.

Page 30: programare in Java

32 CURS 3

Foarte des in programe apar atribuiri de forma:

<variabila x> = <variabila x><op><expresie> unde <op> este unul din operatorii +, -, *, / sau % , cum ar fi de exemplu x= x + 2 sau greutate = greutate + (greutate*12). Limbajul Java ofera posibilitatea de a scrie simplificat astfel de expresii folosind operatorii += , -=, *=, /= si %=. Astfel in loc de x= x+2 vom putea scrie x+=2 iar inloc de greutate = greutate + (greutate*12) putem scrie greutate+= (greutate*12).

Precedenta Operatorilor Asa cum am mai spus expresiile numerice se evalueaza de la stanga spre

dreapta, intai efectuandu-se operatiile multiplicative (*, /,%) iar apoi cele aditive (+,-). Astfel de exemplu ordinea efectuarii operatiilor la evaluarea expresiei

6 * 2 / 4 + 5 * 3 este prezentata in figura 3.3. Trebuie avute in vedere urmatoarele aspecte:

1. Daca se codifica o expresie algebrica in program trebuie avuta in atentie ordinea de efectuarea operatiilor. Astfel de exemplu pentru a codifica expresia:

vom fi tentati sa scriem:

x / y * z

zyx⋅

Figura 3.3 – Ordinea de efectuare a operatiilor si precedenta operatorilor ceea ce este incorect. La evaluare se va imparti variabila x la variabila y iar rezultatul obtinut se va inmulti cu variabila z. Aceasta ar corespunde de fapt expresiei algebrice:

zyx

O varianta de codificarea corecta in program este expresia:

Page 31: programare in Java

PROGRAMARE IN JAVA - Note de curs 33

x / y / z

impartind pe x la y si rezultatul astfel obtinut la z. O alta varia nta ar fi explicitarea ordinii dorite a efectuarii operatiilor prin folosirea parantezelor:

x / ( y * z )

In acest caz se efectueaza intai operatiile din paranteze adica se inmulteste y cu z iar apoi variabila x este impartita la rezultatul astfel obtinut.

2. Neacordarea atentiei cuvenite ordinii de efectuare a operatiilor poate duce uneori la rezultate eronate. Astfel avand in vedere ca expresia 2/3 fiind o expresie cu termeni intregi, va avea si valoarea rezultata din evaluare tot o valoare intreaga. Astfel valoarea rezultata este 0. Avand deci in vedere ca expresia n/m (unde n si m sunt date de tip intreg ) are valoarea 0 daca n<m sa codificam acum expresia a carei valoare este evident 8:

1232

Daca aceasta expresie o codificam in program:

2 / 3 * 12

desi totul pare corect, rezultatul obtinut va vi 0 deoarece operatiile efectuandu-se una cate una, de la stanga spre dreapta, impartirea 2/3 ne da rezultatul 0 care inmultit cu 12 ne va da tot 0 in loc de rezultatul corect 8. Putem evita astfel de situatii reordonand termenii expresiei:

2 * 12 / 3 Acum la evaluare se va calcula intai produsul 2*12 iar rezultatul 24 se va imparti apoi la 3 obtinandu-se 8 adica rezultatul corect.

Implementarea ciclurilor cu contor In rezolvarea unor probleme apare frecvent necesitatea repetarii unei procesari

asupra unui numar cunoscut de seturi de date. Solutia consta in utilizarea unei variabile care sa contorizeze seturile de date procesate deja. Dupa fiecare procesare aceasta variabila este incrementata astfel ca in functie de valoarea acesteia se poate determina daca mai sunt date de procesat. Astfel daca variabila contor a depasit numarul cunoscut de seturi de date ce trebuiau procesate ciclul se termina.

Pentru a implementa un astfel de ciclu trebuie sa stabilim urmatoarele: numele variabilei contor valoarea initiala a variabilei contor valoarea cu care variabila contor este incrementata/decrementata dupa

fiecare ciclare (pasul) valoarea finala a variabilei contor, care odata atinsa determina terminarea

ciclarii (conditia de iesire din ciclu)

Page 32: programare in Java

34 CURS 3

Ca exemplu sa proiectam algoritmul care asigura calculul si afisarea valorilor de la 1 la 10. Schema logica a algoritmului este redata in figura 3.4.

Figura 3.4 – Ciclu cu contor

Acest algoritm poate fi implementat in Java folosind structura de control

repetitiva while ca in programul din exemplul de mai jos:

class CicluCuContor{ public static void main(String[] arguments){

int contor = 1; while(contor <= 10){

System.out.println(contor); contor++;

} } }

Asa cum am stabilit, variabila contor folosita in programul nostru pentru a tine evidenta numarului de procesari a fost initializata cu valoarea 1.

La fiecare procesare valoarea acestei variabile este afisata dupa care aceasta este incrementata cu 1 (pasul de incrementare este 1). Procesarea se repeta pana cand variabila contor atinge valoarea 11, momentul in care conditia de ciclare contor<= 10 nu mai este satisfacuta.

Tinand cont de proprietatile operatorului de postincrementare, programul poate fi simplificat comasand instructiunile: ...

.println(contor); System.outcontor++; ... intr-o singura instructiune: ... System.out.println(contor++); ...

Page 33: programare in Java

PROGRAMARE IN JAVA - Note de curs 35

Intr-adevar, aici, fiind folosit operatorul de postincrementare, valoarea variabilei contor este intai afisata si abia dupa aceia aceasta este incrementata. Programul in aceasta versiune devine ceva mai compact: class CicluCuContor{

public static void main(String[] arguments){ int contor = 1; while(contor <= 10)

System.out.println(contor++); } }

Programul va fi editat in fisierul CicluCuContor.java. Comenzile de compilare

si executie a programului continut de acest fisier precum si rezultatele afisate sunt redate in figura 3.5.

Figura 3.5 - Compilarea si executia aplicatiei CicluCuContor.java

Sumar Pe parcursul acestui curs am tratat problemele fundamentale privind tipurile

elementare de date ale limbajului Java, declararea variabilelor, operatori si utilizarea lor in expresii.

Tipurile de date discutate au fost int, float, char, String si boolean. Au fost prezentati operatorii aritmetici +, -, *, /, %, ++, --, +=, -=, *=, /= si

%=, operatorul de concatenare al sirurilor +, utilizarea parantezelor ( ) si operatorul de atribuire =.

S-a discutat de asemenea ordinea de efectuare a operatiilor si precedenta operatorilor in evaluarea expresiilor.

S-a prezentat structura si implementarea unui ciclu cu contor folosind structura repetitiva while.

Exercitii 1. Determinati valorile fiecarei variabile dupa efectuarea calculelor. Se

presupune ca initial fiecare variabila contine valoarea 10: a) produs *= x++; b) fractie /= ++x; 2. Gasiti si corectati erorile din instructiunile de mai jos: a) while(c <= 10){

produs *= c; ++c;

Page 34: programare in Java

36 CURS 3

b) if(x > 10) X += 3++;

c) int x = 1, total; while(x <= 10){

total += x; ++x;}

3. Ce va afisa programul urmator: class Mister{

public static void main(String[] arguments){ int y, x =1, total = 0; while(x <= 10){ y= x*x;

System.out.println(y); total +=y; ++x;

} System.out.println(“Total =”+total); } }

Page 35: programare in Java

PROGRAMARE IN JAVA - Note de curs 61

Curs 6

Generarea numerelor aleatoare Vom studia in continuare metoda random a clasei Math. Aceasta metoda genereaza

o valoare aleatoare de tip double, n ∈[0,1). Programul urmator genereaza 10 astfel de valori aleatoare: class RandomTest{ public static void main(String[] arguments){ for(int i=1;i<=10;i++) System.out.println(Math.random());

} }

Programul va fi editat in fisierul RandomTest.java. Comenzile de compilare si executie a programului continut de acest fisier precum si rezultatele afisate sunt redate in figura 6.1.

Figura 6.1- Compilarea si executia aplicatiei RandomTest.java

De regula, valorile generate de aceasta metoda nu se gasesc in plaja de valori dorita de programator. De exemplu pentru a simula rezultatele aruncarii unui zar, valorile trebuie sa fie intregi si sa se gaseasca in domeniul [1,6]. Solutia in acest caz este de a calcula valorile necesare pe baza valorilor aleatoare generate de metoda random. In cazul zarului noile valori se vor calcula cu instructiunea: ... val = 1+(int) (Math.random()*6); ... Numarul 6 folosit pentru ajustarea valorii generate de metoda random se numeste factor de scalare. Programul urmator va simula “aruncarea“ de 10 ori a unui zar.

Programul va fi editat in fisierul Zar.java. Comenzile de compilare si executie a programului continut de acest fisier precum si rezultatele afisate sunt redate in figura 6.2.

Page 36: programare in Java

62 CURS 6 class Zar{ public static void main(String[] arguments){ int val; for(int i=1;i<=10;i++){ val = 1+(int) (Math.random()*6); System.out.println(val); } } }

Figura 6.2- Compilarea si executia aplicatiei Zar.java Pentru a ne convinge ca probabilitatea de aparitie a oricarui numar din cele 6 posibile este egala, vom executa programul urmator care simuleaza aruncarea de 6000 de ori a unui zar si calculeaza frecventa de aparitie a fiecarei valori. class ZarTest{ public static void main(String[] arguments){ int val; int frecventa1=0,frecventa2=0, frecventa3=0,frecventa4=0, frecventa5=0,frecventa6=0; for(int i=1;i<=6000;i++){ val = 1+(int) (Math.random()*6); switch(val){ case 1: frecventa1++; break; case 2: frecventa2++; break; case 3: frecventa break;

3++;

case 4: frecventa4++; break; case 5: frecventa5++; break; case 6: frecventa break;

6++;

} } System.out.println(“val\tfrecventa”); System.out.println(1+”\t”+frecventa1); System.out.println(2+”\t”+frecventa2); System.out.println(3+”\t”+frecventa3); System.out.println(4+”\t”+frecventa4); System.out.println(5+”\t”+frecventa5); System.out.println(6+”\t”+frecventa6); } }

Page 37: programare in Java

PROGRAMARE IN JAVA - Note de curs 63

Programul va fi editat in fisierul ZarTest.java. Comenzile de compilare si executie a programului continut de acest fisier precum si rezultatele afisate sunt redate in figura 6.3.

Figura 6.3- Compilarea si executia aplicatiei ZarTest.java

Analizand aceste rezultate observam ca frecventa de aparitie valorilor este aproximativ egala ( circa 1000 de apritii ale fiecarei valori). De asemenea, ruland de mai multe ori programul Zar.java constatam ca de fiecare data obtinem o alta secventa de 10 numere aleatoare.

Constante si variabile globale Rescriind programul din exemplul anterior sub forma modulara am obtinut: class ZarTest{ static int frecventa1=0,frecventa2=0, frecventa3=0,frecventa4=0, frecventa5=0,frecventa6=0; static final int ARUNCARI=6000;

static void simulareZar(int n){ int val;

for(int i=1;i<=n;i++){ val = 1+(int) (Math.random()*6); switch(val){ case 1: frecventa1++; break; case 2: frecventa2++; break; case 3: frecventa3++; break; case 4: frecventa4++; break; case 5: frecventa5++; break; case 6: frecventa6++; break; } } } static void afisare(){ System.out.println("val\tfrecventa"); System.out.println(1+"\t"+frecventa1); System.out.println(2+"\t"+frecventa2); System.out.println(3+"\t"+frecventa3); System.out.println(4+"\t"+frecventa4); System.out.println(5+"\t"+frecventa5); System.out.println(6+"\t"+frecventa6); }

Page 38: programare in Java

64 CURS 6 public static void main(String[] arguments){ simulareZar(ARUNCARI); afisare(); } } Aici metoda principala main apeleaza doua metode “subalterne”, simulareZar si afisare. Metoda simulareZar simuleaza aruncarea zarului de n ori si calculeaza frecventele de aparitie ale valorilor 1..6. Numarul de aruncari n este parametrul metodei simulareZar. Metoda afiseaza frecventele de aparitie ale valorilor 1..6 calculate de metoda simulareZar. Aici apar doua elemente noi. Frecventele de aparitie sunt contorizate de variabilele de tip int frecventa1...frecventa6. Daca aceste variabile ar fi fost locale metodei simulareZar ele nu ar fi acesate de metoda afisare si reciproc, daca ar fi fost declarate in corpul metodei afisare nu ar fi putut fi incrementate de metoda simulareZar fiindu-i necunoscute acesteia. Solutia a constat in declararea lor in afara corpului vreuneia din metodele clasei ZarTest devenind astfel variabile globale pentru metodele clasei ZarTest. Ele constitue de fapt o zona comuna de memorie pentru toate metodele clasei in cadrul careia au fost declarate. Spunem ca ele sunt vizibile pentru toate metodele clasei in care au fost declarate. Acelasi lucru este valabil si pentru variabila de tip int ARUNCARI. La declararea acesteia s-a folosit pe langa declaratia de tip si cuvantul rezervat Java final. Acesta o transforma intr-o variabila read-only adica intr-o constanta. Valoarea unei astfel de variabile se stabileste la declarare si nu mai poate sa fie modificata in timpul executiei programului. De regula, pentru claritatea programului, (desi nu este obligatoriu) astfel de variabile sunt denumite folosind majuscule pentru a pune in evidenta ca au o valoare fixata – ca sunt constante. Ca si metodele, variabilele globale din cadrul clasei ZarTest au fost declarate statice din motive pe care le vom discuta ulterior. Structura functionala a aplicatiei ZarTest.java poate fi reprezentata ca in figura 6.4.

Figura 6.4- Reprezentarea structurii functionale a aplicatiei ZarTest.java

In aceasta figura au fost reprezentate atat modulele de cod care coopereaza la realizarea procesarilor necesare cat si blocul de memorie comuna repartizata pentru variabilele globale ale aplicatiei.

Page 39: programare in Java

PROGRAMARE IN JAVA - Note de curs 65

Variabile automate O variabila este creata in momentul declararii acesteia. La creerea variabilei ei i

se aloca un bloc de memorie dimensionat sa pastreze date de tipul specificat la declararea variabilei. Acest bloc de memorie este asociat de catre compilator cu identificatorul (numele) variabilei si este accesat (inscris/citit) prin intermediul acestuia. Variabilele locale ale unei metode sunt create in momentul in care aceasta este apelata si incepe sa fie executata. Ele insa sunt distruse prin eliberarea blocurilor de memorie alocata, incetand sa mai existe in momentul in care executia metodei inceteaza, la revenirea in metoda apelanta. Astfel de variabile care au o durata de viata limitata se numesc automate. Ele sunt create automat la executia blocului de instructiuni care constitue corpul metodei in care au fost declarate si sunt distruse automat la iesirea din blocul respectiv de instructiuni. Acest mecanism este valabil nu numai pentru variabilele declarate in corpul unei functii ci si pentru orice variabila declarata in cadrul unui bloc de instructiuni ( incadrat de acolade ). Denumim domeniu de existenta al variabilei portiunea de program in care aceasta variabila exista si poate fi accesata. Ca o consecinta a acestui mecanism valorile continute de variabilele locale nu se pastreaza de la un apel la altul al metodei nefiind garantat ca la al doilea apel i se va aloca acelasi bloc de memorie ca la primul apel sau ca acel bloc nu a fost intre timp folosit de o alta variabila la executia unei alte metode. Din acest motiv valoarea continuta de o variabila automata la creere este arbitrara si trebuie sa fie explicit initializata inainte de a fi folosita.

Portiunea de program in care o variabila poate fi accesata se numeste domeniu de vizibilitate a variabilei. Acest domeniu este stabilit de cateva reguli stricte. Astfel, o variabila declarata in cadrul unui bloc de instructiuni nu este vizibila din afara blocului fiind insa vizibila din interiorul blocurilor de instructiuni incuibate in blocul respectiv.

Recursivitate Existenta variabilelor automate permite implementarea in limbajul java

recursivitatii. Acest mecanism consta in apelarea succesiva a unei metode de catre ea insasi. Asa cum o metoda poate apela o alta metoda, o metoda se poate apela pe ea insasi. Acest lucru este posibil numai daca in urma acestui apel variabilele sale nu sunt suprascrise prin executarea din nou a codului metodei astfel ca la revenirea din apel, variabilele locale ale metodei apelante nu mai contin valorile pe care le contineau inainte de apel. Deoarece, asa cum am vazut variabilele automate sunt create la apelul metodei si distruse la terminarea executiei, fiecare apel recursiv va crea un nou set de variabile locale propriu. Astfel codul metodei apelate va prelucra propriul set de variabile fara a altera valorile setului de variabile proprietate a metodei apelante. Astfel, desi codul executat la fiecare apel recursiv este acelasi, memoria de date prelucrata de fiecare apel este diferita.

Sa exemplificam cele prezentate mai sus prin rezolvarea urmatoarei probleme: Sa se elaboreze un program care sa calculeze si sa afiseze valoarea

factorialului numarului 10. Factorialul este definit prin produsul:

n!=1 ⋅ 2 ⋅... (n-1) ⋅ n

Page 40: programare in Java

66 CURS 6

O prima abordare ar fi utilizarea unui algoritm iterativ la calcularea acestui produs. Algoritmul propus este reprezentat prin schema logica din figura 6.5

Figura 6.5- Algoritmul iterativ de calcul al factorialului Programul Java care implementeaza acest algoritm folosind o structura

repetitiva for este: class FactorialIterativ{

rial(int n){ static int facto int fact=1;

10;k++) for(int k=1;k<= fact *= k;

eturn fact; r } public static void main(String[] arguments){

ystem.out.println(“10! =”+factorial(10)); S}

} Programul va fi editat in fisierul FactorialIterativ.java. Comenzile de compilare si

executie a programului continut de acest fisier precum si rezultatele afisate sunt redate in figura 6.6.

Figura 6.6- Compilarea si executia aplicatiei FactorialIterativ.java Sa vedem acum in figura 6.7 cum arata schema logica a algoritmului recursiv de

calcul al factorialului. Acest algoritm se bazeaza pe faptul ca formula de calcul a factorialului poate fi scrisa si intr-o forma recurenta:

Page 41: programare in Java

PROGRAMARE IN JAVA - Note de curs 67

n! = (n-1)! ⋅ n

Figura 6.7- Algoritmul recursiv de calcul al factorialului Algoritmul se bazeaza pe urmatorul mecanism. Doresc spre exemplu sa calculez

3!. Intrucat n==3 este diferit de 1, voi calcula 3! ca fiind produsul dintre 3 si factorialul lui (3-1) . Pentru a efectua acest produs trebuie sa calculez mai intai cat este (3-1)!. Pentru aceasta aplic acelasi algoritm. Deoarece 3-1 este 2 iar 2 este diferit de 1, voi calcula (3-1)! ca fiind produsul dintre 2 si (2-1)!. Pentru a efectua acest produs trebuie sa calculez mai intai cat este (2-1)!. Pentru aceasta aplic acelasi algoritm. Intrucat 2-1 este egal cu 1, conform algoritmului, (2-1)! este 1. Acum pot sa ma intorc la calculul produsului precedent si sa il efectuez. Obtin (3-1)!=2*1=2. Avand aceasta valoare pot sa ma intorc cu inca un pas si sa calculez produsul 3*(3-1)! = 3*2=6. Aceasta este valoarea rezu

Pltata pentru 3!. Intr-adevar 3!=3*2*1.

acest algoritm folosind o structura de

){

*factorial(n-1);

rogramul Java care implementeazaselectie if/else este: class FactorialRecursiv{ static int factorial(int n int fact; if(n==1) fact=1; else

fact=n System.out.println(n+"! ="+fact); return fact; }

public static void main(String[] arguments){ factorial(10); } }

Programul va fi editat in fisierul FactorialRecursiv.java. Comenzile de compilare si executie a programului continut de acest fisier precum si rezultatele afisate sunt redate

Page 42: programare in Java

68 CURS 6 in figura 6.8. Analizand rezultatele afisate de acest program vedem ca metoda factorial s-a autoapelat de 10 ori si abia dupa aceia, cand n a devenit 1 a inceput sa

intoarca v alculat complet (1!) si terminand cu ultimul (10!).

e baza celor doua exemple prezentate putem face o comparatie intre algoritmii

realizeaza explicit prin utilizarea unei structuri repetitive. In

irea spatiului de memorie maxim repartizat programului (prin alocari succesive

nt mai lenti (bazandu-se pe apeluri repetate a unei unei

ritmii iterativi. Algoritmii recursivi sunt mai periculosi putand duce la blocarea intregului

alorile calculate care au fost afisate incepand cu primul factorial c

Figura 6.8- Compilarea si executia aplicatiei FactorialRecursiv.java

Precursivi si cei iterativi.

Algoritmii iterativi se construiesc folosind structuri de control repetitive

(for, while, do/while); Amandoua categoriile de algoritmi se bazeaza pe repetarea unei anumite

procesari pana la obtinerea rezultatului dorit. in cazul algoritmilor iterativi aceasta secazul algoritmilor recursivi repetarea se realizeaza prin apeluri succesive ale metodei.

Atat in cazul algoritmilor iterativi cat si in cazul celor recursivi incetarea repetitiei se face in urma unui test de terminare. La algoritmii iterativi repetarea se termina cand invariantul ciclului capata valoarea false. Recursia se incheie cand este detectat un caz de baza pentru care se cunoaste valoarea ce trebuie returnata. In aceste conditii, in cazul unor erori de logica in proiectarea algoritmului, este posibil ca atat ciclul iterativ cat si recursia sa fie infinite, sa nu se termine niciodata. La implementarea unui astfel de algoritm programul care il implementeaza se va “bloca” fiind executat la nesfarsit daca algoritmul este iterativ sau va duce la depas

de memorie - la fiecare recursie - pentru noi seturi de variabile locale). Din cele de mai sus putem trage concluzii:

Algoritmii recursivi sumetode operatie ce necesita un timp relativ lung in comparatie cu executiainstructiuni de calcul)

Algoritmii recursivi solicita mai multa memorie decat algo

sistem prin depasirea spatiului de memorie maxim admis.

Page 43: programare in Java

PROGRAMARE IN JAVA - Note de curs 69

In aceste conditii, algoritmii iterativi suunei probleme date e va apela la un a

nt preferabili celor recursivi. La rezolvarea n

ica s ca parametri (parametrii actuali ai metodei)catre care din

ula a

a rea a 2-a a unei valori. Una din aceste metode are ca

un rezultat de tip int. A doua metoda patrat ble si intoarce un rezultat de tip double.

float b=2.5;

Programul va fi editat in fisierul Supradef ire.java. Comenzile de compilare si executie a pr afisate sunt redate in figura 6.9.

s lgoritm recursiv numai in cazul in care ualgoritm iterativ nu este evident.

Supradefinirea metodelor Limbajul Java permite ca mai multe metode sa fie declarate folosind acelasi identificator (nume) cu conditia ca ele sa fie diferentiate prin setul de parametri(parametrii metodelor respective sa difere ca numar, ordine sau tip). Acest mecnism se numeste supradefinire. La apelul metodei, compilatorul Java va identifdupa setul de valori transmimetodele cu acelasi nume sa directeze apelul. Supra definirea se foloseste de regpentru a declara un grup de metode care efectueaza aceeasi proocesare dar asuprunor date de tipuri diferite. In programul din exemplul urmator cele doua metode numite patrat realizeazoperatia de ridicare la puteparametru o valoare de tip int si intoarceare ca parametru o valoare de tip douclass Supradefinire{

){ static int patrat(int x return x*x;

} static double patrat(double x){ return x*x; } nts){ public static void main(String[] argume int a=5; System.out.println(a ”^2 =”+patrat(a));

System.out.println(b ”^2 =”+patrat(b)); } }

inogramului continut de acest fisier precum si rezultatele

Figura 6.9- Compilarea si executia aplicatiei Supradefinire.java

Structuri de date Programele elaborate de noi pana acum rezolvau probleme de natura

computationala, prelucrand date ce modelau obiecte specifice acestui domeniu. Calculatoarele pot insa prelucra informatii apartinand nu numai domeniului matematici. Exista astfel programe de gestiune a bazelor de date care prelucreaza informatii privind inventarul unor obiecte sau gestioneaza personalul unei intreprinderi clientii unei firme sau pacientii unui spital. Datele prelucrate de astfel de

Page 44: programare in Java

70 CURS 6 programe au un caracter eterogen trebuind sa modeleze caracteristicile specifice obiectelor sau persoanelor reale. Aceste caracteristici pot exprimate folosind tipurile de date primitive deja studiate. De exemplu informatia privind un obiect de inventar ar putea cuprinde urmatoarele denumirea, valoarea de inventar, numarul de inventar. In cazul gestiunii stocurilor informatia ar consta din denumire, cantitate, pret unitar. In cazul unui salariat informatia cu privire la acesta ar putea fi numele, varsta, vechimea, functia, adresa, telefon, starea civila, salariul, numarul legitimatiei, numarul de ore lucrate. Toate aceste date se pot exprima folosind tipurile de date cunoscute. Astfel pentru date de gen nume, denumire, adresa, telefon, numar de inventar, numar legitimatie se poate folosi tipul String. Pentru numar de ore lucrate, vechime, varsta se poate folosi tipul int. Pentru salariu si pentru cantitate poate fi folosit ipul double. Pentru o mai buna claritate din punct de vedere conceptual a programelor, toate datele privind un obiect de inventar, un material din stoc sau o persoana pot fi grupate intr-o singura entitate care poate fi manipulata de program ca un tot unitar, similar tipurilor primitive de date. O astfel de grupare cu structura eterogena (formata din date de tip

. Ea modeleaza in program, cu precizia necesara, nventar, materialului sau a persoanei. In Java gruparea

ace in cadrul unui modul separat prin declaratiei de mai jos:

String culoare; St

nui obiect real intr-o data de acest tip este nevoie de un bloc de memorie sufici

ensiunii locatiilor de memorie necesare memorarii tuturor componentelor struct ii).

eclararea unei variabile de tipul respectiv nu duce si la aloca memoriei necesare stocarii informatiilor privind atributele obiectului. Astfel

null deoarece inca nu refera nici un bloc de memorie. Alocarea unui ploc de memorie

diferit) se numeste structura de datectului de icaracteristicile obie

unor date intr-o structura se poate fplulunei clase ca in exem

public class Automobil{ String tip; int capacitate;

int nr_usi; ring nr_circulatie; String serie_sasiu; boolean inmatriculat; }

Declaratia clasei ca fiind publica permite accesul unei metode din exteriorul modulului la datele “incapsulate” in ea. Aceasta declaratie de clasa nu se refera de fapt la un automobil anume, continand doar caracteristicile specifice obiectelor din categoria automobilelor. Ea descrie de fapt atributele unei clase de obiecte, ceea ce justifica utilizarea cuvantului rezervat class si constitue de fapt definirea unui tip nou de data care modeleaza un obiect din lumea reala. Aceasta descriere este folosita la alocarea de memorie cand se creeaza date de acest tip. Evident ca pentru a memora atributele u

ent de mare pentru a putea stoca toata informatia privind obiectul. Dimensionarea acestui bloc se face pe baza descrierii facute in declaratia clasei (prin sumarea dim

urIn cazul claselor, d

readeclaratia:

Automobil masinaMea;

nu va aloca memorie pentru stocarea datelor privind tipul, culoarea, etc. autoturismului meu. Aceasta deoarece variabila masinaMea nu este chiar bloculd de memorie ci o referinta la un bloc de memorie. initial aceasta variabila are valoarea

Page 45: programare in Java

PROGRAMARE IN JAVA - Note de curs 71

se face cu operatorul new. Acesta are ca argument tipul de data care urmeaza sa fie continut de blocul de memorie alocat. Operatorul va intoarce referinta la blocul de

emorie dimensionat pe baza tipului specificat ca argument. Un astfel de mod de a. Deci pentru a aloca memorie pentru

pastrarea atributelor autoturismului meu, trebuie folosita secventa :

ul la elementele structurii se face prin numele variabilei referinta urmat de

utului accesat:

aMea.tip = “Dacia”;

emorie nu mai este necesar, prin simpla atribuire a

ea structurilor de date este prezentat in doua module. Primul modul consta din clasa Automobil ava:

String marca;

constituit din clasa Structura care contine metoda main(). Codul

ts){ ();

tem.out.println("capacitate:"+masinaMea.capacitate);

bserva din listing ca la comanda de compilare a fisierului Structura.java, compilatorul a detectat referirea la clasa Automobil. Ca urmare a cautat fisierul Automobil.java in directorul curent (.\\)si l-a compilat automat ca facand parte din aplicatie.

malocare a memoriei se numeste alocare dinamic

Automobil masinaMea= new Automobil();

accesoperatorul deacces si numele atrib ... asinmmasinaMea.culoare=”Alb13”; masinaMea.capacitate = 1300; ... Alocarea dinamica de memorie asigura folosirea rationala a acesteia. Astfel, in momentul in care blocul de mvalorii null variabilei referinta, acesta este disponibilizat putand fi alocat pentru satisfacerea altor cereri. De asemenea blocul de memorie este automat eliberat la distrugerea variabilei referinta. Programul care exemplifica definirea si utilizarcontinuare. El este alcatuit dindefinita in fisierul Automobil.j blic class Automobil{ pu int capacitate; String culoare; } Al doilea modul este acestei clase este continut de fisierul Structura.java: class Structura{

en public static void main(String[] argum Automobil masinaMea = new Automobil masinaMea.marca="Dacia";

masinaMea.capacitate = 1300; masinaMea.culoare="Alb 13"; System.out.println("Masina mea");

System.out.println("marca:"+masinaMea.marca); ysS System.out.println("culoare:"+masinaMea.culoare); } }

Comenzile de compilare si executie a programului continut de aceste fisiere precum si rezultatele afisate sunt redate in figura 6.10.

Se o

Page 46: programare in Java

72 CURS 6

Figura 6.10- Compilarea si executia aplicatiei Structura.java

Sumar In cadrul acestui curs am abordat urmatoarele probleme

utilizarea metodei random a clasei Math pentru generarea numerelor aleatoare. S-a prezentat modul de scalare a acestor valori pentru a se incadra intr-un anumit domeniu. Am elaborat un program prin care am testat repartizarea uniforma a probabilitatii de aparitie a valorilor generate de functia random.

am abordat aspecte legate de domeiul de definitie si de vizibilitate a variabilelor. s-au discutat tipurile de variabile automate, globale si constante. s-a definit si exemplificat modul proiectarea algoritmilor recursivi si s-au

comparat performantele acestora in comparatie cu algoritmii iterativi s-a prezentat mecanismul de supradefinire a metodelor in limbajul java si s-a

exemplificat utilitatea acestui mecanism. in finalul cursului s-a definit conceptul de structura de date si s-a exemplificat

modul de implementare si utilizare a structurilor de date in limbajul Java. S-a prezentat si operatorul new si mecanismul alocarii dinamica a memoriei.

Exercitii 1. Elaborati un program Java care calculeaza si afiseaza volumul a 10 sfere folosind

instructiunea: ... volum = (4/3)*Math.PI*Math.pow(raza,3); ... Valorile razei vor fi aleatoare, situandu-se in domeniul [5,25]. 2. Pentru fiecare set de numere de mai jos scrieti cate o singura instructiune care

genereaza la intamplare si afiseaza unul din numerele setului. a) 2, 4, 6, 8, 10; b) 3, 5, 7,9, 11; c) 6, 10, 14, 18, 22; 3. Elaborati o metoda int putereIntreaga(int baza, int exponent) care calculeaza si

intoarce valoarea : bazaexponent

De exemplu putereIntreaga(3,4) calculeaza si intoarce valoarea 3*3*3*3 = 91.

Page 47: programare in Java

PROGRAMARE IN JAVA - Note de curs 85

Curs 8

Tablourile Java prevede o clasa “prefabricata” Array (tablou) de obiecte care

implementeaza o structura de date compusa din mai multe elemente de acelasi tip. Clasa Array defineste de fapt un grup de locatii succesive de memorie in care vor fi stocate date. Accesul la o anumita locatie se face prin intermediul numelui tabloului si a unui index intreg cuprins intre paranteze drepte, care specifica numarul locatiei la care se face accesul:

<nume tablou>[<index>] In figura 8.1 este prezentat un exemplu al unui tablou cu 10 elemente.

Figura 8.1- Structura unui tablou cu 10 elemente Pe langa memoria alocata elementelor, obiectul tablou mai are in componenta

variabila membru length (lungime) continand dimensiunea (numarul de elemente) tabloului. astfel in cazul tabloului din exemplul nostru, x.length contine valoarea 10.

Sintaxa instructiunii de declarare a unui tablou este: <tip> <nume>[] = new <tip>[<lungime>];

Pentru a declara tabloul din exemplul de mai sus vom folosi deci instructiunea: ... int x[] = new int[10]; ...

Page 48: programare in Java

86 CURS 8 Aceasta instructiune contine de fapt in prima parte declararea unei variabile de tip referinta la un obiect tablou iar in a doua parte alocarea de memorie pentru acest obiect. Putem deci sa descompunem instructiunea in doua: ... int x[];// declararea variabilei de tip refrinta la un tablou de int x = new int[10];// Alocarea de memorie ... Vom folosi un tablou reg pentru realizarea unei noi versiuni a clasei Stack elaborata in cursul precedent. De data aceasta stiva va fi implementata folosind un tablou de 4 elemente de tip double. Elementele tabloului vor corespunde celor 4 registri astfel: reg[0] - registrul x reg[1] – registrul y reg[2] - registrul z reg[3] – registrul t Metodele push, pop si top vor fi adaptate pentru a lucra cu noua structura de date: class Stack{ private double reg[]; public Stack(){// constructor stiva reg=new double[4]; for(int i=0;i<reg.length;i++) reg[i]=0.0; } public void push(double val){// ridica stiva for(int i=reg.length –1;i>0;i--) reg[i]=reg[i-1]; reg[0]=val; } public double pop(){//coboara stiva double val=reg[0]; for(int i=0;i<reg.length-1;i++) reg[i]=reg[i+1]; reg[3]=0.0 return val; } public double top(){return reg[0];}//intoarce varful stivei } Inlocuind fisierele Register.java si Stack.java cu acest modul editat intr-un nou fisier Stack.java programul CalcTest va functiona fara sa-i aducem modificari si fara a modifica nici modulul din Calculator.java. Listingul din figura 8.2 demonstraeaza acest lucru. Vom sterge in prealabil fisierele Stack.class si Register.class de pe disc. La incercarea de a executa programul CalcTest.class acesta va incarca modulul Calculator.class care la randul sau va incerca sa incarce modulul Stack.class. Deoarece nu il gaseste fiind sters, interpretorul va semnala eroarea (clasa Stack nu este definita) si isi va inceta activitatea. Acum vom edita fisierul Stack.java in noua sa versiune si il vom compila rezultand modulul Stack.class necesar functionarii modulului Calculator.class. Executand acum programul CalcTest.class acesta va functiona normal afisand aceleasi rezultatele ca si in versiunea sa precedenta.

Acest exemplu demonstreaza cat de usor pot fi facute modificari in programele proiectate orientat pe obiecte. Schimbarea unei componente cu alta mai performanta dar compatibila la nivel de interfata nu a necesitat modificari in restul programului pe care nici nu a fost nevoie nici macar sa-l mai recompilam. Operatia a decurs similar inlocuirii unui procesor pe placa de baza cu altul de acelasi tip (compatibil la nivel de pini) dar mai performant.

Page 49: programare in Java

PROGRAMARE IN JAVA - Note de curs 87

Figura 8.2- Inlocuirea modulului Stack.class in programul CalcTest.java

Referinta this La instantierea unei clase intr-un obiect acesta este referit printr-o valoare

referinta. La apelul oricarei metode aceasta primeste ca parametru implicit referinta la obiectul careia ii apartine metoda. Aceasta referinta este accesibila prin parametrul implicit this. Sa ilustram aceasta printr-un exemplu:

Fie clasa: class ThisData{ private int data; public ThisData(int val){ data = val; } public toString(){ int data=0; return “this.data =”+this.data; } } Constructorul clasei initiaza variabila data cu valoarea primita ca parametru. Metoda toString formeaza si intoarce un string continand si data setata de constructor. In cadrul metodei am declarat o variabila locala de acelasi tip int si cu acelasi nume data initializata cu 0. Aceasta “mascheaza” in mod normal variabila membru data care astfel devine inaccesibila. Metoda toString primeste insa ca parametru implicit referinta this la obiectul din care face parte. Folosind aceasta referinta pot sa accesez variabila membru data a obiectului referit de this. Iata si clasa de testare a acestui mecanism: class ThisTest{ public static void main(String[] arguments){ ThisData a=new ThisData(1), b=new ThisData(2);//crearea obiectelor a si b System.out.println(a.toString());//Afisare obiect a System.out.println(b.toString());//Afisare obiect b } } Listingul din figura 8.3 ne arata comenzile de editare, compilare si executie a acestui program.

Page 50: programare in Java

88 CURS 8

Figura 8.3- Testarea parametrului this O alta aplicatie a parametrului this este apelul in lant al metodelor unui obiect. Sa o urmarim pe un exemplu. Definim clasa Timp: class Timp{ private int ora, min, sec; // Constructori public Timp(){ setTimp(0,0,0); } public Timp(int h){setTimp(h,0,0);} public Timp(int h,int m){setTimp(h,m,0);} public Timp(int h, int m, int s){ setTimp(h,m,s);} // Metode public void setTimp(int h,int m, int s){ setOra(h);setMin(m);setSec(s);return this; } public Timp setOra(int h){ ora=((h>=0 && h<24)?h:0); return this; } public Timp setMin(int m){ min=((m>=0 && m<60)?m:0); return this; } public Timp setSec(int s){ sec=((s>=0 && s<60)?s:0); return this; } public int getOra(){return ora;} public int getMin(){return min;} public int getSec(){return sec;} public String toString12h(){ return ((ora==12 || ora==0)? 12:ora%12)+”:”+ (min<10?”0”:””)+min+”:”+(sec<10?”0”:””)+sec+ (ora<12 ?”AM”:”PM”); } public String toString24h(){ return ora+”:”+(min<10?”0”:””)+min+”:”+(sec<10?”0”:””)+sec; } } Sunt trei aspecte de subliniat cu privire la definitia acestei clase. Primul este supradefinirea constructorului Timp. Al doilea aspect priveste returnarea parametrului implicit this de catre metodele setOra, setMin si setSec. Valoarea returnata va fi folosita pentru exemplificarea inlantuirii apelurilor acestor metode. al treilea aspect priveste prevederea metodelor set si get pentru accesul variabilelor membre private. Aceste metode permit accesul din exterior la variabilele private dar metodele de tipul set efectueaza o validare a datelor inscrise in aceste variabile.

Page 51: programare in Java

PROGRAMARE IN JAVA - Note de curs 89

Modulul de test al acestei clase este: class TimpTest{ public static void main(String[] arguments){ Timp t=new Timp(); t.setOra(14).setMin(7).setSec(30);//Apelul inlantuit System.out.println(t.toString12h()); System.out.println(t.toString24h()); System.out.println(“Timp nou:\n”+ t.setTimp(20,20,20).toString12h());// Apel inlantuit System.out.println(t.toString24h()); } } Apelul inlantuit: ... t.setOra(14).setMin(7).setSec(30) ... se evalueaza de la stanga spre dreapta (operatorul de acces . se asociaza de la stanga la dreapta). Astfel intai se executa metoda t.setOra(14) . Aceasta intoarce o referinta la obiectul t astfel incat in continuare se executa t.setMin(7).setSec(30). Deci se va executa metoda t.setMin(7). Intrucat si aceasta intoarce o referinta la t, in final se va executa si t.setSec(30). Cele de mai sus sunt valabile si pentru cel de al doilea apel inlantuit din program. Listingul compilarii si executiei acestui program este redat in figura 8.4:

Figura 8.4- Apelul inlantuit al metodelor

Membrii statici ai unei clase In mod normal, am vazut ca memoria pentru instantele unei clase se aloca

dinamic, folosind explicit operatorul new. Daca totusi membrul unei clase se declara ca fiind static, compilatorul le va aloca automat memorie in momentul in care clasa este incarcata. Pe de o parte acest mecanism permite accesul acestor membri (daca sunt publici) fara a crea vre-o instanta a clasei. Este astfel cazul functiei main declarata public static. Este de asemenea cazul claselor studiate de noi, Math si System, ale caror metode pot fi apelate fara a crea instante ale acestor clase. Accesul membrilor statici ai clasei se face cu operatorul de acces “.” precedat de numele clasei ( de exemplu Math.pow(2,3)). O clasa poate sa aibe atat membri statici cat si membri dinamici. Numai membrilor statici ale clasei li se aloca memorie si numai acestia pot fi accesati prin intermediul numelui clasei. Pentru a accesa membrii dinamici trebuiesc create dinamic instante ale clasei. La crearea instantelor unei astfel de clase, membrilor dinamici ai acesteia trebuie sa li se aloce memorie folosind operatorul new. Blocul de memorie se aloca membrilor statici o singura data, la incarcarea clasei, si este comun tuturor instantelor clasei spre deosebire de blocurile de memorie alocate dinamic, proprii fiecarei instante create (figura 8.5).

Page 52: programare in Java

90 CURS 8

Figura 8.5- Alocarea statica si dinamica a memoriei pentru membrii clasei Accesul unei metode statice la variabilele dinamice ale unei instante poate fi facut folosind parametrul implicit this .

Sa exemplificam utilitatea existentei unei zone comune de memorie pentru toate instantele unei clase, prin urmatorul program:

Sa presupunem ca vrem sa scriem un joc in care o flota de nave a Rebelilor intentioneaza un atac asupra unei baza stelare a Imperiului. Rebelii stiu ca un astfel de atac are sanse de succes numai daca numarul lor de nave este mai mare sau egal cu 10. Fiecare navaa flotei Rebelilor este modelata de un obiect obtinut prin instantierea clasei Nava. Pentru a declansa atacul, fiecare nava trebuie sa cunoasca numarul de nave de care dispune flota Rebelilor. In momentul in care acest numar este mai mare sau egal cu 10 nava devine agresiva, deschizand focul asupra bazei stelare. Acest numar poate fi cunoscut tuturor navelor implicate prin prevederea unei variabile comune nrNave, accesibile fiecarei instante. Aceasta variabila este incrementata ori de cate ori in formatie soseste o noua nava (se creaza o noua instanta a clasei Nava). class Nava{ private static int nrNave=0; private String nume; public Nava(String _nume){ //Constructor nrNave++;nume=_nume; System.out.println(nume+“ in formatie”); } public void actiune(){if(nrNave >= 10) ataca();else asteapta();} private void ataca(){System.out.println(nume + “ ataca baza”);} private void asteapta(){ System.out.println(nume + “ gata de lupta”); } } class StarWars{ public static void main(String[] arguments){ Nava flota[]=new Nava[10]; for(int i=1;i<10;i++) flota[i]=new Nava(“Red”+i); for(int i=1;i<10;i++) flota[i].actiune(); flota[0]= new Nava(“Red Leader”); for(int i=0;i<10;i++) flota[i].actiune(); } }

Page 53: programare in Java

PROGRAMARE IN JAVA - Note de curs 91

Figura 8.6- Editarea, compilarea si executia programului StarWars Comenzile de editare, compilare si executie a fisierelor programului precum si rezultatele afisate sunt redate in figura 8.6. Codul de testare a clasei Nava este editat in fisierul StarWars.java. I cadrul metodei main este declarat un tablou flota de 10 elemente de tip Nava. Elementele acestui tablou sunt referinte la obiecte din clasa Nava. Elementele tabloului, initializate implicit cu null la creere, primesc valori in cadrul unui ciclu for pe masura ce noi instante ale clasei Nava sunt create. Ciclul for urmator apeleaza metoda actiune a fiecarui obiect de tip Nava astfel creat. Deoarece numarul de nave, continut de variabila comuna nrNave, este mai mic decat 10, nici una din ele nu ataca baza. Abia cand, prin “sosirea” navei amiral, numarul lor atinge valoarea necesara declansarii atacului ele intra in lupta in urma apelului in ultimul ciclu for din program a metodei actiune pentru fiecare obiect in parte.

Programarea orientata pe obiecte In aceasta sectiune vom discuta tehnologiile de baza aplicate in programarea

orientata pe obiecte (POO). Acestea sunt mostenirea si polimorfismul. Mostenirea este o forma de refolosire a modulelor deja elaborate care permite definirea de noi clase pornind de la o clasa existenta. Noile clase “mostenesc” atributele si comportamentul (variabilele si metodele) clasei de baza “imbogatindu-le” cu functiuni si atribute suplimentare ( prin adaugare de noi variabile si metode). Putem defini clase generice care doar sa specifice metodele abstracte de prelucrare a unor date generice (de tip neprecizat) fara a concretiza in ce consta aceasta prelucrare. Astfel de clase nu pot fi utile la crearea de obiecte dar pot fi folosite in calitate de clase de baza ale caror

Page 54: programare in Java

92 CURS 8 subclase sa implementeze prin supradefinire algoritmi concreti pentru metodele respective adecvati procesarii unor tipuri de date concrete. Acest mecanism este numit polimorfism. Cele doua mecanisme permit refolosirea eficienta a modulelor software deja elaborate permitand extinderea functionalitatii unei clase de baza (superclase) prin definirea noilor functiuni intr-o clasa derivata (subclasa) fara sa fie nevoie de modificarea codului superclasei. Obiectele obtinute prin instantierea unei subclase sunt considerate si obiecte ale superclasei ceea ce permite de exemple gruparea a mai multor obiecte apartinand unor subclase diferite ale aceeasi superclase intr-un singur tablou de obiecte de tipul superclasei. Aceste mecanisme deschid perspective deosebite in ingineria software in ce priveste eficienta in proiectarea si implementarea unor pachete de programe complexe. Vom analiza in continuare pe baza unor exemple folosirea celor doua mecanisme POO in elaborarea programelor.

Mostenirea Vom defini in continuare o clasa Mamifer:

public class Mamifer{ public String nume; public Mamifer(String _nume){nume = _nume;}//Constructorul public void dormi() {System.out.println(nume +“: ZZZZ ZZZZZZZZ ZZZZ”);} }

Aceasta clasa o vom folosi pentru a defini noi clase derivate care mostenesc atributele si comportamentul obiectelor descrise de aceasta clasa dar le extind cu noi atribute si actiuni posibile: public class Dulau extends Mamifer{ public Dulau(String nume){super(nume);}//Constructorul public void vorbeste(){System.out.println(nume+“: Ham!”);} } public class Motan extends Mamifer{ public Motan(String nume){super(nume);}//Constructorul public void vorbeste(){System.out.println(nume +“: Miau!”);} } public class Postas extends Mamifer{ public Postas(String nume){super(nume);}//Constructorul public void vorbeste(){System.out.println(nume +“: Posta!”);} } Constructorii aceste subclase invoca constructorul superclasei referit de super. Sa folosim aceste clase in programul urmator: class Specii{ public static void main(String[] arguments){ Dulau dulau=new Dulau (“Grivei”); Motan motan = new Motan (“Pacepa”); Postas postas = new Postas (“Nae”); System.out.println(“Intai cerem motanului sa vorbeasca:”); motan.vorbeste(); System.out.println(“Acum este momentul sa vorbeasca postasul:”); postas.vorbeste(); System.out.println(“Iar acum sa vorbeasca dulaul:”); dulau.vorbeste(); System.out.println(“Este timpul ca toti sa faca nani.”); motan.dormi();dulau.dormi();postas.dormi(); } }

Page 55: programare in Java

PROGRAMARE IN JAVA - Note de curs 93

Comenzile de compilare si executie a fisierelor programului precum si rezultatele afisate sunt redate in figura 8.7.

Figura 8.7- Comenzile de compilare si executie programului Specii Pentru definirea unei subclase nu trebuie modificat si deci nici recompilat codul superclasei. Programatorul nici nu trebuie sa dispuna de codul sursa al superclasei ci numai de modulul .class obtinut din compilarea acesteia. Sa folosim acest mecanism pentru a extinde functiunile calculatorului stiva elaborat in cursul trecut transformandu-l intr-un calculator stiintific definit de clasa CalculatorSt. Vom realiza aceasta prin adaugarea urmatoarelor operatii:CS – schimbarea semnului; INV - 1/x; SQRT - x1/2; SQR - x2; POW - xy; EXP - ex; LOG - ln(x); class CalculatorSt extends Calculator{ public CalculatorSt(){// Constructorul super();//apelul constructorului superclasei } public void cs(){s.push(-s.pop());display();} public void inv(){s.push(1/s.pop());display();} public void sqrt(){s.push(Math.sqrt(s.pop()));display();} public void sqr(){ double a= s.pop(); s.push(a*a);display(); } public void pow(){s.push(Math.pow(s.pop(),s.pop()));display();} public void exp(){s.push(Math.exp(s.pop()));display();} public void log(){s.push(Math.log(s.pop()));display();} } Se vede ca aceasta clasa foloseste in noile metode adaugate accesul la stiva s si la metoda display a superclasei Calculator. Aceasta ridica o problema deoarece acestti membri ai superclasei au fost declarati privati si deci nu pot fi accesati din afara clasei. Nu este bine sa-i declaram publici din motivele enuntate la momentul respectiv. Pentru ca totusi sa –i putem accesa din subclasele derivate se va folosi specificatorul protected. Membrii declarati protected ai superclasei vor fi accesibili din subclase si inaccesibili din afara clasei de baza si a claselor derivate din aceasta. Aceasta este singura modificare pe care trebuie sa o aducem clasei Calculator si ea se datoreste numai necunoasterii de catre noi la momentul elaborarii acesteia a specificatorului de acces protected. In mod normal superclasa trebuie astfel proiectata incat sa nu necesite modificarea si recompilarea codului sursa. Vom putea acum sa scriem un program care sa calculeze o expresie continand si functiile implementate mai sus cum ar fi:

-9 ⋅ 52 + e2⋅5

Page 56: programare in Java

94 CURS 8 Programul care efectueaza calcul acestei expresii folosind o instanta a aceastei clase derivate este: class CalcStTest{ public static void main(String[] arguments){ CalculatorSt c = new CalculatorSt();// constructor c.enter(9); c.cs();c.enter(5);c.sqr(); c.mul(); c.enter(2); c.enter(5);c.mul();c.exp();c.sum(); } } Comenzile de compilare si executie a fisierelor programului precum si rezultatele afisate sunt redate in figura 8.7.

Figura 8.8- Comenzile de compilare si executie programului CalcStTest

Polimorfismul

In sectiunea anterioara am definit clasa Mamifer al carui scop nu era creerea de obiecte ci de a servi ca baza pentru definirea unor clase derivate. Stiind ce comportament general vor avea obiectele din aceasta clasa o putem defini astfel: abstract public class Mamifer{ public String nume; public Mamifer(String _nume){nume = _nume;}//Constructorul public void dormi() {System.out.println(nume +“: ZZZZ ZZZZZZZZ ZZZZ”);} abstract public void vorbeste(); }

Stiind ca obiectele din toate subclasele derivate vor “vorbi”, numai ca o vor face fiecare in felul specific subclasei careia ii apartin, am adaugat clasei de baza metoda abstracta vorbeste fara insa a concretiza implementa algoritmul concret al acestei actiuni. Aceasta metoda abstracta va fi definita concret in fiecare subclasa derivata: public class Dulau extends Mamifer{ public Dulau(String nume){super(nume);}//Constructorul public void vorbeste(){System.out.println(nume+“: Ham!”);} } public class Motan extends Mamifer{ public Motan(String nume){super(nume);}//Constructorul public void vorbeste(){System.out.println(nume +“: Miau!”);} } public class Postas extends Mamifer{ public Postas(String nume){super(nume);}//Constructorul public void vorbeste(){System.out.println(nume +“: Posta!”);} }

Obiecte apartinand acestor subclase pot fi grupate toate intr-un tablou cu elementele de tip Mamifer. La apelul metodei vorbeste a elementelor tabloului va fi

Page 57: programare in Java

PROGRAMARE IN JAVA - Note de curs 95

automat detectata subclasa careia ii apartine obiectul si va fi apelata metoda corespunzatoare: class SpeciiNoi{ public static void main(String[] arguments){ Mamifer personaje[]=new Mamifer[3]; personaje[0]=new Dulau (“Grivei”); personaje[1]= new Motan (“Pacepa”); personaje[2] = new Postas (“Nae”); System.out.println(“Intai cerem motanului sa vorbeasca:”); personaje[1].vorbeste(); System.out.println(“Acum este momentul sa vorbeasca postasul:”); personaje[2].vorbeste(); System.out.println(“Iar acum sa vorbeasca dulaul:”); personaje[0].vorbeste(); System.out.println(“Este timpul ca toti sa faca nani.”); for(int i=0;i<3;i++) personaje[i].dormi(); } }

Programul va afisa aceleasi rezultate ca si cele obtinute de programul Specii (figura 8.7).

Clase si metode finale Am vazut ca variabilele membre ale unei unei clase pot fi declarate finale

folosind specificatorul final. In acest fel valoarea variabilei nu mai poate fi modificata ramanand constata pe tot parcursul executiei programului.

Putem aplica specificatorul final si metodelor unei clase. In acest mod aceste metode nu mai pot fi supradefinite intr-o subclasa derivata.

Metodele declarate de tip static si/sau private sunt implicit finale. Si clasele pot fi declarate finale. O clasa declarata finala nu mai poate fi folosita

ca superclasa pentru derivarea unor subclase. Cu alte cuvinte, ea nu mai poata fi mostenita.

Finalizatori Distrugerea unui obiect al unei clase se poate face prin atribuirea valorii null referintei catre acesta. Blocul de memorie alocat obiectului va fi astfel eliberat si va fi colectat de interpretor pentru a fi folosit la alte cereri de alocare de memorie. Inainte de a “muri” interpretorul va executa automat metoda finalize() care este publica si de tip void (nu intoarce nimic).

Ca si constructorul clasei aceasta nu trebuie neaparat declarata. Asa cum constructorul clasei exista implicit si are numele clasei, nu are parametri nu face nimic si nu intoarce nimic, putand fi supradefinit de programator daca este cazul, si finalizatorul clasei se va defini numai daca sunt necesare niste procesari inainte de “decesul” obiectului. Spre deosebire de constructori, metoda public void finalize() a unei clase nu poate fi supradefinita. O clasa nu poate avea decat un singur finalizator.

Ca exemplu vom modifica clasa Nava care definea comportamentul navelor spatiale ale flotei Rebelilor. Daca un obiect din aceasta clasa este distrus, numarul de nave al flotei continut de variabila comuna (statica) nrNave va fi decrementata.

Daca numarul de nave in timpul atacului scade sub 8, flota se va retrage. Decizia asupra retragerii va fi luata de fiecare nava in parte prin evaluarea la comanda actioneaza a variabilei nrNave. Vom adauga deci clasei Nava metodele private void pleaca(), public void finalize() si vom simula distrugerea unor nave prin atribuirea valorii null referintelor catre obiectele care le modeleaza.

Page 58: programare in Java

96 CURS 8 class xWing{ private static int nrNave=0; private boolean inAtac=false; private String nume; public xWing(String _nume){ //Constructor nrNave++;nume=_nume; System.out.println(nume+" in formatie"); } public void actiune(){ if (inAtac) if( nrNave<9){ inAtac=false; retragere(); }else ataca(); else if(nrNave >= 10){ inAtac=true; ataca(); }else asteapta(); } private void ataca(){System.out.println(nume + " ataca baza");} private void asteapta(){ System.out.println(nume + " gata de lupta"); } private void retragere(){ System.out.println(nume + " se retrage"); } public void finalize(){ --nrNave; System.out.println("Au mai ramas "+ nrNave +" nave"); } } class StarWars2{ public static void main(String[] arguments){ xWing flota[]=new xWing[10]; for(int i=1;i<10;i++) flota[i]=new xWing("Red"+i); flota[0]= new xWing("Red Leader"); for(int k=0;k<3;k++){ for(int i=0;i<10;i++) if(flota[i]!=null)flota[i].actiune(); flota[k]=null; System.gc(); } } } In acest program, oride cate ori o nava este distrusa ( se atribuie referintei catre obiectul care o modeleaza valoarea null ), este solicitata expres colectarea memoriei eliberate cu instructiunea de apel a metodei gc() (garbage collection – colectarea gunoiului in engleza) a clasei System: ... System.gc(); ... Daca colectarea blocurilor de memorie eliberate nu s-ar forta prin acest apel, interpretorul ar fi facut aceasta operatie cand ar fi vrut el (dupa terminarea programului nostru) si nu am fi obtinut efectul scontat. Chiar si asa daca analizam rezultatele afisate la executia programului (figura 8.9) observam ca aceasta colectare este efectuata cu o oarecare intarziere – pe la mijlocul pasului urmator al ciclului for

Page 59: programare in Java

PROGRAMARE IN JAVA - Note de curs 97

interior, nu inaintea acestuia. Explicatia consta in faptul ca colectarea blocurilor de memorie eliberate este facuta de un task care se executa cvasi-paralel cu taskul programului nostru. Vom detalia acest aspect cand vom vorbi despre firele de executie (thread-uri)

Figura 8.9- Rezultatele afisate la executia programului StarWars2

Sumar In cadrul acestui curs am abordat urmatoarele probleme

crearea instantelor multiple ale unei clase prin declararea unor variabile referinta care refera memoria alocata dinamic pentru fiecare insatnta cu operatorul new.

Clasele permit incapsularea daatelor la un loc cu codul in cadrul fiecarei instante a clasei

Specificatorii de acces public si privat permit restrictionarea accesului unui modul utilizator din exterior la membrii clasei (variabile si/sau metode).

Restrictionarea accesului duce la cresterea sigurantei programului. Instantele unei clase se numesc obiecte ele putand modela cu precizia dorita

obiecte ale lumii reale. abordarea orientata pe obiecte a elaborarii programelor si aplicarea metodologiei

top-down de proiectare creste eficienta in munca de programare.

Exercitii Elaborati un program Java care folosind clasa Calculator sa calculeaze si sa

afiseze expresiile:

Page 60: programare in Java

98 CURS 9

Curs 9

Interfete Un principiu de baza in ingineria software il constitue separarea specificatiilor unui modul de implementarea interna a acestuia. In cazul claselor, iterfata este constituita din setul de metode publice ce pot fi apelate de un modul exterior, constituind puntea dintre lumea exterioara si implementarea interna a clasei. Fie de exemplu cele doua implementari elaborate de noi ale stivei folosite de clasa Calculator. Prima dintre ele folosea 4 obiecte din clasa Register: class Stack{ private Register x,y,z,t; public Stack(){ x=new Register();// constructor obiect y=new Register(); z=new Register(); t=new Register(); } public void push(double val){// ridica stiva t.sto(z.get()); z.sto(y.get()); y.sto(x.get()); x.sto(val); } public double pop(){//coboara stiva double val=x.get(); x.sto(y.get()); y.sto(z.get()); z.sto(t.get()); t.sto(0.0); return val; } public double top(){return x.get();}//intoarce varful stivei } iar cea de a doua folosea un tablou: class Stack{ private double reg[]; public Stack(){// constructor stiva reg=new double[4]; for(int i=0;i<reg.length;i++) reg[i]=0.0; } public void push(double val){// ridica stiva for(int i=reg.length –1;i>0;i--) reg[i]=reg[i-1]; reg[0]=val; } public double pop(){//coboara stiva double val=reg[0];

Page 61: programare in Java

PROGRAMARE IN JAVA - Note de curs 99

for(int i=0;i<reg.length-1;i++) reg[i]=reg[i+1]; reg[3]=0.0 return val; } public double top(){return reg[0];}//intoarce varful stivei } Amandoua variantele pot fi reprezentate ca in figura 9.1.

Figura 9.1- Reprezentarea schematica a obiectelor clasei Stack

Am vazut ca pentru proiectarea programului are importanta doar cunoasterea interfetei clasei Stack, formata din metodele publice pushi, pop si top. Aceste trei metode publice constituie interfata prin care modulele exterioare interactioneaza cu obiectele clasei Stack. Cunoasterea implementarii interne a acestor metode in clasa Stack nu este importanta pentru proiectarea modulelor care folosesc obiecte din aceasta clasa. Pentru utilizator nu este important daca clasa este implementata folosind componente registre sau un tablou. In cazul celor doua variante insa interfata si implementarea nu sunt separate. O solutie este folosirea unei clase abstracte: public abstract class StackInterface{

le val); public abstract void push(doub public abstract double pop();

public abstract double top(); } Vom defini clasa Stack ca subclasa derivata din StackInterface: c ss Stack extends Stackla Interface{ private double reg[];

ubli a p c Stack(){// constructor stiv... implementare interna ...

} ubli stiva p c void push(double val){// ridica

... implementare interna push ... } public double pop(){//coboara stiva

... implementare interna pop ... } public double top(){

... implementare interna top ... }

} Acum dispunem atat de specificatia metodelor publice ale clasei adica definitia interfetei acesteia in clasa abstracta StackInterface si separat de implementarea particulara a clasei definita de clasa Stack. Clasa poate fi folosita si pentru realizarea unor alte implementari ale interfetei. class Stack extends StackInterface { ... implementare clasa Stack ... }

Page 62: programare in Java

100 CURS 9 Limbajul Java ofera insa in locul artificiului declararii unei clase abstracte un mecanism propriu pentru definirea interfetelor. Astfel in locul definitiei clasei abstracte StackInterface putem defini explicit o interfata echivalenta: public interface StackInterface{ void push(double val); double pop(); double top(); } Acum putem defini o clasa Stack care implementeaza interfata StackInterface si nu extinde prin supradefinirea metodelor clasa abstracta StackInterface ca in cazul precedent. class Stack implements StackInterface { ... implementare clasa Stack ... } Sa exemplificam cele prezentate mai sus printr-un exemplu. Vom defini interfata Patrulater. interface Patrulater{

double calculArie(); String toString();

} Aceasta interfata poate fi folosita la definirea claselor Dreptunghi si Trapez: class Dreptunghi implements Patrulater{ private double latime, inaltime; public Dreptunghi(double l,double h){latime=l;inaltime=h;} public double calculArie(){return latime*inaltime;} public String toString(){ return “Dreptunghi(“+latime+”,”+inaltime+”)”; } } class Trapez implements Patrulater{ private double bMare,bMica,inaltime ; public Trapez(double b,double _b,double h ){ bMare=b;bMica=_b;inaltime=h; } public double calculArie(){return (bMare+bMica)*inaltime/2;} public String toString(){ return “Trapez(“+bMare+”,”+bMica+”,”+inaltime+”)”; } } Ambele clase Dreptunghi si Trapez implementeaza (diferit) aceeasi interfata Patrulater. Programul propus de testare a acestor doua clase este: class Figuri{ public static void main(String[] arguments){ Dreptunghi d=new Dreptunghi(5,10); Trapez t=new Trapez(10,5,4); System.out.println(“Arie “+d.toString()+”=”+d.calculArie()); System.out.println(“Arie “+t.toString()+”=”+t.calculArie()); } }

Figura 9.2- Compilarea si executia programului Figuri.java

Page 63: programare in Java

PROGRAMARE IN JAVA - Note de curs 101

Listingul din figura 9.2 ne arata comenzile de compilare si executie a acestui program precum si rezultatele afisate de acesta. Interfetele pot contine si variabile statice finale (constante) ca in exemplul urmator: interface ZileSaptamina{ static final int LUNI = 0; static final int MARTI = 1; static final int MIERCURI = 2; static final int JOI = 3; static final int VINERI = 4; static final int SIMBATA = 5; static final int DUMINICA = 6; } class Zi implements ZileSaptamina { public static void main(String[] arguments){ int zi=4; switch(zi){ case LUNI: System.out.println(“LUNI”);break; case MARTI: System.out.println(“MARTI”);break; case MIERCURI: System.out.println(“MIERCURI”);break; case JOI: System.out.println(“JOI”);break; case VINERI: System.out.println(“VINERI”);break; case SIMBATA: System.out.println(“SIMBATA”);break; case DUMINICA: System.out.println(“DUMINICA”);break; } } } Listingul compilarii si executiei acestui program este redat in figura 9.3:

Figura 9.3- Compilarea si executia programului Zi.java

Interfete multiple O clasa poate implementa in acelasi timp mai multe interfete ca mai jos:

public class ViataMea extends ViataGrea implements Nastere,Scoala,Armata, Servici,Pensie,Moarte{ ... implementare ... } Clasa ViataMea trebuie sa prevada definitii pentru toate metodele specificate de interfetele Nastere,Scoala,Servici,Pensie si Moarte. Unele din aceste metode pot fi mostenite de la superclasa ViataGrea. In caz contrar, daca vreuna din metodele specificate de interfata ramane nedefinita, se va genera o eroare la compilare. O interfata poate mosteni metodele specificate de la alte interfete: public interface Servici extends Desteptare, Munca, Somn{ ... } O clasa care implementaeaza interfata Servici va trebui sa defineasca pe langa metodele specificate de aceasta si toate metodele specificate de interfetele pe care aceasta le-a mostenit: Desteptare, Munca, Somn. Trebuie facuta o distinctie clara intre clasele abstracte si interfete. O clasa poate mosteni numai o singura clasa abstracta dar poate implementa mai multe interfete. In cazul implementarii unei interfete, clasa trebuie sa prevada obligatoriu definitii pentru toate metodele specificate de aceasta.

Page 64: programare in Java

102 CURS 9

Input/output In programele elaborate de noi pana in prezent am folosit pentru afisarea

rezultatelor la consola componenta out iar pentru citirea datelor de la consola componenta in a clasei System. Componenta out este o instanta a fluxului standard de iesire date definita in clasa System: ... static PrintStream out; ...

Similar, componenta in este o instanta a fluxului standard de intrare date definita in clasa System: ... static InputStream in; ...

Atat PrintStream cat si InputStream sunt obiecte predefinite in Java API. Deoarece afisarea prin metodele println si print nu ne-a creat probleme, sa ne

ocupam de componenta in. Aceasta este o instanta a clasei InputStream care defineste un flux de date de intrare. Clasa InputStream este definita in Java API ca o clasa abstracta. Fluxul de date este generat de consola sistemului fiind constituit din perechi de octeti reprezentand codurile caracterelor introduse de la tastatura. (Caracterele in Java sunt codificate conform codului Unicode – o extensie pe 2 octeti a codului ASCII). Programul poate extrage cate o pereche de octeti de date (codul unui caracter) din flux apeland metoda int read() a obiectului out.

int read() throws IOException; Am folosit acest mecanism in exemplul din sectiunea “Definirea metodelor” a

cursului 5. Am spus atunci ca in cazul in care se detecteaza o eroare la citirea din fluxul de date este generata o exceptie putand fi executata o procedura de tratare a exceptiei respective. Pentru a ignora exceptiile generate la citire, se poate specifica clauza throws. Intrucat exceptiile generate la operatiile de I/O sunt continute in pachetul java.io, programul trebuie sa contina la inceput instructiunea de a incarca clasele predefinite din acest pachet: import java.io.*; ...

Pentru a “sari” peste caracterele de control ‘\n’ -“new line” generate de apasarea tastei ENTER am apelat metoda skip() care elimina din fluxul de date urmatoarea pereche de octeti. Clasa InputStream prevede si o metoda mai performanta, prin supradefinirea metodei read():

int read(byte b[]) throws IOException; Aceasta citeste octetii in tabloul specificat de referinta b[] si intoarce numarul de octeti extrasi din flux. Aceste trei metode prezentate mai sus nu ofera decat un suport foarte primitiv de introducere a datelor. Ar trebui sa elaboram metode proprii de prelucrare a octetilor preluati din fluxul de date de intrare care sa transformam aceste date primare in valori de tip int, double, String, etc. Dar de ce sa inventam roata? Pachetul java.io din Java API prevede clase mult mai puternice pentru gestiunea fluxurilor de date de intrare cum ar fi clasa DataInputStream definita ca: class DataInputStream extends FilterInputStream implements DataInput Clasa FilterInputStream este o versiune ne-abstarcta a clasei InputStream definita in java.io din Java API ca: class FilterInputStream extends InputStream

Page 65: programare in Java

PROGRAMARE IN JAVA - Note de curs 103

Interfata DataInput definita tot in java.io declara o serie de metode ce definesc comportamentul unui flux de intrare date independent de platforma implementate de clasa DataInputStream. Un obiect din aceasta clasa este construit pe baza unui obiect de tipul InputStream transmis ca parametru constructorului: DataInputStream input = new DataInputStream(in); Aici in este un obiect din clasa InputStream. Pe langa metodele mostenite de la clasa InputStream pe care le-am discutat deja, clasa prevede o serie de metode care permit printre altele si citirea din fluxul de intrare date a unor valori de tip byte, char, short, int, long, double, float, boolean si String. Avem astfel de exemplu metodele: final boolean readBoolean() throws IOException; final byte readByte() throws IOException; final char readChar() throws IOException; final short readShort()throws IOException; final int readInt()throws IOException; final int readLong()throws IOException; final double readDouble()throws IOException; final float readFloat()throws IOException; final String readLine()throws IOException; Pentru noi este utila in acest caz numai metoda readLine care preia intr-un String toti octetii din fluxul de intrare date, pana la primul caracter ‘\n’.Sa exemplificam utilizarea clasei DataInputStream prin programul urmator: import java.io.*; class Zi implements ZileSaptamina { public static void main(String[] arguments) throws IOException{ int zi; String data; DataInputStream input = new DataInputStream(System.in); while(true){ System.out.print(“Introduceti un numar intreg (0-6):”); System.out.flush(); data = input.readLine(); zi=Integer.parseInt(data); if(zi<0 || zi>6) break; switch(zi){ case LUNI: System.out.println(“LUNI”);break; case MARTI: System.out.println(“MARTI”);break; case MIERCURI: System.out.println(“MIERCURI”);break; case JOI: System.out.println(“JOI”);break; case VINERI: System.out.println(“VINERI”);break; case SIMBATA: System.out.println(“SIMBATA”);break; case DUMINICA: System.out.println(“DUMINICA”);break; } System.out.flush(); } System.out.println(“Bye!”); } } Programul implementeaza interfata ZileSaptamana definita in sectiunea precedenta si continand constante asociate zilelor saptamanii. Programul citeste de la consola un sir de caractere prin preluarea lui din fluxul de intrare cu metoda readLine care returneaza un String. Succesiunea de cifre introdusa de la tastatura si terminata cu ENTER este grupat intr-un obiect de tip String si returnat de aceasta metoda. Acest grup de cifre este extras din String si convertit la intreg. In acest program am folosit pentru conversia din String la intreg metoda statica parseInt a clasei predefinite Integer. Rezultatele compilarii si executiei programului sunt prezentate in figura 9.4.

Page 66: programare in Java

104 CURS 9

Figura 9.4- Compilarea si executia programului Zile.java

Caractere si obiecte de tip String Datele constante de tip caracter se reprezinta prin incadrarea intre apostroafe a

caracterului respectiv: ‘<caracter>’. O data codificata astfel reprezinta de fapt o valoare intreaga egala cu codul asociat caracterului. Astfel de exemplu ‘A’ reprezinta de fapt valoarea intreaga 65. Un String este un obiect care grupeaza un sir de caractere intr-un ansamblu unitar. Un String constant se reprezinta prin sirul de caractere continut incadrat de ghilimele: “<sir de caractere>”. De exemplu “(40)01-4100400” (un numar de telefon) este un string constant. String-urile constante se mai numesc si obiecte String anonime. Java trateaza toate obiectele String anonime cu acelasi continut ca fiind un singur (acelasi) obiect cu mai multe referinte. Un String poate fi atribuit la declarare unei variabile referinta la String: ...

ng culoare = “Rosu”; Stri...

Aici variabila culoare este o referinta la obiecte din clasa String. Acestei variabile i se atribuie o valoare care refera Stringul anonim “Rosu”. Exista sapte tipuri de constructori pentru obiecte de tip String. 1. Instructiunile: ... String s;

new String(); s = ... creeaza un String vid, care nu contine nici un caracter, avand lungimea 0. Referinta la acest obiect este atribuita variabilei s. 2. Instructiunile: ...

String s,s1=”Sursa”;new String(s1); s =

... creeaza un obiect String al carui continut este copiat din String-ul referit de s1. Referinta la acest obiect este atribuita variabilei s. 3. Instructiunile: ...

charArray[] = new char[10]; char... String s; s = new String(charArray);

Page 67: programare in Java

PROGRAMARE IN JAVA - Note de curs 105

... creeaza un obiect String al carui continut este preluat din tabloul de caractere referit de charArray. Referinta la acest obiect este atribuita variabilei s. 4. Instructiunile: ... char charArray[] = new char[10]; ... String s; s = new String(charArray,5,4); ... creeaza un obiect String al carui continut este initializat prin preluaarea a 4 caractere din tabloul referit de charArray incepand cu deplasamentul 5. Daca deplasamentul sau numarul de caractere preluate sunt prea mari astfel incat este depasita limita tabloului, este generata o exceptie StringIndexOutOfBoundsException.Referinta la acest obiect este atribuita variabilei s. 5. Instructiunile: ... char charArray[] = new char[10]; ... String s; s = new String(charArray,0,5,4); ... creeaza un obiect String al carui continut este initializat prin preluarea a 4 caractere din tabloul referit de charArray incepand cu deplasamentul 5. Daca deplasamentul sau numarul de caractere preluate sunt prea mari astfel incat este depasita limita tabloului, este generata o exceptie StringIndexOutOfBoundsException. Al doilea argument al constructorului (0), numit hibyte reprezinta un octet de mascare a caracterelor preluate din tablou. In Java caracterele sunt codate pe 2 octeti (Unicode). Mascand octetul superior (hibyte=0) codul caracterelor preluate este echivalent codului ASCII. Referinta la acest obiect este atribuita variabilei s. 6. Instructiunile: ... char charArray[] = new char[10]; ... String s; s = new String(charArray,0); ... creeaza un obiect String al carui continut este initializat prin preluarea caracterelor din tabloul referit de charArray. Al doilea argument al constructorului (0), este octetul de mascare hibyte care se aplica tuturor caracterelor preluate. Referinta la acest obiect este atribuita variabilei s. 7. Instructiunile: ... StringBuffer buffer; String s; buffer = new StringBuffer(); buffer.append(“Sursa”); s = new String(buffer); ... creeaza un obiect String al carui continut este copiat din obiectul din clasa StringBuffer referit de buffer. Un obiect StringBuffer este un String care poate fi redimensionat dinamic. Astfel la creere, obiectul referit de buffer este un sir de caractere vid, de lungime 0. Obiectul este redimensionat dinamic la adaugarea cu metoda append a caracterelor din String-ul anonim “Sursa”. Referinta la acest obiect este atribuita variabilei s.

Page 68: programare in Java

106 CURS 9

Metodele length, charAt, getChars si getBytes Asa cum s-a aratat mai sus, String-urile sunt obiecte care incapsuleaza alaturi de

sirul de caractere continut si metodele de prelucrare specifice ale acestuia: Ca si tablourile, String-urile isi cunosc propria lungime. Spre deosebire de

obiectele tablou care contineau o variabila length a carei valoare reprezenta numarul de elemente ale tabloului, obiectele din clasa String nu contin o astfel de variabila. I schimb contin metoda length() care returneaza numarul de caractere continute de sir (lungimea sirului). Astfel instructiunile:

... String culoare = “Rosu”; System.out.println(“Sirul \”“+culoare+ ”\” are lungimea “+culoare.length()); ...

vor afisa la consola: Sirul “Rosu” are lungimea 4

Elementele sirului continut de un obiect din clasa String sunt indexate de la 0 la n-1 unde n este numarul de caractere din sir. Pentru a obtine caracterul de pe pozitia k se foloseste metoda charAt(k).Daca deplasamentul k este mai mare decat permite lungimea siruluieste generata o exceptie StringIndex-OutOfBoundsException. Astfel instructiunile:

... String culoare = “Rosu”; System.out.println(“Sirul \”“+culoare+ ”\” incepe cu caracterul “+culoare.charAt(0)); ... vor afisa la consola: Sirul “Rosu” incepe cu caracterul R

Metoda getChars() permite copierea incepand cu un deplasament dat unui numar specificat de caractere din String. Caracterele sunt copiate intr-un tablou de caractere.Daca deplasamentul sau numarul de caractere preluate sunt prea mari astfel incat este depasita limita String-ului, este generata o exceptie StringIndexOutOfBoundsException. Instructiunile urmatoare copiaza din sirul “0123456789” in tabloul charArray 5 caractere, incepand cu pozitia 3 si terminand cu pozitia 8 exclusiv - (‘3’,’4’,’5’,’6’,’7’). Ultimul argument al metodei (4) specifica deplasamentul in tablou incepand cu care se vor copia caracterele. Astfel charArray[4]va contine caracterul ‘3’, charArray[5] caracterul ‘4’, etc.

... String s = “0123456789”; char charArray=new char[10]; s.getChars(3,8,charArray,4); ...

Similara metodei getChars() este si metoda getBytes() cu deosebirea ca din String sunt copiati numai octetii inferiori ai caracterelor. Instructiunile urmatoare copiaza din sirul “0123456789” in tabloul byteArray octetii inferiori a 5 caractere, incepand cu pozitia 3 si terminand cu pozitia 8 exclusiv - (‘3’,’4’,’5’,’6’,’7’). Astfel byteArray [4] va contine 51, byteArray [5] 52, etc.

... String s = “0123456789”; byte byteArray=new byte[10]; s.getBytes(3,8,byteArray,4); ...

Page 69: programare in Java

PROGRAMARE IN JAVA - Note de curs 107

Compararea sirurilor de caractere Inainte de a discuta despre compararea String-urilor sa urmarim rezolvarea unei probleme de sortare. Avand dat un tablou de elemente intregi oarecare, sa ordonam aceste elemente in ordine crescatoare. Pentru aceasta trebuie sa folosim un algoritm de sortare. Un astfel de algoritm este metoda buble-sort. Aceasta metoda se bazeaza pe urmatorul mecanism: Se porneste de la secventa de N numere pe care dorim sa le ordonam crescator {xk}k=0,1,...,N-1 , de exemplu 6,3,2,4,1 (N=5). Comparam primul numar cu al doilea. Daca primul numar este mai mare ca cel de al doilea le interschimbam. Astfel deoarece 6 > 3, 3 ia locul lui 6 iar 6 locul lui 3. Se obtine secventa 3,6,2,4,1. Se compara al doilea numar cu al treilea. Daca al doilea numar este mai mare ca cel de al treilea le interschimbam. astfel deoarece 6 > 2, 2 ia locul lui 6 iar 6 locul lui 2. Se obtine secventa 3,2,6,4,1. Continuand similar pentru urmatoarele numere pana la penultimul se obtinesecventa: 3,2,4,1,6. Se observa ca cel mai mare numar din secventa s-a deplasat la capatul secventei, asa cum se ridica spre suprafata o bula intr-un pahar de apa minerala. Se repeta pasii anteriori, de aceasta data pentru secventa fara ultimul element care se stie ca este cel mai mare si deci va ramane oricum pe ultima pozitie: Secventa prelucrata este 3,2,4,1. Se compara x0 cu x1. Deoarece 3 > 2 se interschimba 3 cu 2. Se obtine 2,3,4,1. Se compara x1 cu x2. Deoarece 3 < 4 elementele nu se interschimba, secventa ramanand 2,3,4,1. Se compara x2 cu x3. Deoarece 4 > 1 se interschimba 4 cu 1. Se obtine 2,3,1,4. Din nou cel mai mare numar din secventa s-a “ridicat” pe ultima pozitie. Repetand aceleasi prelucrari secventei ramase 2,3,1 se obtine secventa 2,1,3. Repetand din nou prelucrarea secventei 2,1 se obtine 1,2. Aici algoritmul se termina deoarece secventa ramasa 1 contine un singur element. Am obtinut deci in final secventa ordonata crescator 1,2,3,4,6. Observam ca pentru o secventa cu N elemente, avem N-1 procesari. Dupa fiecare procesare secventa prelucrata se “scurteaza” cu cate un element. Implementarea in limbajul Java a algoritmului de sortare prin metoda bulelor este prezentata in continuare. Rezultatele compilarii si executiei programului sunt prezentate in figura 9.5. class BubleSort{ static void swap(int[] x, int k){ int t; t=x[k]; x[k]=x[k+1]; x[k+1]=t; } static void sort(int[] x){ int N=x.length,i,k; for(i=0;i<N-1;i++) for(k=0;k<N-i-1;k++) if(x[k]>x[k+1])swap(x,k); } static void print(int[] x){ int N=x.length; System.out.print("{ "); for(int i=0;i<N;i++){ System.out.print(x[i]+" ");

Page 70: programare in Java

108 CURS 9 } System.out.println("}"); } public static void main(String[] arguments){ int x[]=new int [10]; for(int i=0;i<10;i++) x[i]=1+(int)(Math.random()*10); System.out.println("Secventa initiala:"); print(x); sort(x); System.out.println("Secventa sortata:"); print(x); } }

Figura 9.5- Compilarea si executia programului BubleSort.java Avand in vedere ca datele de tip caracter sunt reprezentate in memorie prin codurile asociate care sunt de fapt numere intregi, putem sa modificam programul de mai sus pentru a sorta secvente de caractere: class CharSort{ static void swap(char[] x, int k){ char t; t=x[k]; x[k]=x[k+1]; x[k+1]=t; } static void sort(char[] x){ int N=x.length,i,k; for(i=0;i<N-1;i++) for(k=0;k<N-i-1;k++) if(x[k]>x[k+1])swap(x,k); } static void print(char[] x){ int N=x.length; System.out.print("{ "); for(int i=0;i<N;i++){ System.out.print(x[i]+" "); } System.out.println("}"); } public static void main(String[] arguments){ char x[]=new char [10]; for(int i=0;i<10;i++) x[i]=(char)(65+(int)(Math.random()*10)); System.out.println("Secventa initiala:"); print(x); sort(x); System.out.println("Secventa sortata:"); print(x); }

Page 71: programare in Java

PROGRAMARE IN JAVA - Note de curs 109

}

Figura 9.6- Compilarea si executia programului CharSort.java

Acelasi algoritm il putem aplica pentru a sorta un tablou de String-uri (de exemplu o lista de nume) in ordine alfabetica. Pentru aceasta avem nevoie de a compara doua elemente de tip String ale tabloului. De data aceasta nu putem folosi pentru comparare operatorii relationali pentru ca sirurile de caractere nu sunt scalari. Clasa String ofera pentru aceasta operatie metoda compareTo(). Aceasta metoda se bazeaza pe compararea lexicografica a sirurilor. Ea intoarce 0 daca sirurile de caractere comparate sunt identice, un numar negativ daca sirul argument cu care se face compararea este “mai mare” decat sirul a carui metoda compareTo() este invocata si un numar pozitiv daca sirul argument este “mai mic”. Aceasta valoare returnata de metoda compareTo() se obtine din diferenta codurilor primelor caractere diferite din cele doua siruri. Astfel instructiunile: ... String s1 = “S1”,s2=”S2”; System.out.println(s1.compareTo(s2)); ... vor afisa la consola: -1 deoarece primele caractere diferite din cele doua siruri sunt ‘1’ cu codul 49 din s1 si ‘2’ cu codul 50 din s2. Deoarece este invocata metoda compareTo() a sirului s1, compararea se face prin diferenta dintre primul caracter diferit a lui s1 si a primului caracter diferit a lui s2. Diferenta dintre acestea este –1. Deci sirul s2 este mai mare decat s1. Folosind metoda compareTo() vom modifica programul anterior pentru a sorta un tablou de siruri de caractere: class StringSort{ static void swap(String[] x, int k){ String t; t=x[k]; x[k]=x[k+1]; x[k+1]=t; } static void sort(String[] x){ int N=x.length,i,k; for(i=0;i<N-1;i++) for(k=0;k<N-i-1;k++) if(x[k].compareTo(x[k+1])>0)swap(x,k); } static void print(String[] x){ int N=x.length; System.out.print("{ "); for(int i=0;i<N;i++){ System.out.print(x[i]+" "); } System.out.println("}"); }

Page 72: programare in Java

110 CURS 9 public static void main(String[] arguments){ System.out.println("Secventa initiala:"); print(arguments); sort(arguments); System.out.println("Secventa sortata:"); print(arguments); } }

Figura 9.7- Compilarea si executia programului StringSort.java

In cazul compararii la egalitate a sirurilor se poate folosi in locul metodei compareTo() metoda equals() mostenita de la superclasa Object a clasei String. Aceasta metoda permite testarea la egalitate a doua obiecte. Metoda intoarce true daca obiectele sunt egale sau false in caz contrar. In programul urmator este testat argumentul de tip String din linia de comanda cu care acesta se lanseaza in executie cu String-ul “hallo”. class EqTest{ public static void main (String[] arguments){ if(arguments.length != 1){ System.out.println("Usage: java EqTest <string>"); System.exit(0); } if(arguments[0].equals("hallo")) System.out.println(arguments[0]+"== hallo"); else System.out.println(arguments[0]+"!= hallo"); } } Rezultatele compilarii si executiei programului sunt prezentate in figura 9.8.

Figura 9.8- Compilarea si executia programului EqTest.java

Pentru compararea sirurilor de caractere cu ignorarea diferentei dintre literele mici si mari clasa String prevede metoda equalsIgnoreCase(): class EqICTest{ public static void main (String[] arguments){ if(arguments.length != 1){ System.out.println("Usage: java EqTest <string>"); System.exit(0);

Page 73: programare in Java

PROGRAMARE IN JAVA - Note de curs 111

} if(arguments[0].equalsIgnoreCase("hallo")) System.out.println(arguments[0]+"== hallo"); else System.out.println(arguments[0]+"!= hallo"); } } Rezultatele executiei programului sunt prezentate in figura 9.9.

Figura 9.9- Compilarea si executia programului EqICTest.java Pe langa metodele de testare la egalitate discutate mai sus, clasa String prevede si o metoda regionMatches() prin care putem testa la egalitate numai portiuni ale sirurilor comparate. Metoda intoarce true daca subsirurile sunt egale si false in caz contrar. Astfel instructiunile: ... String s1 = “Primul sir S1”,s2=”Al doilea sir S2”; System.out.println(s1.regionMatches(7,s2,10,3)); ... vor compara subsirul de 3 caractere cu deplasamentul 7 din sirului s1(“sir”) cu subsirul de 3 caractere cu deplasamentul 10 din sirului s2(“sir”). Intrucat cele doua subsiruri sunt egale, metoda regionMatches() va returna valoarea true care va fi afisata. Primul argument (7) specifica deplasamentului subsirului in sirul s1 a carui metoa regionMatches() este invocata. Al doilea argument (s2) indica sirul cu care se face comparatia, al treilea argument (10) specifica deplasamentului subsirului in sirul s2 iar ultimul, (3), lungimea subsirurilor comparate. Pentru cazurile in care vrem sa verificam daca sirul incepe/se termina cu o anumita secventa de caractere clasa String prevede metodele startsWith() si endsWith(). Astfel instructiunile: ... String nume[] = new String[100]; ...// initializare tablou for(int i=0;i<100;i++) if(s1[i].startsWith(“Mihai”)) System.out.println(nume[i]); ... vor afisa toate sirurile din tabloul nume care incep cu subsirul “Mihai”. Similar, instructiunile: ... String nume[] = new String[100]; ...// initializare tablou for(int i=0;i<100;i++) if(s1[i].endsWith(“Ionescu”)) System.out.println(nume[i]); ... vor afisa toate sirurile din tabloul nume care se termina cu subsirul “Ionescu”. Metoda startsWith() mai are o varianta cu doua argumente:

s1.startsWith(s2,n) care verifica daca sirul s2 se regaseste in sirul s1 la pozitia n.

Page 74: programare in Java

PROGRAMARE IN JAVA - Note de curs 169

Curs 14

Exceptii In timpul executiei unui program pot sa apara situatii exceptionale care sa duca la oprirea programului sau la obtinerea unor rezultate eronate. Sa analizam urmatorul exemplu: import java.awt.*; class Divizor extends Frame{ private Label l1=new Label("Introduceti deimpartitul"), l2=new Label("Introduceti impartitorul"+ " si apasati tasta ENTER"); private TextField in1=new TextField(), in2=new TextField(), out=new TextField(); private int p,q; public Divizor(){ super("Test impartire"); out.setEditable(false); setLayout(new GridLayout(5,1)); add(l1);add(in1); add(l2);add(in2); add(out); resize(300,150); show(); } public boolean handleEvent(Event e){ if(e.id==Event.WINDOW_DESTROY){ hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public boolean action(Event e,Object o){ String s1,s2; if(e.target==in2){ p=Integer.parseInt(s1=in1.getText()); in1.setText(""); q=Integer.parseInt(s2=in2.getText()); in2.setText(""); out.setText(s1+" / "+s2+" = "+String.valueOf(p/q)); } return true; } public static void main(String[] arg){ Divizor f=new Divizor(); } } programul va crea doua campuri de editare in1 si in2 care vor servi pentru introducerea a doua stringuri de intrare s1 si s2 ce vor fi convertite in numere intregi si stocate in variabilele intergi p si q. De asemenea este creat un camp de editare “read

Page 75: programare in Java

170 CURS 14 only”, out, pentru afisarea rezultatelor. Dupa terminarea introducerii datelor, continutul campurilor in1 si in2 este sters iar i campul out va fi afisat un string continand rezultatul impartirii p/q. De exemplu dupa introducerea valorilor 4 si 2 in campurile in1 si in2 fereastra afisata de program este cea din figura 14.1.

Fig.14.1 Fereastra afisata de programul Divizor.class Ce se va intampla insa daca se introduc ca date de intrare valorile 4 si 0. Daca programul ar fi incercat sa efectueze impartirea, in final, procesorul ar fi generat o intrerupere interna numita exceptie tratata de sistemul de operare. Totusi aparent in fereastra aplicatiei nu se intampla nimic, datele introduse fiind pur si simplu ignorate. In schimb la consola apare afisat mesajul din figura 14.2.

Fig.14.2 Mesajul aparut la consola in cazul impartirii prin 0 Acest mesaj ma avertizeaza asupra exceptiei aparute, categoria acesteia (java.lang.ArithmeticException, cauza - impartire prin zero - si indica sursa acesteia – exceptia a aparut la executia instructiunii din linia 38 a programului din fisierul p14_1.java, in metoda Divizor.action, apelata de metoda java.awt.Component.handleEvent in fisierul Component.java linia 908, apelata de metoda Divizor.handleEvent in linia 29 din fisierul p14_1.java, apelata de... etc. Aceasta lista de apeluri succesive (stiva de apeluri) faciliteaza localizarea erorii aparute la executia programului, indicand precis instructiunea care nu a functionat corect. Explicatia acestui mesaj si a faptului ca programul nu s-a blocat si continue sa lucreze normal in continuare consta in detectarea exceptiei inainte ca ea sa se produca de fapt si sa cauzeze o eroare fatala de sistem. Clasele proiectate de noi pot si ele sa realizeze detectarea si tratarea exceptiilor aparute in timpul executiei programului. Daca dorim ca programul anterior sa evite generarea unei exceptii si sa semnaleze utilizatorului printr-un mesaj ca datele introduse sunt eronate am putea sa facem urmatoarea modificare in metoda action(): public boolean action(Event e,Object o){ String s1,s2; if(e.target==in2){

t(s1=in1.getText()); p=Integer.parseIn in1.setText(""); q=Integer.parseIn in2.setText("");

t(s2=in2.getText());

out.setText(s1+" / "+s2+" = "+div(p,q));

Page 76: programare in Java

PROGRAMARE IN JAVA - Note de curs 171

} return true; } Metoda div(int x,int y) apelata pentru efectuarea impartirii celor doua argumente face si validarea datelor afisand mesajul corespunzator: private String div(int x, int y){ String rezultat=””; if(y==0) if(x==0) rezultat= “Rezultat nedefinit”; else rezultat= x<0? ”Eroare: Infinit negativ”: ”Eroare: Infinit pozitiv”; return rezultat; } Mesajele afisate de in fereastra aplicatiei la introducerea unei valori nule pentru divizor sunt redate in figura 14.3.

Fig.14.3 Fereastra afisata de programul Divizor.class Exista multe alte tipuri de exceptii ce pot apare. Iata cateva dintre acestea: depasirea limitelor unui tablou (index prea mare), index negativ, depasire aritmetica (valoarea rezultata este prea mare pentru tipul respectiv de data), inexistenta unei clase sau a unei metode, parametri de tip eronat la apelul unei metode, insuficienta memorie pentru alocare la instantierea unui obiect, etc. De exemplu daca o metoda a unei clase X.class apeleaza o metoda a altei clase Y.class si intamplator fisierul Y.class a fost sters de pe disc, se genereaza o exceptie ClassNotFoundException. Si generarea acestei exceptii poate fi evitata prin verificarea existentei fisierului respectiv pe disc inainte de a apela la metodele lui. Incercarea de a preantampina toate exceptiile in aceasta maniera (prin prevederea unor secvente de validare chiar in locul unde ar putea apare erorile) duce la un cod sursa “poluat” de diferite teste de validare devenind foarte greu de inteles si de modificat. Realizatorii limbajului Java au ales pentru tratarea exceptiilor mecanismul similar celui descris in standardul ANSI pentru tratarea exceptiilor in limbajul C++. Acest mecanism prevede tratarea separata a exceptiilor – in afara codului principal, in care aceste exceptii pot apare. Codul de tratare a exceptiilor este executat numai in momentul in care apare exceptia, mecanismul fiind asemanator cu cel de tratarea intreruperilor intr-un sistem de operare (executia programului este oprita si se executa o rutina de tratare a intreruperii respective. La terminarea executiei acestei rutine controlul este redat programului intrerupt reluandu-se executia acestuia). In cazul exceptiilor, metoda care a provocat exceptia este oprita si se executa secventa de cod care trateaza exceptia de tipul respectiv. Mecanismul de tratare a exceptiilor in Java

Page 77: programare in Java

172 CURS 14 permite unui program sa “prinda” (sa detecteze) o exceptie produsa intr-o sectiune a sa si sa o proceseze prin executia unei proceduri de recuperare numita exception handler. Acest mecanism permite “prinderea” tuturor exceptiilor posibile, a mai multor exceptii inrudite sau numai a unei anume exceptii. Aceasta permite realizarea unor programe robuste, reducand probabilitatea aparitiei unor erori fatale la executia programului fara ca aceasta sa fie detectata si tratata corespunzator astfel incat sa se asigure recuperarea erorii, permitand ca executia programului sa poata fi continuata. In mod normal exceptia produsa intr-o metoda va fi tratata in metoda apelanta. In momentul in care s-a produs exceptia, metoda este terminata fortat, revenindu-se la metoda apelanta in procedura de tratare a exceptiei. Daca aceasta metoda nu prevede o procedura de tratare a exceptiei respective, si aceasta metoda va fi terminata, revenindu-se fortat la metoda care a apelat metoda apelanta a metodei care a provocat exceptia, exceptia urmand a fi tratata de handlerul metodei apelante a metodei apelante, etc. Astfel revenirea in lantul apelurilor are loc pana la metoda care prevede handler-ul de tratare a exceptiei respective. Un program poate sa ignore aparitia exceptiilor dar aceasta poate avea un efect devastator in cazul aplicatiilor comerciale sau critice. in cazul unor aplicatii pentru uz personal este uzual ca multe erori sa fie ignorate sau sa fie tratate prin terminarea executiei programului pentru a evita obtinerea unor rezultate eronate. O astfel de abordare nu este insa acceptabila in cazul aplicatiilor critice care vor necesita o procesare corespunzatoare a exceptiei asigurandu-se executia in continuare a programului in conditii de siguranta.

Tratarea exceptiilor in Java La proiectarea unei clase, daca exista posibilitatea ca la executia unei metode a clasei sa apara o eroare, aceasta metoda poate fi prevazuta cu proprietatea de a “arunca” (genera) o exceptie. In acest stadiu nu exista garantia ca in afara metodei exista un handler al exceptiei – o secventa de cod care sa trateze aceasta exceptie daca ea va fi generata. daca o astfel de procedura exista, exceptia “aruncata” de metoda va fi “prinsa” si tratata. Secventa de program care se banuieste ca poate “arunca” o exceptie este incadrata intr-un bloc try (incearca) urmat imediat de unul sau mai multe blocuri catch (prinde). Fiecare bloc catch specifica un tip de exceptie si handler-ul acesteia adica secventa de instructiuni pentru tratarea acestei exceptii. Ultimul bloc catch poate fi urmat de un bloc optional finally continand o secventa de instructiuni care este executata intotdeauna, indiferent daca survine sau nu o exceptie. Acest bloc este locul ideal in care sa se realizeze eliberarea resurselor alocate in timpul executiei. Cand o exceptie este “aruncata” executia blocului try este abandonata si se cedeaza controlul blocului catch corespunzator tipului respectiv de exceptie. daca la executia blocului try nu este generata nici o exceptie, executia esete continuata cu instructiunea imediat urmatoare ultimului bloc catch. Daca dupa ultimul bloc catch urmeaza un bloc finally se executa secventa de instructiuni din acest bloc (indiferent daca s-a “aruncat” o exceptie sau nu). Exceptiile pe care le poate arunca o metoda se specifica prin clauza throws (arunca). Exceptia este “aruncata” din interiorul unui bloc try din corpul metodei. Instructiunea care a generat exceptia se numeste punctul de “aruncare” al exceptiei. Spre deosebire de tratarea intreruperilor, din momentul in care s-a produs “aruncarea”, blocul in care s-a produs exceptia expira, programul nu va mai relua executia din acest punct dupa recuperarea erorii. Exceptia “aruncata” este un obiect continand informatii privind tipul erorii produse si alte date semnificative pentru recuperarea acesteia. pentru a exemplifica cele prezentate mai sus sa rescriem programul anterior folosind try, throw si catch pentru a detecta, indica si procesa

Page 78: programare in Java

PROGRAMARE IN JAVA - Note de curs 173

exceptia “impartire prin 0”. Pentru inceput vom cauta o clasa Exception care corespunde tipului nostru de exceptie. In pachetul java.lang avem definite urmatoarele clase de exceptii : Exception

ClassNotFoundException CloneNotSupportedException IllegalAccessException InstantiationException InterruptedException NoSuchMethodException RunTimeException

ArithmeticException ArrayStoreException ClassCastException IllegalArgumentException

IllegalThreadStateException NumberFormatException

IllegalMonitorStateException IndexOutOfBoundsException

ArrayIndexOutOfBoundsException StringIndexOutOfBoundsException

NegativeArraySizeException NullPointerException SecurityException

Dintre acestea vom alege clasa ArithmeticException care corespunde cel mai bine tipului de eroare ce dorim sa-l tratam in aplicatia noastra (vezi si mesajul afisat la consola – fig.14.2). Modificarile aduse programului anterior vor afecta doar metodele action() si div(): public boolean action(Event e,Object o){ String s1,s2; if(e.target==in2){ p=Integer.parseInt(s1=in1.getText()); in1.setText(""); q=Integer.parseInt(s2=in2.getText()); in2.setText(""); try{// Detectia exceptiei out.setText(s1+" / "+s2+" = "+ div(p,q)); } catch(ArithmeticException exception) {//Prinderea exceptiei out.setText(exception.toString()); } } return true; } private String div(int x, int y) throws ArithmeticException { if(y==0) throw new ArithmeticException();//Aruncarea exceptiei return String.valueOf(x/y); }

Page 79: programare in Java

174 CURS 14 Metoda div() a fost prevazuta cu clauza throws ArithmeticException permitandu-i astfel sa “arunce” exceptii din aceasta clasa. In cadrul acestei metode daca argumentul y este nul, se creaza un obiect din clasa ArithmeticException care este “aruncat” cu instructiunea : throw new ArithmeticException(); Exceptia este detectata in blocul try din metoda action() si “prinsa” de blocul catch care va afisa stringul ce caracterizeaza exceptia exact acelasi ca si in figura 14.2. De data aceasta in fereastra consola nu va mai aparea nici un mesaj deoarece exceptia a fost prinsa si tratata de clasa definita de noi. Dupa introducerea valorilor 5 si 0 in campurile in1 si in2 fereastra afisata de program este cea din figura 14.4.

Fig.14.4 Fereastra afisata de programul Divizor.class Programul a afisat in campul out mesajul standard corespunzator acestei exceptii. Constructorul clasei ArithmeticException(String msg) permite setarea stringului ce caracterizeaza exceptia. Folosind acest constructor in metoda div() : private String div(int x, int y) throws ArithmeticException { if(y==0) throw new ArithmeticException(“Impartire prin 0”); return String.valueOf(x/y); } Fereastra afisata de program in acest caz este cea din figura 14.5.

Fig.14.5 Fereastra afisata de programul Divizor.class Acum putem modificand metoda div() sa semnalam prin mesaje diferite si un rezultat Infinit negativ, nedeterminat sau Infinit pozitiv dupa valorile deimpartitului: private String div(int x, int y)

throws ArithmeticException { if(y==0) if(x==0)

ithmeticException( “Rezultat nedefinit”); throw new Ar else if(x<0)

ow new ArithmeticException( ”Eroare: Infinit negativ”): thr else

ption( ”Eroare: Infinit pozitiv”); throw new ArithmeticExce return String.valueOf(x/y); }

Page 80: programare in Java

PROGRAMARE IN JAVA - Note de curs 175

Mesajele afisate de program in acest caz pentru cele trei situatii pentru care sunt “aruncate” de catre metoda div() exceptii din clasa ArithmeticException sunt cele din figura 14.6.

Fig.14.6 Fereastra afisata de programul Divizor.class Putem de asemenea sa cream propria noastra clasa derivata din clasa ArithmeticException. class DivByZeroException extends ArithmeticException{ public DivByZeroException(){ super(“Impartire prin 0”);} } Modificand corespunzator blocul catch al metodei action() si metoda div() : public boolean action(Event e,Object o){ ... catch(DivByZeroException exception) {//Prinderea exceptiei out.setText(exception.toString()); } ... } private String div(int x, int y) throws DivByZeroException { if(y==0) throw new DivByZeroException(); return String.valueOf(x/y); } mesajul afisat in campul out la tratarea acestei exceptii va fi cel din figura 14.7:

Fig.14.7 Fereastra afisata de programul Divizor.class Operatorul throw “arunca” un obiect din clasa Trowable sau dintr-o clasa derivata din aceasta cum ar fi clasa Exception sau clasa ArithmeticException derivata din clasa Exception. odata ce obiectul a fost aruncat, revenirea din metoda care a efectuat aruncarea (sau din lantul de apeluri) nu se mai face, blocul try este si el terminat fortat, controlul cedandu-se blocului catch corespunzator, destinat procesarii obiectului aruncat. Astfel daca modificam metoda div() ca aceasta sa apeleze la randul sau o metoda validare() in cadrul careia sa se efectueze aruncarea :

Page 81: programare in Java

176 CURS 14 private String div(int x, int y) { validare(y) return String.valueOf(x/y); } private void validare(int y) throws DivByZeroException { if(y==0) throw new DivByZeroException();} la aruncarea exceptiei, se va revenirea din lantul de apeluri div()->validare() direct in blocul catch(DivByZeroException exception) care contine handlerul exceptiei DivByZeroException. Blocul try va fi terminat si el. Acest lucru este valabil si pentru un lant de apeluri oricat de lung. Toate metodele din lant vor fi abandonate, blocul try in care sa-a initiat apelul inlantuit devine expirat si se executa blocul catch corespunzator exceptiei. In baza acestui mecanism, chiar daca aparent nu apare o instructiune throw, el totusi va detecta toate exceptiile care pot aparea la executia instructiunilor ce il compun. exceptiile pot fi aruncate de exemplu de constructorii diferitelor obiecte instantiate in cadrul blocului. Astfel de exemplu programul de mai jos desi nu mai contine metoda div() care sa o arunce, va detecta si trata exceptia aritmetica “impartire prin zero” aruncata chiar de sistem afisand acelasi mesaj ca si cel din figura 14.4. import java.awt.*; class Divizor extends Frame{ private Label l1=new Label("Introduceti deimpartitul"), l2=new Label("Introduceti impartitorul"+ " si apasati tasta ENTER"); private TextField in1=new TextField(), in2=new TextField(), out=new TextField(); private int p,q; public Divizor(){ super("Test impartire"); out.setEditable(false); setLayout(new GridLayout(5,1)); add(l1);add(in1); add(l2);add(in2); add(out); resize(300,150); show(); } public boolean handleEvent(Event e){ if(e.id==Event.WINDOW_DESTROY){ hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public boolean action(Event e,Object o){ String s1,s2; if(e.target==in2){ p=Integer.parseInt(s1=in1.getText()); in1.setText(""); q=Integer.parseInt(s2=in2.getText()); in2.setText("");

Page 82: programare in Java

PROGRAMARE IN JAVA - Note de curs 177

// Detectia exceptiei try{

out.setText(s1+" / "+s2+" = "+String.valueOf(p/q)); }

catch(ArithmeticException exception) {//Prinderea exceptiei out.setText(exception.toString()); } } return true; } public static void main(String[] arg){ Divizor f=new Divizor(); } } Lantul de apeluri din care se face “aruncarea” fiind similar cu cel din figura 14.2. Striva de apeluri poate fi afisata prin apelul printStackTrace() definita in clasa Thowable si mostenita de toate clasele derivate. De asemenea poate fi utila si metoda getMessage() care intoarce un String continand mesajul setat la instantierea obiectului. Utilizarea acestor metode poate fi exemplificata modificand ca mai jos blocul catch din exemplul anterior:

... catch(ArithmeticException exception) {//Prinderea exceptiei

out.setText(exception.getMessage ()); exception.printStackTrace(); } ... Vom obtine la generarea unei exceptii aritmetice mesajele de la consola si din fereastra aplicatiei prezentate in figura 14.8

Fig.14.8 Utilizarea metodelor getMessage() si printStackTrace() Blocul catch va “prinde” nu numai exceptiile din clasa specificata ca argument ci si toate exceptiile din subclase ale acesteia. De exemplu catch(ArithmeticException e) va prinde si exceptiile din clasa definita de noi DivByZeroException iar catch(Exception e) va prinde toate tipurile de exceptii deoarece acestea sunt toate subclase ale clasei Exception. Daca pentru o exceptie nu este gasit un handler, dupa afisarea mesajelor corespunzatoare la consola, o aplicatie fara interfata grafica este terminata iar una prevazuta cu interfata grafica isi va continua executia asteptand si prelucrand noi evenimente. Toate variabilele si obiectele locale blocului try sunt distruse la aparitia unei exceptii deoarece blocul expira in momentul in care exceptia este prinsa si tratata de blocul catch. La distrugerea acestor obiecte si colectarea lor de catre garbage colector sunt apelate metodele finalize() ale acestora.

Page 83: programare in Java

178 CURS 14

Multithreading Daca incercam sa privim procesarea datelor de catre o aplicatie ca pe o activitate similara unei activitati productive, atunci programele elaborate de noi pina in prezent corespund activitatii unei intreprinderi cu un singur lucrator. Acest lucrator este si patron si executant. Sa consideram urmatorul exemplu. Aplicatia noastra trebuie sa “planteze” 80 de puncte pe ecran. Programul va fi urmatorul: class Plantator{ private int nrP; public Plantator(int n){nrP=n;} private void planteaza(){ for(int i=0;i<100000;i++); // temporizare pe durata

// plantarii unui punct System.out.print("."); System.out.flush(); } private void lucreaza(){ for(int i=0;i<nrP;i++) planteaza(); } public static void main(String[] argv){ Plantator p=new Plantator(80); p.lucreaza(); } } Continuand paralela, de ce nu ar putea plantatorul nostru sa foloseasca pentru “plantarea” punctelor pe ecran un lucrator care sa efectueze munca. Programul ar arata astfel: class Plantator{ public static void main(String[] argv){ Lucrator l=new Lucrator(80); l.lucreaza(); } } class Lucrator{ private int nrP; public Lucrator(int n){nrP=n;} private void planteaza(){ for(int i=0;i<100000;i++); // temporizare pe durata

// plantarii unui punct System.out.print("."); System.out.flush(); } public void lucreaza(){ for(int i=0;i<nrP;i++) planteaza(); } } Daca insa plantatorul nostru ar dori sa puna la munca doi lucratori impartind munca intre ei: class Plantator{ public static void main(String[] argv){ Lucrator l1=new Lucrator(40),

l2=new Lucrator(40); l1.lucreaza(); l2.lucreaza();

Page 84: programare in Java

PROGRAMARE IN JAVA - Note de curs 179

} } O stfel de diviziunea muncii nu ar avea sens deoarece cele doua obiecte Lucrator l1 si l2 nu ar lucra simultan. Intai ar lucra l1, executandu-se instructiunea l1.lucreaza() si abia dupa ce s-ar termina activitatea acestuia s-ar executa si instructiunea l2.lucreaza(). Altfel ar sta treaba daca cei doi lucratori ar lucra in acelasi timp, in paralel. Acest lucru devine posibil daca definim clasa Lucrator ca fiind subclasa a clasei Thread. Aceasta clasa implementeaza un “fir de executie” paralela. Ea este definita ca implementand interfata Runable: class Thread implements Runable Interfata Runable defineste o singura metoda – metoda run(): interface Runable{ void run(); } Metoda run() joaca rolul metodei main() din aplicatiile cu un singur fir de executie (thread) cum au fost cele prezentate de noi pana in prezent. Cand se lanseaza un nou fir de executie, incepe sa fie executata metoda run(). Aceasta se executa in paralel cu celalalte fire de executie (cum ar fi cel care executa metoda main()). Din aceasta metoda, daca ea este supradefinita intr-o subclasa, pot fi apelate celalalte metode ale clasei. Lansarea firului de executie se face cu metoda start() definita in clasa Thread. Aceasta creaza firul de executie si apeleaza metoda run() a acestuia. Vom modifica programul anterior astfel incat sa fie folositi doi lucratori. De data aceasta ( ca sa punem in evidenta activitatea paralela a celor doi lucratori) unul va “planta” puncte iar celalalt stelute. Programul este urmatorul: class Plantator{ public static void main(String[] argv){ Lucrator l1=new Lucrator(40,"."), l2=new Lucrator(40,"*"); l1.start();l2.start(); } } class Lucrator extends Thread{ private int nrP; private String sP; public Lucrator(int n, String s){nrP=n;sP=s;} private void planteaza(){ for(int i=0;i<100000;i++); // temporizare pe durata

// plantarii unui punct System.out.print(sP); System.out.flush(); } public void lucreaza(){ for(int i=0;i<nrP;i++) planteaza(); } public void run(){lucreaza();} } Programul va afisa 80 de caractere alternand puncte cu stelute ceea ce demonstreaza ca cei doi “lucratori” isi desfasoara activitatea in paralel cum se vede si din figura 14.9

Page 85: programare in Java

180 CURS 14

Fig.14.9 Rezultatele afisate de aplicatia Plantator.class Analizand aceste rezultate se pot face doua observatii. Prima priveste executia paralela a celor doua thread-uri. Stelutele si punctele nu alterneaza uniform, un punct o steluta, desi amandoi lucratorii lucreaza cu aceeasi viteza. Aceasta se explica prin faptul ca executia threadurilor este de fapt nu paralela ci cvasiparalela. Existand un singur procesor, acesta este partajat intre cele doua fire de executie ( plus firul de executie principal, in care s-au creat si pornit cele doua thread-uri) plus garbage collector-ul care lucreaza in background. Fiecaruia din aceste fire de executie i se cedeaza controlul procesorului pentru o anumita cuanta de timp. Daca am micsora “durata plantarii” – temporizarea introdusa prin ciclul for in metoda planteaza() – atunci pana sa apuce cel de al doilea thread lucrator sa preia controlul procesorului si sa inceapa sa planteze stelute, primul thread deja si-ar fi terminat treaba plantand pe ecran 40 de puncte la rand. A doua observatie se refera la faptul ca desi cei doi lucratori si-au terminat de plantat punctele si stelutele programul nu s-a terminat ramanand blocat. Pentru a termina programul, cele doua thread-uri trebuiesc oprite. Pentru ca Plantatorul sa stie cand sa opreasca pe cei doi lucratori vom completa codul clasei Lucrator cu un indicator boolean privat ready care este trecut in starea true cand lucratorul – instanta a clasei Lucrator - si-a terminat munca. Starea acestui indicator poate fi aflata de “plantator” prin apelul metodei publice isReady(). Firul de executie principal, Plantatorul va testa periodic (intr-un ciclu while) daca cei doi lucratori si-au terminat treaba si daca da, le opreste din activitate apeland metoda stop(). Abia acum poate si firul principal de executie sa se termine Odata toate firele de executie terminate, se termina si aplicatia. Cu aceste modificari programul devine: class Plantator{ public static void main(String[] argv){ Lucrator l1=new Lucrator(40,"."), l2=new Lucrator(40,"*"); l1.start();l2.start(); while(!(l1.isReady()&&l2.isReady()));// Gata munca? l1.stop();l2.stop(); } } class Lucrator extends Thread{ private int nrP; private boolean ready; private String sP; public Lucrator(int n, String s){nrP=n;sP=s;ready=false;} private void planteaza(){ for(int i=0;i<100000;i++); // temporizare pe durata

// plantarii unui punct System.out.print(sP); System.out.flush(); } public void lucreaza(){ for(int i=0;i<nrP;i++) planteaza(); ready=true; } public boolean isReady(){return ready;} public void run(){lucreaza();} }

Page 86: programare in Java

PROGRAMARE IN JAVA - Note de curs 181

Curs 15

Ciclul de viata al unui fir de executie Un obiect din clasa Thread din momentul instantierii sale pana la oprirea si distrugerea sa se poate afla in diferite stari reprezentate in figura 15.1

Fig.15.1 – Ciclul de viata al unui thread

In aceast graf s-au reprezentat starile, tranzitiile posibile intre stari si conditiile in care aceste tranzitii au loc. Astfel distingem urmatoarele stari:

Born – thread-ul a fost creat. El ramane in aceasta stare pana la apelul de catre alt thread a metodei start().

Ready – threadul este gata pentru executie, asteptand sa i se cedeze controlul procesorului pe durata unei cuante de timp

Running – threadul a preluat controlul procesorului si se executa pe durata unei cuante de timp

Waiting – la fel ca si un automobil care se opreste in coada unui sir de masini asteptand la un semafor culoarea verde pentru a porni mai departe, in urma unei instructiuni wait(s) threadul, este dezactivat fiind amplasat , eventual alaturi de alte threaduri, intr-o coada de asteptare. Starea “semaforului” s este supravegheata de un thread dedicat care in momentul in care “semaforul” ajunge in starea necesara (are culoarea verde) va activa primul thread din coada de asteptare apeland metoda notify() sau apeland metoda notifyAll(), va activa toate threadurile din coada. In momentul in care un trhread aflat in starea Waiting este astfel notificat, el trece in starea Ready, asteptand sa ii vina randul sa preia controlul procesorului.

Page 87: programare in Java

182 CURS 15

Sleeping - in urma unei instructiuni sleep(T) threadul este dezactivat pe o perioada de timp T. Dupa ce timpul T s-a scurs, threadul este trecut automat in starea Ready.

Suspended - in urma unei instructiuni suspend() threadul este dezactivat pe o perioada nedeterminata de timp. El poate fi reactivat numai de catre un alt thred care va apela metoda resume(), moment in care trece in starea Ready.

Blocked – Pe timpul unei operatii de I/O, activitatea threadului este blocata pana cand operatia de I/O este efectuata. De exemplu daca threadul prea un caracter dintr-un stream, activitatea sa va fi blocata pana cand caracterul nu este disponibil in stream si nu este citit cand threadul este trecut in starea Ready.

Dead – threadul trece in aceasta stare cand metoda sa run() se termina sau in urma apelarii metodei sale stop() prin care este “omorat”, activitatea sa incetand definitiv. Metoda stop() trimite threadului un obiect din clasa ThreadDeath – o subclasa a clasei Error.

Ca exemplu, vom folosi metoda sleep() pentru a realiza temporizarea operatiei de “plantare” a unui punct din programul anterior. Aceasta metoda primeste ca argument durata in milisecunde a suspendarii activitatii treadului Lucrator. Metoda este definita in clasa Thread astfel:

static void sleep(long ms) throws InterruptedException static void sleep(long ms, int ns) throws InterruptedException

unde ms reprezinta durata in milisecunde iar ns o temporizare suplementara de precizie exprimata in nanosecunde. Vom modifica metoda planteaza() a acestuia inlocuind ciclul de temporizare cu un apel la metoda sleep(). Apelul la aceasta metoda va trece threadul Lucrator respectiv in starea Sleeping pe durata specificata, realizandu-se astfel temporizarea dorita a operatiei. ... private void planteaza(){ sleep(500); // temporizare 0.5s pe durata // plantarii unui punct System.out.print(sP); System.out.flush(); } ... Incercand sa compilam programul astfel modificat, va fi semnalata eroarea: p15_1.java(17): Exception java.lang.InterruptedException must be caugth, or itmust be declared in the throws clause of this method. sleep(500) ^ 1 error Aceasta ne spune ca la executia metodei sleep() poate sa fie generata o exceptie din clasa java.lang.InterruptedException. Acest lucru rezulta si din definitia data mai sus a metodei, unde se specifica prin clauza throws ca aceasta “arunca” o astfel de exceptie. Deoarece metoda sleep() este apelata in metoda planteaza(), exceptia trebuie fie detectata si prinsa in cadrul acestei metode fie sa fie “aruncata” mai departe pentru a fi tratata in afara. In primul caz codul metodei planteaza() va fi ... private void planteaza(){ try{

Page 88: programare in Java

PROGRAMARE IN JAVA - Note de curs 183

sleep(500); // temporizare 0.5s pe durata // plantarii unui punct } catch(InterruptedException exception){}//daca apare exceptia,

//nu se face nimic System.out.print(sP); System.out.flush(); } ... A doua varianta nu este posibila deoarece daca metoda planteaza() va arunca exceptia, aceasta, pe aceleasi considerente, trebuie “prinsa” in metoda lucreaza() care apeleaza metoda planteaza(). Daca si pentru aceasta metoda vom specifica prin clauza throws sa “arunce” exceptia, aceasta va trebui sa fie prinsa in metoda run(). Metoda run() nu poate sa arunce aceasta exceptie mai departe deoarece in acest caz nu va mai corespunde cu interfata Runable si deci nu va mai supradefini metoda run() a clasei Thread. Deci odata si odata tot va trebui undeva sa prindem si sa tratam exceptia si cel mai bine este sa o facem acolo unde ea poate fi detectata prima data, adica in metoda planteaza(). Rezultatul afisat la consola de programul astfel modificat este prezentat in figura 15.2.

Fig.15.2 - Rezultatul executiei programului Plantator.class

Niveluri de prioritate a firelor de executie Gestionarea threadurilor (comutari, treceri dintr-o stare in alta, etc.) intra in sarcina unui thread planificator care se executa cu prioritate maxima, inaintea oricarui alt thread aflat in starea Ready. Planificatorul amplaseaza threadurile aflate in stare Ready in zece liste circulare, fiecare dintre acestea avand asociat un nivel de prioritate. Fiecare thread are la randul sau specificat un nivel de prioritate (o valoare intreaga cuprinsa intre 1 si 10). Threadul care a junge in starea Ready, este amplasat de Planificator in lista corespunzatoare nivelului sau de prioritate. La un moment dat numai un singur thread se poate afla in starea Running. Acesta se numeste threadul curent. In momentul in care threadului curent ii expira cuanta de timp alocata sau executa metoda yield(), el trece in starea Ready si planificatorul il amplaseaza in lista corespunzatoare nivelului sau de prioritate dupa care trebuie sa aleaga un nou thread pe care sa-l treaca in starea Running. El alege acest thread din lista cu prioritatea maxima 10. Daca aceasta lista nu contine nici un thread va cauta in lista cu prioritatea imediat urmatoare 9. Daca si aceasta lista este vida, va alege din lista cu threadurile cu prioritatea 8, etc. Dac la un moment dat, un thread cu prioritate mai mare este introdus in lista de threaduri Ready (in urma unei tranzitii din starile Born, Waiting, Sleeping, Suspended sau Blocked) dupa ce threadul curent de prioritate inferioara este trecut in starea Ready ( dupa expirarea cuantei de timp alocate sau apelarea metodei yield()) controlul este cedat threadului nou aparut, de prioritate mai mare. Pentru a ne clarifica consecintele unei astfel de planificari a executiei threadurilor, sa analizam de exemplu cazul din figura 15.3. In acest caz, vor fi executate pana la terminare, primind succesiv cate o cuanta de timp controlul procesorului, threadurile A si B cu nivelul de prioritate 10. Dupa ce lista de threaduri Ready de pe nivelul de prioritate 10 va fi vida, va fi executat pana la terminare threadul C de pe nivelul 9. Vor urma threadurile D, E

Page 89: programare in Java

184 CURS 15 si F avand nivelul de prioritate 7 deoarece lista de pe nivelul 8 este vida. deoarece au nivelul de prioritate cel mai mic (1), threadurile J si K vor fi executate ultimele, dupa terminarea threadului G de pe nivelul 6 si a threadurilor H si I de pe nivelul 5. Prioritatea threadului este stabilita la instantiere ca fiind egala cu prioritatea threadului parinte. Astfel prioritatea threadurilor l1 si l2 din exemplul nostru va fi egala cu

prioritatea threadului principal care le-a creat (in cadrul metodei main()). Fig.15.3 Planificarea pe niveluri de prioritate a executiei threadurilor

Prioritatea threadurilor poate fi obtinuta prin apelul metodei getPriority() si modificata cu metoda setPriority() care “arunca” o exceptie din clasa java.lang.IllegalArgumentException daca argumentul are o valoare in afara domeniului admis [1,10]. Clasa Thread defineste trei constante corespunzand valorilor uzuale ale nivelurilor de prioritate:

Thread.MIN_PRIORITY = 1; Thread.MAX_PRIORITY=10; Thread.NORM_PRIORITY = 5;

Page 90: programare in Java

PROGRAMARE IN JAVA - Note de curs 185

Unele metode ale clasei Thread Threadul construit cu constructorul Thread() capata implicit numele “Thread-” + n

unde n este numarul de ordine al threadului (Thread-1 daca este primul, Thread-2 daca este al doilea, etc.) Programul urmator creaza patru threaduri care dupa o temporizare aleatoare isi afiseaza numele si se termina.

Numele este afisat apeland metoda getName() care intoarce un String continanad numele threadului.

class Plantator{ public static void main(String[] argv){ Lucrator l1=new Lucrator(), l2=new Lucrator(), l3=new Lucrator(), l4=new Lucrator(); l1.start();l2.start();l3.start();l4.start(); while(!(l1.isReady()&&l2.isReady()&&l3.isReady()&&l4.isReady())); l1.stop();l2.stop();l3.stop();l4.stop(); } } class Lucrator extends Thread{ private boolean ready; private int sleepTime; public Lucrator(){ sleepTime=(int)(Math.random()*5000); System.out.println(getName()+":"+sleepTime); } public boolean isReady(){return ready;} // executia threadului public void run(){ try{ sleep(500); } catch(InterruptedException e){ System.err.println("Exception"+ e.toString()); } System.out.println(getName()); ready=true; } }

Page 91: programare in Java

186 CURS 15 Rezultatele afisate de program in trei executii succesive sunt redate in figura 15.4

Fig.15.4 - Rezultatul executiei programului Plantator.class Pe langa constructorul Therad() folosit de noi in exemplul precedent, clasa Thread mai prevede si alti constructori. Unul dintre acestia este Thread(String nume). Acest constructor permite atribuirea threadului creat a unui nume specificat ca parametru. Vom modifica programul anterior pentru a folosi acest constructor: class Plantator{ public static void main(String[] argv){ Lucrator l1=new Lucrator(“Hose”), l2=new Lucrator(“Diego”), l3=new Lucrator(“KuntaKinte”), l4=new Lucrator(“Izaura”); l1.start();l2.start();l3.start();l4.start(); while(!(l1.isReady()&&l2.isReady()&&l3.isReady()&&l4.isReady())); }

l1.stop();l2.stop();l3.stop();l4.stop();

} class Lucrator extends Thread{ private boolean ready; private int sleepTime; public Lucrator(String nume){ super(nume); sleepTime=(int)(Math.random()*5000); System.out.println(getName()+":"+sleepTime); } public boolean isReady(){return ready;} // executia threadului

Page 92: programare in Java

PROGRAMARE IN JAVA - Note de curs 187

public void run(){ try{ sleep(500); } catch(InterruptedException e){ System.err.println("Exception"+ e.toString()); } System.out.println(getName()); ready=true; } } Rezultatele afisate de program in trei executii succesive sunt redate in figura 15.5

Fig.15.4 - Rezultatul executiei programului Plantator.class Numele unui thread poate fi setat si dupa instantiere cu metoda setName(String

nume). Metoda start() deja folosita de noi in toate exemplele anterioare permite lansarea

in executie a threadului prin trecerea lui in starea Ready si amplasarea in lista de threaduri ready cu nivelul de prioritate corespunzator. Metoda revine imediat dupa aceea la metoda apelanta aceasta continuandu-si executia pana la expirarea cuantei de timp. Metoda “arunca” o exceptie din clasa java.lang. IllegalThreadState Exception daca se incearca lansarea in executie a unui thread care a fost deja lansat odata anterior.

Metoda stop() termina executia unui thread trecandu-l in starea Dead. Din aceasta stare, threadul nu mai poate fi scos chiar daca se incearca relansarea lui cu metoda start(). Din acest moment, memoria alocata lui poate fi eliberata (prin atribuirea valorii null variabilei care il refera) pentru a fi colectata de threadul garbage collector (un thread cu prioritate minima).

Metoda isAlive() permite verificarea daca threadul este “viu” adica daca a fost lansat cu start() dar inca nu a fost oprit cu stop(). metoda intoarce true daca threadul este “viu” si false in caz contrar.

Metoda t.join(long ms) blocheaza threadul care a efectuat apelul asteptand ms milisecunde ca threadul t caruia apelul i-a fost adresat sa “moara” intrand in starea Dead. Daca metoda join() nu are argument sau are argument 0 asteptarea are loc pe o durata nedeterminata. O astfel de asteptare este intr-o anumita masura periculoasa deoarece poate duce fie la o amanare nedeterminata a reluarii threadului care a facut apelul fie la o interblocare a treadurilor in cazul in care treadul apelat asteapta la randul sau decesul threadului apelant. Daca argumentul este diferit de 0, threadul asteapta intervalul specificat dupa care isi reia executia. Metoda “arunca” exceptia InterruptedException ca si metoda sleep().

Metoda suspend() duce la trecerea threadului in starea Suspended – executia acestuia fiind suspendata pana la apelarea metodei resume() care il trece in starea Ready permitand astfel reluarea executiei acestuia.

Page 93: programare in Java

188 CURS 15

Metoda toString() a clasei Thread intoarce un String continand numele si nivelul de prioritate al threadului precum si grupul caruia ii apartine acesta. Mai multe threaduri pot fi grupate intr-un obiect ThreadGroup. De regula threadurile sunt incadrate in acelasi grup cu threadul care le-a creat.

Metoda ThreadGroup getThreadGroup() intoarce o referinta la grupul caruia ii apartine threadul.

Clasa Thread prevede o metoda statica public static Thread currentThread(), care intoarce o referinta la threadul curent.

Metoda static int enumerate(Thread tarray[]) completeaza tabloul tarray cu referinte la toate threadurile active din grupul threadului care a facut apelul. Metoda intoarce numarul de threaduri plasate in tablou.

Metoda void setDaemon(boolean on) transforma threadul intr-un thread daemon daca argumentul on are valoarea true. Daca un program nu are toate threadurile Un thread daemon este un thread “server” care se executa in background si la ale carui servicii apeleaza alte threaduri “client” (non-daemon)ale aplicatiei. O aplicatie se termina daca executia tuturor threadurilor client non-daemon s-a terminat. Aceasta se face pe considerentul ca daca mai exista threaduri daemon in executie, ele nu mai au pe cine sa serveasca si deci pot fi “omorate” fara urmari.

Metoda boolean isDaemon() intoarce true daca threadul este daemon si false in caz contrar.

Metodele getName(), setName(), getPriority(), setPriority(), getThreadGroup(), join(), suspend(), resume(), setDaemon(), isDaemon() si stop() sunt metode finale, care nu pot fi supradefinite in subclasele derivate din clasa Thread.

Interfata Runnable Asa cum am vazut, clasa Thread implementeaza interfata Runnable care specifica metoda publica void run(). Putem folosi aceasta interfata pentru a defini propriile noastre clase care o implementeaza. Aceste clase vor defini fiecare, pe langa alti diversi membri (variabile si metode), o varianta proprie a metodei public void run(). Aceste clasele proprii care implementeaza interfata Runnable nu sunt threaduri dar pot fi folosite pentru a crea instante ale clasei Thread. In acest scop clasa Thread are definiti urmatorii doi constructori:

Thread(Runnable target) Thread(Runnable target, String nume)

La instantierea unui obiect Thread cu unul din acesti constructori va fi creat un fir de executie a carei metoda run() nu va mai fi cea implicita (care nu face nimic) fiind inlocuita cu metoda run() a clasei tinta (target) folosita la instantiere. Sa exemplificam cele aratate mai sus prin urmatorul program avand aceeasi functionalitate ca si cel din exemplul cu doi lucratori care plantau puncte si stelute la consola. class Plantator{ public static void main(String[] argv){ Lucrator l1=new Lucrator(40,"."), l2=new Lucrator(40,"*"); Thread t1=new Thread(l1), t2=new Thread(l2); t1.start();t2.start(); while(!(l1.isReady()&&l2.isReady())); t1.stop();t2.stop();

Page 94: programare in Java

PROGRAMARE IN JAVA - Note de curs 189

} } class Lucrator implements Runnable{ private int nrP; private boolean ready; private String sP; public Lucrator(int n, String s){nrP=n;sP=s;ready=false;} private void planteaza(){ try{ Thread.sleep(500); } catch(InterruptedException e){} System.out.print(sP); System.out.flush(); Thread.yield(); } private void lucreaza(){ for(int i=0;i<nrP;i++) planteaza(); ready=true; } public boolean isReady(){return ready;} public void run(){lucreaza();} } Spre deosebire de programul initial, deoarece metodele sleep() si yield() nu sunt definite in clasa Lucrator, ele trebuiesc apelate cu specificarea clasei (daca sunt statice) sau obiectul caruia ii apartin. In cazul nostru, deoarece aceste metode ale clasei Thread sunt statice le vom apela cu Thread.sleep() respectiv Thread.yield(). Pentru a apela alte metode nestatice ale threadului curent, trebuie sa obtinem referinta la instanta clasei Threed a threadului curent in care se face apelul. Aceasta referinta o putem obtine apeluland metoda statica Thread.currentThread(). Astfel pentru a apela metoda getName() am fi folosit instructiunea: Thread.currentThread().getName(); Relatia dintre clasa Plantator, clasa Lucrator si clasa Thread este reprezentata schematic in figura 15.6.

Page 95: programare in Java

190 CURS 15

Fig.15.6 - Relatia dintre clasa Plantator, clasa Lucrator si clasa Thread

Mecanismul descris mai sus este preferabil celui bazat pe mostenire (folosit in exemplele anterioare ). Spre deosebire de C++ care permitea mostenirea multipla, limbajul Java nu permite mostenirea decat a unei singure clase. Astfel ca o clasa care mosteneste clasa Thread nu mai poate mosteni o alta clasa ceea ce este un dezavantaj. In schimb o clasa care implementeaza interfata Runnable poate mosteni o alta clasa, putand in acelasi timp sa serveasca pentru instantierea unui Thread.

Partajarea datelor intre threaduri In exemplul anterior cele doua threaduri aveau acelasi cod (codul metodei run() si al celorlalte metode ale clasei Lucrator prcum si codul metodelor sleep() si yeld() ale clasi Thread) dar acest cod accesa date diferite. Threadul t1 accesa datele continute de instanta l1 a clasei Lucrator iar t2 datele continute de instanta l2 (variabilele nrP, sP si ready). Putem crea threaduri care sa aibe atat codul cat si datele diferite. De exemplu unul din threaduri sa planteze ca si in exemplul precedent puncte iar celalalt sa planteze cifre cuprinse intre 0 si 9, generate aleator. Programul urmator defineste o

Page 96: programare in Java

PROGRAMARE IN JAVA - Note de curs 191

clasa Lucrator care este mostenita de doua clase diferite Lucrator1 si Lucrator2 care in plus implementeaza interfata Runnable. Cele doua clase derivate au codul metodei run() diferit, astfel ca si threadurile create pe baza acestor doua clase difera. datele procesate de threaduri sunt si ele diferite, t1 prelucrand datele continute de variabilele membru ale obiectului l1 iar t2 ale obiectului l2. class Plantator{ public static void main(String[] argv){ Lucrator1 l1=new Lucrator1(40,"."); Lucrator2 l2=new Lucrator2(40); Thread t1=new Thread(l1), t2=new Thread(l2); t1.start();t2.start(); while(!(l1.isReady()&&l2.isReady())); t1.stop();t2.stop(); } } class Lucrator { protected int nrP; protected boolean ready; protected String sP; public Lucrator(int n){nrP=n;ready=false;} protected void planteaza(){ try{ Thread.sleep(100); } catch(InterruptedException e){} System.out.print(sP); System.out.flush(); Thread.yield(); } public boolean isReady(){return ready;} } class Lucrator1 extends Lucrator implements Runnable{ public Lucrator1(int n,String s){super(n);sP=s;} public void run(){ for(int i=0;i<nrP;i++) planteaza(); ready=true; } } class Lucrator2 extends Lucrator implements Runnable{ public Lucrator2(int n){super(n);} public void run(){ for(int i=0;i<nrP;i++){ sP=String.valueOf((int)(Math.random()*10)); planteaza(); } ready=true; }

Page 97: programare in Java

192 CURS 15 } Rezultatul afisat la consola de program este prezentat in figura 15.7.

Fig.15.7 - Rezultatul executiei programului Plantator.class

Mai multe threaduri cu cod diferit pot procesa un set de date comun ca in exemplul urmator in care unul din threaduri calculeaza suma iar celalalt produsul elementelor unui acelasi vector de valori intregi. class Matematician{ public static void main(String[] arg){ int data[]={1,2,3,4,5}; Suma s=new Suma(data); Prod p=new Prod(data); Thread t1=new Thread(s,"Suma"), t2=new Thread(p,"Produs"); t1.start();t2.start(); while(!(s.isReady()&&p.isReady())); t1.stop();t2.stop(); } } class Suma implements Runnable{ private boolean ready; private int suma; private int data[]; public Suma(int d[]){data=d;ready=false;} public void run(){ suma=0; for(int i=0; i<data.length;i++){ suma += data[i]; System.out.println( Thread.currentThread().getName()+"="+suma); Thread.yield(); } ready=true; } public boolean isReady(){return ready;} } class Prod implements Runnable{ private boolean ready; private int produs; private int data[]; public Prod(int d[]){data=d;ready=false;} public void run(){ produs=1; for(int i=0; i<data.length;i++){ produs *= data[i]; System.out.println(

Page 98: programare in Java

PROGRAMARE IN JAVA - Note de curs 193

Thread.currentThread().getName()+"="+produs); Thread.yield(); } ready=true; } public boolean isReady(){return ready;} } Cele doua threaduri au codul metodei run() diferit fiind construiti pe baza a doua obiecte Runnable diferite. Primul calculeaza suma elementelor unui tablou de intregi iar celalalt produasul elementelor unui tablou de intregi. Tabloul de intregi prelucrat de fiecare thread este referit de variabila membru data initializata de constructorul clasei cu o referinta primita ca argument. Metoda main() creaza o instanta s a clasei Suma si o instanta p a clasei Prod, specificand ca argument al constructorilor o referinta la acelasi tablou de date data, astfel incat metodele run() ale celor doua obiecte s si p, desi au codul diferit vor procesa de fapt acelasi date. Cele doua obiecte Runnable, s si p sunt folosite fiecare la instantierea a cate unui thread – t1 si respectiv t2. La instantierea threadurilor s-au folosit constructori in varianta care seteaza si numele threadului. Astfel threadul t1 va avea numele “Suma” si metoda run() a lui s calculand suma iar t2 va avea numele “Produs” si metoda run() a lui p calculand produsul elementelor aceluiasi tablou de intregi data. Pentru a pune in evidenta functionarea concurenta a threadurilor, metodele run ale acestora afiseaza si rezultatele intermediare obtinute pe parcursul procesarii datelor. In figura 15.8 sunt prezentate rezultatele afisate de program.

Fig.15.8 Rezultatele afisate la executia programului Matematician.class

Page 99: programare in Java

PROGRAMARE IN JAVA - Note de curs 193

Curs 16

Sincronizarea firelor de executie Fie programul urmator care utilizeaza doua threaduri Lucrator pentru a “planta” puncte si stelute pe ecran: class Plantator{ public static void main(String[] argv){ Lucrator l1=new Lucrator(40,"."), l2=new Lucrator(40,"*"); Thread t1=new Thread(l1), t2=new Thread(l2); t1.start();t2.start(); while(!(l1.isReady()&&l2.isReady())); t1.stop();t2.stop(); } } class Lucrator implements Runnable{ private int nrP; private boolean ready; private String sP; public Lucrator(int n, String s){nrP=n;sP=s;ready=false;} private void planteaza(){ System.out.print(sP); System.out.flush(); Thread.yield(); } public boolean isReady(){return ready;} public void run(){ for(int i=0;i<nrP;i++) planteaza(); ready=true; } }

Fig.16.1 – Rezultatele afisate de programul Plantator.class Daca analizam rezultatele afisate de programul precedent, prezentate in figura 16.1 vom vedea ca desi concurente, cele doua threaduri “planteaza” “produsele” lor ( puncte si stelute) asincron – uneori reuseste unul dintre ele sa planteze cateva puncte succesiv, alteori reuseste celalalt thread sa planteze cateva stelute succesiv. Daca dorim ca punctele si stelutele sa fie “plantate” succesiv ( un punct, o steluta), trebuie sa gasim un mecanism ajutator pentru sincronizarea activitatii celor doua threaduri. Ne putem inspira din mecanismele utilizat in dirijarea traficului feroviar. Aici in acest scop se folosesc semafoare – fie luminoase, cu doua culori, rosu si verde, fie mecanice cu un brat. Daca semaforul are culoarea verde sau bratul este ridicat trenul poate trece. In caz contrar trenul trebuie sa astepte culoarea verde sau ridicarea bratului semaforului. Vom folosi o variabila booleana de tip “flag” care daca are valoarea true permite threadului t1 sa planteze un punct iar daca are valoarea false va permite threadului t2 sa “planteze” o steluta. Daca t1 gaseste “flagul” avand valoarea false, el

Page 100: programare in Java

194 CURS 16 va trebui sa astepte ca acesta sa capete valoarea true pentru a-si “planta” punctul. La fel, daca t2 gaseste “flagul” avand valoarea true va trebui sa astepte ca acesta sa capete valoarea false pentru a-si “planta” steluta. Dupa ce “plantarea” a fost efectuata, threadul curent va comuta “flagul” pe pozitia complementara pentru a-si interzice o noua “plantare” si in acelasi timp pentru a autoriza celalalt thread sa isi faca “plantarea”. Programul este urmatorul: class Plantator{ public static void main(String[] argv){ Semafor semafor=new Semafor(); Lucrator l1=new Lucrator(semafor,40,"."), l2=new Lucrator(semafor,40,"*"); Thread t1=new Thread(l1,"T1"), t2=new Thread(l2,"T2"); t1.start();t2.start(); while(!(l1.isReady()&&l2.isReady())); t1.stop();t2.stop(); } } class Lucrator implements Runnable{ private Semafor semafor; private int nrP; private boolean ready; private String sP; public Lucrator(Semafor semafor, int n, String s) { nrP=n;sP=s;ready=false; this.semafor=semafor; } private void planteaza(){ System.out.print(sP); System.out.flush(); } public boolean isReady(){return ready;} public void run(){ for(int i=0;i<nrP;i++){ if(Thread.currentThread().getName().equals("T1")) while(!semafor.verdeT1());//T1 asteapta culoarea verde else while(!semafor.verdeT2());//T2 asteapta culoarea verde planteaza(); semafor.schimba();// autorizeaza celalalt thread Thread.yield();// comuta threadul } ready=true; } } class Semafor{ private boolean flag; public Semafor(){flag=false;} public void schimba(){ flag=!flag;} public boolean verdeT1(){return flag;} public boolean verdeT2(){return !flag;} } Aici am definit o clasa Semafor care incapsuleaza “flag”-ul alaturi de procedurile de setare si testare a starii acestuia. Un obiect – instanta a clasei Semafor – este creat si referinta catre acesta transmisa constructorului celor doua obiecte Runnable l1 si l2. El va fi o resursa comuna pentru cele doua threaduri t1 si t2 care prin apelarea

Page 101: programare in Java

PROGRAMARE IN JAVA - Note de curs 195

metodelor sale vor putea sa determine si sa modifice starea flagului sincronizandu-si activitatea in functie de aceasta. Desi t1 si t2 au acelasi cod (aceeasi metoda run() a clasei Lucrator), in cadrul acestui cod se face identificarea dupa nume a threadului curent. Astfel, se obtine referinta la threadul curent apeland metoda statica Thread.currentThread(), folosind aceasta referinta, se obtine apeland metoda getName() stringul continand numele threadului curent si se face comparatia la egalitate cu numele threadului t1 (“T1”) apeland metoda clasei String, equals(“T1”). Aceasta metoda intoarce true daca numele threadului curent este “T1” si false in caz contrar. In functie de valoarea booleana astfel obtinuta, in cadrul unui bloc de selectie dublu if/else, se fac procesarile adecvate threadului in cauza. Daca valoarea obtinuta este true inseamna ca threadul curent este t1 si se apeleaza metoda semafor.verdeT1() in cadrul unui ciclu while care implementeaza operatia de asteptare. Ciclul while se executa atat timp cit semafor.verdeT1() intoarce false. Daca threadul curent nu este t1, atunci el este threadul t2 si se executa ciclul while de asteptare cat timp semafor.verdeT2() intoarce false. Odata obtinuta permisiunea de “plantare”, ciclul de asteptare este terminat si threadul curent efectueaza “plantarea” comutand apoi semaforul prin apelul metodei semafor.schimba(), autorizand astfel iesirea din ciclul de asteptare a threadului colaborator si trecandu-se pe sine in starea de asteptare. Programul va afisa la executie rezultatele din figura 16.2:

Fig.16.2 – Rezultatele afisate de programul Plantator.class in versiunea sincronizata

Sincronizarea sectiunilor critice ale threadurilor In limbajul Java, orice obiect din clasa Object sau dintr-o subclasa a acesteia are o proprietate numita lock – lacat. De fapt aceasta trebuie privita mai curand ca o cheie cu care se poate descuia o usa spre o incapere din obiectul respectiv. Aceasta cheie este obiectului. Pentru a patrunde in incapere, un thread trebuie sa ia cheia cu care sa descuie usa de acces. Threadul pastreaza cheia asupra sa cat timp se afla in incapere incaperea. La iesirea din incapere, threadul pune cheia la loc. Daca in timp ce incaperea este ocupata de un thread un alt thread doreste sa patrunda in incapere, nu va gasi cheia (aceasta fiind detinuta de threadul care se afla deja in incapere) si atunci se va aseza la coada asteptand aparitia cheii. Acelasi lucru se va intampla si cu alte threaduri care vor sa intre in incapere, si acestea asezandu-se la coada si asteptand aparitia cheii. In momentul in care incaperea este libera si cheia este disponibila threadul aflat primul la coada va iesi din starea de asteptare, va lua cheia si va patrunde in incapere. Acest mecanism permite ca in cazul mai multor threaduri ruland concurent, o secventa de cod sa fie garantat executata doar de un singur thread si numai unul. Celalalte threaduri care trebuie sa execute si ele acea secventa astepta pana ce threadul care a inceput executia acesteia sa o si parcurga pana la capat (chiar daca pentru executia secventei respective ii sunt necesare mai multe cuante de timp) Acest mecanism se aplica de regula secventelor critice de program a caror executie concurenta de catre doua sau mai multe threaduri ar duce la o functionare incorecta a programului. Ca sa dam un exemplu din lumea reala sa luam cazul in care doua dactilografe au de batut doua texte diferite la masina de scris. Pana intr-un punct, amandoua primesc textele lucrand in paralel – prima de la director iar cealalta de la contabilul sef. Tot in paralel, fiecare se indreapta spre masina de scris care din pacate este numai una. Daca in acest moment ar incepe sa bata la masina concurent, fiecare

Page 102: programare in Java

196 CURS 16 textul sau, una apasand o tasta, apoi cealalta, textul imprimat ar fi un mixaj neinteligibil al celor doua texte. Solutia este ca sa se asigure accesul la masina de scris numai a unei singure dactilografe, cealalta trebuind sa astepte eliberarea masinii de scris pentru a putea incepe dactilografierea propriului text. Dactilografa care a ajuns prima la masina de scris si a ocupat-o, nu o elibereaza decat atunci cand a terminat de dactilografiat textul propriu. Mecanismul care asigura un astfel de mod de lucru a acestei sectiuni critice din activitatea dactilografelor este cel descris anterior. In program sectiunea critica a unui thread se inscrie intr-un bloc care este “sincronizat” cu un obiect ( de regula cu obiectul careia ii apartine codul): sinchronized(obj){ // ia cheia si intra in incapere ... // executa activitatea critica }// paraseste incaperea si elibereaza cheia In momentul in care un threadul incepe sa execute aceasta secventa, toate celalalte threaduri care vor incerca sa execute aceeasi secventa vor fi blocate intr-o coada de asteptare pana cand threadul care deja executa secventa nu o va termina iesind din blocul sincronizat. Sa urmarim urmatorul program care modeleaza accesul la o singura masina de scris a doua dactilografe. class Office{ public static void main(String[] argv){ MasinaDeScris ms=new MasinaDeScris(); String doc1="Directiva Nr.1/06.1999\n", doc2="Nota Contabila Nr.1/06.1999\n"; Dactilografa ana=new Dactilografa(ms,doc1), maria=new Dactilografa(ms,doc2); Thread t1=new Thread(ana,"Ana"), t2=new Thread(maria,"Maria"); t1.start();t2.start(); while(!(ana.isReady()&&maria.isReady())); t1.stop();t2.stop(); } } class Dactilografa implements Runnable { private boolean ready; private MasinaDeScris ms; private String doc; public Dactilografa(MasinaDeScris ms,String doc){ this.doc=doc; this.ms=ms; } public boolean isReady(){return ready;} public void run(){ for(int i=0;i<doc.length();i++){ ms.print(doc.charAt(i)); Thread.yield(); } ready=true; } } class MasinaDeScris { public void print(char c){ try{ Thread.sleep(10); }

Page 103: programare in Java

PROGRAMARE IN JAVA - Note de curs 197

catch(InterruptedException exception){} System.out.print(c); System.out.flush(); } } In figura 16.3 sunt redate rezultatele a trei executii succesive ale programului care confirma necesitatea accesului sincronizat al dactilografelor la masina de scris.

Fig.16.3 – Rezultatele afisate de programul Office.class

Pentru a asigura accesul sincronizat va trebui sa redefinim clasa MasinaDeScris ca subclasa a clasi Object (pentru a putea folosi un obiect din aceasta clasa pentru sincronizare) si sa incadram sectiunea critica din metoda run() intr-un bloc de sincronizare cu obiectul ms (instanta a clasei MasinaDeScris). Programul rezultat este urmatorul: class Office{ public static void main(String[] argv){ MasinaDeScris ms=new MasinaDeScris(); String doc1="Directiva Nr.1/06.1999\n", doc2="Nota Contabila Nr.1/06.1999\n"; Dactilografa ana=new Dactilografa(ms,doc1), maria=new Dactilografa(ms,doc2); Thread t1=new Thread(ana,"Ana"), t2=new Thread(maria,"Maria"); t1.start();t2.start(); while(!(ana.isReady()&&maria.isReady())); t1.stop();t2.stop(); } } class Dactilografa implements Runnable { private boolean ready; private MasinaDeScris ms; private String doc; public Dactilografa(MasinaDeScris ms,String doc){ this.doc=doc; this.ms=ms; } public boolean isReady(){return ready;} public void run(){ synchronized(ms){ for(int i=0;i<doc.length();i++){ ms.print(doc.charAt(i)); Thread.yield(); } } ready=true; } }

Page 104: programare in Java

198 CURS 16 class MasinaDeScris extends Object { public void print(char c){ try{ Thread.sleep(10); } catch(InterruptedException exception){} System.out.print(c); System.out.flush(); } } Rezultatele afisate de program sunt redate in figura 16.4.

Fig.16.4 – Rezultatele afisate de programul Office.class

Metode sincronizate, monitoare Asa cum am vazut in exemplul precedent, sincronizarea ofera un mecanism care permite partajarea resurselor intre threaduri. In acest exemplu, o instanta a clasei MasinaDeScris era o resursa folosita in comun de catre cele doua threaduri Dactilografa. De regula o resursa partajata intre mai multe threaduri este un obiect continand informatia in variabilele sale membru. Am vazut ca in cazul programelor cu un singur fir de executie (singlethread) pentru a asigura accesarea corecta a acestor date, variabilele trebuie sa fie declarate private iar obiectul sa fie prevazut cu metode publice adecvate pentru accesarea si eventual procesarea acestor date. In cazul programelor cu mai multe fire de executie (multithread) trebuiesc luate masuri suplimentare de siguranta pentru prevenirea coruperea datelor datorita executiei concurente de catre mai multe threaduri ale metodelor obiectului. Sa consideram urmatoarea secventa de instructiuni din cadrul unei metode: class Resursa { private int zi,luna,an; ... public void up(){ zi++; if(zi > nrZileInLuna(luna,an)){ zi=1; luna++; ...// procesare an } } ... } unde zi, luna, an sunt variabile membru ale obiectului din clasa Resursa, partajat intre threadurile t1 si t2. Sa presupunem ca threadul t1 incepe executia acestei secvente si incrementand variabila zi obtine o valoare mai mare ca numarul de zile in luna respectiva. Ca urmare t1 va incepe sa execute corpul instructiunii if moment in care expira cuanta de timp alocata executiei sale si threadul este trecut in starea Ready. Sa presupunem ca un al doilea thread t2 incepe executia aceleiasi secvente. El va incrementa variabila zi si va obtine o valoare mai mare ca numarul de zile in luna si va incepe la randul sau sa execute corpul instructiunii de selectie if, va inscrie valoarea 1 in variabila zi, va incrementa variabila luna moment in care ii expira si lui cuanta de

Page 105: programare in Java

PROGRAMARE IN JAVA - Note de curs 199

timp, executia fiind reluata de threadul t1 din punctul in care a fost intrerupt adica in corpul instructiunii de selectie. El va inscrie in variabila zi valoarea 1 si va incrementa variabila luna. Intr-o astfel de executie, variabila luna a fost incrementata de doua ori in loc sa fie incrementata odata ceea ce evident duce la un rezultat eronat(s-a sarit peste o luna). Pentru a evita astfel de situatii secventa data trebuie sincronizata cu un obiect prevenind astfel executia concurenta a metodei de catre threadul t2 in timp ce ea este executata si de t1. Threadul t2 va fi trecut in starea Waiting si pus intr-o coada de asteptare la obiectul folosit pentru sincronizare pana cand t1 va termina executia metodei. Pentru sincronizare se va folosi chiar obiectul partajat referit cu this, care face parte din clasa Resursa, subclasa a clasei Object: class Resursa extends Object{ private int zi,luna,an; ... public void up(){ synchronized(this){ zi++; if(zi > nrZileInLuna(luna,an)){ zi=1; luna++; ...// procesare an } } } ... } In acest caz, cand intreg corpul metodei este sincronizat chiar cu obiectul caruia metoda ii apartine (referinta la acesta fiind this), limbajul Java prevede o scriere prescurtata echivalenta: class Resursa extends Object{ private int zi,luna,an; ... public synchronized void up(){ zi++; if(zi > nrZileInLuna(luna,an)){ zi=1; luna++; ...// procesare an } } ... } Se spune despre metoda up ca este sincronizata. Un obiect care contine cel putin o metoda sincronizata se numeste monitor.

Interblocarea threadurilor La proiectarea programelor Java multithread trebuie avut grija sa se evite interblocarea threadurilor. Fenomenul are loc in urmatoarele conditii. Sa ne imaginam urmatorul scenariu. Sa presupunem ca avem doua monitoare m1 si m2. Fiecare dispune de metodele sincronizate f1() si f2(). Metoda m1.f1() apeleaza metoda m2.f2() iar metoda m2.f1() apeleaza metoda m1.f2(). Metodele monitoarelor m1 si m2 sunt apelate de doua threaduri t1 si t2 astfel: t1 apeleaza m1.f1() iar t2 pe m2.f1(). Apeland metoda m1.f1(), threadul t1 isi insusseste cheia de la monitorul m1. Daca in acest moment ii expira cuanta de timp alocata, el este intrerupt si intra in executie threadul t2. Acesta la randul sau apeleaza metoda m2.f1() si isi insuseste cheia de la monitorul

Page 106: programare in Java

200 CURS 16 m2. Continuind executia, acestei metode, aceasta va apela metoda m1.f2(). Threadul t2 nu detine cheia de la monitorul m1 (aceasta fiind inca in posesia threadului intrerupt t1) asa ca se va bloca asteptand ca t1 sa elibereze cheia. La reactivarea threadului t1 pentru o noua cuanta de timp, acesta va continua executia din punctul in care a fost intrerupt adica din interiorul metodei m1.f1(). Executand aceasta metoda, la un moment dat va apela metoda m2.f2(). Deoarece nu detine cheia de la monitorul m2 (aceasta fiind in posesia threadului t2) se va bloca asteptand ca t2 sa elibereze cheia. Dar t2 se afla de asemenea in starea Waiting de asteptare pana cand threadul t1 va elibera cheia de la m1 si in consecinta cele doua threaduri raman blocate definitiv asteptandu-se reciproc.

Firele de executie in AWT Chiar si la executia unui program singlethread Java se lanseaza pe langa threadul principal si doua threaduri utilitare de prioritate scazuta – unul pentru colectarea blocurilor de memorie eliberate si unul pentru finalizatori ( care executa metodele finalize() la distrugerea obiectelor). Daca programul creaza o interfata grafica, se lanseaza inca un thread secundar care implementeaza functiile AWT gestionand evenimentele si executand metodele handleEvent() si paint() ale interfetei. acest thread este in majoritatea timpului in asteptare fiind activat numai la aparitia unui eveniment cum ar fi apasarea unei taste sau miscari ale soarecului si atunci cand o portiune din fereastra a fost invalidata prin acopreirea de catre alta fereastra ceea ce necesita redesenarea portiunii “expuse”. Cand se doreste redesenarea ferestrei se apeleaza metoda repaint() a componentei AWT, care simuleaza expunerea intregii suprafete a acesteia, provocand astfel redesenarea ei. Invalidarea simulata cu repaint() are acelasi efect ca si o expunere reala cauzand activarea threadului AWT care va apela metoda paint() corespunzatoare. Chiar daca interfata grafica consta din mai multe ferestre deschise cu mai multe componente fiecare, nu exista decat un singur thread care gestioneaza intregul sistem de componente AWT. Aceasta are o consecinta importanta. Metodele executate de thread pentru procesarea evenimentului trebuie sa fie cat mai scurte pentru ca daca ele efectueaza o procesare de durata mai mare, threadul AWT va fi ocupat cu aceasta procesare si nu va putea sa preia un nou eveniment pana nu termina procesarea. Evenimentul survenit intre timp va fi preluat din coada de evenimente si tratat cu o intarziere egala cu durata procesarii evenimentului precedent. Daca aceasta durata este prea mare ea devine sesizabila operatorului si duce la o functionare greoaie a programului. care raspunde cu intarziere la comenzile date prin intermediul interfetei grafice. De exemplu daca interfata este prevazuta cu butonul “Abort” care ar trebui sa inchida fereastra si sa termine aplicatia, “apasarea” butonului va fi sesizata de thread abia dupa terminarea procesarii in curs. Daca durata acestei procesari este de ordinul milisecundelor utilizatorul nu va sesiza intarzierea. Daca insa procesarea dureaza secunde, aceasta intarziere devine deja sesizabila si incomodeaza. In figura 16.5a sunt reprezentate starile prin care trece threadul AWT in cazul prezentat mai sus. Solutia pentru evitarea inconvenientul prezentat, consta in crearea unui thread server insarcinat cu aceasta procesare de durata. Crearea acestuia degreveaza threadul AWT de sarcina unei procesarii indelungate, acesta avand sarcina doar sa activeze threadul server fiind liber in continuare sa preia si sa trateze alte evenimente. Starile prin care trec cele doua threaduri sunt prezentate in figura 16.5b.

Page 107: programare in Java

PROGRAMARE IN JAVA - Note de curs 201

Fig.16.5 Starile threadului AWT la procesarea de durata a unui eveniment Ca exemplu sa analizam programul urmator: import java.awt.*;

Frame{ class MyFrame extends private Button b; public MyFrame(){ super("Test AWT thread"); b=new Button("Procesare"); setLayout(new FlowLayout());

add(b);resize(300,100);show(); }

t e){ public boolean handleEvent(Even if(e.id==e.WINDOW_DESTROY){ hide();

Page 108: programare in Java

202 CURS 16 dispose(); System.exit(0); return true; } return super.handleEvent(e); } public boolean action(Event e, Object o){ for(long i=0;i<10000000;i++); return true; } public static void main(String[] args){ MyFrame f=new MyFrame(); } } Acest program creaza o fereastra cu un buton avand eticheta “Procesare” (figura 16.6) .

Fig.16.6 Fereastra afisata de programul MyFrame.class Metoda action() a clasei MyFrame trateaza evenimentele produse la actionarea acest buton prin executarea unui ciclu for cu 10.000.000 de iteratii care iau un timp considerabil ( de circa 10 secunde). Daca se actioneaza butonul de sistem “x” se genereaza un eveniment cu identificatorul WINDOW_DESTROY care va fi tratat de tratat de metoda handleEvent() prin inchiderea ferestrei si terminarea programului. Daca se actioneaza butonul “Procesare” si imediat butonul de terminare, programul nu se va termina imediat, trecand un timp de circa 10 secunde pana la inchiderea ferestrei si terminarea programului. Aceasta se explica prin faptul ca threadul AWT care a fost activat de evenimentul generat la actionarea butonului “Procesare” este ocupat cu executia celor 10.000.000 de iteratii ale ciclului for din metoda action(). Abia dupa ce termina executia acestei metode si revine in starea de asteptare a evenimentelor de la interfata grafica, si gasind aici evenimentul WINDOW_DESTROY se va activa executand metoda handleEvent() pentru acest eveniment, terminand astfel aplicatia. Conform celor aratate anterior, solutia este crearea unui thread “Server” care sa deserveasca threadul AWT, preluand asupra sa procesarea evenimentelor de actionare a butonului “Procesare” (efectuarea celor 10.000.000 de iteratii) .Sarcina threadului AWT la aparitia evenimentului de la butonul “Procesare” se reduce doar la activarea threadului server, fiind astfel degrevat de sarcina procesarii indelungate si fiind rapid gata de tratarea unui nou eveniment. Programul urmator pune in practica considerentele enuntate mai sus. In acest program, interfata grafica a fost completata cu un camp text pentru afisarea starii threadului “Server” printr-un mesaj adecvat. Activarea threadului “Server” se face de catre metoda action() prin apelul la metoda resume() a acestuia avand ca efect trecerea lui din starea Suspended in starea Ready si facandu-l astfel eligibil pentru a fi trecut de planificator in starea Running pe o cuanta de timp. Dupa terminarea celor 10.000.000 de iteratii in cadrul metodei run() (pe durata a mai multe cuante de timp, fiind executat concurent cu threadul principal si cu thredul AWT), threadul revine in starea Suspended prin apelul metodei suspend(). In aceasta versiune, programul va fi terminat cvasiinstantaneu la actionarea butonului de sistem “x” chiar daca se afla in cursul procesarii initiate prin actionarea

Page 109: programare in Java

PROGRAMARE IN JAVA - Note de curs 203

butonului“Procesare”. Programul sursa este redat mai jos: import java.awt.*; class MyFrame extends Frame{ private Server server; private Thread t; private Button b; private TextField msg; public MyFrame(){ super("Test AWT thread"); b=new Button("Procesare"); msg=new TextField(30); msg.setEditable(false); setLayout(new FlowLayout()); add(b);add(msg);resize(300,100);show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ t.stop(); hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public boolean action(Event e, Object o){ t.resume(); return true; } public static void main(String[] args){ MyFrame f=new MyFrame(); f.server=new Server(f.msg); f.t=new Thread(f.server); f.t.setDaemon(true); f.t.start(); } } class Server implements Runnable{ private TextField msg; public Server(TextField msg){this.msg=msg;} public void run(){ for(;;){ msg.setText("Server thread suspended"); Thread.currentThread().suspend(); msg.setText("Server thread running"); for(long i=0; i<10000000;i++); } } } Programul incepe prin a defini clasa MyFrame al carei constructor va crea, butonul, campul text de afisare a starii threadului server, va crea si seta managerul de amplasare al ferestrei, va adauga aceste componente ferestrei, o va redimensiona la 300x100 pixeli si o va face vizibila pe ecran. Tot in clasa MyFrame sunt definite metodele de tratare a evenimentelor handleEvent() si action() precum si metoda statica main(). Aceasta din urma creaza fereastra, obiectul Runnable server si cu ajutorul acestuia threadul ce va deservi threadul AWT pentru procesare. In continuare metoda main() steaza threadul server ca Daemon si il activeaza prin apelul metodei start() a acestuia. Threadul server incepe executia metodei run() definita in clasa Server, si dupa afisarea mesajului de stare intra imediat in starea Suspended prin

Page 110: programare in Java

204 CURS 16

apelul metodei sale suspend() . Din aceasta stare nu va iesi decat in urma executiei de catre threadul AWT a metodei acction() la tratarea evenimentului de actionare a butonului “Procesare”. La executia acestei metode threadul AWT va apela metoda resume() a threadului server reactivandu-l. Treadul server reactivat va relua executia concurenta a metodei run() din punctul in care a fost suspendat, afisiand un mesaj de stare adecvat si efectuand procesarea (cele 10.000.000 de iteratii ale ciclului for ). La terminarea iteratiilor, threadul va relua executia ciclului infinit al metodei, afisand din nou mesajul de suspenadare si apeland metoda sa suspend() care il va aduce iar in starea Suspended pana la o noua apasare a butonului “Procesare”. La apasarea butonului de sistem “x” threadul AWT executa metoda handleEvent() in cadrul careia detecteaza evenimentul WINDOW_DESTROY asociat acestei actiuni, termina executia threadului server apeland metoda stop() a acestuia, inchide fereastra si termina aplicatia. In figura 16.7 este redata ferastra aplicatiei cu mesajele de stare a threadului.

Fig.16.7 Fereastra afisata de programul MyFrame.class

Page 111: programare in Java

PROGRAMARE IN JAVA - Note de curs 205

Curs 17

Comunicatia intre threaduri Asa cum am vazut in exemplele anterioare programele multithread se proiecteaza pornind de la ideia cooperarii a doua sau mai multe threaduri la rezolvarea problemei. Activitatea unui program singlethread putea fi asemuita cu munca din atelierul unui mestesugar in carea acesta face singur toate operatiile de prelucrare asupra materiei prime pentru obtinerea in final a produsului finit. De exemplu pentru a fabrica ace, mestesugarul va reitera succesiv urmatorul lant de operatii – va debita dintr-un colac de sarma de otel o bucata de lungimea necesara, va ascuti aceasta bucata de sarma la unul din capete si ii va executa un orificiu la celalalt capat. Activitatea unui program multhithread poate fi comparata cu productia dintr-o intreprindere industriala in care se aplica diviziunea muncii, avand mai multi lucratori specializati intr-o singura operatie. Astfel vom avea trei categorii de muncitori – unii care efectueaza operatia de debitare, altii care ascut bucatile de sarma la un capat si altii care executa orificiile de la celalalt capat obtinand produsul finit. Intr-o astfel de organizare a productiei muncitorii de specializari diferite trebuie sa coopereze transmitand de la unul la altul produsele intermediare. Intr-o astfel de cooperare unii muncitori joaca rolul de producatori iar altii de consumator. De exemplu un muncitor care face operatia de debitare joaca rolul de producator. Muncitorul care ia bucatile de sarma debitate de colegul sau si le ascute la un capat joaca rolul de consumator. La randul sau acelasi muncitor joaca rolul de producator in relatia sa cu muncitorul care executa gaurirea acelor ascutite de el. Activitatile executate de producatori si consumatori au durate diferite ele desfasurandu-se asincron. Astfel operatia de debitare a sarmei s-ar putea sa aibe o durata mai mica decat cea de ascutire a unei astfel de bucati. In consecinta, stocul de bucati debitate ar creste continuu deoarece muncitorul care efectueaza ascutirea nu are timp sa consume intreaga productie a colegului sau. Activitatea producator-consumator trebuie deci sincronizata. In cazul exemplului nostru sincronizarea se desfasoara astfel. Producatorul executa produsul si incearca sa-l depuna intr-o cutie. Daca cutia este deja plina el se opreste asteptand ca partenerul sau sa consume din stoc, facand astfel loc pentru depunerea a noi produse. In a doua varianta in care durata operatiei de prelucrare efectuata de consumator este mai mica decat cea efectuata de producator, consumatorul va gasi cutia goala si va astepta ca producatorul sa depuna in ea un produs. Pentru mari eficienta prin reducerea timpilor de asteptare pot fi prevazuti in prima varianta mai multi consumatori la un singur producator si mai multi producatori la un singur consumator in cea de a doua. Cutia in care producatorii depun iar consumatorii ridica produsele joaca rol de tampon (buffer in limba engleza) intre acestia servind la sincronizarea activitatii lor. In cazul programelor multithreading proiectarea acestora se poate face pornind de la analogia cu o astfel de intreprindere descrisa mai sus, threadurile avand rolul producator-consumator similar muncitorilor specializati pe operatii diferite din cazul prezentat. La elaborarea programului trebuie tinut cont de regulile de sincronizare a threadurilor si de partajare a datelor discutate in cursul precedent. Sa analizam programul urmator:

Page 112: programare in Java

206 CURS 17 import java.awt.*; class MyFrame extends Frame{ private Producator p; private Consumator c; private Thread prod,cons; private Button b; private TextField msg1,msg2; private Buffer buf; public MyFrame(){ super("Test Producator - Consumator"); b=new Button("Start"); msg1=new TextField(30); msg2=new TextField(3); msg1.setEditable(false); msg2.setEditable(false); setLayout(new FlowLayout()); add(b);add(msg1);add(msg2);resize(300,100);show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ prod.stop(); cons.stop(); hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public boolean action(Event e, Object o){ b.disable(); prod.resume(); return true; } public static void main(String[] args){ MyFrame f=new MyFrame(); f.buf=new Buffer(); f.p=new Producator(f.msg1,f.buf); f.c=new Consumator(f.msg2,f.buf,f.b); f.prod=new Thread(f.p); f.cons=new Thread(f.c); f.p.setPartner(f.cons); f.c.setPartner(f.prod); f.prod.start(); f.cons.start(); } } class Producator implements Runnable{ private TextField msg; private Buffer buf; private Thread partner; private int k; private StringBuffer sb; public Producator(TextField msg, Buffer buf){ sb=new StringBuffer(); this.msg=msg; this.buf=buf; } public void setPartner(Thread partner){this.partner=partner;}

Page 113: programare in Java

PROGRAMARE IN JAVA - Note de curs 207

public void run(){ for(;;){ for(int i=0;i<5;i++){ Thread.currentThread().suspend(); try{ Thread.sleep(1000); } catch(InterruptedException ex){} buf.store(k=1+(int)(Math.random()*10)); sb.append(k+" "); msg.setText(sb.toString()); partner.resume(); } sb.setLength(0); } } } class Consumator implements Runnable{ private TextField msg; private Buffer buf; private Thread partner; private Button b; public Consumator(TextField msg, Buffer buf, Button b){ this.msg=msg; this.buf=buf; this.b=b; } public void setPartner(Thread partner){this.partner=partner;} public void run(){ for(;;){ for(int i=0,sum=0;i<5;i++){ Thread.currentThread().suspend(); sum+=buf.get(); msg.setText(String.valueOf(sum)); if(i<4)partner.resume(); } b.enable(); } } } class Buffer{ private int data; public void store(int val){data=val;} public int get(){ return data;} } Aici producatorul este modelat de threadul prod construit pe baza unui obiect Runnable din clasa Producator. Consumatorul este modelat printr-un thread cons, construit pe baza unui obiect Runnable din clasa Consumator. Odata creati si activati in metoda main() prin apelul metodelor start() ale threadurilor, producatorul si consumatorul incep executia metodelor run() definite in clasele Runnable Producator, respectiv Consumator. Aceste metode contin doua cicluri incuibate for – ciclul exterior fiind un ciclu infinit iar al cel interior un ciclu cu contor cu 5 iteratii. Amandoua threadurile intra imediat in starea Suspended prin executia in prima instructiune a ciclului cu contor a apelului Thread.currentThread().suspend(). La actionarea butonului “Start”, threadul AWT executa metoda action() dezactivand

Page 114: programare in Java

208 CURS 17 butonul si apeland metoda prod.resume() care va activa threadul producator. Producatorul elaboreaza dupa o temporizare de 1 secunda un numar aleator cuprins intre 1 si 10 si il depune intr-un obiect buf din clasa Buffer avand capacitate de stocare de o valoare intreaga. Depunerea se face apeland metoda buf.store() definita in clasa Buffer. Dupa aceasta threadul producator adauga numarul generat la un StringBuffer si afiseaza continutul acestuia intr-un camp text needitabil. Dupa afisarea numarului, threadul producator activeaza threadul consumator prin apelul metodei resume() a acestuia. cu aceasta prima iteratie se termina, contorul de iteratii i este testat si incrementat dupa care producatorul executa din nou instructiunea Thread.currentThread().suspend() care il aduce din nou in starea Suspended. In acest timp, consumatorul care a fost activat de producator preia data din buffer cu apelul metodei buf.get() si o aduna la variabila sum (initializata cu 0) afisand noua valoare a acesteia intr-un camp text needitabil. Dupa afisare consumatorul reactiveaza threadul producator prin apelul metodei acestuia resume()( daca contorul i<4 adica daca nu este ultima iteratie). Dupa aceasta, consumatorul trece la o noua iteratie a ciclului, testand si incrementand contorul i si apeland dupa aceea Thread.currentThread().suspend() care il aduce din nou in starea Suspended. aceste cicluri se repeta pana la terminarea a cinci iteratii in urma carora in campul text gestionat de producator se gasesc afisate cele 5 numere generate iar in cel gestionat de consumator suma acestor numere. La a cincea iteratie threadul producator dupa generarea si afisarea numarului incepe o noua iteratie a ciclului exterior, initializeaza contorul i al ciclului interior cu 0 si executa apelul Thread.currentThread().suspend() care il aduce din nou in starea Suspended. El nu mai este reactivat de consumator in ultima iteratie ramanand in aceasta stare. Consumatorul la randul sau dupa preluarea ultimei date generate de producator din buffer, calcularea si afisare sumei iese din ciclul cu contor fara sa mai apeleze metoda resume a producatorului. Dupa iesirea din ciclul cu contro, consumatorul reactiveaza butonul “Start” si incepe o noua iteratie a ciclului infinit, prin inceperea executiei a unui nou ciclu cu contor. initializeaza contorul i si variabila sum la 0 si executa apelul Thread.currentThread().suspend() intrand si el in starea Suspended. Activitatea descrisa mai sus a threadurilor producator si consumator va fi reluata numai dupa o noua actionare a butonului “Start”. In figura 17.1 sunt redate imagini ale ferestrei aplicatiei in cele trei faze de executie a threadurilor – inainte de apasarea butonului cand ambele threduri sunt suspendate, in timpul generarii si procesarii datelor cand threadurile se activeaza reciproc si dupa terminarea procesarii unui set de 5 date, cand threadurile sunt din nou suspendate.

Fig.17.11 – Activitatea threadurilor producator-consumator

Page 115: programare in Java

PROGRAMARE IN JAVA - Note de curs 209

Comunicatia folosind metodele wait() si notify() Aplicatia din exemplul precedent asigura comunicatia intre doua threaduri folosind metodele suspend() si resume(). Utilizarea acestor metode devine dificila in cazul cooperarii intre mai mult de dou threaduri. In exemplul precedent, in mod intentionat am introdus o temporizare in secventa de “producere” a numarului aleator. Deoarece “consumul” este mult mai rapid, threadul consumator isi petrece o mare parte din timp in asteptare. In aceasta perioada de timp, procesorul este nefolosit. Am putea mari eficienta aplicatiei daca am introduce mai multe threaduri producatoare. In acest caz utilizarea metodele suspend() si resume() devine incomoda. Limbajul Java prevede pentru sincronizarea comunicatiilor intre threaduri un mecanism bazat pe doua metode – wait() si notify(), definite in clasa Object si in subclasele acesteia. Am vazut ca instantele acestor clase implementeaza o coada de asteptare la care se ataseaza threadurile care incearca sa acceseze metodele sincronizate ale obiectului in perioada in care “cheia” acestuia este detinuta de un alt thread care executa o astfel de metoda sincronizata. Un thread poate sa se ataseze de buna voie la coada de asteptare trecand astfel in starea Waiting prin apelul metodei wait() a acestuia. Threadul va iesi din aceasta stare cand un alt thread va apela metoda notify() a aceluiasi obiect. Blocurile de instructiuni in care se apeleaza metodele wait() si notify() trebuie sa fie sincronizate. In plus, metoda wait() “arunca” o exceptie din clasa InterruptedException si deci trebuie apelata in cadrul unui bloc try urmat de un bloc catch. Intrucat daca aceasta metoda esueaza, ea trebuie apelata din nou si din nou, pana reuseste. De aceea de regula se incadreaza intr-un ciclu while sau do/while care se executa atat timp cat apelul metodei esueaza. Pentru a exemplifica acest mecanism, vom modifica programul anterior pentru a implementa aceleasi functiuni dar folosind pentru comunicatie mecanismul descris mai sus. import java.awt.*; class MyFrame extends Frame{ private Producator p; private Consumator c; private Thread prod,cons; private Button b; private TextField msg1,msg2; private Buffer buf; public MyFrame(){ super("Test Producator - Consumator"); b=new Button("Start"); msg1=new TextField(30); msg2=new TextField(3); msg1.setEditable(false); msg2.setEditable(false); setLayout(new FlowLayout()); add(b);add(msg1);add(msg2);resize(300,100);show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ prod.stop(); cons.stop(); hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e);

Page 116: programare in Java

210 CURS 17 } public boolean action(Event e, Object o){ b.disable(); prod.resume(); return true; } public static void main(String[] args){ MyFrame f=new MyFrame(); f.buf=new Buffer(); f.p=new Producator(f.msg1,f.buf); f.c=new Consumator(f.msg2,f.buf,f.b); f.prod=new Thread(f.p); f.cons=new Thread(f.c); f.prod.start(); f.cons.start(); } } class Producator implements Runnable{ private TextField msg; private Buffer buf; private int k; private StringBuffer sb; public Producator(TextField msg, Buffer buf){ sb=new StringBuffer(); this.msg=msg; this.buf=buf; } public void run(){ for(;;){ Thread.currentThread().suspend(); for(int i=0;i<5;i++){ try{ Thread.sleep(1000); } catch(InterruptedException ex){} buf.store(k=1+(int)(Math.random()*10)); sb.append(k+" "); msg.setText(sb.toString()); } sb.setLength(0); } } } class Consumator implements Runnable{ private TextField msg; private Buffer buf; private Button b; public Consumator(TextField msg, Buffer buf, Button b){ this.msg=msg; this.buf=buf; this.b=b; } public void run(){ for(;;){ for(int i=0, sum=0;i<5;i++){ sum+=buf.get(); msg.setText(String.valueOf(sum)); } b.enable();

Page 117: programare in Java

PROGRAMARE IN JAVA - Note de curs 211

} } } class Buffer extends Object{ private int data; public synchronized void store(int val){ data=val; notify(); } public synchronized int get(){ boolean isException; do{ try{ wait(); isException=false; } catch(InterruptedException exception){isException=true;} }while(isException) return data; } } Deosebirile fata de programul anterior constau in urmatoarele: La pornire threadul productor este suspendat dar threadul consumator este activ si incearca sa preia o data din Buffer. in acest scop el apeleaza metoda sincronizata get(). Executand aceasta metoda, el apeleaza la inceputul ei metoda wait() si intra intr-o stare de asteptare. La actionarea butonului “Start” este executata de catre thradul AWT metoda action care apeleaza metoda resume() a threadului producator care este astfel activat. Threadul consumator genereaza numarul aleator, il afiseaza si apeleaza metoda sincronizata store a bufferului pentru a-l inscrie. Dupa ce data a fost inscrisa, threadul producator executand metoda store() apeleaza metoda notify() care “trezeste” treadul consumator din starea de asteptare. Threadul producator revine din metoda store si incepe sa genereze o noua data in timp ce threadul consumator revine in metoda sa run(), actualizeaza suma si o afiseaza dupa care trece la urmatoarea iteratie procesul repetandu-se de 5 ori. Dupa a cincea iteratie ciclul cu contor se termina, ciclul producator reseteaza StringBufferul si intra din nou in starea Suspended. Consumatorul reinitializeaza controrul si suma la 0 intrand in asteptarea furnizarii unei noi date in buffer. Procesul descris mai sus se reia dupa o noua actionare a butonului.

Clasa Vector Clasa Vector definita in pachetul java.util implementeaza o structura de tip tablou cu alocare dinamica de memorie. Aceasta clasa este utila atunci cand numarul de elemente ale tabloului nu este dinainte cunoscut. Un obiect din aceasta clasa va creste automat (alocindu-si dinamic memoria necesara) atunci cand ii sunt adaugate noi elemente. In plus clasa permite operatii suplimentare cum ar fi eliminarea unui element din vector sau inserterea unui nou element in interiorul vectorului. Elementele vectorului sunt instante ale clasei Object sau subclase ale acesteia si deci atunci cand se doreste adaugarea in vector a unor date primitive (cum ar fi date de tipul int ) se va recurge tot la obiecte din clasele corespunzatoare (cum ar fi Integer pentru date de tip int ). Constructorii clasei Vecor sunt:

Vector() – construieste un vector cu capacitatea de 10 elemente. Vector(int cp) – construieste un vector cu capacitatea de cp elemente.

Page 118: programare in Java

212 CURS 17

Vector(int cp, int cpIncr) – construieste un vector cu capacitatea de cp elemente. cand vectorul trebuie sa creasca, capacitatea lui creste cu cpIncr.

Metodele clasei sunt: final sinchronized void addElement(Object obj) – adauga la vector, pe prima

pozitie libera obiectul obj. daca nu mai este loc, capacitatea vectorului este marita cu valoarea din variabila protejata capacityIncrement. Daca aceasta este zero, capacitatea vectorului se dubleaza.

final int capacity() – intoarce capacitatea vectorului. final synchronized void copyInto(Object tablou[]) – copiaza elementele vectorului

intr-un tablou static (standard) final synchronized Object elementAt(int index) – intoarce obiectul de la pozitia

index din vector. final synchronized Object firstElement() - intoarce primul element din vector. final synchronized void ensureCapacity(int cp) – redimensioneaza vectorul

asigurand o capacitate de cp elemente final int indexOf(Object elm) – intoarce indexul primului element elm din vector

sau –1 daca elementul nu este gasit. final synchronized int indexOf(Object elm, int start) – intoarce indexul primului

element elm din vector sau –1 daca elementul nu este gasit. Cautarea incepe de la indexul start spre sfarsitul vectorului.

final synchronized void insertElementAt(Object elm,int index) – inserteaza elementul elm pe pozitia index.

final boolean isEmpty() – intoarce true daca vectorul nu conine nici un element si false in caz contrar.

final synchronized Object lastElement() - intoarce ultimul element din vector. final synchronized int lastIndexOf(Object elm) – intoarce indexul ultimului

element elm din vector sau –1 daca elementul nu este gasit. final synchronized int lastIndexOf(Object elm, int start) – intoarce indexul primului element elm din vector sau –1 daca elementul nu este gasit. Cautarea incepe de la indexul start catre inceputul vectorului.

final synchronized void removeAllElements() – sterge toate elementele vectorului. final synchronized boolean removeElement(Object elm) – elimina elementul elm

din vector daca il gaseste si intoarce true. In caz contrar intoarce false. final synchronized void removeElementAt(int index) – elimina din vector

elementul de pe pozitia index. final synchronized void setElementAt(Object elm,int index) – inlocuieste elementul

de pe pozitia index cu elementul elm. final synchronized void setSize(int sz) – seteaza o noua dimensiune a vectorului sz. final int size() – intoarce dimensiunea vectorului. final synchronized String toString() – intoarce un string continand toate

elementele vectorului. final synchronized void trimToSize() – redimensioneaza vectorul la dimensiunea

minima necesara pentru stocarea elementelor deja continute de vector. Se vede din metodele enumerate mai sus ca aceasta clasa este proiectata pentru a fi folosita in programele multithreading, majoritatea fiind sincronizate. Aceasta clasa este ideala pentru a asigura comunicarea dintre threaduri. Sa exemplificam utilizarea unui obiect din clasa Vector ca buffer in cazul unui program cu mai multe threaduri producator si un singur thread consumator. Programul este prezentat mai jos:

Page 119: programare in Java

PROGRAMARE IN JAVA - Note de curs 213

Metodele wait() si notify() au o proprietate foarte importanta. Daca in momentul in care un thread apeleaza metoda notify() a unui obiect nu exista nici un thread in asteptare, notificarea respectiva este pierduta. Acesta este motivul pentru care aceste metode se folosesc de regula in asociere cu o variabila flag. De asemenea metioda wait() mai are doua versiuni in care threadul este pus in starea de asteptare pe o perioada de timp (de timeout ) specificata in milisecunde sau milisecunde si nanosecunde, ca argument al metodei. Dupa expirarea acestei perioade de timp, threadul este reactivat. Mai trebuie specificat de asemenea in legatura cu metodele wait() si notify() un amanunt important. Daca in asteptare se afla mai multe threaduri, numai unul va fi reactivat fara ca sa stim cu certitudine care. Limbajul Java in mod intentionat (pentru a asigura un mare grad de libertate la implementarea limbajului) nu garanteaza ca threadul reactivat prin apelul metodei notify() este un anumit thread (de exemplu primul sosit in coada de asteptare). Daca se doreste activarea garantata a threadurilor in ordinea sosirii in coada se procedeaza ca in exemplul urmator:

Page 120: programare in Java

PROGRAMARE IN JAVA - Note de curs 217

Curs 18

Controlul activarii threadurilor Asa cum am vazut, threadurile care intra in asteptare prin apelul metodei white() a unui monitor fiind plasate intr-o coada de asteptare din care sunt reactivate prin apelul metodei notify() a monitorului. Aceasta metoda activeaza un singur thread din cele aflate in asteptare. Limbajul Java nu specifica pe care dintre ele. daca dorim ca threadurile sa fie activate in ordinea sosirii, trebuie sa introducem un mecanism ajutator cum ar fi cel din exemplul urmator: import java.util.*; class FiFo{// controleaza ordinea FIFO de activare a thredurilor private Vector lacate = new Vector(); public void oprire(){ Object lacat=new Object();// un nou lacat synchronized (lacat){// ia cheia de la lacat lacate.addElement(lacat);// adauga lacatul in oprite System.out.println( Thread.currentThread().getName()+" in asteptare"); try{ lacat.wait();//elibereaza cheia si asteapta activarea } catch(InterruptedException e){} } } public void restart(){ Object lacat=null; synchronized(lacate){ if(lacate.size()!=0){// exista threaduri in asteptare lacat=lacate.firstElement();// ia primul lacat lacate.removeElementAt(0);// elimina-l din de vector } } if(lacat!=null){ synchronized(lacat){ // ia cheia si... lacat.notify(); // activeaza thredului care asteapta // la acest lacat } } } public static void main(String[] args){ FiFo fifoQueue=new FiFo(); Runnable r = new Run(fifoQueue); Thread t; // lanseaza 10 threaduri for(int i=0;i<10;i++){ t=new Thread(r);// creaza un thread nou si... t.start(); // lanseaza-l in executie } // reactiveaza 10 threaduri for(int i=0;i<10;i++){ try{ Thread.sleep(

(long)(Math.random()*500));// asteapta un timp

Page 121: programare in Java

218 CURS 18 } catch (InterruptedException e){} System.out.println(

"Reactivare un thread din coada FIFO"); fifoQueue.restart(); } } } class Run implements Runnable{ FiFo queue; public Run(FiFo fifoQueue){queue=fifoQueue;} public void run(){ System.out.println( Thread.currentThread().getName()+" in executie"); queue.oprire(); System.out.println( Thread.currentThread().getName()+" restartat"); } }

Fig.18.1 Mesajele afisate de programul FiFo.class

Programul creaza si executa 10 threaduri. Fiecare dintre acestea acceseaza sincronizat metoda oprire() a clasei FiFo (First in, First out). In cadrul acestei metode, la fiecare acces se creaza un obiect lacat din clasa Object adaugat ca element nou vectorului

Page 122: programare in Java

PROGRAMARE IN JAVA - Note de curs 219

lacate. Dupa aceea threadul care a creat un obiect lacat apeleaza metoda wait() a acestuia intrand in starea de asteptare. In acest fel toate cele 10 threaduri se vor afla in stare de asteptare atasate fiecare la propriul sau obiect lacat plasat in ordinea crearii (si deci in ordinea opririi theadurilor) in vectorul lacate. Pentru a reactiva threadurile exact in ordinea in care acestea au intrat in starea de asteptare threadul principal va apela succesiv metoda restart() a clasei FiFo. Aceasta va extrage din vector obiectele lacat incepand cu primul (deci exact in ordinea in care au fost introduse) si apeland metoda notify() pentru fiecare astfel de element. In acest fel cele 10 threaduri vor fi restartate exact in ordinea in care au fost oprite. Mesajele afisate de aplicatie si prezentate in figura 18.1 ne confirma functionarea acestui mecanism in conformitate cu cele aratate anterior. In acest exemplu ordinea de intrare in starea Waiting a threadurilor a fost 1, 3, 4, 2, 5, 6, 7, 8, 9, 10. Din mesajele afisate la reactivare se observa ca threadurile au fost restartate in aceeasi ordine.

Comunicarea prin streamuri Un stream (curent, flux) de date poate fi privit ca o conducta de legatura intre un rezervor de date si un consumator (stream de intrare) sau intre un produator si un rezervor de date de date(stream de iesire). Consumatorul “absoarbe” datele din rezervorul de date printr-un stream de intrare asa cum trage o pompa apa dintr-un put. Producatorul pompeaza datele printr-un stream de iesire in rezervor asa cum pompa trimite apa prin conducta in chiuveta. Limbajul Java defineste in pachetul java.io doua tipuri abstracte de streamuri derivate din clasa Object:

InputStream – stream de intrare OutputStream – stream de iesire.

Fiind clase abstracte, ele nu pot fi folosite pentru instantieri de obiecte ci numai pentru a defini metodele necesare pentru a asigura transferul datelor de la rezervor la consumator respectiv de la producator spre rezervor. Aceste metode sunt: - pentru clasa InputStream

InputStream() – constructorul clasei int avaiable() throws IOException –intoarce numarul de octeti disponibili in

stream. void close() throws IOException – “Inchide” streamul, rupand legatura cu sursa de

date. synchronized void mark(int limita) – pune un marcaj in pozitia curenta din stream.

Apeland ulterior metoda reset(), se poate reveni din pozitia respectiva la pozitia astfel marcata. Parametrul limit specifica numarul de octeti care poate fi citit diincolo de marcaj. Daca se depaseste aceasta limita, marcajul este sters.

boolean markSupported() intoarce true daca streamul curent suporta marcarea. int read() throws IOException – citeste si intoarce un octet din stream. Intoarce -1

daca s-a semnalat sfarsitul de fisier – (fizic sau un caracter de control corespunzator)

int read(byte b[]) throws IOException – citeste din stream numarul de octeti necesar in tabloul b. Intoarce numarul de octeti citit (in mod normal cat dimensiunea tabloului) sau -1 daca s-a semnalat sfarsitul de fisier – (fizic sau un caracter de control corespunzator)

int read(byte b[],int offset, int len) throws IOException – citeste din stream in tabloul b, incepand cu indexul offset, cel mult numarul de octeti specificat de parametrul len. Intoarce numarul de octeti citit sau -1 daca s-a semnalat sfarsitul de fisier – (fizic sau un caracter de control corespunzator)

Page 123: programare in Java

220 CURS 18

synchronized void reset() throws IOException – restabileste pozitia curenta in stream pe marcajul stabilit cu metoda mark().

long skip(long num) throws IOException – sare in stream peste numarul specificat de octeti

- pentru clasa OutputStream OutputStream() – constructorul clasei void close() throws IOException – “Inchide” streamul, rupand legatura cu

rezervorul de date. void flush() throws IOException – in aceasta clasa metoda nu face nimic, in

clasele derivate cu bufferizare (cum este de exemplu System.out) poate fi folosita pentru a “goli” bufferul in stream, “fortand” transmisia octetilor continuti de acesta in rezervor.

abstract void write(int b) throws IOException – inscrie octetul b in stream. void write(int b[]) throws IOException – inscrie toti octetii continuti de tabloul b

in stream. void write(int b[], int offset, int len) throws IOException – inscrie in stream cel

mult len octeti continuti de tabloul b incepand cu indexul offset. Pornind de la aceste clase abstracte, limbajul Java defineste printre altele subclasele PipedInputStream respectiv PipedOutputStream. Prin conectarea ca sursa de date a unui astfel stream de iesire a unui thread producator cu un stream de intrare al unui thread consumator se poate realiza comunicarea intre cele doua threaduri asa cum

este reprezentata in figura 18.2

Fig.18.2 Comunicatia intre threaduri realizata prin streamuri Metodele clasi PipedInputStream sunt:

PipedInputStream() – constructorul clasei, instantiaza un stream de intrare din clasa PipedInputStream. Inainte de a fi folosit, acesta va trebui conectat la un stream de iesire instanta a clasei PipedOutputStream.

PipedInputStream(PipedOutputStream src) throws IOException – constructorul

Page 124: programare in Java

PROGRAMARE IN JAVA - Note de curs 221

clasei, instantiaza un stream de intrare din clasa PipedInputStream conectandu-l la streamul de iesire src, instanta a clasei PipedOutputStream.

void close() throws IOException – “Inchide” streamul, rupand legatura cu streamul sursa de date.

void connect(PipedOutputStream src) throws IOException- conecteaza streamul de intrare la streamul de iesire specificat, src, instanta a clasei PipedOutputStream.

int read() throws IOException – citeste si intoarce un octet din stream. int read(byte b[],int offset, int len) throws IOException – citeste len octeti din

stream, memorandu-i ca elemente ale tabloului b incepand cu indexul offset. Intoarce numarul de octeti citit.

Atunci cand un thread citeste dintr-un stream de intrare apeland metoda read() a acestuia, el este blocat pana cand operatia de citire nu este terminata ( pana octetul sau blocul de octeti nu a fost efectiv preluat de la sursa de date care s-ar putea sa intarzie furnizarea acestora). Metodele clasi PipedOutputStream sunt:

PipedOutputStream() – constructorul clasei, instantiaza un stream de iesire din clasa PipedOutputStream. Inainte de a fi folosit, acesta va trebui conectat la un stream de iesire instanta a clasei PipedInputStream.

PipedOutputStream (PipedInputStream dest) throws IOException – constructorul clasei, instantiaza un stream de iesire din clasa PipedOutputStream conectandu-l la streamul de intrare dest, instanta a clasei PipedInputStream.

void close() throws IOException – informeaza streamul receptor ca a receptionat ultimul sau octet si “inchide” efectiv streamul rupand legatura.

void connect(PipedInputStream dest) throws IOException- conecteaza streamul de iesire la streamul de intrare specificat, dest, instanta a clasei PipedInputStream.

abstract void write(int b) throws IOException – inscrie octetul b in stream. void write(int b[], int offset, int len) throws IOException – inscrie in stream len

octeti continuti de tabloul b incepand cu indexul offset. Programul urmator exemplifica utilizarea streamurilor in comunicatia dintre threaduri. import java.io.*; class RxTx { public static void main(String[] args){ PipedInputStream in=new PipedInputStream(); PipedOutputStream out=new PipedOutputStream(); Producator p=new Producator(out); Consumator c=new Consumator(in); Thread tx=new Thread(p), rx=new Thread(c); p.connectTo(c.getStream()); tx.start(); rx.start(); while(p.isRunning() || c.isRunning()); tx.stop(); rx.stop(); } } class Producator implements Runnable{ private PipedOutputStream out; private boolean running; public Producator(PipedOutputStream out){ this.out=out;

Page 125: programare in Java

222 CURS 18 running=true; } public void connectTo(PipedInputStream in){ try{ out.connect(in); } catch (Exception ex){ System.out.println(ex.toString()); ex.printStackTrace(); } } public boolean isRunning(){ return running;} public void run(){ byte val; try{ for(int i=0; i<5;i++){ val=(byte)(Math.random()*256); System.out.print("Scriu "+ val+" "); out.write(val); System.out.println("...OK..."); Thread.currentThread().yield(); Thread.sleep((long)(Math.random()*1000)); } out.close(); running=false; } catch(Exception ex){ System.out.println(ex.toString()); ex.printStackTrace(); } } } class Consumator implements Runnable{ private PipedInputStream in; private boolean running; public Consumator(PipedInputStream in){ this.in=in; running=true; } public PipedInputStream getStream(){ return (in); } public boolean isRunning(){ return running;} public void run(){ byte val; try{ while((val=(byte)in.read()) !=-1){ System.out.print("Citesc "+(byte)val +" "); System.out.println("...OK..."); Thread.currentThread().yield(); Thread.sleep((long)(Math.random()*1000)); } System.out.println("Legatura inchisa."); running=false; } catch(Exception ex){ System.out.println(ex.toString()); ex.printStackTrace(); } } }

Page 126: programare in Java

PROGRAMARE IN JAVA - Note de curs 223

In cadrul metodei main() a clasei RxTx se creaza perechea de streamuri de date in si out din clasele PipedInputStream respectiv PipedOutputStream. De asemenea se creaza doua obiecte Runnable p si c, instante ale claselor Producator respectiv Consumator. Referintale la streamurile in si out sunt memorate in c respectiv p. La randul lor obiectele Runnable p si c sunt folosite la instantierea threadurilor tx respectiv rx. Threadul producator tx, executand metoda run() a clasei p genereaza 5 valori aleatoare de tip byte si le scrie in streamul de iesire out dupa care inchide streamul si se termina. Threadul consumator rx, executand metoda run() a clasei c citeste din streamul de intrare in valori de tip byte pana cand valoarea citita este egala cu –1 indicand ca sursa de date s-a terminat (sfarsit de fisier), dupa care inchide streamul de intrare si se termina. Ambele threaduri semnaleaza terminarea prin inscrierea valorii false in variabila proprie running. Threadul principal monitorizeaza executia celor doua threaduri rx si tx prin verificarea valorii acestei variabile prin apelul metodelor isRunning(). In momentul in care ambele metode intorc valoarea false threadul principal opreste cele doua threaduri rx si tx si se termina. Mesajele afisate la executia programului sunt prezentate in figura 18.3

Fig.18.3 mesajele afisate la executia programului RxTx.class

Gestionarea grupurilor de threaduri Deoarece thread-urile sunt procese in mare masura independente este uneori avantajos ca ele sa fie grupate pentru a gestiona mai eficient activitatea lor. Clasa ThreadGroup permite crearea unor “colectii” de threaduri. In plus aceasta clasa permite introducerea unei relatii ierarhice intre threadurile apartinand unor grupuri diferite. Astfel un ThreadGrup poate sa aiba un ThreadGroup parinte. Toate threadurile dintr-un grup se pot accesa reciproc si pot accesa threadurile din ThreadGroup-urile subordonate (copil) dar nu pot accesa thredurile din ThreadGroup-ul parinte si din ThreadGroup-urile de deasupra acestuia. Clasa ThreadGroup are urmatoarele metode:

ThreadGroup(String nume) – constructor al clasei. Grupul de threaduri instantiat primeste numele nume.

ThreadGroup(ThreadGroup parinte, String nume) – constructor al clasei. Grupul de threaduri instantiat primeste numele nume si are ca parinte ThreadGroup-ul specificat prin argumentul parinte.

synchronized int activeCount() – intoarce numarul de threaduri active din grup. synchronized int activeGroupCount() – intoarce numarul de grupuri active de

threaduri din grup. final synchronized void destroy() – distruge grupul fara a afecta starea threadurilor

componente.

Page 127: programare in Java

224 CURS 18

int enumerate(Thread list[]) – Plaseaza in tabloul list cate o referinta la fiecare thread al grupului. Intoarce numarul de elemente ale tabloului.

int enumerate(Thread list[], boolean recursiv) – Daca parametrul recursiv are valoarea true plaseaza in tabloul list cate o referinta la fiecare thread din grup si din grupurile subordonate. Daca parametrul recursiv are valoarea false atunci va plasa in tabloul list cate o referinta numai la threadurile din grup. Intoarce numarul de elemente ale tabloului.

int enumerate(ThreadGroup list[]) – Plaseaza in tabloul list cate o referinta la fiecare ThreadGroup copil subordonat grupului. Intoarce numarul de elemente ale tabloului.

int enumerate(ThreadGroup list[],boolean recursiv) – Daca parametrul recursiv are valoarea true plaseaza in tabloul list cate o referinta la fiecare ThreadGroup copil subordonat grupului si la toate ThreadGroup-urile subordonate ThreadGroup-urilor copil, etc . Intoarce numarul de elemente ale tabloului. final int getMaxPriority() – intoarce prioritatea maxima a thredurilor din grup (acestea pot avea prioritati diferite). final String getName() – intoarce un string continand numele grupului.

final ThreadGroup getParent() – intoarce o referinta la ThreadGroup-ul parinte. final boolean isDaemon() – intoarce valoarea flagului daemon din acest grup. Toate threadurile create in acest grup vor “mosteni” acest flag. syncronized void list() – afiseaza lista tuturor threadurilor si ThreadGroup-urilor continute la consola sistemului (System.out). final boolean parentOf(ThreadGroup altGrup) – intoarce true daca grupul este parintele lui altGrup. final synchronized void resume() – apeleaza metoda resume() pentru toate threadurile din grup si din grupurile copil subordonate. final void setDaemon(boolean daemon) – seteaza flagul daemon al grupului. final synchronized void setMaxPriority(int nivel) – seteaza nivelul de prioritate al tuturor threadurilor nou create in grup la valoarea nivel. final synchronized void stop() – apeleaza metoda stop() pentru toate threadurile din grup si din grupurile copil subordonate. final synchronized void suspend() – apeleaza metoda suspend() pentru toate threadurile din grup si din grupurile copil subordonate. String toString() – intoarce un string continand numele grupului si nivelul maxim de prioritate al threadurilor continute de acesta. void uncaughtException(Thread t, Throwable e) – este o metoda apelata de sistem pentru exceptiile “neprinse”

Organizarea cooperarii intre threaduri Mecanismele de comunicatie inthre threaduri descrise anerior pot fi folosite pentru implementarea unor mecanisme mult mai complexe, utile in proiectarea aplicatiilor cu mai multe fire de executie. Din exemplele precedente se observa avantajele deosebite pe care le prezinta programarea concurenta in comparatie cu programarea clasica. In programarea procedurala aplicatia era organizata sub forma unui ansamblu de proceduri a caror aplicare succesiva asupra datelor de intrare ducea intr-un final la obtinerea rezultatelor. Abordarea problemei se facea prin aplicarea metodologiei top-down constand in rafinari succesive de la complex la simplu, problemele de complexitate mare fiind descompuse in subprobleme mai putin complexe care la randul sau erau descompuse in subprobleme mai simple, detalierea continuand pana la

Page 128: programare in Java

PROGRAMARE IN JAVA - Note de curs 225

obtinerea unui set de subprobleme a caror rezolvare nu mai prezenta dificultate si riscul unor erori era redus la minim. Setul de proceduri si succesiunaea apelurilor acestora reproducea etapele de detaliere ale problemei. Programarea orientata presupune o abordare complet diferita, constand in proiectarea structurii aplicatiei pe principii asemanatoare proiectarii hardware. Astfel pornind de la module existente cu o functionalitate adecvata, se construiesc module cu functionalitati noi si de complexitate mai ridicata, care la randul lor se folosesc pentru a realiza module si mai complexe, procesul continuind pana la obtinerea produsului final capabil de rezolvarea problemei date (metodologia down-top). Programarea concurenta deschide perspective noi in proiectarea aplicatiilor completand facilitatile oferite de programarea orientata obiect. Intr-o astfel de abordare aplicatia poate fi gandita facand analogia dintre activitatea threadurilor si activitatea unor persoane care coopereaza pentru obtinerea unui produs final. In activitatea de prelucrare a datelor , threadurile apeleaza la metodele a diverse obiectelor la fel cum muncitorii folosesc diverse utilaje si instrumente pentru prelucrarea materiei prime pentru obtinerea produselor finite. Proiectarea aplicatia consta in definirea obiectelor-utilaje, a threadurilor-executanti care sa le foloseasca pentru prelucrarea datelor si organizarea activitatii eficiente a acestora pentru obtinerea rezultatelor- produs finit. Organizarea activitatii threadurilor cuprinde doua laturi. Prima care priveste organizarea modului de acces la resurse ( folosirea in comun a obiectelor-utilaje) iar a doua priveste organizarea modului de cooperare intre threadurile – executant. Intr-o astfel de abordare, relatia dintre doua threaduri care coopereaza este aceea de client-server. O astfel de relatie este mai mult decat relatia producator-consumator implementata de noi in exemplele anterioare. Intr-o astfel de relatie, unul dintre threaduri joaca rolul de client iar celalalt de server. Threadul-client in activitatea desfasurata apeleaza la serviciile oferite de threadul-server asa cum clientul unui restaurant apeleaza la serviciile oferite de chelner. La randul sau chelnerul apeleaza de data aceasta in calitate de client la serviciile bucatarului iar bucatarul la serviciile ajutoarelor sale. Mecanismul de comunicatie dintre client si server din momentul in care clientul a solicitat un serviciu serverului pana in momentul in care este servit si poate sa-si continue activitatea se numeste rendez-vous. Sa analizam pentru exemplificarea cooperarii intre threaduri cazul urmator. Avem doua threaduri – unul producator si altul consumator. Intr-unul din exemplele anterioare transferul de date intre cele doua threaduri se facea prin intermediul unui buffer un obiect care implementa metodele store() si get(). Interactiunea dintre threaduri si acest buffer putem sa o reprezentam schematic ca in figura 18.4.

Fig.18.4 – Transferul de date intre threaduri prin intermediul unui obiect buffer

In acest caz threadurile se folosesc de obiectul buffer asa cum muncitorii se folosesc de un utilaj. Threadurile Prod si Cons sunt componente active ale aplicatiei, executanti, iar obiectul Buffer este o componente pasive folosita de executanti pentru

Page 129: programare in Java

226 CURS 18 realizarea unor operatii asupra datelor. Componentele active le-am reprezentat prin paralelograme pentru a sublinia caracterul lor paralel (concurent). Componenta pasiva, l-am reprezentat asa cum ne-am obisnuit deja printr-un dreptunghi. In cazul pe care ni-l propunem sa-l analizam vom inlocui obiectul pasiv buffer cu un al treilea thread care va fi solicitat de catre producator sa primeasca si sa pastreze temporar datele produse iar de catre consumator sa-i inmaneze datele detinute, primite de la threadul producator. Acest al treilea thread joaca rolul de server in timp ce thredurile producator si consumator joaca rolul clientilor, apeland la serviciile serverului. Schematic, cooperarea dintre cele trei threaduri se poate reprezenta ca in figura 18.5.

Fig.18.5 Cooperarea dintre threaduri client si server Spre deosebire de threadurile client care sunt autonome, threadul server Buffer are doua intrari – store si get fiecare intrare corespunzind unui serviciu oferit de server. In acest exemplu threadurile client nu au intrari, fiind dupa cum am mai spus autonome. Cele mai frecvente cazuri sunt insa cele in care un thread server apeleaza la randul lui la serviciile altor threaduri pentru care el este client. In cazul nostru, cooperarea dintre threaduri se desfasoara dupa urmatorul scenariu. Spre exemplul threadul Prod a generat o data si vrea sa o transmita spre pastrare bufferului. El incearca sa acceseze intrarea store a acestuia. Daca serverul este in acel moment ocupat cu alte treburi, clientul va gasi aceasta intrare inchisa si va incepe sa astepte ca ea sa se deschida. Dupa un anumit interval de timp serverul isi termina treburile cu care era ocupat si accepta cereri de servicii pe intrarea store, descizand-o. Aici va gasi clientul care a adormit asteptandu-l cu cererea in mana. Aici incepe practic rendez-vous –ul dintre client si server. Serverul va prelua cererea clientului care continua sa astepte terminarea rendez-vous-ului, va stoca data intr-o variabila interna si il va “trezi” pe client, moment in care care rendez-vous-ul se termina. Clientul se va intoarce la treburile sale ( generarea unei noi date). Serverul la randul sau isi va continua activitatea acceptand o cerere la intrarea get. Pentru aceasta el deschide intrarea get unde sa presupunem ca nu gaseste nici un client in asteptare. In aceste conditii serverul se aseaza comod si incepe sa astepte el sosirea la aceasta intrare a unui client cu o cerere de extragere a unei date. Cand threadul Cons se hotaraste sa preia date de buffer el va accesa intrarea get. Intrucat o gaseste deschisa cu serverul in asteptare de clienti, rendez-vous-ul dintre client si server incepe imediat. Clientul trezeste serverul si incepe sa astepte sa fie servit. Serverul preia cererea clientului, extrage data si o inmineaza cluientului moment in care care rendez-vous-ul se termina. Clientul isi termina asteptarea si isi reia activitatea (prelucrarea datei obtinute de la server). Serverul se intoarce si el la treburile sale putand de

Page 130: programare in Java

PROGRAMARE IN JAVA - Note de curs 227

exemplu accepta din nou o cerere la intrarea store. Diagrama desfasurarii in timp a executiei celor trei threaduri este prezentata in figura 18.6.

Fig.18.6 Diagrama desfasurarii in timp a executiei threadurilor Aceasta este numai o varianta de scenariu de cooperare clienti-server, putand fi imaginate si alte variante in care de exemplu clientul daca gaseste intrarea inchisa asteapta numai un timp limitat sau de loc rendez-vous-ul. De asemenea serverul daca nu gaseste un client la deschiderea unei intrari, poate sa o inchida imediat la loc si sa se indrepte spre cealalta intrare fara sa mai astepte deloc. De asemenea sunt posibile scenarii in care mai multi clienti incearca sa acceseze o intrare a serverului si gasind-o inchisa sau serverul fiind ocupat cu servirea unui client ( un rendez-vous este in curs de desfasurare) se aseaza intr-o coada asteptand disciplinati sa le vina randul sa fie serviti. Deoarece spre deosebire de limbajul Ada, Java nu este prevazut cu un suport pentru acest tip de comunicatie, implementarea acestui mecanism avansat de coorperare intre threaduri se poate construi prin utilizarea unor monitoare de tip semafor pentru modelarea intrarilor threadului server. Un astfel de monitor se poate construi prin instantierea unei clase ce va mosteni clasa Object (care permite definirea de metode sincronizate) si va avea definite doua variabile membru :

public int cients – destinata contorizarii threadurilor-client aflate in asteptare pentru a fi servite. La crearea obiectului nici un client nu se va afla in asteptare la aceasta intrare si deci vriabila este initializata cu valoarea 0.

public boolean open – specificand starea intrarii. Daca serverul a deschis intrarea si se afla in asteptarea unei cereri aceasta variabila are valoarea true. La crearea obiectului, intrarea este inchisa si deci vriabila este initializata cu valoarea false.

De asemenea clasa va fi completata cu definitia unei metode sincronizate ce implementeaza serviciul oferit de server:

public synchronized <tip rezultat> request(<lista parametri>) – incrementeaza contorul de clienti si daca intrarea este deschisa (entry.open == true - serverul este in asteptare de clienti) notyifica serverul. Daca intrarea este inchisa, pune

Page 131: programare in Java

228 CURS 18

threadul client in asteptare. Dupa notificarea de catre server se executa sectiunea metodei ce implementeaza serviciul cerut.

Pe de alta parte obiectul Runnable folosit la constructia serverului va defini si metoda private void accept(Entry entry) – care, daca nu sunt clienti in asteptare

(entry.clients==0), va deschide intrarea (entry.open==true) si va pune serverul in asteptare. Daca sunt clienti in asteptare va notifica pe unul dintre ei (entry.notify()), declansand astfel continuarea executiei de catre acesta a sectiunii metodei entry.request() care implementeaza serviciul cerut.

De asemenea mecanismul de rendez-vous poate fi implementat mult mai simplu folosind pentru comunicatie streamuri din clasa PipedInputStream si PipedOutputStream.

Page 132: programare in Java

PROGRAMARE IN JAVA - Note de curs 229

Curs 19

Fisiere Datele (date initiale, rezultate intermediare si rezultatele finale) memorate in variabile pe perioada executiei programului sunt pierdute in momentul in care variabilele respective expira si sunt distruse sau la terminarea programului. Fisierele sunt folosite pentru pastrarea pentru un timp indelungat a unui volum mare de date, chiar si dupa ce programul care le-a creat isi termina executia. Datele pastrate in fisiere sunt numite si date persistente. In claculator, fisierele sunt pastrate in dispozitivele de memorie auxiliara cum ar fi discuri si benzi magnetice sau discuri optice si gestionate de sistemul de operare. Aceasta inseamna ca operatii cu fisiere precum cautarea, crearea, stergerea, redenumirea, accesarea (scrierea/citirea in/din fisiere) si altele asemenea se fac de aplicatie prin apelul rutinelor corespunzatoare ale sistemului de operare. Dupa modul de acces al aplicatiilor la datele pastrate in fisiere, acestea se pot clasifica in fisiere cu acces secvential si fisiere cu acces aleator.

Ierarhizarea datelor Datorita faptului ca dispozitivele de memorie ale calculatoarelor sunt realizate cu elemente cu doua stari (avand capacitatea de un bit), datele sunt stocate in aceste dispozitive sub forma binara, fiind reprezentate prin prin succesiuni de valori 1 si 0. Fiecare astfel de valoare este memorata intr-un element de memorie prin setarea starii sale in una din cele doua posibile, corespunzator valorii memorate. pentru memorarea unei date vor fi deci necesare mai multe elemente binare de memorie. Pentru pastrarea datelor, memoria se aloca in blocuri de avand capacitatea de 1 byte = 8 bit ( 8 elemente binare). Astfel pentru memorarea unei date se vor aloca unul su mai multe blocuri cu capacitatea de 1 byte, atat cat este necesar pentru memorarea tipului respectiv de data. De exemplu pentru memorarea unei date de tip caracter este necesar un singur bloc cu capacitatea de 1 byte. La scrierea programelor, si la introducerea de la tastatura/citirea de pe ecran a datelor in timpul executiei aplicatiei, programatorului/operatorului i-ar fi foarte dificil sa lucreze cu datele reprezentate in format binar. Este mult mai usor ca datele sa fie prezentate in aceasta etapa sub forma simbolica folosind cifre - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, litere – A, B, C, ... a, b, c, ... precum si alte semne - @, #, $, %, “, ?, !, ..., etc. Toate aceste simboluri folosite la scrierea programelor si reprezentarea datelor se numesc caractere si formeaza setul de caractere al calculatorului respectiv. Deoarece calculatorului nu poate memora si procesa decat date binare, aceste caractere sunt codate, fiecarui caracter fiindu-i asociat un cod binar, o combinatie unica de 1 si 0. La introducerea lor in calculator se face codarea sub forma binara iar la afisare sunt decodate, pe ecran afisandu-se simbolul corespunzator codului binar. Astfel cand se apasa pe tastatura o tasta avand inscriptionata pe ea simbolul 1, catre calculator este transmisa data 00110001, acesta fiind codul binar al cifrei 1. De asemenea cind un program trimite spre placa grafica a calculatorului codul binar 00110001, aceasta va determina afisarea pe display a simbolului 1. Limbajul Java foloseste pentru codarea caracterelor codul Unicode. Acest cod foloseste doi byte pentru reprezentarea codului. In acest cod, toate caracterele litere, cifre si semne (de pe tastatura ) contin in octetul superior 00000000.

Page 133: programare in Java

230 CURS 19 Caracterele se pot folosi pentru introducerea/afisarea unor informatii (date) nestructurate cum ar fi textele unor documente. In acest caz informatia poate fi cel mult vazuta ca o succesiune de linii de text separate prin caracterul de control ‘\r’(00001101) care la afisare determina trecerea cursorului pe randul urmator. Pe de alta parte informatia poate fi memorata si sub o forma structurata constand dintr-o succesiune de “inregistrari” fiecare astfel de inregistrare fiind la randul sau impartita pe campuri. Un camp este un grup de caractere avand o semnificatie convenita. de exemplu un camp constand numai din litere poate avea semnificatia numelui unei persoane. De exemplu o inregistrare privind un anumit salariat al unei intreprinderi poate contine urmatoarele campuri: 1. Numar de legitimatie 2. Nume 3. Adresa 4. Salariu tarifar orar 5. Numar de ore lucrate 6. Salariul brut 7. Salariul net Pentru fiecare angajat al intreprinderii poate exista o astfel de inregistrare, campurile acesteia continand datele corespunzatoare angajatului respectiv. acest ansamblu de inregistrari constitue un fisier. Fisierul de personal al unei companii mici contine un numar mic de inregistrari ( de exemplu 30) dar fisierul unei companii mari poate atinge un numar de zeci de mii de astfel de date. In figura 19.1 este reprezentata ierarhizarea datelor descrisa mai sus.

Fig.19.1 Ierarhizarea datelor

Page 134: programare in Java

PROGRAMARE IN JAVA - Note de curs 231

Pentru a usura cautarea in fisier a unei anumite inregistrari cel putin unul din campuri al inregistrarii se alege drept cheie de cautare. Campul cheie trebuie astfel ales incat sa identifice in mod unic inregistrarea cautata. Cheia de cautare poate sa fie de exemplu numarul de legitimatie al unui anumit angajat deoarece identifica in mod unic inregistrarea acestuia ( nu exista doi angajati cu acelasi numar de legitimatie). Exista mai multe moduri de a organiza inregistrarile in fisier. Un mod este de a inscrie inregistrarile intr-un fisier secvential, una dupa alta, in ordinea valorilor din campul cheie ales. De regula intreprinderile stocheaza informatiile privind activitatea lor in mai multe fisiere de date - date de personal, date privind stocurile, date privind situatia facturilor, date privind operatiile contabile, etc. Unele date sunt interdependente. Un grup de fisiere continand date interdependente astfel incat modificarea unor date dintr-un fisier implica modificarea datelor din restul de fisiere din grup formeaza o baza de date. Un pachet de programe destinat gestiunii fisierelor unei baze de date constitue un sistem de gestiune a bazelor de date (SGBD).

Clasa File si operatii cu fisiere Pentru a efectua diferite operatii asupra unui fisier aplicatia trebuie sa stabileasca intai o legatura cu sistemul de operare care il gestioneaza fizic pe disc, prin creerea unei “interfete” cu serviciile oferite de acesta. Aceasta “interfata” este implementata de clasa File care defineste cate o metoda pentru fiecare operatie uzuala de gestiune a unui fisier. Atunci cand o astfel de metoda este apelata si executata de un fir de executie al aplicatiei, ea apeleaza la randul sau rutina corespunzatoare a sistemului de operare care la randul sau va efectua operatia dorita. Relatia aplicatie - sistem de operare – fisier este reprezentata schematic in figura 19.2.

Fig.19.2 - Relatia aplicatie - sistem de operare – fisier Threadul se va folosi de obiectul file ca de un instrument comandand operatiile asupra fisierului asa cum un operator coonduce de la pupitrul de comanda al unui furnal operatiile de obtinere a unei sarje de fonta. Aici operatorul este omologul threadului, pupitrul de comanda echivalentul obiectului File iar furnalul este echivalentul sistemului de operare. Utilizarea unei astfel de interfete asigura si independenta de platforma (hardware si sistem de operare). Pupitrul este o “cutie neagra” prevazuta cu

Page 135: programare in Java

232 CURS 19 o interfata – butoane, manete, aparate indicatoare. Teoretic, pentru obtinerea unei sarje de fonta, acelasi operator, va putea da aceeasi secventa de comenzi de la un pupitru cu aceleasi butoane si aparate indicatoare si pentru un furnal american si pentru un furnal japonez. Conditia este ca semnalele de comanda generate de pupitru in urma apasarii butoanelor sa initieze corect procesele ce se desfasoara in furnal (hardware-ul pupitrului sa fie adaptat la tipul furnalului) La fel si clasa File este o “cutie neagra” metodele si variabilele sale fiind “interfata” clasei, butoanele manetele si aparatele indicatoare. Modul in care aceste metode sunt “cablate” in interiorul clasei difera de la un sistem de operare la altul dar operatiile asupra fisierului sunt aceleasi. Clasa File este o subclasa a clasei Object si face parte din pachetul java.io definind urmatoarele variabile si metode:

final static String pathSeparator – string continand simbolul folosit pentru separarea cailor (PATH) (“;” in DOS si Windows)

final static char pathSeparatorChar – caracterul folosit pentru separarea cailor (PATH) (“;” in DOS si Windows)

final static String separator – string continand simbolul folosit pentru separarea numelor de directoare intr-o cale (“\” in DOS si Windows)

final static char separator – caracterul folosit pentru separarea numelor de directoare intr-o cale (“\” in DOS si Windows)

File(String path) – constructorul clasei. Are ca argument un string continand calea spre fisier.

File(String dir, String nume) – constructorul clasei. Are ca argument un string continand numele directorului in care se afla fisierul si un alt string continand numele fisierului.

File(String path, String nume) – constructorul clasei. Are ca argument un string continand calea spre fisier si un alt string continand numele fisierului.

boolean canRead() – intoarce true daca programul poate citi din fisier. boolean canWrite() – intoarce true daca programul poate scrie in fisier. boolean delete() – Strege fisierul de pe disc. Intoarce true daca operatia a reusit. boolean equals(Object altObiect) – Compara obiectul this.File cu obiectul

altObiect. Intoarce true daca altObiect este un obiect File avand aceeasi cale. boolean exists() – intoarce itrue daca fisierul exista. boolean delete() – Verifica daca fisierul exista pe disc. Intoarce true daca fisierul a

fost gasit. String getAbsolutePath() - intoarce un string continand calea absoluta catre fisier

(de la radacina). String getName() - intoarce un string continand numele fisierului. String getParent() - intoarce un string continand numele directorului parinte (toata

calea mai putin numele fisierului). String getPath() - intoarce un string continand toata calea, inclusiv numele

fisierului. int hashCode()- intoarce o valoare intreaga continand hash-code –ul fisierului

calculat pe stringul continand calea. Este util in cautarea si identificarea rapida a acestuia fiind putin probabil ca doua fisiere sa aibe acelasi hash-cod.

boolean isAbsolute() – intoarce true daca calea este absoluta. boolean isDirectory() – intoarce true daca obiectul descrie un director. boolean isFile() – intoarce true daca obiectul descrie un fisier. long lastModified() – intoarce o valoare fara semnificatie (similara cu hash-codul)

calculata pe baza timpului de la ultima modificare. Se foloseste numai la compararea vechimii a doua fisiere.

Page 136: programare in Java

PROGRAMARE IN JAVA - Note de curs 233

long length() – intoarce dimensiunea fisierului in bytes. String[] list() – intoarce un tablou de stringuri continand lista numelor tuturor

fisierelor din calea curenta. String[] list(Filenamefilter filter) – intoarce un tablou de stringuri continand lista

numelor tuturor fisierelor din calea curenta care corespund cu criteriile de filtrare specificate de obiectul filter.

boolean mkdir() – creaza directorul curent. Intoarce true daca operatia a reusit. boolean mkdirs() – creaza calea curenta. Intoarce true daca operatia a reusit. boolean renameTo(File newName) – redenumeste fisierul curent. Intoarce true

daca operatia a reusit. String toString() – intoarce calea spre fisier.

Accesarea datelor din fisiere Pentru efectuarea operatiilor de citire/scriere din/in fisiere limbajul Java prevede un mecanism bazat pe stream-uri. Astfel in cazul citirii de catre un thread a datelor dintr-un fisier, acesta se va folosi de un obiect din clasa FileInputStream a carui sursa de date este fisierul respectiv (figura 19.3 a). In cazul scrierii de date, threadul se va folosi de un obiect din clasa FileOutputStream conectat la fisierul respectiv pe folosindu-l ca rezervor de date (figura 19.3 b).

Fig.19.3 Accesarea fisierelor

Clasele FileInputStream si FileOutputStream prevazute in pachetul java.io sunt versiuni robuste, specializate in accesarea fisierelor, ale claselor InputStream si OutputStream. Aceste clase clase ofera numai o parte din metodele specificate de clasele InputStream si OutputStream permitand numai citirea respectiv scrierea din/in fisiere pe disc fara insa sa includa si metode de gestiune a streamurilor. Astfel clasa FileInputStream defineste urmatoarele metode:

FileInputStream(File file) thows FileNotFoundException – constructor al unui stream de intrare conectat la fisierul specificat.

FileInputStream(String name) thows FileNotFoundException – constructor al unui stream de intrare conectat la fisierul cu numele specificat.

int avaiable() throws IOException – intoarce numarul de bytes disponibili. void close() throws IOException – inchide streamul si fisierul. protected void finalize() throws IOException – metoda callback apelata automat

Page 137: programare in Java

234 CURS 19

inainte de distrugerea obiectului. int read() throws IOException – citeste si intoarce un byte din stream. Intoarce –

1 daca s-a atins sfarsitul de fisier. int read(byte b[]) throws IOException – citeste in tabloul specificat ca argument

bytes din stream. Intoarce numarul de bytes cititi sau –1 daca s-a atins sfarsitul de fisier.

int read(byte b[], int offset, int len) throws IOException – citeste si inscrie incepand cu pozitia offset in tabloul specificat ca argument len bytes din stream. Intoarce numarul de bytes cititi sau –1 daca s-a atins sfarsitul de fisier.

long skip(long num) ) throws IOException – extrage si ignora num octeti din stream. Intoarce numarul efectiv de octeti extrasi.

In clasa FileOutputStream sunt definite metodele urmatoare: FileOutputStream(File file) thows IOException – constructor al unui stream de

iesire conectat la fisierul specificat prin file. FileOutputStream(String name) thows IOException – constructor al unui stream

de iesire conectat la fisierul specificat prin stringul name. void close() throws IOException – inchide streamul si fisierul. protected void finalize() throws IOException – metoda callback apelata automat

inainte de distrugerea obiectului. void write(int b) throws IOException – scrie byte-ul b in stream. void write(byte b[]) throws IOException – scrie tabloul de bytes b in stream. void write(byte b[], int offset, int len) throws IOException – scrie len octeti din

tabloul de bytes b incepand cu indexul offset in stream. Programele urmatoare demonstreaza utilizarea streamurilor pentru crearea si acesarea fisierelor import java.io.*; class Con2File{ public static void main(String[] args) throws IOException { if(args.length==0){ System.out.println("Usage: Con2File <file_name>"); System.exit(0); } FileOutputStream out=new FileOutputStream(args[0]); int b; while((b=System.in.read())!=-1)out.write(b); } } Programul de mai sus preia codurile caracterelor tastate la consola ( fisierul standard de intrare accesat print streamul System.in) si ii depune intr-un fisier al carui nume este preluat din linia de comanda. Programul se termina cand se detecteaza la citirea din streamul de intrare System.in sfarsitul de fisier. Intrucat fisierul consola nu poate avea un sfarsit fizic, acesta easte semnalat prin tastarea combinatiei de taste <CTRL>Z. import java.io.*; class Copy{ public static void main(String[] args) throws IOException { if(args.length < 2){ System.out.println("Usage: Copy <src> <dest>"); System.exit(0); } FileInputStream in=new FileInputStream(args[0]);

Page 138: programare in Java

PROGRAMARE IN JAVA - Note de curs 235

FileOutputStream out=new FileOutputStream(args[1]); int b; while(( b=in.read())!=-1)out.write(b); } } Programul de mai sus realizeaza copierea unui fisier in alt fisier. El preia octetii din fisierul sursa si ii depune intr-un fisier destinatie. Numele celor doua fisiere sunt preluate din primele doua argumente ale liniei de comanda. import java.io.*; class File2Con{ public static void main(String[] args) throws IOException { if(args.length==0){ System.out.println("Usage: File2Con <file_name>"); System.exit(0); } FileInputStream in=new FileInputStream(args[0]); int b; while((b=in.read())!=-1){ if(b=='\r')System.out.print('\n'); else System.out.print((char)b); } System.out.flush(); } } Programul de mai sus realizeaza copierea unui fisier la consola (fisierul standard de iesire accesat prin streamul de iesire System.out). El preia octetii din fisierul sursa al carui nume este preluat din primul argument al liniei de comanda si ii trimite la consola. In figura 19.4 este prezentat listingul unor comenzi de executie a programelor de mai sus si a unor comenzi date sistemului de operare precum si rezultatelor afisate la consola in urma acestor comenzi.

Page 139: programare in Java

236 CURS 19

Fig.19.4 – Crearea si accesarea fisierelor

Interconectarea streamurilor Am vazut ca streamurile FileInputStream si FileOutputStream nu implementeaza decat operatiile primitive de citire/scriere a unor date de tip byte din/in fisiere. Imediat ce un byte de date este depus in streamul FileOutputStream el este scris in fisierul destinatie. Similar, imediat ce se executa o citire dintr-un stream FileInputStream acesta este “golit” de byte-ul continut si un nou byte este adus automat din fisier in stream pregatindu-se o noua citire. Pentru a putea folosi mecanisme mai evoluate de acces la date trebuie sa folosim streamuri care implementeaza aceste mecanisme. Aceste streamuri sunt si ele de doua feluri – de intrare si de iesire fiind subclase ale claselor abstracte InputStream si OutputStream insa au ca sursa/rezervor de date streamuri primitive cum ar fi FileInputStream si FileOutputStream. Rolul acestor streamuri este de a perfectiona interfata de acces la date. Sa analizam de exemplu clasele BufferedInputStream si BufferedOutputStream. Sursa de date pentru BufferedInputStream poate fi orice stream subclasa a clasei abstracte InputStream, in cazul nostru un obiect FileInputStream (figura 19.5a). Rezervorul de date in care sunt trimisi octetii din BufferedOutputStream este un obiect FileOutputStream (figura 19.5b).

Fig.19.5 Filtrarea streamurilor

Clasa BufferedInputStream implementeaza o proprietate importanta din punct de vedere al eficientei operatiilor de preluare a datelor de la sursa de date - bufferizarea. In loc ca streamul sa “incarce” cate un octet de la sursa, el incarca un bloc de octeti odata, printr-o singura operatie de acces. Blocul de date este stocat intr-un buffer intern de unde acestea sunt preluate de thread octet cu octet la fiecare apel al metodei read(). In momentul in care bufferul este golit, automat se efectueaza o reincarcare a sa prin aducerea unui nou bloc de bytes de la sursa de date, iarasi printr-o singura operatie de acces. Similar, clasa BufferedOutputStream implementeaza scrierea bufferizata a datelor. Datele trimise de thread in loc sa fie directate direct spre destinatie, sunt depuse intr-un buffer. In momentul in care bufferul este plin, intreg continutul su este trimis la destinatie, printr-o singura operatie de acces. Daca se doreste scrierea datelor din buffer inainte de umplerea acestuia se apeleaza metoda flush().

Page 140: programare in Java

PROGRAMARE IN JAVA - Note de curs 237

La construirea unui obiect BufferedInputStream acesta este legat de un stream sursa de date – in cazul nostru un obiect din clasa FileInputStream specificat ca argument al unuia din constructorii clasei:

BufferedInputStream(InputStream in) – se creaza un streeam de intrare bufferizat cu o dimensiune implicita a bufferului.

BufferedInputStream(InputStream in, int size) – se creaza un streeam de intrare bufferizat cu o dimensiune size a bufferului.

clasa defineste si urmatoarele variabile protejate:

protected byte buf[] – referinta la bufferul de date. protected int count – numarul de octeti continut de buffer protected int marklimit - limita maxima pana la care se poate avansa relativ la

marcaj inainte ca acesta sa fie sters. protected int markpos – pozitia marcajului. protected int pos – pozitia curenta in buffer.

Similar un stream BufferedOutputStream trebuie legat la un stream de iesire – in cazul nostru FileOutputStream - specificat ca argument al constructorului clasei:

BufferedrOutputStream(OutputStream out) se creaza un streeam de iesire bufferizat cu o dimensiune implicita a bufferului.

BufferedrOutputStream(OutputStream out, int size) se creaza un streeam de iesire bufferizat cu o dimensiune size a bufferului.

clasa defineste si urmatoarele variabile protejate:

protected byte buf[] – referinta la bufferul de date. protected int count – numarul de octeti continut de buffer

Programul urmator este o versiune modificata, bazata pe utilizarea streamurilor cu bufferizare a programului Copy din exemplul precedent, import java.io.*; class Copy{ public static void main(String[] args) throws IOException { if(args.length < 2){ System.out.println("Usage: Copy <src> <dest>"); System.exit(0); } BufferedInputStream in=new BufferedInputStream( new FileInputStream(args[0])); BufferedOutputStream out=new BufferedOutputStream( new FileOutputStream(args[1])); int b; while(( b=in.read())!=-1)out.write(b); out.flush(); } } Streamurile bufferizate pot “interfata” si alte streamuri primitive de intrare/iesire cum ar fi cele din clasele ByteArrayInputStream/ByteArrayOutputStream sau StringBufferInputStream. Clasa ByteArrayInputStream este o subclasa a clasei abstracte InputStream care are ca sursa de date un tablou de octeti. Ea are constructorii:

ByteArrayInputStream (byte buf[]) – se creaza un streeam de intrare care are ca sursa de date tabloul de bytes buf. Referinta la buffer este pastrata in variabila

Page 141: programare in Java

238 CURS 19

protected byte buf[]. ByteArrayInputStream (byte buf[], int offset, int length) – se creaza un streeam de

intrare care are ca sursa de date tabloul de bytes buf. Citirea va incepe de la octetul cu indexul offset si va continua pana vor fi preluati length octeti dupa care se semnaleaza sfarsit de fisier. Variabilele protejate ale clasei protected int pos (indicand pozitia curenta) si respectiv protected int count (indicand numarul de bytes continuti de stream) sunt setate corespunzator. Referinta la buffer este pastrata in variabila protected byte buf[].

Clasa ByteArrayOutputStream este o subclasa a clasei abstracte InputStream care are ca “rezervor” de date un tablou de octeti. Ea are constructorii:

ByteArrayInputStream (byte buf[]) – se creaza un streeam de intrare care are ca sursa de date tabloul de bytes buf. Referinta la buffer este pastrata in variabila protected byte buf[].

ByteArrayInputStream (byte buf[], int offset, int length) – se creaza un streeam de intrare care are ca sursa de date tabloul de bytes buf. Citirea va incepe de la octetul cu indexul offset si va continua pana vor fi preluati length octeti dupa care se semnaleaza sfarsit de fisier. Variabilele protejate ale clasei protected int pos (indicand pozitia curenta) si respectiv protected int count (indicand numarul de bytes continuti de stream) sunt setate corespunzator. Referinta la buffer este pastrata in variabila protected byte buf[].

Clasa StringBufferInputStream este o subclasa a clasei abstracte InputStream care are ca sursa de date un String. Ea are constructorul:

StringBufferInputStream (String s) – se creaza un streeam de intrare care are ca sursa de date stringul s folosit ca buffer de octeti. Referinta la buffer este pastrata in variabila protected String buffer.

Clasa defineste si variabilele protejate protected int pos (indicand pozitia curenta in stream) si protected int count (indicand numarul de bytes continuti de stream)

Formatarea datelor la accesul fisierelor Streamurile prezentate anterior ofera un acces primitiv la datele din fisiere, extragand/inscriind octeti de date. Programele insa de regula luccreaza cu alte tipuri de date – intregi, valori reale, date de tip String, etc. Pentru a citi/scrie din/in fisiere astfel de tipuri de date fluxul de octeti trebuie formatat. Aceasta inseamna ca la citire, pentru a obtine un intreg, trebuie cititi patru octeti si asamblati impreuna pentru a obtine valoarea intreaga respectiva. La scriere operatia de formatare este inversa, o valoare de tip int de exemplu trebuie dezasamblata in patru octeti ce vor fi trimisi apoi succesiv la destinatie. Pentru a putea citi/scrie date formatate un stream trebuie sa implementeze o interfata definind un pachet de metode publice specializate care sa realizeze “inpachetarea” octetilor cititi intr-un tip de date particular respectiv “despachetarea” la scriere a valorii de un tip particular in octetii componenti. Limbajul prevede doua astfel de interfete – DataInput definind metode pentru citirea formatata a datelor si DataOutput pentru scrierea formatata a datelor. Interfata defineste urmatoarele DataInput metode:

abstract boolean readBoolean() throws IOException – citeste din stream si intoarce o valoare de tip boolean.

abstract byte readByte() throws IOException – citeste din stream si intoarce o

Page 142: programare in Java

PROGRAMARE IN JAVA - Note de curs 239

valoare de tip byte. abstract char readChar() throws IOException – citeste din stream si intoarce o

valoare de tip char. abstract double readDouble() throws IOException – citeste din stream si intoarce

o valoare de tip double. abstract float readFloat() throws IOException – citeste din stream si intoarce o

valoare de tip float. abstract void readFully(byte b[]) throws IOException – citeste octeti din stream

pana cand tabloul b de este completat in intregime. abstract void readFully(byte b[],int offset,int length) throws IOException – citeste

octeti din stream pana cand tabloul b de este completat incepand cu pozitia offset cu un numar de length bytes.

abstract int readInt() throws IOException – citeste din stream si intoarce o valoare de tip int.

abstract long readLong() throws IOException – citeste din stream si intoarce o valoare de tip long.

abstract short readShort () throws IOException – citeste din stream si intoarce o valoare de tip short.

abstract int readUnsignedByte () throws IOException – citeste din stream si intoarce o valoare de tip unsigned byte.

abstract int readUnsignedShort () throws IOException – citeste din stream si intoarce o valoare de tip unsigned short.

abstract String readUTF () throws IOException – citeste din stream si intoarce un String in format UTF (Unicode).

abstract int skipBytes (int num) throws IOException – “sare” peste cel mult num octeti din stream si intoarce numarul efectiv de octeti extrasi.

Interfata defineste urmatoarele DataOutput metode:

abstract void write(byte b[],int offset, int len ) throws IOException – scrie in stream len octeti din bufferul b, incepand cu elementul de la indexul offset.

abstract void write(byte b[] ) throws IOException – scrie in stream octetii continuti de bufferul b.

abstract void write(int b ) throws IOException – scrie in stream o valoarea de tip int transmisa ca argument.

abstract void writeBoolean(boolean v) throws IOException – scrie in stream o valoarea de tip boolean transmisa ca argument.

abstract void writeByte(int v) throws IOException – scrie in stream un byte cu valoarea transmisa ca argument.

abstract void writeBytes(String s) throws IOException –scrie in stream octetii continuti de stringul s.

abstract void writeChar(int v) throws IOException – scrie in stream o valoarea de tip char transmisa ca argument.

abstract void writeChars(String s) throws IOException –scrie in stream caracterele continute de stringul s.

abstract void writeDouble(double v) throws IOException – scrie in stream o valoarea de tip double transmisa ca argument.

abstract void writeFloat(float v) throws IOException – scrie in stream o valoarea de tip float transmisa ca argument.

abstract void writeInt(int v) throws IOException – scrie in stream o valoarea de tip

Page 143: programare in Java

240 CURS 19

int transmisa ca argument. abstract void writeLong(long v) throws IOException – scrie in stream o valoarea

de tip long transmisa ca argument. abstract void writeShort(int v) throws IOException – scrie in stream o valoarea de

tip short transmisa ca argument. abstract void writeUTF (String s) throws IOException – scrie in stream un String

in format UTF (Unicode). Implementarea interfetei DataInput este realizata de clasa DataInputStream care in plus extinde clasa FilterInputStream mostenind si toate metodele acesteia (o versiune non-abstracta a clasi InputStream). Constructorul acestei clase este:

public DataInputStream(InputStream in) – construieste un stream de intrare DataInputStream conectat la streamul de intrare in ca sursa de date. Acest stream poate fi oricare din streamurile de intrare primitive discutate (FileInputStream, ByteArrayInputStream, StringBufferInputStream, PipedInputStream).

Clasa defineste metodele:

final int read(byte b[]) throws IOException – citeste octeti din stream pana cand tabloul b si intoarce numarul de octeti citit.

final int read(byte b[],int offset,int length) throws IOException – citeste un numar de length octeti din stream pana cand tabloul b incepand cu indexul offset. Metoda intoarce numarul de octeti citit.

final boolean readBoolean() throws IOException – citeste din stream si intoarce o valoare de tip boolean.

final byte readByte() throws IOException – citeste din stream si intoarce o valoare de tip byte.

final char readChar() throws IOException – citeste din stream 2 octeti si intoarce o valoare de tip char pe 16 biti.

final double readDouble() throws IOException – citeste din stream 8 octeti si intoarce o valoare de tip double pe 64 de biti.

final float readFloat() throws IOException – citeste din stream 4 octeti si intoarce o valoare de tip float pe 32 de biti.

final void readFully(byte b[]) throws IOException – citeste octeti din stream si nu revine pana cand tabloul b de este completat in intregime.

final void readFully(byte b[],int offset,int length) throws IOException – citeste octeti din stream si nu revine pana cand tabloul b nu este completat incepand cu pozitia offset cu un numar de length bytes.

final int readInt() throws IOException – citeste din stream 4 octeti si intoarce o valoare de tip int pe 32 de biti.

final String readLine () throws IOException – citeste succesiv octeti din stream pana la primul caracter de control ‘\n’ sau ‘\r’, perechea “\r\n” sau pana este detectat sfarsitul de fisier (EOF). Intoarce un String continand caracterele respective.

final long readLong() throws IOException – citeste din stream 8 octeti si intoarce o valoare de tip long pe 64 de biti.

final short readShort () throws IOException – citeste din stream 4 octeti si intoarce o valoare de tip short pe 32 de biti.

final int readUnsignedByte () throws IOException – citeste din stream si intoarce

Page 144: programare in Java

PROGRAMARE IN JAVA - Note de curs 241

o valoare de tip unsigned byte. final int readUnsignedShort () throws IOException – citeste din stream 2 octeti si

intoarce o valoare de tip unsigned short pe 16 biti.. final String readUTF () throws IOException – citeste din stream si intoarce un

String in format UTF (Unicode). final int skipBytes (int num) throws IOException – “sare” peste cel mult num octeti

din stream si intoarce numarul efectiv de octeti extrasi. Implementarea interfetei DataOutputeste realizata de clasa DataOutputStream care in plus extinde clasa FilterOutputStream mostenind si toate metodele acesteia (o versiune non-abstracta a clasi OutputStream). Constructorul acestei clase este:

public DataOutputStream(OutputStream out) – construieste un stream de intrare DataOutputStream conectat la streamul de intrare out ca “rezervor” de date. Acest stream poate fi oricare din streamurile de intrare primitive discutate (FileOutputStream, ByteArrayOutputStream, PipedOutputStream).

Clasa defineste variabila protected int written continand numarul de octeti inscrisi in stream si metodele:

void flush() throws IOException – are ca efect golirea bufferului streamului la care este conectat (numai daca acesta este bufferizat).

final int size() throws IOException – intoarce valoarea continuta de variabila written reprezentand numarul de octeti expediati.

synchronized void write(byte b[],int offset, int len ) throws IOException – scrie in stream len octeti din bufferul b, incepand cu elementul de la indexul offset.

synchronized void write(int b ) throws IOException – scrie in stream o valoarea de tip int transmisa ca argument.

final void writeBoolean(boolean v) throws IOException – scrie in stream o valoarea de tip boolean transmisa ca argument.

final void writeByte(int v) throws IOException – scrie in stream un byte cu valoarea transmisa ca argument.

final void writeBytes(String s) throws IOException –scrie in stream octetii continuti de stringul s.

final void writeChar(int v) throws IOException – scrie in stream o valoarea de tip char transmisa ca argument.

final void writeChars(String s) throws IOException –scrie in stream caracterele continute de stringul s.

final void writeDouble(double v) throws IOException – scrie in stream o valoarea de tip double transmisa ca argument.

final void writeFloat(float v) throws IOException – scrie in stream o valoarea de tip float transmisa ca argument.

final void writeInt(int v) throws IOException – scrie in stream o valoarea de tip int transmisa ca argument.

final void writeLong(long v) throws IOException – scrie in stream o valoarea de tip long transmisa ca argument.

final void writeShort(int v) throws IOException – scrie in stream o valoarea de tip short transmisa ca argument.

final void writeUTF (String s) throws IOException – scrie in stream un String in format UTF (Unicode).

Page 145: programare in Java

242 CURS 19

Programul urmator este o versiune modificata, bazata pe utilizarea streamurilor cu bufferizare a programului File2Con prezentat anterior. El foloseste metoda readLine() a clasei DataInputStream pentru a citi linii de text din fisier intr-un string pe care il afiseaza apoi la consola: import java.io.*; class File2Con{ public static void main(String[] args) throws IOException { String s; if(args.length==0){ System.out.println("Usage: File2Con <file_name>"); System.exit(0); } DataInputStream in=new DataInputStream( new FileInputStream(args[0])); while((s=in.readLine())!=null){ System.out.println(s); System.out.flush(); } } }

Page 146: programare in Java

PROGRAMARE IN JAVA - Note de curs 243

Curs 20

Clasa PrintStream Aceasta clasa defineste un tip de stream de iesire derivat din FilterOutputStream destinat transmiterii de date sub forma de text. Am folosit frecvent acest tip de stream de iesire apeland metodele lui System.out deoarece obiectul System.out este o instanta a clasei PrintStream. Clasa are doi constructori:

PrintStream(OutputStream out) – care construieste un obiect PrintStream conectat la un stream de iesire out ca destinatie a datelor. Acest stream poate fi oricare din streamurile de intrare primitive discutate (FileOutputStream, ByteArrayOutputStream, PipedOutputStream).

PrintStream(OutputStream out, boolean autoflush) – care construieste un obiect PrintStream conectat la un stream de iesire out ca destinatie a datelor. Acest stream poate fi oricare din streamurile de intrare primitive discutate (FileOutputStream, ByteArrayOutputStream, PipedOutputStream). Daca parametrul autoflush are valoarea true bufferul va fi golit dupa fiecare linie de text transmisa (treminata cu caracterul de control ‘\n’).

Metodele clasei sunt:

boolean checkError() - intoarce true daca a aparut o eroare la scrierea datelor in stream.

void close() – inchide streamul. void flush() – goleste bufferul. void print(boolean b) – tipareste o valoare de tip boolean. void print(double d) – tipareste o valoare de tip double. void print(float f) – tipareste o valoare de tip float. void print(long l) – tipareste o valoare de tip long. void print(int i) – tipareste o valoare de tip int. void print(char c) – tipareste o valoare de tip char. synchronized void print(char s[]) – tipareste toate caracterele continute de tabloul

s. synchronized void print(String s) – tipareste toate caracterele continute de

stringul s. void print(Object o) – tipareste valoarea intoarsa de metoda String.valueOf(o). synchronized void println(boolean b) – tipareste o valoare de tip boolean urmat

de caracterul ‘\n’ (trece la linie noua). synchronized void println(double d) – tipareste o valoare de tip double urmat de

caracterul ‘\n’ (trece la linie noua). synchronized void println(float f) – tipareste o valoare de tip float urmat de

caracterul ‘\n’ (trece la linie noua). synchronized void println(long l) – tipareste o valoare de tip long urmat de

caracterul ‘\n’ (trece la linie noua). synchronized void println(int i) – tipareste o valoare de tip int urmat de caracterul

‘\n’ (trece la linie noua).

Page 147: programare in Java

244 CURS 20

synchronized void println(char c) – tipareste o valoare de tip char urmat de caracterul ‘\n’ (trece la linie noua).

synchronized void println(char s[]) – tipareste toate caracterele continute de tabloul s urmat de caracterul ‘\n’ (trece la linie noua).

synchronized void println(String s) – tipareste toate caracterele continute de stringul s urmat de caracterul ‘\n’ (trece la linie noua).

synchronized void println() - tipareste caracterul ‘\n’ (trece la linie noua). void write(int b ) – scrie un singur octet in stream. void write(byte b[],int offset, int len) – scrie in stream len octeti din bufferul b,

incepand cu elementul de la indexul offset.

Exemplu de creare a unui fisier cu acces secvential Programul urmator creeaza un fisier secvential format din inregistrari continand urmatoarele informatii despre studenti: nume, prenume, grupa si nota obtinuta. Programul are o interfata formata din patru campuri text in care se vor putea introduce informatiile respective si doua butoane – “Adaugare” care comanda adaugarea inregistrarii la fisier si “Terminare” care listeaza continutul fisierului la consola si termina executia programului. Interfata grafica este gestionata de un manager de amplasare GridBagLayout si este proiectata ca in figura 20.1.

Fig.20.1 Proiectarea interfetei grafice Dupa fiecare adaugare, continutul campurilor de editare este sters. Programul este urmatorul: import java.awt.*; import java.io.*; class Evidenta extends Frame{ private Label lNume,lPNume,lGrupa,lNota; private TextField tNume,tPNume,tGrupa,tNota; private Button bAdd,bTerm; private GridBagLayout gbl; private GridBagConstraints gbc; private String nume; private DataOutputStream out; public Evidenta(){ super("Fisier secvential"); lNume=new Label("Nume"); lPNume=new Label("Prenume"); lGrupa=new Label("Grupa");

Page 148: programare in Java

PROGRAMARE IN JAVA - Note de curs 245

lNota=new Label("Nota"); tNume=new TextField(20); tPNume=new TextField(20); tGrupa=new TextField(4); tNota=new TextField(2); bAdd=new Button("Adaugare"); bTerm=new Button("Terminare"); setLayout(gbl=new GridBagLayout()); gbc=new GridBagConstraints(); gbc.fill=gbc.NONE; gbc.weightx=1; gbc.weighty=1; gbc.anchor=gbc.EAST; addComponent(lNume,gbl,gbc,0,0,1,1); addComponent(lPNume,gbl,gbc,1,0,1,1); addComponent(lGrupa,gbl,gbc,2,0,1,1); addComponent(lNota,gbl,gbc,3,0,1,1); gbc.fill=gbc.HORIZONTAL; gbc.weightx=2; addComponent(tNume,gbl,gbc,0,1,1,1); addComponent(tPNume,gbl,gbc,1,1,1,1); addComponent(tGrupa,gbl,gbc,2,1,1,1); addComponent(tNota,gbl,gbc,3,1,1,1); gbc.weightx=1; gbc.fill=gbc.BOTH; addComponent(bAdd,gbl,gbc,4,0,1,1); gbc.weightx=2; addComponent(bTerm,gbl,gbc,4,1,1,1); resize(300,150); show(); } private void addComponent(Component c, GridBagLayout gbl, GridBagConstraints gbc, int row, int col, int width, int height) { gbc.gridx=col; gbc.gridy=row; gbc.gridwidth=width; gbc.gridheight=height; gbl.setConstraints(c,gbc); add(c); } public boolean handleEvent(Event e){ if(e.id== Event.WINDOW_DESTROY){ quit(); return true; } return super.handleEvent(e); } private void quit(){ try{ out.close(); DataInputStream in=new DataInputStream( new FileInputStream("Studenti.dat")); while((nume=in.readUTF())!=null){ System.out.println(nume+"; "+in.readUTF()+"; "+ in.readUTF()+"; "+in.readInt()+";");

Page 149: programare in Java

246 CURS 20 } in.close(); } catch(Exception ex){} dispose(); hide(); System.exit(0); } public boolean action(Event e, Object o){ if(e.target==bTerm) quit(); else if(e.target==bAdd){ try{ out.writeUTF(tNume.getText()); out.writeUTF(tPNume.getText()); out.writeUTF(tGrupa.getText()); out.writeInt(Integer.parseInt(tNota.getText())); tNume.setText(""); tPNume.setText(""); tGrupa.setText("");tNota.setText(""); } catch(Exception ex){} } return true; } public static void main(String[] args)throws IOException { Evidenta e=new Evidenta(); e.out=new DataOutputStream( new FileOutputStream("Studenti.dat")); } } El creaza si afiseaza fisierul Studenti.dat. Interfata grafica si rezultatele afisate de program sunt prezentate in figura 20.2.

Fig.20.2 – Interfata grafica si rezultatele afisate de programul Evidenta.class

Actualizarea fisierelor secventiale Prin operatia de deschidere a unui fisier acesta daca nu exista este creat si apoi deschis iar daca exista deja, este deschis dar toate datele din el sunt sterse. Din acest motiv operatia de adaugare/modificare de inregistrari intr-un fisier secvential este destul de greoaie. Programul din exemplul precedent crea la fiecare executie un alt fisier Studenti.dat , cel existent anterior fiind sters. Daca se doreste adaugarea de noi inregistrari la fisierul creat la executia precedenta programul ar trebui modificat astfel: - Fisierul existent Studenti.dat trebuie redenumit in fisierul Studenti.old - Se creaza si se deschide un nou fisier Studenti.dat - Inregistrarile din fisierul Studenti.old se copiaza in fisierul Studenti.dat - Se continua programul prin adaugarea de noi inregistrari.

Page 150: programare in Java

PROGRAMARE IN JAVA - Note de curs 247

Programul urmator este o versiune modificata a programului anterior care daca fisierul Studenti.dat nu exista il creaza iar daca exista adauga noi inregistrari in fisier la inregistrarile deja existente. Modificarile afecteaza doar metoda main() astfel: import java.awt.*; import java.io.*; class Evidenta extends Frame{ //... Variabile si metode identice cu cele dinprogramul precedent ... public static void main(String[] args)throws IOException { int b; Evidenta e=new Evidenta(); File f=new File("Studenti.dat"), o=new File("Studenti.old"); if(f.exists()){ //"Studenti.dat" exista f.renameTo(o);// redenumeste "Studenti.dat"

// in "Studenti.old" DataInputStream in=new DataInputStream( new FileInputStream(o));//Sursa e.out=new DataOutputStream( new FileOutputStream("Studenti.dat"));//Destinatie while((b=in.read())!=-1) e.out.write(b);//copiere in.close();// inchide “Studenti.old” o.delete();//Sterge "Studenti.old" }else// Deschide un nou fisier "Studenti.dat" e.out=new DataOutputStream( new FileOutputStream("Studenti.dat")); } } In acest program am folosit metodele exists(), renameTo() si delete() definite in clasa File pentru a determina daca fisierul “Studenti.java” exista, pentru redenumirea lui in “Studenti.old” si in final stergerea sa dupa efectuarea copierii din “Studenti.old” in noul fisier “Studenti.dat”. ceva mai complicat stau lucrurile daca se doreste stergerea sau modificarea unei inregistrari. Stergerea unei inregistrari presupune crearea unui nou fisier in care sa se copieze toate inregistrarile dinantea si de dupa inregistrarea ce trebuieste eliminata. Modificarea unei inregistrari presupune copierea intr-un nou fisier a tuturor inregistrarilor precedente inregistrarii modificate, adaugarea la acest fisier a inregistrarii modificate si copierea restului de inregistrari din fisierul vechi in fisierul nou. Insertia unei noi inregistrari intr-o anumita pozitie in fisier presupune de asemenea copierea intr-un nou fisier a tuturor inregistrarilor ce preced pozitia de insertie, adaugarea noii inregistrari si copierea in continuare a restului de inregistrari din fisierul original. Toate aceste operatii ca si operatia de cautare a unei anume inregistrari din fisier, presupun citirea tuturor inregistrarilor precedente ceea ce poate deveni o operatie mare consumatoare de timp daca numarul de inregistrari din fisier este mare (de ordinul sutelor de mii sau chiar milioanelor). Aceasta poate deveni un impediment serios in cazul realizarii unor sisteme de tranzactionare in timp real (cum ar fi de exemplu cel de rezervare a locurilor) in care raspunsul la interogarea bazei de date trebuie sa fie dat intr-un timp rezonabil de scurt. Aceste inconveniente ce apar in cazul

Page 151: programare in Java

248 CURS 20 utilzarii fisierelor cu acces secvential pot fi evitate daca se folosesc fisiere cu acces aleator.

Fisiere cu acces aleator Fisierele cu acces aleator sunt utilizate in cazul aplicatiilor in care timpul de acces la informatie este critic – asa cum sunt aplicatiile cu tranzactii in timp real, de exemplu aplicatiile bancare sau cele de rezervare automata a locurilor. In aceste cazuri programul trebuie sa proceseze tranzactia (depunerea/extragerea de numerar in/din contul clientului respectiv cautarea unui loc liber si inregistrarea rezervarii acestuia) intr-un fisier de date de dimensiuni mari intr-un timp acceptabil pentru utilizator (de ordinul secundelor). Limbajul Java nu impune o anumita structura pentru fisiere, astfel incat programul trebuie efectiv sa creeze un fisier cu acces aleator implementand una dintr-o varietate mare de tehnici posibile. Cea mai simpla dintre aceste tehnici presupune utilizarea unor inregistrari avand toate aceeasi lungime. Folosind inregistrari de lungime fixa, poate calcula cu usurinta deplasamentul inregistrarii cautate aceasta fiind dependenta de valoarea campului cheie al inregistrarii si de lungimea inregistrarii. Asa cum vom vedea in continuare, cunoasterea acestui deplasament permite accesul imediat la inregistrarea cautata, fara a citi in prealabil toate inregistrarile ce o preced (ca in cazul fisierelor secventiale). Un fisier Java cu acces aleator este ca un tren cu mai multe vagoane (Figura 20.3). Unele vagoane sunt incarcate, altele nu. Datele pot fi insertate in fisier fara a distruge informatia deja continuta de acesta. De asemenea informatia existenta in fisier poate fi actualizata sau stearsa fara a fi necesara rescrierea intregului fisier ca in cazul fisierelor cu acces secvential.

Fig.20.3 – Fisier cu acces aleator Un fisier cu acces aleator poate fi creat si exploatat de un program Java cu ajutorul clasei RandomAccessFile. Aceasta este de fapt un stream “duplex”, implementand ambele interfete DataInput si DataOutput. Streamul defineste metodele de scriere/citire formatata din fisier specificate de acestea. In plus clasa permite, la deschiderea fisierului, specificarea modului de acces la fisier. fisierul poate fi deschis in modul read-only – numai pentru citire, sau in modul read/write – citire si scriere. In momentul in care acest stream este conectat la un fisier, citirea/scrierea datele se face intr-o locatie aflata la un deplasament relativ la inceputul fisierului indicat de pointerul de pozitie in fisier. Cand se efectueaza citirea unei valori int din fisier, 4 octeti sunt preluati de stream incepand cu cel aflat la deplasamentul indicat de pointerul de pozitie. Pe masura ce octetii sunt cititi, poiterul este incrementat cu 1, avansand pe urmatorul octet. La scrierea in stream a unei valori de tip double, aceasta este despachetata in cei 8 octeti componenti care sunt inscrisi succesiv in locatia din fisier aflata la deplasamentul indicat de pointerul de pozitie. Dupa scrierea fiecarui

Page 152: programare in Java

PROGRAMARE IN JAVA - Note de curs 249

octet, pointerul este incrementat cu 1, trecand pe pozitia urmatoare pentru o noua scriere, pana cand toti octetii au fost inscrisi. Metodele definite de clasa RandomAccessFile sunt:

RandomAccessFile(File file, String mode) throws IOException – creaza streamul si deschide fisierul specificat de file in modul de aces read-only daca parametrul mode este “r” sau red/write daca este “rw”.

RandomAccessFile(String file, String mode) throws IOException – creaza streamul si deschide fisierul cu numele specificat de stringul file in modul de aces read-only daca parametrul mode este “r” sau red/write daca este “rw”.

void close() – inchide fisierul. long getFilePointer() throws IOException – intoarce valoarea curenta a

pointerului de pozitie in fisier (deplasamentul in octeti relativ la inceputul fisierului pe care este pozitionat pointerul de pozitie).

long length() throws IOException – intoarce dimensiunea in octeti a fisierului. int read() throws IOException – citeste si intoarce un singur byte. int read(byte b[]) throws IOException – citeste octeti din stream pana cand tabloul

b si intoarce numarul de octeti citit. int read(byte b[],int offset,int length) throws IOException – citeste un numar de

length octeti din stream pana cand tabloul b incepand cu indexul offset. Metoda intoarce numarul de octeti citit.

final boolean readBoolean() throws IOException – citeste din stream si intoarce o valoare de tip boolean.

final byte readByte() throws IOException – citeste din stream si intoarce o valoare de tip byte.

final char readChar() throws IOException – citeste din stream 2 octeti si intoarce o valoare de tip char pe 16 biti.

final double readDouble() throws IOException – citeste din stream 8 octeti si intoarce o valoare de tip double pe 64 de biti.

final float readFloat() throws IOException – citeste din stream 4 octeti si intoarce o valoare de tip float pe 32 de biti.

final void readFully(byte b[]) throws IOException – citeste octeti din stream si nu revine pana cand tabloul b de este completat in intregime.

final void readFully(byte b[],int offset,int length) throws IOException – citeste octeti din stream si nu revine pana cand tabloul b nu este completat incepand cu pozitia offset cu un numar de length bytes.

final int readInt() throws IOException – citeste din stream 4 octeti si intoarce o valoare de tip int pe 32 de biti.

final String readLine () throws IOException – citeste succesiv octeti din stream pana la primul caracter de control ‘\n’ sau ‘\r’, perechea “\r\n” sau pana este detectat sfarsitul de fisier (EOF). Intoarce un String continand caracterele respective.

final long readLong() throws IOException – citeste din stream 8 octeti si intoarce o valoare de tip long pe 64 de biti.

final short readShort () throws IOException – citeste din stream 4 octeti si intoarce o valoare de tip short pe 32 de biti.

final int readUnsignedByte () throws IOException – citeste din stream si intoarce o valoare de tip unsigned byte.

final int readUnsignedShort () throws IOException – citeste din stream 2 octeti si intoarce o valoare de tip unsigned short pe 16 biti..

Page 153: programare in Java

250 CURS 20

final String readUTF () throws IOException – citeste din stream si intoarce un String in format UTF (Unicode).

void seek(long pos) throws IOException – pozitioneaza pointerul pe pozitia pos. final int skipBytes (int num) throws IOException – “sare” peste cel mult num octeti

din stream si intoarce numarul efectiv de octeti extrasi. void write(byte b[],int offset, int len ) throws IOException – scrie in stream len

octeti din bufferul b, incepand cu elementul de la indexul offset. void write(byte b[] ) throws IOException – scrie in stream octetii din bufferul b. final void writeBoolean(boolean v) throws IOException – scrie in stream o

valoarea de tip boolean transmisa ca argument. final void writeByte(int v) throws IOException – scrie in stream un byte cu

valoarea transmisa ca argument. final void writeBytes(String s) throws IOException –scrie in stream octetii

continuti de stringul s. final void writeChar(int v) throws IOException – scrie in stream o valoarea de tip

char transmisa ca argument. final void writeChars(String s) throws IOException –scrie in stream caracterele

continute de stringul s. final void writeDouble(double v) throws IOException – scrie in stream o valoarea

de tip double transmisa ca argument. final void writeFloat(float v) throws IOException – scrie in stream o valoarea de

tip float transmisa ca argument. final void writeInt(int v) throws IOException – scrie in stream o valoarea de tip int

transmisa ca argument. final void writeLong(long v) throws IOException – scrie in stream o valoarea de

tip long transmisa ca argument. final void writeShort(int v) throws IOException – scrie in stream o valoarea de tip

short transmisa ca argument. final void writeUTF (String s) throws IOException – scrie in stream un String in

format UTF (Unicode).

Exemplu de utilizare a fisierelor cu acces aleator Programul urmator va implementa un mecanism de acces cu parola a unor utilizatori multipli. Fiecare utilizator va fi inregistrat intr-o baza de date cu un nume de user si o parola. La inceperea executiei, programul va permite utilizatorului fie sa isi introduca numele de user si parola pentru acces, fie daca este un utilizator nou, sa-si creeze contul inregistrandu-se in baza de date. Programul va tine pentru fiecare utilizator evidenta numarului de sesiuni de lucru efectuate. La inceperea unei sesiuni programul va afisa un mesaj de salut pentru userul respectiv. 1. Proiectarea interfetei grafice: Interfata grafica va consta dintr-o fereastra din clasa Frame continand doua “panouri” suprapuse (folosind CardLayout managerul). Prmul panou (gestionat de un GridBagLayout manager), afisat la lansarea in executie a programului, va contine etichete, campurile de text pentru afisarea unor mesaje, introducerea numelui de user si a parolei precum si butoanele de confirmare, renuntare, reset si deschidere de cont nou acestea fiind amplasate ca in figura 20.4.

Page 154: programare in Java

PROGRAMARE IN JAVA - Note de curs 251

Fig.20.4 – Amplasarea controalelor in panoul nr.1 al interfetei grafice Daca este actionat butonul bNew va fi creata si afisata o fereastra de dialog in cre utilizatorul va introduce informatiile necesare deschiderii unui cont nou. Fereastra de dialog va fi modala si va contine etichete, campuri text pentru introducerea numelui utilizatorului, a numelui de user ales a parolei si a parolei repetate (pentru evitarea unei tastari gresite) si afisare de mesaje, un buton “Ok”, un buton “Reset” si un buton “Cancel”. Amplasarea acestor controale in fereastra de dialog va fi gestionata de un GridBagLayout manager ca in figura 20.5.

Fig.20.5 – Amplasarea controalelor in fereastra de dialog a interfetei grafice

Pe perioada cat fereastra de dialog este afisata, toate controalele din panoul nr.1 vor fi dezactivate. La actionarea butonului bOk informatiile din fereastra de dialog vor fi citite, validate si inscrise sub forma de inregistrare in fisierul bazei de date. Cel de al doilea panou (gestionat de managerul implicit BorderLayout) va contine un

Page 155: programare in Java

252 CURS 20 control Canvas amplasat in centru pe care vor fi desenate informatiile specificate in formularea problemei si un buton “Terminare”, amplasat la “sud”, actionarea caruia duce la incheierea sesiunii de lucru si revenirea la panoul 1. La lansarea programului “Canvas”-ul nu are nimic desenat pe suprafata sa. Desenarea se face numai la deschiderea unei sesiuni noi. 2. Proiectarea bazei de date Baza de date de tip relational va fi formata din doua fisiere: usr.ndx si usr.dat. Fisierul usr.ndx este un fisier index iar fisierul usr.dat contine informatia privind utilizatorii inregistrati in baza de date. Fisierul usr.ndx are inregistrari de lungime fixa de 16 bytes. O astfel de inregistrare are doua campuri id si ndx continand primul un intreg lung reprezentand identificatorul utilizatorului (campul cheie) iar al doilea un intreg lung reprezentand pointerul catre informatia despre utilizatorul respectiv in fisierul usr.dat( deplasamentul acestei informatii relativ la inceputul fisierului). Structura bazei de date si relatia intre inregistrarile din fisierul usr.ndx si cele din fisierul usr.dat este reprezentata in figura 20.6.

Fig.20.6 – Structura bazei de date 3. Calculul identificatorului Valoarea intreaga din campul id al unei inregistrari din usr.ndx trebuie sa identifice in mod unic utilizatorul. Am fi putut sa inscriem in acest camp numele de user ales de utilizator dar aceasta ar fi implicat la cautarea in fisier a informatiei despre un anumit user compararea octet cu octet a unor stringuri, operatie consumatoare de timp. Din acest motiv, am preferat ca pe baza primelor maxim opt caractere ale numelui userului sa formam un intreg lung reprezentat pe 8 bytes. Operatia de comparare a intregilor se face prin scadere, deci printr-o singura instructiune masina, fiind mult mai rapida decat compararea a doua siruri de 8 octeti fiecare. Identificatorul se calculeaza ca fiind intregul lung avand ca reprezentare in memorie aceiasi 8 octeti care compun si stringul. Reprezentarea si formula de calcul pentru stringul de 8 caractere “ABCDEFGH” a acestui identificator este exemplificata in figura 20.7. Astfel codul literei A va fi octetul cel mai semnificativ al intregului (octetul 7) iar codul literei H va fi octetul cel mai putin semnificativ (octetul 0).

Page 156: programare in Java

PROGRAMARE IN JAVA - Note de curs 253

Fig.20.7 Calculul identificatorului utilizatorului

Metoda getID() din programul urmator implementeaza algoritmul de calcul al acestui identificator: class UsrID{ private static long getID(String s){ byte b[]=new byte[8]; int len=s.length(),i; long id=0; if(len<8){ s.getBytes(0,len,b,0); for(i=len;i<8;i++)b[i]=0; }else s.getBytes(0,7,b,0); for(i=0;i<8;i++){ id *=256; id +=b[i]; } return id; } public static void main(String[] args){ if(args.length==0){ System.out.println("Usage:UsrID <username>"); System.exit(0); } System.out.println("User ID: "+ getID(args[0])); } } Listingul din figura 20.8 arata rezultatul afisat de program pentru argumentul ABCDEFGH in linia de comanda.

Fig.20.8 Calculul identificatorului utilizatorului ABCDEFGH 4. Criptarea parolei Pentru ca parola unui utilizator sa ramana secreta chiar si pentru cineva care are acces la fisierul de date usr.dat aceasta trebuieste criptata inainte de a fi inscrisa in fisier. Am ales un algoritmul de criptare foarte simplu bazat pe shift-are codului fiecarui caracter cu o valoare intreaga cuprinsa intre 1 si 9 aceasta fiind una din cifrele cheii de criptare. Cheia de criptare este formata din mai multe cifre fiecare din acestea fiind folosita ciclic la criptarea unui caracter. Cifrul ales pentru criptarea parolelor este 123. Mecanismul de criptare este descris in figura 20.9.

Page 157: programare in Java

254 CURS 20

Fig.20.9 Algoritmul de criptare al parolei ABCDEFGH cu cifrul 123

Metoda encrypt() din programul urmator implementeaza acest algoritmul de criptare al parolei: class Password{ private static String encrypt(String s){ byte b[]=new byte[s.length()]; s.getBytes(0,b.length,b,0); for(int i=0;i<b.length;i++)b[i]=(byte)(b[i]+ i%3 + 1); return new String(b,0); } public static void main(String[] args){ if(args.length==0){ System.out.println("Usage:Password <password>"); System.exit(0); } System.out.println("Encripted password: "+ encrypt(args[0])); } } Listingul din figura 20.10 arata rezultatul afisat de program pentru argumentul ABCDEFGH in linia de comanda.

Fig.20.10 Criptarea parolei ABCDEFGH cu cifrul 123

Page 158: programare in Java

PROGRAMARE IN JAVA - Note de curs 255

Curs 21

Exemplu de utilizare a fisierelor cu acces aleator (continuare) 5. Algoritmul de inregistrare a unui utilizator nou in baza de date Dupa completarea campurilor text din fereastra de dialog cu informatiile necesare si actionarea butonului “Ok” programul va face intai validarea numelui utilizatorului prin preluarea in variabila String usrName a sirului din campul tName. Se verifica daca stringul nu este vid. Daca este vid se afiseaza un mesaj de eroare in campul tMsg. Daca numele utilizatorului este valid se continua cu validarea celor doua stringuri introduse ca parola in campurile tPsw1 si tPsw2. Daca ele nu sunt identice se afiseaza un mesaj de eroare in campul tMsg. Daca sunt identice parola din campul tPsw1 va fi criptata aplicandu-i algoritmul prezentat la punctul 4 si memorata intr-o variabila String psw. Se preia in variabila usr stringul din campul tUsr. Se verifica daca stringul nu este vid. Daca este vid se afiseaza un mesaj de eroare in campul tMsg. Daca numele de user nu este vid se genereaza identificatorul utilizatorului, aplicand asupra sa algoritmul prezentat la punctul 3. Valoarea astfel obtinuta este memorata in variabila long usrID. Se deschide/creaza fisierul cu acces aleator usr.ndx si se citesc succesiv inregistrarile din acesta in variabilele long id si long pos. Dupa fiecare citire se compara valoarea din id cu valoarea din usrID. Daca cele doua valori sunt egale inseamna ca deja exista un utilizator cu acest nume. Ca urmare se inchide fisierul si se afiseaza un mesaj de eroare in campul tMsg. Daca se ajunge la sfarsitul fisierului fara a gasi un identificator egal cu cel din variabila usrID inseamna ca numele de user ales este valid si se scrie in fisier valoarea din usrID. Se deschide fisierul cu acces aleator usr.dat si se inscrie lungimea acestuia (obtinuta apeland metoda length()) in fisierul usr.ndx ca pointer la noua inregistrare ce va fi facuta in usr.dat. Se pozitioneaza pointerul in fisierul usr.dat la sfarsitul acestuia (apeland metoda seek()) si se inscriu pe rand informatiile privind utilizatorul continute de variabilele usrName, usr, usrID si se initializeaza contorul de sesiuni de lucru ale utilizatorului inscriind in fisier (int) 0. Se inchid fisierele bazei de date si fereastra de dialog. 6. Algoritmul de autentificare a utilizatorului Dupa completarea campurilor text din fereastra de dialog cu informatiile necesare si actionarea butonului “Ok” programul va face intai validarea numelui utilizatorului prin preluarea in variabila String usr a sirului din campul tUsr. Se verifica daca stringul nu este vid. Daca este vid se afiseaza un mesaj de eroare in campul tMsg. Daca numele utilizatorului nu este vid se genereaza identificatorul utilizatorului, aplicand asupra stringului referit de usr algoritmul prezentat la punctul 3. Valoarea astfel obtinuta este memorata in variabila long usrID. Se deschide fisierul cu acces aleator usr.ndx si se citesc succesiv inregistrarile din acesta in variabilele long id si long pos. Dupa fiecare citire se compara valoarea din id cu valoarea din usrID. Dca se ajunge la sfarsitul fisierului fara ca sa fie gasita o inregistrare al carei cheie sa se potriveasca cu identificatorul utilizatorului se afiseaza un mesaj de eroare corespunzator in campul tMsg. Daca dupa o citire variabilele id si usrID sunt egale

Page 159: programare in Java

256 CURS 21 inseamna ca utilizatorul cu numele respectiv exista in baza de date. Se deschide fisierul cu acces aleator usr.dat si se pozitioneaza pointerul din acest fisier pe pozitia pos (apeland metoda seek()). Se continua cu citirea din fisierul usr.dat a intregii inregistrari in variabilele String usrName, String usr, String psw. Parola din campul tPsw se cripteaza intr-o variabila String usrPsw aplicandu-i algoritmul prezentat la punctul 4. Se compara la egalitate sirurile referite de psw si usrPsw. Daca cele doua siruri sunt egale deschiderea sesiunii este acceptata. 7. Algoritmul de desfasurare al unei sesiuni de lucru Se citeste din fisierul usr.dat valoarea contorului de sesiuni in variabila int cnt. Se incrementeaza cnt. Se obtine in variabila long pos pozitia curenta in fisier apeland metoda getFilePointer() si se decrementeaza cu 4 (lungimea unei date de tip int). Se da inapoi pointerul cu 4 octeti pentru a putea inscrie noua valoare a contorului apeland metoda seek(pos). Se scrie in fisier valoarea continuta de cnt. Se inchid fisierele bazei de date. Se pozitioneaza flagul boolean sesiune in true. Acest flag este testat de metoda paint() ori de cate ori este apelata. Daca acesta are valoarea true metoda paint() va “redesena” informatiile despre user pe suprafata canvas-ului. In caz contrar nu deseneaza nimic. Dupa setarea flagului se apeleaza metoda repaint() care va determina executia metodei paint() si se face vizibil panoul 2 aducandul in varful stivei peste panoul 1. In continuare aplicatia va astepta actionarea butonului “Terminare”. La semnalarea acestui eveniment, flagul este readus pe pozitia false, panoul 2 este “ascuns” sub panoul 1 si se asteapta deschiderea unei noi sesiuni sau inregistrarea unui nou utilizator. 8. Proiectarea structurii aplicatiei In aceasta etapa se determina clasele de obiecte care sa implementeze interfetele grafice si algoritmii stabiliti la pasii anteriori. de asemenea trebuiesc stabilite interconexiunile functionale intre obiectele din clasele de mai sus care vor intra in componenta aplicatiei. Structura obtinuta a aplicatiei este redata in figura 21.1.

Fig.21.1 – structura aplicatiei RAFTest In diagrama de structura din aceasta figura sunt puse in evidenta legaturile dintre

Page 160: programare in Java

PROGRAMARE IN JAVA - Note de curs 257

obiecte componente ale aplicatiei apartinand unor clase a caror descriere este data in continuare:

Clasa principala RAFTest care contine metoda main() va fi derivata din clasa Frame. Metoda statica main() va crea fereastra aplicatiei RAFTest wnd=new RAFTest() la creere, constructorul clasei RAFTest() va seta textul din bara de titlu, va crea un manager de amplasare CardLayout mainLayout=new CardLayout(), il va seta ca manager de amplasare a ferestrei principale, va crea cele doua panouri Panel1 p1=new Panel1() si Panel2 p2=new Panel2(), le va adauga ferestrei, va redimensiona si va face vizibila fereastra. Pentru comanda managerului de amplasare este prevazuta metoda public void showPanel(int panelNr) care va aduce in varful stivei formate din cele doua panouri pe cel indicat de argument.

Clasa Panel1 defineste variabila membru Panel2 p2. Constructorul clasei Panel1 va initializa referinta p2 cu o valoare primita ca argument. Va crea etichetele lUsr, lPwd campurile text tUsr, tPsw, tMsg si butoanele, bOk, bRst, bNew, bQuit din figura 20.4. Campul text tMsg va fi setat read-only. Va fi creat managerul de amplasare GridBagLayout pLayout cu obiectul asociat GridbagConstraints gbc. Controalele vor fi adaugate la panou conform proiectului, folosind metoda addComponent() definita si utilizata in unele exemple anterioare. Va fi redefinita metoda action() astfel incat sa trateze evenimentele generate de butoane. Daca butonul actionat este bOk se va incepe procedura de autentificare a utizatorului descrisa la punctul 6. Daca autentificarea a reusit se sterg informatiile din campurile text ale panoului se se deschide sesiunea de lucru apeland metoda startSesiune()a clasei Panel2. Daca butonul actionat este bRst se sterg informatiile din campurile text ale panoului. Daca se actioneaza butonul bQuit se termina aplicatia. Daca butonul actionat este bNew se trece la procedura de inregistrare a unui user prin creerea unei fereastre de dialog NewUserDialog dlg= NewUserDialog ().

Clasa NewUserDialog este derivata din clasa Dialog. Constructorul clasei

NewUserDialog() va seta textul din bara de titlu a ferestrei, va dezactiva controalele ferestrei parinte, va crea etichetele lName, lUsr, lPsw1, lPsw2 campurile text tName, tUsr, tPsw1, tPsw2, tMsg si butoanele, bRst, bNew, bCancel din figura 20.5. Campul text tMsg va fi setat read-only. Va fi creat un manager de amplasare GridBagLayout dLayout cu obiectul asociat GridbagConstraints gbc. Controalele vor fi adaugate la panou conform proiectului, folosind metoda addComponent() definita si utilizata in unele exemple anterioare. Va fi redefinita metoda action() astfel incat sa trateze evenimentele generate de butoane. Daca butonul actionat este bOk se va apela metoda private boolean addUser() care implementeaza procedura de inregistrare a utizatorului descrisa la punctul 5. Metoda intoarce true daca inregistrarea a decurs corect sau false daca a esuat. Ea va inscrie in campul tMsg mesajul de eroare corespunzator. Daca metoda intoarce true controalele ferestrei parinte sunt reactivate iar fereastra de dialog este inchisa. Daca butonul actionat este bRst se sterg informatiile din campurile text ale panoului. Daca se actioneaza butonul bCancel controalele ferestrei parinte sunt reactivate iar fereastra de dialog este inchisa.

Clasa Panel2 defineste variabilele String usr, int cnt si boolean sesiune al carei rol

a fost discutat la punctul anterior. Constructorul clasei Panel2 va crea un obiect

Page 161: programare in Java

258 CURS 21

Display display, subclasa a clasei Canvas si un buton bExit. Cele doua componente vor fi amplasate de managerul implicit BorderLayout in centru si respectiv la sud. Va fi redefinita metoda action() astfel incat la actionarea butonului bExit va seta pe false valoarea variabilei membru sesiune va atribui valoarea null variabilei usr si valoarea 0 lui cnt comandand apoi managerul de amplasare al ferestrei parinte sa faca vizibil panoul 1. Clasa va prevedea metoda public void startSesiune(String usr, int cnt) care va seta variabilele usr , cnt si sesiune comandand apoi managerul de amplasare al ferestrei parinte sa faca vizibil panoul 2. comanda managerului de amplasare se face apeland metoda showPanel(int panelNr) a clasei parinte. Clasa mai prevede metodele boolean isSesiune() care intoarce valoarea flagului sesiune, String getUser() care intoarce stringul usr si int getCount() care intoarce valoarea lui cnt.

Clasa Display extends Canvas redefineste metoda paint() astfel aceasta ori de cate

ori este apelata sa testeze starea flagului sesiune a clasei parinte apeland metoda isSesiune() a acesteia. Daca valoarea intoarsa de aceasta este true metoda va “desena” pe suprafata componentei un mesaj de salut continand numele utilizatorului curent. Aceste informatii sunt obtinute prin apelul metodele getUser() si getCount() ale clasei parinte.

9. Implementare Mai jos este redat listingul complet al programului care implementeaza clasele proiectate la punctul precedent: import java.awt.*; import java.io.*; class RAFTest extends Frame{ private boolean canClose; private Panel1 p1; private Panel2 p2; private CardLayout mainLayout; public RAFTest(){//Constructorul clasei super("Random Access File Demo");// titlu fereastra canClose=true;//autorizare inchidere fereastra mainLayout=new CardLayout(); setLayout(mainLayout);// setare manager de amplasare // creare celor doua panouri si adaugarea lor la fereastra p2=new Panel2(); p1=new Panel1(p2); add("Panel 1",p1);add("Panel 2",p2); // dimensionarea si afisarea ferestrei resize(300,120); show(); } public boolean handleEvent(Event e){// procesarea evenimentelor if(e.id==e.WINDOW_DESTROY){// cerere sistem de inchidere // a fereastrei quit();// inchiderea fereastrei si terminarea aplicatiei return true; } return super.handleEvent(e);// procesarea celorlalte evenimente } public void quit(){//inchiderea fereastrei si terminarea // aplicatiei if(canClose){// daca terminarea aplicatiei este autorizata hide();// ascunde fereastra

Page 162: programare in Java

PROGRAMARE IN JAVA - Note de curs 259

dispose();//elibereaza resursele alocate ferestrei System.exit(0);// termina aplicatia } } public void closeEnable() {canClose=true;}// autorizare terminare public void closeDisable() {canClose=false;}// interzicere terminare public void showPanel(int nr){// aduce in varful stivei panoul nr. switch(nr){ case 1: mainLayout.first(this);//panoul 1 in varful stivei break; case 2: mainLayout.last(this);// panoul 2 in varful stivei break; } } public static void main(String[] args){ RAFTest win=new RAFTest();// creaza fereastra principala } } class Panel1 extends Panel{// Panoul numarul 1 private String name;// numele utilizatorului private int cnt;// numar de sesiuni de lucru efectuate private Panel2 p2;// referinta la panoul 2 private NewUserDialog dlg;// fereastra de dialog // Componentele panoului nr.1 // etichete private Label lUsr=new Label("User :"), lPsw=new Label("Password :"); // campuri text private TextField tUsr= new TextField(), tPsw= new TextField(), tMsg= new TextField(); // butoane private Button bOk= new Button("Ok"), bRst= new Button("Reset"), bNew= new Button("New"), bQuit = new Button("Quit"); // managerul de amplasare private GridBagLayout gl=new GridBagLayout(); private GridBagConstraints gc=new GridBagConstraints(); public Panel1(Panel2 p){// Constructorul panoului nr.1 // setari p2=p;// referinta panou nr.2 tMsg.setEditable(false);// campul de mesaje-> read-only tPsw.setEchoCharacter('*');// caracter de mascare parola setLayout(gl);// manager de amplasare // amplasare componente pe panoul nr.1 gc.weightx=0; gc.weighty=0; gc.anchor=gc.EAST; //Amplasarea etichetelor gc.fill=gc.NONE; addComponent(lUsr,gl,gc,0,0,1,1); addComponent(lPsw,gl,gc,1,0,1,1); //Amplasarea campurilor text gc.fill=gc.HORIZONTAL; gc.weightx=1; addComponent(tUsr,gl,gc,0,1,3,1); addComponent(tPsw,gl,gc,1,1,3,1);

Page 163: programare in Java

260 CURS 21 addComponent(tMsg,gl,gc,3,0,4,1); //Amplasarea butoanelor gc.weighty=1; gc.fill=gc.BOTH; addComponent(bOk,gl,gc,2,0,1,1); addComponent(bRst,gl,gc,2,1,1,1); addComponent(bNew,gl,gc,2,2,1,1); addComponent(bQuit,gl,gc,2,3,1,1); } /*******************************************************************\ * Metoda addComponent este folosita de constructor pentru adaugarea * * unei componente c la panou. Componenta va fi amplasata in celula * * din randul row si coloana col si va avea latimea width si inalti- * * mea hight celule. Ocuparea spatiului astfel alocat se face conform* * specificatiilor din obiectul GridBagConstraints – parametrul gc. * \*******************************************************************/ private void addComponent(Component c, GridBagLayout gl, GridBagConstraints gc, int row,int col, int width, int height){ gc.gridy=row; gc.gridx=col; gc.gridwidth=width; gc.gridheight=height; gl.setConstraints(c,gc); add(c); } // Procesarea evenimentelor generate la actionarea butoanelor public boolean action(Event e, Object o){ RAFTest parent=(RAFTest)getParent();// fereastra parinte if(e.target==bOk){// actionarea butonului “Ok” if(autentificareUser()){// Daca userul a fost acceptat parent.showPanel(2);// Adu in varf panoul nr. 2 p2.startSesiune(name, cnt);// Incepe sesiunea } }else if(e.target==bRst){ // actionarea butonului “Reset” tUsr.setText("");// sterge continutul campurilor tPsw.setText("");// user, parola si mesaje tMsg.setText(“”); }else if(e.target==bNew){ // actionarea butonului “New” disableComponents();// dezactiveaza toate componentele disable();// panoului si panoul in sine. dlg=new NewUserDialog(this);//creaza fereastra de dialog }else if(e.target==bQuit){ // actionarea butonului “Quit” parent.quit();// Solicita terminarea aplicatiei } return true; } /*******************************************************************\ * Metoda error este apelata de metoda de autentificare a utiliza- * * torului dupa numele de user si parola. In timpul acestei operatii * * pot apare diferite erori. Fiecarui tip de eroare i-a fost asociat * * un numar intreg err.Daca nu a aparut nici o eroare, err=0. Metoda * * error() afiseaza mesajul corespunzator tipului de eroare si retur-* * neaza true daca err=0 sau false daca r!=0. * \*******************************************************************/ private boolean error(int err){ // tablou de stringuri continand mesajele de eroare String msg[]={"", "Trebuie completat campul User!",

Page 164: programare in Java

PROGRAMARE IN JAVA - Note de curs 261

"User neinregistrat!", "Parola incorecta!", "Eroare la accesarea bazei de date"}; // afiseaza mesajul asociat erorii nr.err tMsg.setText(msg[err]); // Intoarce true daca err=0 sau false in caz contrar return (err==0? true:false); } /*******************************************************************\ * Metoda autentificareUser() citeste si valideaza numele de user * * introdus de utilizator acesta trebuind sa nu fie un string vid. * * Daca numele de user este valid se genereaza pe baza lui identifi- * * catorul usrID folosit drep cheie de cautare in fisierul cu aces * * aleator usr.ndx. Daca inregistrarea cu campul id=usrID este gasita* * valoarea din campul pos a acesteia este folosita pentru a accesa * * inregistrarea cu datele utilizatorului din fisierul usr.dat. Se * * cripteaza parola introdusa de utilizator in usrPsw si se compara * * cu stringul citit din fisierul usr.dat din campul parola in varia-* * bila psw. Daca stringurile sunt identice utilizatorul este accep- * * tat si metoda intoarce true. In caz contrar, sau daca pe parcursul* * executiei apar alte erori metoda intoarce false. * \*******************************************************************/ private boolean autentificareUser(){ int err=0,len,i; long usrID=0,id,pos; String psw,usr,usrPsw; byte b[]=new byte[8];; RandomAccessFile ndx,dat; usr=tUsr.getText(); if((len=usr.length())==0)return error(1); // calculeaza id-ul userului if(len<8){ usr.getBytes(0,len,b,0); for(i=len;i<8;i++)b[i]=0; }else usr.getBytes(0,7,b,0); for(i=0;i<8;i++){ usrID *=256; usrID +=b[i]; } // validare nume user try{ pos=0; // deschide fisierele usr.ndx si usr.dat ndx=new RandomAccessFile("usr.ndx","rw"); dat=new RandomAccessFile("usr.dat","rw"); // cauta inregistrarea cu campul id=usrID try{ while((id=ndx.readLong())!=usrID){ ndx.skipBytes(8); }// Daca s-a gasit, citeste campul pos pos=ndx.readLong(); }catch(EOFException e){err=2;}// EOF ->nu s-a gasit ndx.close();// inchide fisierul if(err!=0) { dat.close(); return error(err); } usrPsw=tPsw.getText();// citeste parola introdusa // Criptare parola b=new byte[usrPsw.length()]; usrPsw.getBytes(0,b.length,b,0);

Page 165: programare in Java

262 CURS 21

for(i=0;i<b.length;i++)b[i]+=(byte)(i%3+1); usrPsw=new String(b,0); // Citeste inregistrarea din fisierul usr.dat dat.seek(pos); name=dat.readUTF();// nume utilizator usr =dat.readUTF();// nume de user psw =dat.readUTF();// parola cnt =dat.readInt();// contorul de sesiuni cnt++;// actualizeaza contorul dat.seek(pos);// repozitioneaza pointerul in usr.dat // inscrie inregistrarea cu contorul actualizat dat.writeUTF(name); dat.writeUTF(usr); dat.writeUTF(psw); dat.writeInt(cnt); dat.close();// inchide fisierul usr.dat if(!usrPsw.equals(psw))err=3;// compara parolele }catch(IOException e){// eroare la accesarea fisierelor err=4; System.out.println(e.toString()); } return error(err); } /*******************************************************************\ * metodele enableComponent() si disableComponent() activeaza respec-* * tiv dezactiveaza campurile de editare si butoanele panoului nr.1 * \************************************** public void enableComponents(){ tUsr.enable(); tUsr.enable();

*****************************/

bOk.enable(); bRst.enable(); bNew.enable(); bQuit.enable(); } public void disableComponents(){ tUsr.disable(); tUsr.disable(); bOk.disable(); bRst.disable(); bNew.disable(); bQuit.disable(); } } class NewUserDialog extends Dialog{//fereastra de dialog private Panel1 panel;// referinta la panoul nr1 // creare etichete private Label lName=new Label("Name :"), lUsr=new Label("User :"), lPsw1=new Label("Password:"), lPsw2=new Label("Reenter :"); // creare campuri text private TextField tName= new TextField(), tUsr= new TextField(), tPsw1= new TextField(), tPsw2= new TextField(), tMsg= new TextField(); // creare butoane private Button bOk= new Button("Ok"), bRst= new Button("Reset"), bCancel = new Button("Cancel"); // creare manager de amplasare private GridBagLayout gl=new GridBagLayout(); private GridBagConstraints gc=new GridBagConstraints(); public NewUserDialog(Panel1 p){// Constructorul clasei // seteaza parintele, titlul si tipul ferestrei de dialog super((Frame)p.getParent(),"New Usr Account",true);

Page 166: programare in Java

PROGRAMARE IN JAVA - Note de curs 263

// interzice terminarea aplicatiei ((RAFTest)getParent()).closeDisable();

panel=p; tMsg.setEditable(false);// camp mesaje -> read-only

tPsw1.setEchoCharacter('*');// caracter mascare parola tPsw2.setEchoCharacter('*'); // caracter mascare parola setLayout(gl);// seteaza managerul de amplasare gc.weightx=0; gc.weighty=0; gc.anchor=gc.EAST; //Amplasarea etichetelor gc.fill=gc.NONE; addComponent(lName,gl,gc,0,0,1,1); addComponent(lUsr,gl,gc,0,3,1,1); addComponent(lPsw1,gl,gc,1,0,1,1); addComponent(lPsw2,gl,gc,1,3,1,1); //Amplasarea campurilor text gc.fill=gc.HORIZONTAL; gc.weightx=1; addComponent(tName,gl,gc,0,1,2,1); addComponent(tUsr,gl,gc,0,4,2,1); addComponent(tPsw1,gl,gc,1,1,2,1); addComponent(tPsw2,gl,gc,1,4,2,1); addComponent(tMsg,gl,gc,3,0,6,1); //Amplasarea butoanelor gc.weighty=1; gc.fill=gc.BOTH; addComponent(bOk,gl,gc,2,0,2,1); addComponent(bRst,gl,gc,2,2,2,1); addComponent(bCancel,gl,gc,2,4,2,1); resize (300,120); show(); } /*******************************************************************\ * Metoda addComponent este folosita de constructor pentru adaugarea * * unei componente c la panou. Componenta va fi amplasata in celula * * din randul row si coloana col si va avea latimea width si inalti- * * mea hight celule. Ocuparea spatiului astfel alocat se face conform* * specificatiilor din obiectul GridBagConstraints – parametrul gc. * \*******************************************************************/ private void addComponent(Component c, GridBagLayout gl, GridBagConstraints gc, int row,int col, int width, int height){ gc.gridy=row; gc.gridx=col; gc.gridwidth=width; gc.gridheight=height; gl.setConstraints(c,gc); add(c); }

// Procesarea evenimentelor generate la actionarea butoanelor public boolean action(Event e, Object o){ if(e.target==bOk){// actionare buton “Ok” // inregistrare user nou si terminare if(addUser())cancel(); }else if(e.target==bRst){ // actionare buton “Ok” reset();// stergerea tuturor campurilor text }else if(e.target==bCancel){ // actionare buton “Cancel” cancel();// terminare }

Page 167: programare in Java

264 CURS 21 return true; } private void cancel(){// procedura de terminare dialog panel.enableComponents();// reactiveaza componentele panel.enable(); // panoului 1 si panoul insusi // autorizare terminare aplicatie ((RAFTest)getParent()).closeEnable(); hide();// ascunde fereastra de dialog dispose();// elibereaza resursele alocate ferestrei } private void reset(){// sterge continutul campurilor text tName.setText(""); tUsr.setText(""); tPsw1.setText(""); tPsw2.setText(""); tMsg.setText(“”); } /*******************************************************************\ * Metoda error este apelata de metoda addUser() de inregistrare a * * utilizatorului in baza de date. In timpul acestei operatii pot * * apare diferite erori. Fiecarui tip de eroare i-a fost asociat un * * numar intreg err.Daca nu a aparut nici o eroare, err=0. Metoda * * error() afiseaza mesajul corespunzator tipului de eroare si retur-* * neaza true daca err=0 sau false daca r!=0. * \************************************** private boolean error(int err){

*****************************/

String msg[]={"", "Trebuie completat campul Nume!", "Eroare la introducerea parolei!", "Trebuie completat campul User!", "User existent. Alegeti alt nume de user!", "Eroare la accesarea bazei de date"}; tMsg.setText(msg[err]); return (err==0? true:false); } /*******************************************************************\ * Metoda addUser() implementeaza procedura de inregistrare a utili- * * zatorului in baza de date formata din fisierele usr.ndx si usr.dat* \*******************************************************************/ private boolean addUser(){ String name,usr,psw; int len,i; long usrID=0,id; int err=0; RandomAccessFile ndx,dat; byte b[]=new byte[8]; name=tName.getText();

// validarea numelui utilizatorului if(name.length()==0)return error(1);

// validare parola1 prin comparare cu parola2 psw=tPsw1.getText(); if(!psw.equals(tPsw2.getText()))return error(2); // validare nume user usr=tUsr.getText(); if((len=usr.length())==0)return error(3); // calculeaza id-ul userului if(len<8){ usr.getBytes(0,len,b,0); for(i=len;i<8;i++)b[i]=0; }else usr.getBytes(0,7,b,0); for(i=0;i<8;i++){usrID *=256; usrID +=b[i];} orul usrID exista // verificare daca userul cu identificat try{// deschide fisierele bazei de date ndx=new RandomAccessFile("usr.ndx","rw");

Page 168: programare in Java

PROGRAMARE IN JAVA - Note de curs 265

dat=new RandomAccessFile("usr.dat","rw"); try{ while(true){// citeste o inregistrare din usr.ndx id=ndx.readLong();// citeste id if(id==usrID){// daca user existent err=4;// eroare break;// terminare citire } ndx.skipBytes(8);// urmatoarea inregistrare } }catch(EOFException e){}// sfirsit de fisier if(err!=0) {// eroare la inregistrare user ndx.close();// inchide fisierul usr.ndx dat.close();//inchide fisierul usr.dat return error(err);// intoarce false } // Criptare parola b=new byte[psw.length()]; psw.getBytes(0,b.length,b,0); for(i=0;i<b.length;i++)b[i]+=(byte)(i%3+1); psw=new String(b,0); // inregistreaza user in fisierul usr.ndx ndx.writeLong(usrID);// inscrie identificatorul long pos=dat.length();//calculeaza pozitia in usr.dat ndx.writeLong(pos);// inscrie pointerul in usr.dat ndx.close();// inchide fisierul index usr.ndx dat.seek(pos);// pozitioneaza pointerul in usr.dat dat.writeUTF(name);// inscrie numele utilizatorului dat.writeUTF(usr);// inscrie numele de user dat.writeUTF(psw);// inscrie parola dat.writeInt(0);//inscrie contorul de sesiuni = 0 dat.close();// inchide fisierul usr.dat }catch(IOException e){// eroare la accesarea fisierelor err=5; System.out.println(e.toString()); } return error(err); } public boolean handleEvent(Event e){//procesarea evenimentelor if(e.id==e.WINDOW_DESTROY){// cerere sistem de inchidere cancel();// terminare dialog return true; } return super.handleEvent(e);// tratarea altor evenimente } } // panoul numarul 2 class Panel2 extends Panel{ private boolean sesiune=false; private String name; private int cnt; // crearea componentelor panoului nr.2 private Display dsp=new Display(this);// componenta display private Button bE public Panel2(){

xit=new Button("Exit");// butonul “Exit”

setLayout(new BorderLayout());// managerul de amplasare add("Center",dsp); // adaugare componente add("South",bExit); } /*******************************************************************\ * Metoda startSesiune() initiaza operatiile de incepere a unei sesi-*

Page 169: programare in Java

266 CURS 21

* uni de lucru a utilizatorului autentificat curent * \*******************************************************************/ public void startSesiune(String name,int cnt){ sesiune=true;this.name=name; this.cnt=cnt;repaint(); } // Interfata cu display-ul public boolean isSesiune(){return sesiune;}

public String getUser(){ return name; } public int getCount(){ return cnt;} public boolean action(Event e, Object o){ sesiune=false; ((RAFTest) getParent()).showPanel(1); return true; } } class Display extends Canvas{ Panel2 p; public Display(Panel2 p2){ p=p2; setBackground(Color.white); } public void paint(Graphics g){ if(p.isSesiune()){ Dimension d=size(); g.drawString("Iti multumesc "+p.getUser(), 0,d.height/2-20); g.drawString("ca apelezi a "+p.getCount()+"-a oara ", 0,d.height/2); g.drawString("la serviciile mele!", 0,d.height/2+20); } } } In figura 21.2 este prezentata interfata grafica afisata de aplicatie in diferite etape ale executiei.

Fig.21.2 Interfata grafica a programului RAFTest.class

Page 170: programare in Java

PROGRAMARE IN JAVA - Note de curs 267

Curs 22

Animatie In cadrul cursurilor 12 si 13 am discutat aspecte legate de desenarea pe suprafata unui control derivat din clasa Canvas a primitivelor grafice, al textului si a unor imagini incarcate din fisiere in format GIF sau JPG. In exemplele din acele cursuri desenarea era executata de metoda callback paint() ori de cate ori era necesara “reamprospatarea” ferestrei . Alterarea totala sau partiala a imaginii ferestrei pe display era sesizata de sistemul de operare ( care gestioneaza sistemul de ferestre) si acesta ere cel care determina executia metodei paint() ori de cate ori considera ca este necesar. Ca urmare imaginea desenata era statica (cel mult redimensionata in cazul modificarii dimensiunii “canvasului” de catre managerul de amplasare) modificandu-se doar ocazional la redimensionarea ferestrei. Animatia consta in reprezentarea succesiva a unor cadre, fiecare cadru continand o alta imagine (vezi figura 22.1).

Fig.22.1 Succesiunea de cadre a unui desen animat Daca succedarea cadrelor are loc la intervale de timp suficient de mici astfel incat ochiul uman sa nu perceapa pauza dintre cadre, este creata senzatia de miscare – animatia imaginii. Animatia presupune redesenarea periodica a imaginii, la fiecare redesenare fiind “pus pe tapet” un cadru nou, diferit de cel precedent. Am vazut ca metoda paint() poate fi initiata nu numai de sistemul de operare ci si de aplicatie prin apelul metodei repaint(). In acest fel un thread al aplicatiei poate modifica periodic un set de date folosite de metoda paint() la desenarea imaginii, dupa care sa solicite redesenarea acesteia pentru noile date prin apelul metodei repaint(). In acest fel, imaginile afisate se vor modifica periodic realizand astfel animatia. Sa analizam urmatorul exemplu care deplaseaza o discheta in fereastra aplicatiei: import java.awt.*; class Play extends Frame{ private int rataRefresh=100; private Film film=new Film(); public Play(){ super("Demo Animatie"); film.setBackground(Color.white); add("Center",film); resize(200,200);

Page 171: programare in Java

268 CURS 22 show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public static void main(String[] args){ Play win=new Play(); if(args.length!=0) win.rataRefresh=Integer.parseInt(args[0]); for(;;){ try{ Thread.sleep(win.rataRefresh); } catch(InterruptedException e){} win.film.cadruNou(); } } } class Film extends Canvas{ static final byte UP=0; static final byte DOWN=1; static final byte LEFT=2; static final byte RIGHT=3; static final byte WIDTH=80; static final byte HEIGHT=80; static final byte STEP=2; private boolean firstTime=true; private int locx,locy; private byte state=UP; public void paint(Graphics g){ if(firstTime){ locx=(size().width-WIDTH)/2; locy=(size().height-HEIGHT)/2; firstTime=false; } String s="MICRO "; int x[]=new int[6]; int y[]=new int[6]; x[0]=0+locx;x[1]=80+locx;x[2]=80+locx; x[3]=10+locx;x[4]=0+locx;x[5]=0+locx; y[0]=0+locy;y[1]=0+locy;y[2]=80+locy; y[3]=80+locy;y[4]=70+locy;y[5]=0+locy; g.setColor(Color.gray); g.fillPolygon(x,y,6); g.setColor(Color.black); g.drawPolygon(x,y,6); g.setColor(Color.white); g.fillRoundRect(10+locx,0+locy,60,45,10,10); g.setColor(Color.black); g.drawRoundRect(10+locx,0+locy,60,45,10,10); g.draw3DRect(72+locx,2+locy,6,6,false); g.setColor(Color.darkGray); g.fillRect(25+locx,50+locy,45,30); g.setColor(Color.black); g.drawRect(25+locx,50+locy,45,30);

Page 172: programare in Java

PROGRAMARE IN JAVA - Note de curs 269

g.setColor(Color.lightGray); g.fillRect(20+locx,50+locy,40,30); g.setColor(Color.black); g.drawRect(20+locx,50+locy,40,30); g.setColor(Color.darkGray); g.fillRect(30+locx,52+locy,7,25); g.setColor(Color.black); g.drawRect(30+locx,52+locy,7,25); g.drawString(s,25+locx,15+locy); } public void cadruNou(){ Dimension d=size(); switch(state){ case DOWN: locy+=STEP; if(locy > (d.height-HEIGHT))state=UP; break; case UP: locy-=STEP; if(locy < 0)state=RIGHT; break; case RIGHT: locx+=STEP; if(locx > (d.width-WIDTH))state=LEFT; break; case LEFT: locx-=STEP; if(locx < 0)state=DOWN; break; } repaint(); } } In figura 22.2 sunt redate cateva imagini ale ferestrei aplicatiei la momente diferite de timp.

Fig.22.2 – Imagini la diferite momente de timp ale ferestrei aplicatiei Play.class

Metoda paint() a clasei Film deseneaza discheta din figura pe o componenta Canvas, aceasta fiind amplasata in centrul ferestrei si redimensionata de managerul de

Page 173: programare in Java

270 CURS 22 amplasare BorderLayout astfel incat sa ocupe intreaga ferestra. Discheta este desenata cu coltul din stanga sus in punctul de coordonate (locx,locy) ale ferestrei. La prima desenare, aceste coordonate pastrate in variabilele locx si locy sunt stabilite astfel incat discheta sa fie desenata in centrul canvas-ului iar directia de deplasare a acesteia este stabilita in sus (UP). In cadrul metodei main() a aplicatiei este prevazut un ciclu infinit for(;;) care la fiecare iteratie apeleaza metoda cadruNou() a clasei Film. Aceasta metoda in functie de directia de deplasare curenta (UP, DOWN, RIGHT, LEFT – constante definite in clasa Film) data de variabila statei incrementeaza/decrementeaza fie pe locx fie pe locy cu pasul constant STEP = 2. Daca locx sau locy ating valori care ar face ca la desenarea dischetei aceasta sa iasa din limitele canvas-ului ( ale carei dimensiuni se afla apeland metoda size()), directia de deplasare este schimbata. Dupa stabilirea noilor coordonate sau a directiei, este apelata metoda repaint() care la randul sau va determina executia metodei paint(). Aceasta va reface desenul dischetei de data aceasta cu coltul din stanga sus intr-un punct diferit de desenarea precedenta, imaginea dischetei aparand translatata pe orizontala sau pe verticala (in functie de directia de deplasare) cu STEP pixeli. Pentru ca animatia sa nu fie prea rapida, se introduce dupa apelul metodei repaint() o temporizare prin dezactivarea threadului principal pe o perioada de timp specificata de variabila rataRefresh. Dezactivarea threadului principal se face apeland metoda Thred.sleep(). Aceasta rata de “reinprospatare” a desenului are valoarea implicita 100 ms dar poate fi schimbata specificand in linia de comanda un alt timp de refresh ( o valoare intreaga reprezentand temporizarea in ms). De exemplu daca programul se lanseaza in executie cu comanda C:\>java Play 500, rata de refresh va fi setata la 0,5 secunde, discheta “miscandu-se” de 5 ori mai incet decat inainte.

Bucle de imagini Am vazut ca pe o componenta derivata din clasa Canvas se poate desena si o imagine continuta intr-un fisier. Mai multe astfel de imagini (cum ar fi cele din figura 22.1) pot fi afisate succesiv, in bucla realizandu-se astfel animatia acestora. Dupa ce si ulima imagine a fost desenata, se reia succesiunea cu desenarea primei imagini. Programul urmator exemplifica implementarea unei astfel de bucle de imagini. import java.awt.*; class Play extends Frame{ private int rataRefresh=100; private Film film=new Film(); private Button b=new Button("Start"); public Play(){ super("Demo Bucla de Imagini"); add("Center",film); add("South",b); resize(200,200); setResizable(false); show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); }

Page 174: programare in Java

PROGRAMARE IN JAVA - Note de curs 271

public boolean action(Event e, Object o){ if(film.isRunning()){ b.setLabel("Start"); film.stop(); }else{ b.setLabel("Stop"); film.start(); } return true; } public static void main(String[] args){ Play win=new Play(); if(args.length!=0) win.rataRefresh=Integer.parseInt(args[0]); for(;;){ try{ win.film.repaint(); Thread.sleep(win.rataRefresh); } catch(InterruptedException e){} } } } class Film extends Canvas{ private static final int WIDTH=98; private static final int HEIGHT=98; private boolean state=false; private Image img[]=new Image[15]; private int cadru=0; public Film(){ setBackground(Color.black); for(int i=0;i<15;i++){ String imageFile="exp"+(i+1)+".gif"; img[i]=Toolkit.getDefaultToolkit().getImage(imageFile); } } public boolean isRunning(){ return state;} public void start(){ state=true;} public void stop(){ state=false;} public void paint(Graphics g){ int i=0; Dimension d=size(); if(state){ if(cadru >=0 && cadru <3)i=0; else if(cadru >=4 && cadru <12)i=cadru-3; else if(cadru >=12 && cadru <16)i=9; else if(cadru >=16 && cadru <20)i=cadru-6; else if(cadru >=20 && cadru <26)i=14; g.drawImage(img[i], (d.width-WIDTH)/2,(d.height-HEIGHT)/2,this); cadru++; cadru%=26; } } } Programul este foarte asemanator cu cel anterior, cu exceptia faptului ca de aceasta data in loc sa traseze primitive grafice, programul afiseaza imagini preluate din fisiere in format GIF. - Aplicatia incarca imaginile din fisierele exp1.gif ...exp15.gif in tabloul Image

img[] de 15 elemente

Page 175: programare in Java

272 CURS 22 Programul afiseaza 26 de cadre astfel: - cadrele 0-3 – imaginea img[0] - cadrele 4-11 – imaginile img[2..9] - cadrele 12-15 – imaginea img[10] - cadrele 16-19 – imaginile img[11..13] - cadrele 20-25 – imaginea img[14] In figura 22.2 sunt redate cateva imagini ale ferestrei aplicatiei la momente diferite de timp.

Fig.22.2 – Imagini la diferite momente de timp ale ferestrei aplicatiei Play.class

La executia acestui program se constata un fenomen aparent ciudat – in primele cicluri de executie, aplicatia deseneaza numai o parte din imaginile buclei, sarind peste unele imagini si afisand in locul lor “pete” albe. Acest fenomen se explica prin faptul ca pentru incarcarea fiecarei imagini din fisier, metoda loadImage() lanseaza cate un thread separat. Astfel incat unele din aceste threaduri nu apuca sa incarce imaginea in timp ce threadul principal deja apeleaza metoda repaint() care determina threadul AWT care gestioneaza interfata grafica sa execute metoda update() care dupa ce sterge suprafata de desenare cu culoarea de fundal apeleaza metoda paint() pentru desenarea propriuzisa. Aici el afiseaza imaginile referite de elementele tabloului img. Unele dintre aceste elemente refera imagini deja incarcate dar altele inca au valoarea null si nu refera nici o imagine deoarece threadul care o incarca din fisier nu a terminat sau nici nu a inceput inca aceasta operatie. In consecinta metoda g.drawImage() nu va “desena” nimic pe suprafata “canvas”-ului aceasta ramanand alba pana nu se va desena o imagine incarcata deja. Dupa incarcarea imaginii din fisier threadul respectiv este distrus. “Petele” albe care apar intre imagini se datoreaza deci “stergerii” de catre metoda update() a suprafetei de desenare cu culoarea selectata pentru fundal (background). Dupa ce a efectuat aceasta operatie, metoda update(), apeleaza metoda paint(). Aceasta ne indreptateste sa modificam programul anterior astfel: import java.awt.*; class Play extends Frame{ private Film film=new Film();

Page 176: programare in Java

PROGRAMARE IN JAVA - Note de curs 273

private Button b=new Button("Start"); public Play(){ super("Image Loop"); add("Center",film); add("South",b); resize(160,160); setResizable(false); show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public boolean action(Event e, Object o){ if(film.isRunning()){ b.setLabel("Start"); film.stop(); }else{ b.setLabel("Stop"); film.start(); } return true; } public static void main(String[] args){ Play win=new Play(); } } class Film extends Canvas{ private static final int WIDTH=98; private static final int HEIGHT=98; private boolean state=false; private Image img[]=new Image[26]; private int cadru=0; public Film(){ setBackground(Color.black); for(int i=0;i<15;i++){ String imageFile="exp"+(i+1)+".gif"; img[i]=Toolkit.getDefaultToolkit().getImage(imageFile); } } public boolean isRunning(){ return state;} public void start(){ state=true;} public void stop(){ state=false;} public void paint(Graphics g){ int i=0; Dimension d=size(); if(state){ if(cadru >=0 && cadru <3)i=0; else if(cadru >=4 && cadru <12)i=cadru-3; else if(cadru >=12 && cadru <16)i=9; else if(cadru >=16 && cadru <20)i=cadru-6; else if(cadru >=20 && cadru <26)i=14; g.drawImage(img[i], (d.width-WIDTH)/2,(d.height-HEIGHT)/2,this); cadru++; cadru%=26;

Page 177: programare in Java

274 CURS 22 } try{ Thread.sleep(300); } catch(InterruptedException e){} repaint(); } } In aceasta versiune, metoda paint() apeleaza la sfarsitul sau metoda repaint(). Aceasta va apela metoda update() care va sterge “canvas”-ul si va apela metoda paint() care la randul sau va apela metoda repaint() etc. Aparent se formeaza un lant de apeluri infinit care in final va umple stiva si va bloca executia aplicatiei. In realitate threadul AWT care executa metoda paint() va apela numai metoda repaint(), lantul de apeluri oprindu-se aici. Aceasta metoda nu apeleaza direct metoda update() ci produce si transmite sistemului de operare un mesaj-cerere de reimprospatare a ferestrei aplicatiei revenind apoi in repaint() de unde va reveni inapoi in paint() si de aici in update(). Sistemul de operare care gestioneaza mesajele, va plasa mesajul in coada de mesaje a aplicatiei. La terminarea executiei metodei update(), threadul AWT va intra intr-o bucla de preluare a mesajelor primite de la sistemul de operare. Mesajele din coada sunt de fapt notificari ale sistemului de operare asupra evenimentelor privitoare la fereastra aplicatiei – redimensionare, alterarea unei parti din fereastra prin acoperirea de o alta ereastra, minimizare/maximizare, cerere de inchidere, miscare mouse, actionare controale, apasare taste, etc. Daca coada de mesaje este goala, threadul va astepta sosirea unui mesaj. Daca contine mesaje, va extrage si va prelucra primul mesaj, va stabili despre ce eveniment este vorba si va incepe tratarea acestuia. Pana la urma, el va extrage din coada de mesaje si mesajul-cerere de redesenare generatsi expediat tot de el. In consecinta, va incepe executia metodei update(). Procesul se reia astfel la infinit, fara a incarca insa stiva threadului cu adrese de revenire. Aceasta are insa si o consecinta neplacuta. Daca in momentul in care threadul AWT incepe desenarea, utilizatorul actioneaza butonul sistem de terminare a executiei, sistemul de operare va plasa imediat mesajul de notificare corespunzator in coada de mesaje. Intre timp threadul WT este ocupat in continuare cu desenarea unei imagini. Dupa desenare, se dezactiveaza pentru 300 de ms, dupa care se “trezeste” si incepe executia metodei repaint() din care va expedia un mesaj-cerere de initiere a executiei metodei update(). Dupa expedierea mesajului-cerere de redesenare, revine in repaint() din care revine in paint() si inapoi in update(). La revenirea din update(), threadul preia un mesaj din coada de mesaje. Intrucit mesajul de terminare a fost plasat de sistem in coada de mesaje inaintea mesajului cerere de redesenare, el va fi extras inaintea celuilalt, generat si transmis si deci si sosit mai tarziu. Threadul AWT va apela metoda handleEvent() transmitandu-i acest mesaj ca eveniment WINDOW_DESTROY. Metoda handleEvent() va trata acest eveniment inchizand fereastra, eliberand resursele alocate si terminand in final executia aplicatiei. Problema este ca raspunsul la comanda a venit cu o intarziere de peste 300 ms. Daca rata de refresh ar fi fost mai mare, si intarzierea ar fi fost mai mare programul devenind incomod de folosit.

Clasa MediaTracker Pentru a elimina fenomenul neplacut de la inceputul executiei buclei de imagini, cand desenarea cadrelor incepe inainte de a se incarca toate imaginile, poate fi folosita urmatoarea tehnica. Aplicatia este blocata pana cand nu au fost incarcate toate

Page 178: programare in Java

PROGRAMARE IN JAVA - Note de curs 275

imaginile din fisiere. Monitorizarea incarcarii imaginilor din fisiere se face folosind un obiect din clasa MediaTracker. Un astfel de obiect se creaza cu constructorul:

MediaTracker(ImageObserver component) Argumentul constructorului este un obiect tinta al afisarii imaginii apartinand de exemplu unei subclase a clasei Canvas. De exemplu in constructorul clasei Film se poate declara variabila: MediaTracker imageTracker=new MediaTracker(this); Dupa ce s-au facut toate apelurile getImage(), se inregistreaza imaginile in curs de incarcare cu metoda addImage() definita in clasa MediaTracker: imageTracker.addImage(img[i],i); unde i este identificatorul folosit de pentru inregistrarea obiectului(nu este obligatoriu sa fie neaparat i, putand fi o valoare intreaga distincta). Acest identificator va fi folosit pentru a determina aplicatia sa astepte pana cand imaginea inregistrata cu acest numar va fi citita integral din fisier. De exemplu pentru a se astepta incrcarea completa a imaginii inregistrate sub identificatorul 0, se va apela metoda waitForID(): imageTracker.waitForID(0); Acesta apel blocheaza programul a carui executie va fi reluata numai dupa ce incarcarea imaginii cu identificatorul 0 din fisierului va fi terminata. Clasa MediaTracker mai prevede metoda waitForAll() care blocheaza aplicatia pana ce toate imaginile vor fi incarcate. Ambele metode “arunca” o exceptie InterruptedException care trebuie prinsa sau aruncata mai departe de catre metoda in cadrul careia se face apelul. Pentru a nu bloca aplicatia pe perioada incarcarii unei imagini, in acest rastimp ea putand sa faca alte prelucrari, se foloseste metoda clasei MediaTracker , checkID(). Aceasta metoda are doi parametri: boolean checkID(int id, boolean b) – intoarce true daca incarcarea imaginii inregistrate sub identificatorul id s-a terminat si false daca nu a inceput inca sau inca nu s-a terminat. Al doilea parametru b daca are valoarea true forteaza inceperea imediata a incarcarii imaginii din fisier daca aceasta operatie inca nu a inceput. Vom folosi in continuare metoda waitAll() pentru a elimina afisarea a numai unora dintre imagini in primele cicluri ale buclei din programul anterior. Modificarile afecteaza doar constructorul clasei Film astfel: ... public Film(){ setBackground(Color.black); MediaTracker imageTracker=new MediaTracker(this); for(int i=0;i<15;i++){ String imageFile="exp"+(i+1)+".gif"; img[i]=Toolkit.getDefaultToolkit().getImage(imageFile); imageTracker.addImage(img[i],i); }

try{ imageTracker.waitForAll(); } catch(InterruptedException e){} } ...

Dubla bufferizare la desenare Desenarea unei imagini pe suprafata componentei nu se face instantaneu. Din acest motiv, apare o “palpaire” deranjanta a imaginii animate. Solutia consta in realizarea desenului intr-un buffer – obiect al clasei Image. Crearea bufferului se face apeland

Page 179: programare in Java

276 CURS 22 metoda clasei Component, createImage(int latimeImagine, int inaltimeImagine). Aceasta aloca memoria necesara si intoarce referinta la un obiect Image. Odata terminata desenarea, imaginea din buffer este pusa “dintr-un foc” (asa cum sunt desenate imaginile preluate din fisiere) pe suprafata componentei, eliminandu-se astfel palpairea. In plus acest mecanism permite realizarea desenului de catre o alta metoda decat metoda paint() deoarece nu mai suntem legati de contextul grafic de desenare Graphics g transmis acesteia ca parametru. Desenarea in buffer se face folosind contextul grafic al acestuia obtinut prin apelul metodei clasei Image, getGraphics(). Acest context grafic poate fi folosit pentru realizarea desenului in buffer, apeland primitivele grafice cunoscute. In acest fel, imaginile ce urmeaza a fi afisate de metoda paint() pot fi pregatite din timp. Aplicatia urmatoare, este o versiune a primului program din acest curs (fig.22.2) si exemplifica utilizarea acestei tehnici. Modificarile vizeaza numai clasa Film astfel: class Film extends Canvas{ ... private Image diskImage;// se adauga declaratia bufferului // se adauga metoda de desenare in buffer a dischetei public void createDiskImage(){ String s="MICRO "; int x[]={0,80,80,10,0,0}; int y[]={0,0,80,80,70,0}; diskImage=createImage(WIDTH,HEIGHT);// crearea bufferului Graphics g=diskImage.getGraphics();// obtinerea contextului g.setColor(Color.white); g.fillRect(0,0,WIDTH,HEIGHT); // desenarea fundalului imaginii g.setColor(Color.gray); g.fillPolygon(x,y,6); g.setColor(Color.black); g.drawPolygon(x,y,6); g.setColor(Color.white); g.fillRoundRect(10,0,60,45,10,10); g.setColor(Color.black); g.drawRoundRect(10,0,60,45,10,10); g.draw3DRect(72,2,6,6,false); g.setColor(Color.darkGray); g.fillRect(25,50,45,30); g.setColor(Color.black); g.drawRect(25,50,45,30); g.setColor(Color.lightGray); g.fillRect(20,50,40,30); g.setColor(Color.black); g.drawRect(20,50,40,30); g.setColor(Color.darkGray); g.fillRect(30,52,7,25); g.setColor(Color.black); g.drawRect(30,52,7,25); g.drawString(s,25,15); } //se modifica metoda paint, acesteia revenindu-i acum rolul doar //de a comanda crearea si desenarea imaginii in buffer ( numai la //prima executie) si apoi afisarea acesteia in coordonatele //locx,locy modificate la fiecare cadru nou. public void paint(Graphics g){ if(firstTime){// initializari createDiskImage();// creaza imaginea locx=(size().width-WIDTH)/2; locy=(size().height-HEIGHT)/2; firstTime=false;

Page 180: programare in Java

PROGRAMARE IN JAVA - Note de curs 277

} g.drawImage(diskImage,locx,locy,this);// deseneaza imaginea } ... }

Supradefinirea metodei update() O alta cauza a “palpairii” imaginii este “stergerea” suprafetei de desenare cu culoarea de fundal de catre metoda update(), inainte ca aceasta sa invoce metoda paint(). Desi stergerea se face foarte rapid, iar desenarea imaginii este si ea foarte rapida, din momentul din care s-a produs stergerea pana in momentul in care threadul AWT va executa metoda paint() poate sa treaca un timp indelungat (zecimi de secunda). Intarzierile nu sunt permanente, fiind cauzate de evenimente in care sistemul de operare este solicitat cu executia unor threaduri de prioritate mai mare. In acest timp threadul AWT este suspendat si deci suprafata de desenare ramane in perioada respectiva de timp stearsa. Aceasta pauza intre doua desenari succesive, daca este prea lunga, este sezizata de ochiul uman ca “palpaire”. Ea poate fi evitata supradefinind metoda clasei Canvas update() astfel incat aceasta sa apeleze metoda paint() direct, fara a mai efectua stergerea suprafetei de desenare: public void update(Graphics g){paint(g);} In acest fel suprafata de afisare nu mai este stearsa si deci nu va mai aparea palpairea. Stergerea imaginii precedente se va face in buffer cu metoda: g.setColor(<culoare fundal>); g.drawRect(0,0,<latimea imaginii>,<inaltimea imaginii>); ... desenarea imaginii .. Pentru a elimina si aceasta cauza a “palpairii” datorate sistemului, in programul precedent vom aduce urmatoarele modificari metodelor createDiskImage(), update() si paint(): class Film extends Canvas{ ... private Image diskImage; private boolean firstTime=true; private int locx,locy; private Graphics g;// contextul grafic al bufferului ... public void createDiskImage(){ Dimension d=size(); String s="MICRO "; int x[]={locx,80+locx,80+locx,10+locx,locx,locx}; int y[]={locy,locy,80+locy,80+locy,70+locy,locy}; g.setColor(Color.white); g.fillRect(0,0,d.width,d.height); g.setColor(Color.gray); g.fillPolygon(x,y,6); g.setColor(Color.black); g.drawPolygon(x,y,6); g.setColor(Color.white); g.fillRoundRect(10+locx,locy,60,45,10,10); g.setColor(Color.black); g.drawRoundRect(10+locx,locy,60,45,10,10); g.draw3DRect(72+locx,2+locy,6,6,false); g.setColor(Color.darkGray); g.fillRect(25+locx,50+locy,45,30); g.setColor(Color.black); g.drawRect(25+locx,50+locy,45,30);

Page 181: programare in Java

278 CURS 22

g.setColor(Color.lightGray); g.fillRect(20+locx,50+locy,40,30); g.setColor(Color.black); g.drawRect(20+locx,50+locy,40,30); g.setColor(Color.darkGray); g.fillRect(30+locx,52+locy,7,25); g.setColor(Color.black); g.drawRect(30+locx,52+locy,7,25); g.drawString(s,25+locx,15+locy); } public void paint(Graphics gContext){ if(firstTime){ Dimension d=size(); locx=(d.width-WIDTH)/2; locy=(d.height-HEIGHT)/2; diskImage=createImage(d.width,d.height); g=diskImage.getGraphics(); firstTime=false; } createDiskImage(); gContext.drawImage(diskImage,0,0,this); } public void update(Graphics g){paint(g);} ... }

Page 182: programare in Java

PROGRAMARE IN JAVA - Note de curs 279

Curs 23

Trasarea graficelor in planul real Reproducerea pe suprafata unei componente a unei portiuni din spatiul bidimensional real este foarte asemanatoare cu realizarea unei fotografii. Din intreg spatiul, aparatul va surprinde doar cadrul vizat de obiectiv. Imaginea reala din acest cadru va fi scalata de obiectiv si reprodusa la scara pe suprafata filmului prin impresionarea unor granule fotosensibile cu care este acoperita aceasta suprafata. Cu cat granulatia materialului fotosensibil care acopera suprafata filmului este mai fina cu atat rezolutia imaginii obtinute este mai buna. In cazul suprafetei componentei pixelii sunt echivalentii granulelor fotosensibile care acopera suprafata filmului. Spatiul real bidimensional se intinde de la plus la minus infinit. Pentru a trasa un grafic in acest spatiu vom alege un punct O ca origine a sistemului de coordonate astfel incat orice punct de pe suprafata plana va putea fi reprezentat in sistemul de coordonate ales printr-o pereche de numere reale (x,y). Originea sistemului cartezian de coordonate xOy are coordonatele (0,0). Din intreg planul infinit pe care s-a realizat graficul, nu putem reproduce decat o portiune finita pe care o incadram intr-o fereastra dreptunghiulara de latime w.width si inaltime w.height ca in figura 23.1.

Fig.23.1 – Sisteme de coordonate

Aceasta fereastra–cadru va avea propriul sau sistem de coordonate x’Oy’ avand originea amplasata in punctul de coordonate (x0,y0). Un punct de coordonate (x,y) in sistemul xOy va avea in sistemul x’Oy’ coordonatele (x-x0,y-y0). Toate punctele graficului trasat in planul xOy incadrate de fereastra-cadru ar trebui reproduse pe suprafata imaginii. Aici apar insa probleme. Prima problema consta in dimensiunile diferite ale ferestrei – cadru care are dimensiunile (w.width, w.height) si a imaginii cu dimensiunile (img.width, img.height). Aceasta impune introducerea unor coeficienti de scalare a coordonatelor tuturor punctelor reproduse. Astfel factorii de scalare pe abscisa si pe ordonata vor fi:

Page 183: programare in Java

280 CURS 23

heightimgheightimgk

widthimgwidthimgk

y

x

.

...

=

=

A doua problema este sistemul de coordonate x”Oy” al imaginii a carei ordonata este in oglinda fata de sistemul de coordonate x’Oy’ al ferestrei-cadru. aceasta ar face ca reprezentarea graficului din fereastra cadru pe suprafata imaginii sa apara si el in oglinda (rasturnat). Pentru ca acest lucru sa nu se intample, ordonatelor y’ punctelor proiectate li se va aplica dupa scalare si o translformare corectiva. Formulele de calcul ale coordonatelor punctelor proiectate pe suprafata imaginii vor fi astfel:

y

x

kyheightimgykxx

⋅′−=′′⋅′=′′

.A treia problema este ca valorile coordonatelor in fereastra cadru sunt numere reale iar ale coordonatelor imagine sunt numere intregi. deci coordonatele x” si y” calculate cu formulele de mai sus vor fi convertite in valori intregi. In concluzie, odata stabilite dimensiunile ferestrei-cadru si ale imaginii se pot calcula factorii de scalare pe abscisa si pe ordonata. Cunoscind si coordonatele (x0,y0) in planul xOy ale coltului din stanga jos al ferestrei-cadru vom deduce formulele de transformare ale coordonatelor (x,y) unui punct din planul xOy in coordonate imagine (x”,y”):

( )( )y

x

kyyheightimgykxxx

⋅−−=′′⋅−=′′

)((int).)((int)

0

0

Sa construim acum o clasa RealGraphics care joaca rolul aparatului fotografic realizand aceasta transformare. De asemenea el va implementa functiile de desenare in planul real al primitivelor grafice. import java.awt.*; class RealGraphics{ private double xo,yo,kx,ky; private int w,h; private Graphics g; private Image img; public RealGraphics(double x0,double y0, double wW,double wH, Component picture, int iW,int iH) { xo=x0;yo=y0; kx=iW/wW;ky=iH/wH; w=iW;h=iH; img=picture.createImage(iW,iH); g=img.getGraphics(); } public Image getImage(){return img;} public void dispose(){ img=null; System.gc(); } public void clear(){ g.clearRect(0,0,w,h);} public void clearRect(double x, double y,

Page 184: programare in Java

PROGRAMARE IN JAVA - Note de curs 281

double width, double height) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.clearRect(ix,iy,iw,ih); } public void copyArea(double x, double y, double width, double height, double dx,double dy) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky), idx=(int)(dx*kx), idy=(int)(dy*ky); g.copyArea(ix,iy,iw,ih,idx,idy); } public void draw3DRect(double x, double y, double width, double height, boolean raised) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.draw3DRect(ix,iy,iw,ih,raised); } public void drawArc(double x, double y, double width, double height, int startAngle,int arcAngle) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.drawArc(ix,iy,iw,ih,startAngle,arcAngle); } public void drawLine(double x1, double y1, double x2, double y2) { int ix1=(int)((x1-xo)*kx), iy1=h-(int)((y1-yo)*ky), ix2=(int)((x2-xo)*kx), iy2=h-(int)((y2-yo)*ky); g.drawLine(ix1,iy1,ix2,iy2); } public void drawOval(double x, double y, double width, double height) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.drawOval(ix,iy,iw,ih); } public void drawRect(double x, double y, double width, double height)

Page 185: programare in Java

282 CURS 23 { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.drawRect(ix,iy,iw,ih); } public void drawRoundRect(double x, double y, double width, double height, double arcWidth,double arcHeight) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky), iaw=(int)(arcWidth*kx), iah=(int)(arcHeight*ky); g.drawRoundRect(ix,iy,iw,ih,iaw,iah); } public void drawPolygon(double xPoints[],double yPoints[],int nPoints) { int xP[]=new int[nPoints], yP[]=new int[nPoints]; for(int i=0;i<nPoints;i++){ xP[i]=(int)((xPoints[i]-xo)*kx); yP[i]=h-(int)((yPoints[i]-yo)*ky); } g.drawPolygon(xP,yP,nPoints); xP=null;yP=null; System.gc(); } public void drawString(String s,double x,double y) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky); g.drawString(s,ix,iy); } public void fill3DRect(double x, double y, double width, double height, boolean raised) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.fill3DRect(ix,iy,iw,ih,raised); } public void fillArc(double x, double y, double width, double height, int startAngle,int arcAngle) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.fillArc(ix,iy,iw,ih,startAngle,arcAngle); } public void fillOval(double x, double y, double width, double height) {

Page 186: programare in Java

PROGRAMARE IN JAVA - Note de curs 283

int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.fillOval(ix,iy,iw,ih); } public void fillRect(double x, double y, double width, double height) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky); g.fillRect(ix,iy,iw,ih); } public void fillRoundRect(double x, double y, double width, double height, double arcWidth,double arcHeight) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky), iw=(int)(width*kx), ih=(int)(height*ky), iaw=(int)(arcWidth*kx), iah=(int)(arcHeight*ky); g.fillRoundRect(ix,iy,iw,ih,iaw,iah); } public void fillPolygon(double xPoints[],double yPoints[],int nPoints) { int xP[]=new int[nPoints], yP[]=new int[nPoints]; for(int i=0;i<nPoints;i++){ xP[i]=(int)((xPoints[i]-xo)*kx); yP[i]=h-(int)((yPoints[i]-yo)*ky); } g.fillPolygon(xP,yP,nPoints); xP=null;yP=null; System.gc(); } public Rectangle getClipRect(){return g.getClipRect();} public Color getColor(){return g.getColor();} public Font getFont(){return g.getFont();} public FontMetrics getFontMetrics(){return g.getFontMetrics();} public void setColor(Color c){g.setColor(c);} public void setFont(Font f){g.setFont(f);} public void setPaintMode(){g.setPaintMode();} public void setXORMode(Color otherColor){g.setXORMode(otherColor);} public void translate(double x, double y) { int ix=(int)((x-xo)*kx), iy=h-(int)((y-yo)*ky); g.translate(ix,iy); } public String toString(){return g.toString();} } Programul urmator exemplifica utilizarea acestei clase pentru generarea desenului din figura 23.2

Page 187: programare in Java

284 CURS 23

Fig.23.2 Desenarea in coordonate reale

import java.awt.*; class Star extends Frame{ private StarPicture starPicture; public Star(){ super("Real Graphics"); starPicture=new StarPicture(); starPicture.setBackground(Color.white); add("Center",starPicture); resize(400,400); show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public static void main(String[] args){ Star win=new Star(); } } class StarPicture extends Canvas{ private RealGraphics rg; private boolean firstTime=true; public void initStarPicture(){ rg=new RealGraphics(-120,-120,240,240,this,360,360); drawStar(rg); } public void paint(Graphics g){ Dimension d=size(); if(firstTime){ initStarPicture(); firstTime=false; } g.drawImage(rg.getImage(),0,0,d.width,d.height,this); } private void drawStar(RealGraphics rg) { double r1=120,r2=45,r3,a1,a2,a3; rg.clear(); rg.setColor(Color.black); for(int i=0;i<5;i++){

Page 188: programare in Java

PROGRAMARE IN JAVA - Note de curs 285

a1=i*72; a2=a1+36; a3=a1-36; drawStarPoint(rg,0,0,r1*Math.sin(Math.PI*a1/180), r1*Math.cos(Math.PI*a1/180), r2*Math.sin(Math.PI*a2/180), r2*Math.cos(Math.PI*a2/180),10); drawStarPoint(rg,0,0,r1*Math.sin(Math.PI*a1/180), r1*Math.cos(Math.PI*a1/180), r2*Math.sin(Math.PI*a3/180), r2*Math.cos(Math.PI*a3/180),10); } r3=r2*Math.cos(Math.PI/5); for(int i=0;i<5;i++){ a1=i*72; a2=a1+108; a3=a1-108; drawStarPoint(rg,r1*Math.sin(Math.PI*a1/180), r1*Math.cos(Math.PI*a1/180),0,0, r2*Math.sin(Math.PI*a2/180), r2*Math.cos(Math.PI*a2/180),10); drawStarPoint(rg,r1*Math.sin(Math.PI*a1/180), r1*Math.cos(Math.PI*a1/180),0,0, r2*Math.sin(Math.PI*a3/180), r2*Math.cos(Math.PI*a3/180),10); } for(int i=0;i<5;i++){ a1=i*72; a2=a1+36; a3=a2+36; drawStarPoint(rg,r3*Math.sin(Math.PI*a1/180), r3*Math.cos(Math.PI*a1/180), r2*Math.sin(Math.PI*a2/180), r2*Math.cos(Math.PI*a2/180), r3*Math.sin(Math.PI*a3/180), r3*Math.cos(Math.PI*a3/180),10); } } private void drawStarPoint(RealGraphics rg, double x1,double y1, double x2,double y2, double x3,double y3,int n) { double k; for(int i=0;i<=n;i++){ k=(double)i/n; rg.drawLine(x1+(x2-x1)*k,y1+(y2-y1)*k, x2+(x3-x2)*k,y2+(y3-y2)*k); } } } Programul de mai sus creaza un obiect rg din clasa RealGraphics definita de noi anterior si il foloseste pentru desenarea imagini din figura 23.2. Desenarea se face intr-un buffer intern al lui rg. Generarea si afisarea desenului se face in metoda paint() a clasei StarPicture, subclasa a clasei Canvas. Metoda paint() apeleaza metoda drawStar() transmitandu-i acesteia instrumentul de desenare, rg. Metoda sterge bufferul imagine a lui rg, seteaza culoarea de desenare neagra si genereaza din figura apeland de mai multe ori metoda drawStarPoint(), transmitandu-i la randul sau, pe

Page 189: programare in Java

286 CURS 23 langa alti parametri si referinta la obiectul rg pentru desenare. Metoda drawStarPoint() deseneaza in bufferul imagine o figura realizata prin trasarea a mai multor linii pe baza urmatorului algoritm:

Fig.23.3 – desenarea unui colt al stelei Coltul stelei este format din doua segmente de dreapta d1 si d2. Dreapta d1 uneste centrul cercului cu un punct de coordonate polare (r1,a1)(aflat pe cercul de raza r1) iar d2 uneste acest punct de un punct de coordonate polare (r2,a2) )(aflat pe cercul de raza r2). Amble segmente se impart in acelasi numar n de semente egale, rezultand pe dreapta d1 punctele echidistante p0, p1, ...pn iar pe dreapta d2 punctele echidistante q0, q1, ...qn. Punctul p0 se afla in centrul cercului iar pn pe cercul de raza r1. Punctul q0 este identic cu pn iar qn se afla pe cercul de raza r2. Se unesc perechile de puncte (pi,qi) prin segmente de dreapta rezultand un colt al stelei desenate de program. Coordonatele punctelor pi si qi se calculeaza relativ la un sistem de coordonate xOy carteziene cu originea in centrul cercului (punctul p0 are coordonatele (0,0)) Valorile coordonatelor sunt valori reale si se calculeaza ca proiectii ale punctelor pe abscisa si ordonata folosind functiile trigonometrice sin() si cos() implementate de clasa Math. Trasarea segmentelor de dreapta se face in planul real folosind pentru desenare obiectul rg din clasa RealGraphics. Acesta va face toate transformarile necesare, generand desenul in bufferul-imagine. Dupa ce intreg desenul a fost generat, la fel cum fotograful scoate filmul din aparat, metoda paint() “extrage” din obiectul rg acest buffer-imagine invocand metoda getImage() a claseiRealGraphics si il deseneaza pe suprafata componentei starPicture.

Utilizarea unui thread separat pentru animatie In exemplul precedent toate operatiile de calcul necesare trasarii graficului sunt efectuate de threadul AWT. Pe perioada efectuarii calculelor si trasarii graficului thredul nu va putea procesa evenimentele interfetei grafice. Aceasta este un dezavantaj serios daca volumul de calcule este mare si deci necesita un timp indelungat. Solutia o reprezinta insarcinarea unui thread separat cu generarea imaginii, threadul AWT urmand doar sa o afiseze in cadrul metodei paint(). ca in exemplul urmator. import java.awt.*; class Star extends Frame{

Page 190: programare in Java

PROGRAMARE IN JAVA - Note de curs 287

private StarPicture starPicture; private Thread film; public Star(){ super("Real Graphics"); starPicture=new StarPicture(); starPicture.setBackground(Color.white); add("Center",starPicture); resize(400,400); show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ hide(); dispose(); System.exit(0); return true; } return super.handleEvent(e); } public static void main(String[] args){ Star win=new Star(); win.film=new Thread(win.starPicture); win.film.setDaemon(true); win.film.start(); } } class StarPicture extends Canvas implements Runnable{ private RealGraphics rg; private boolean firstTime=true; private boolean ready=true; private int phi=0; public void initStarPicture(){ rg=new RealGraphics(-120,-120,240,240,this,360,360); } public void paint(Graphics g){ Dimension d=size(); if(firstTime){ initStarPicture(); firstTime=false; ready=false; }else if(ready){ g.drawImage(rg.getImage(),0,0,d.width,d.height,this); ready=false; } } private void drawStar(int phi) { double r1=120,r2=45,r3,a1,a2,a3; rg.clear(); rg.setColor(Color.black); for(int i=0;i<5;i++){ a1=i*72-phi; a2=a1+36; a3=a1-36; drawStarPoint(0,0,r1*Math.sin(Math.PI*a1/180), r1*Math.cos(Math.PI*a1/180), r2*Math.sin(Math.PI*a2/180), r2*Math.cos(Math.PI*a2/180),10); drawStarPoint(0,0,r1*Math.sin(Math.PI*a1/180),

Page 191: programare in Java

288 CURS 23 r1*Math.cos(Math.PI*a1/180), r2*Math.sin(Math.PI*a3/180), r2*Math.cos(Math.PI*a3/180),10); } r3=r2*Math.cos(Math.PI/5); for(int i=0;i<5;i++){ a1=i*72-phi; a2=a1+108; a3=a1-108; drawStarPoint(r1*Math.sin(Math.PI*a1/180), r1*Math.cos(Math.PI*a1/180),0,0, r2*Math.sin(Math.PI*a2/180), r2*Math.cos(Math.PI*a2/180),10); drawStarPoint(r1*Math.sin(Math.PI*a1/180), r1*Math.cos(Math.PI*a1/180),0,0, r2*Math.sin(Math.PI*a3/180), r2*Math.cos(Math.PI*a3/180),10); } for(int i=0;i<5;i++){ a1=i*72-phi; a2=a1+36; a3=a2+36; drawStarPoint(r3*Math.sin(Math.PI*a1/180), r3*Math.cos(Math.PI*a1/180), r2*Math.sin(Math.PI*a2/180), r2*Math.cos(Math.PI*a2/180), r3*Math.sin(Math.PI*a3/180), r3*Math.cos(Math.PI*a3/180),10); } } private void drawStarPoint(double x1,double y1, double x2,double y2, double x3,double y3,int n) { double k; for(int i=0;i<=n;i++){ k=(double)i/n; rg.drawLine(x1+(x2-x1)*k,y1+(y2-y1)*k, x2+(x3-x2)*k,y2+(y3-y2)*k); } } public void run(){ for(;;){ if(!ready){ phi+=10; phi%=360; drawStar(phi); ready=true; } repaint(); try{ Thread.sleep(100); } catch(InterruptedException e){} } } public void update(Graphics g){ paint(g);} }

Page 192: programare in Java

PROGRAMARE IN JAVA - Note de curs 289

Deosebirea de programul anterior consta in faptul ca am declarat clasa StarPicture ca implementand interfata Runnable si am definit in cadrul ei metoda run(). In consecinta putem sa cream un thread care sa aibe acces in timp ce executa metoda run() la datele obiectului StarPicture starPicture printre altele si la RealGraphics rg. Metoda run()executata de threadul film, redeseneaza ciclic in bufferul imagine steaua din figura 23.2 rotind-o la fiecare iteratie cu 10 grade (variabila phi este incrementata cu acest pas) dupa care apeleaza repaint(). Pe de alta parte threadul AWT afiseaza imaginea pe suprafata componentei. Activitatea celor doua threaduri este sincronizata prin flagul ready. Cand acest flag are valoarea true threadul AWT stie ca imaginea este gata si deci o obtine apelaind metoda rg.getImage() si o afiseaza, atribuind in acelasi timp valoarea false flagului ready. Pe de alta parte, threadul film incepe redesenarea figurii numai cand flagul ready are valoarea false. Metoda update() este supradefinita pentru a apela direct metoda paint(), fara stergerea prealabila a suprafetei de desenare.

Retele locale de calculatoare Asa cum doua sau mai multe threaduri pot coopera pentru rezolvarea unei probleme, tot asa si doua sau mai multe aplicatii ruland pe calculatoare diferite conectate printr-un canal de comunicatie pot sa interschimbe date intre ele. Pentru transmiterea de date, calculatoarele pot fi intrerconectate intre ele fie local, fie printr-o retea de comunicatii. In figura 23.4 este prezentat un exemplu de interconectare a calculatoarelor intr-o retea locala Ethernet. Pentru aceasta, fiecare calculator este echipat cu un modul de comunicatie numit interfata de retea. Toate calculatoarele sunt conectate prin intermediul acestor module la un cablu coaxial care asigura transmisia datelor intre ele.

Fig.23.4 Retea locala de calculatoare Intrucat toate calculatoarele sunt conectate pe un singur cablu coaxial, accesul la canalul de comunicatie trebuieste sincronizat. Astfel daca unul din calculatoare a ocupat cablul, transmitand date, toate celalalte calculatoare care vor sa transmita si ele asteapta eliberarea cablului (terminarea transmisiei). De asemenea trebuieste rezolvata problema coliziunii, cand doua sau mai multe calculatoare constatand eliberarea canalului de comunicatie incep sa transmita simultan. In aceasta situatie, sesizand coliziunea ele vor inceta transmisia si vor astepta un interval de timp aleator dupa care vor incera din nou sa transmita datele. Intrucat timpul de asteptare estegenerat aleator aleator, este putin probabil ca doua calculatoare sa genereze aceeasi intarziere si sa intre din nou in coliziune. Accesul la canalul de transmisie a

Page 193: programare in Java

290 CURS 23

datelor si rezolvarea coliziunilor este rezolvata pe cale hardware chiar de catre interfetele de retea. Comunicatia intre doua module de retea se desfasoara astfel. Asa cum o scrisoare inainte de a fi expediata este introdusa intr-un plic pe care se scrie pe-o fata adresa destinatarului iar pe cealalta fata adresa expeditorului, datele sunt “impachetate” adaugandu-li-se informatii suplimentare cum ar fi un antet, adresele modulului expeditor si a modulului destinatar, dimensiunea blocului de date, si un cod suma ciclica de control CRC (Cyclic Redundancy Checksum)generat pe baza datelor expediate(adrese,lungime blc de date si blocul de date propriuzis)(figura 23.5).

Fig.23.5 – structura pachetului de date La receptie, aceasta suma ciclica de control (CRC-ul) este folosita pentru verificarea integritatii pachetului. Daca CRC-ul calculat la receptie nu coincide cu cel din pachet, pachetul este retransmis. Fiecare modul de retea are un numar de serie unic, astfel ca in retea nu pot exista doua module cu acelasi cod de identificare. Acest numar serveste ca adresa a modulului in reteaua locala. Toate modulele din retea sunt legate la acelasi cblu astfel incat au acces la toate pachetele de date care sunt transmise pe cablu. Interfetele de retea sunt module inteligente care pot fi comandate de un program prin inscrierea unui cuvant de comanda intr-unul din porturile prin care modulul este conectat la magistrala de date. Programul poate sa transmita interfetei un bloc de octeti (date) pentru a fi expediati la destinatie. Inerfata se ocupa de impachetarea si transmiterea blocului de date, asteptand eliberarea liniei, rezolvand eventualele coliziuni, etc. In tot acest timp, programul care a solicitat transmiterea datelor este liber sa se ocupe de altceva. In momentul in care iterfata a reusit sa transmita datele, ea genereaza o intrerupere a activitatii curente a programului acesta fiind astfel atentionat ca poate sa solicite transmisia unui nou bloc de date. Pe de alta parte, pe un alt calculator din reteaua locala (conectat la acelasi cablu de date printr-o interfata de retea similara) poate fi executat un program care indeplineste functia de destinatar (de exemplu functia lui poate fi sa afiseze datele receptionate din retea). Acest program este intrerupt din activitatea curenta de catre interfata de retea in momentul in care aceasta a receptionat un bloc de date expediat de intrfata de retea a unui alt calculator. La tratarea intreruperii programul va prelua blocul de date si va reveni la activitatea curenta. Problema cu acest model simplist de comunicatie este ca programele din acest exemplu sunt proiectate sa dialogheaza cu un anumit tip de module hardware comandate printr-un set de coduri de comanda specific, adresand anumite porturi cu o anumita adresa, si fiind activate de o anumita intrerupere. Daca un astfel de program va fi executat pe un calculator echipat cu o interfata de retea a unui alt producator, avand un set de comenzi diferit, evident ca el nu va mai comunica corect cu modulul si deci nu va functiona corect. Programele trebuiesc proiectate sa fie cat mai independente de structura hardware a calculatoarelor pe care urmeaza sa fie instalate. Solutia consta in utilizarea unor module software numite drivere care ofera o interfata standard in comunicatia dintre programe si modulele de retea. Modulele hardware sunt livrate de producator insotite de driverele aferente. Pentru a dialoga cu modulul de retea, programele apeleaza functiile standard de citire/scriere/configurare oferite de driver. Programatorul nu trebuie sa cunoasca amanunte privind arhitectura hardware a placii. Implementarea functiilor de catre driver tine insa seama de specificul modulului, inscriind cuvintele de comanda corespunzatoare si datele la porturile modulului astfel incat sa realizeze operatia solicitata.

Page 194: programare in Java

PROGRAMARE IN JAVA - Note de curs 291

Curs 24

Retele de tip Token Ring In continuare este prezentat un mecanism de comunicatie in retea total diferit de cel dat ca exemplu in cursul precedent(Ethernet). Am ales pentru aceasta modelul de retea Token Ring. O astfel de retea este constituita din mai multe calculatoare interconectate prin legaturi punct-la-punct astfel in cat fiecare calculator este legat de doua calculatoare vecine cu cite o legatura punct-la-punct rezulta structura inelara (ring) din figura 24.1.

Fig.24.1 Retea Token Ring Din cele 4 calculatoare din aceasta figura are dreptul sa transmita calculatorul care a receptionat un pachet de date special numit token. Tokenul poate fi deci asemuit cu o cutie care se transmite din mana in mana, de la un calculator la altul. Daca un calculator are de transmis altuia un mesaj, si cutia-token este goala(tokenul este liber) il va depune in cutie si o va transmite mai departe. Cand cutia ajunge la destinatar acesta isi citeste mesajul si il marcheaza corespunzator. Dupa aceasta transmite cutia cu mesajul marcat mai departe pana ajunge inapoi la expeditor. Acesta extrage mesajul din cutie verificand daca este marcat de destinatar, avand astfel confirmarea de primire. Cutia goala o va transmite mai departe pentru a fi folosita de celalalte calculatoare din retea pentru transmiterea altor mesaje.

Page 195: programare in Java

292 CURS 23 Pachetul token are in structura sa un flag numit T. Daca acest bit are valoarea 1 inseamna ca tokenul este liber. Daca este 0 tokenul este ocupat. Daca tokenul este liber (T=1)si calculatorul nu are de transmis nimic, il transmite urmatorului calculator din inel. Daca tokenul este ocupat (T=0), chiar daca calculatorul are ceva de transmis, el va astepta sa primeasca un token liber. Astfel ca se limiteaza sa transmita tokenul calculatorului vecin care se afla in asteptare, “ascultand” linia pentru a sesiza inceputul transmisiei tokenului. Daca calculatorul vecin nu este pe receptie, fiind oprit, el este bypasat hardware de contactul inchis al unui releu care sesizeaza disparitia tensiunii de alimentare. Astfel tokenul va calculatorului urmator din inel care asculta linia asteptand inceputul transmisiei unui token. In momentul in care calculatorul care asculta sesizeaza prezenta semnalului de transmisie pe linie el trece in starea de receptie, primind si procesind informatia continuta de token. In concluzie, un calculator dintr-o astfel de retea se poate afla in una din cele patru stari:

transmisie ascultare bypasat (oprit) receptie date

Daca unul din calculatoare (de exemplu A) tocmai a receptionat de la calculatorul vecin D tokenul liber (T=1). Sa presupunem ca el are de transmis un bloc de date unui alt calculator (sa zicem calculatorului D). In acest caz, A va seta bitul T in 0 (tokenul devenind un token ocupat), va inscrie in token adresa sa si adresa destinatarului (de exemplu calculatorul D), va adauga blocul de date , va calcula si adauga CRC-ul pachetului si va transmite tokenul astfel construit calculatorului vecin B. Calculatorul B, aflat in starea de ascultare, va primi tokenul, si va constata ca este ocupat si va vedea daca nu cumva el este cel care urmeaza sa primeasca date. Constatand ca destinatarul nu este el, va trimite mai departe tokenul calculatorului vecin. Inainte de a-l trimite mai departe, ii va calcula CRC-ul si il va compara cu CRC-ul inscris in token si va seta un flag E (Eroare) in 1 daca cele doua valori nu sunt identice. Sa presupunem ca acesta (C) este oprit. In acest caz releul de bypass este dezexcitat, si contactul sau inchis. Semnalul purtator de informatie va trece astfel de calculatorul C pe linia vecina, ajungand la D. D va receptiona tokenul, il va citi si va constata ca urmeaza sa primeasca niste date de la A. In aceste conditii el va trece in regim de receptie in care nu numai va transmite ma departe tokenul dar, in plus va stoca in memorie pachetul primit pentru a extrage blocul de date transmis de A. Inainte de a transmite tokenul mai departe va seta un flag corespunzator urmatoarelor situatii:

adresa recunoscuta – flagul A Pachetul copiat – flagul C Eroare – flagul E

In final tokenul va ajunge la emitent – calculatorul A. Acesta va examina strea flagurilor tragand concluziile corespunzatoare: AC=00 – adresa destinatarului nu a fost recunoscuta de nici o statie si deci datele nu au fost copiate de destinatar in memorie. Aceasta situatie apare atunci cand destinatarul este oprit. AC=10 – adresa a fost recunoscuta dar datele nu au fost copiate in memoria destinatarului. daca flagul E=1, inseamna ca motivul refuzarii datelor il constitue altwerarea pachetului (CRC –ul calculat nu coincide cu cel inscris in pachet). Daca E=0 motivul este necunoscut. AC=11 Adresa a fost recunoscuta si datele au fost copiate in memoria destinatarului. Daca flagul E=1 inseamna ca datele au ajuns cu bine la destinatar si au fost copiate in memorie si au fost alterate ulterior, o alta statie sesizand eroarea.

Page 196: programare in Java

PROGRAMARE IN JAVA - Note de curs 293

Daca AC=01 inseamna ca o alta statie decat destinatarul a copiat neautorizat datele in memoria sa. Este sarcina statiei A care a adaugat blocul de date in token sa le si extraga, elaborand si transmitand mai departe un token liber pentru a permite si altor statii sa transmita date. Structura unui pachet token este redata in figura 24.2.

Fig.24.2 – structura pachetului token In timpul functionarii, in reteaua Token Ring pot aparea evenimente care perturba functionarea mecanismului descris mai sus. De exemplu daca una din statiile retelei este oprita in momentul in care detine tokenul. Ea nu va mai transmite tokenul si deci celalalte statii din retea vor astepta degeaba sa-l primeasca. O alta situatie de avarie este oprirea statiei care a transmis un token ocupat de un bloc de date catre o alta statie din retea si a fost oprita inainte de a apuca sa extraga datele si sa elibereze tokenul. In aceste conditii tokenul va circula la infinit intre celalalte statii ale retelei fara a mai fi elibert de niciuna dintre ele. In ambele cazuri comunicatia in retea este blocata. Pentru a preantampina aceste fenomene mecanismul de comunicatie in reteaua de tip Token Ring prevede proceduri de detectare a evenimentelor de mai sus si de restabilire a functionarii normale a comunicatiei. Desi aparent toate calculatoarele din retea sunt egale, unele dintre acestea sunt mai egale decat altele. Astfel, pe langa functiunile descrise mai sus unul din calculatoare, la initializarea retelei este desemnat sa joace rolul de monitor activ al comunicatiei (Active Monitor). In aceasta calitate el marcheaza un token ocupat care trece prin dreptul sau prin setarea unui flag M din structura acestuia in starea 1(expeditorul blocului din date din token pozitioneaza acest flag in 0 inainte de a trimite tokenul spre destinatie). Daca monitorul activ va receptiona a doua oara acelasi token ocupat el isi va putea da seama dupa starea flagului M=1 ca aceasta este deja a doua a “calatorie” a datelor si deci dintr-un motiv sau altul expeditorul sau nu a reusit sa il elibereze(fiind probabil oprit). In acest moment, monitorul activ intervine oprind tokenul “orfan” si construind unul nou, liber pe care il va transmite spre calculatorul vecin. Pe de alta parte monitorul activ cronometreaza timpul scurs din momentul in care a expediat un toke. Daca dupa un anumit interval de timp TRT (Token Rotation Time) considerat ca fiind suficient ca un token sa efectueze o calatorie completa in inelul retelei (de exemplu 10 microsecunde), acesta nu ajunge din nou la monitor, acesta va considera ca tokenul s-a pierdut pe traseu fiind “inghitit” de zgomotul retelei sau in urma deconectarii unei statii inainte ca aceasta sa il transmita mai departe. In consecinta el genereaza si trimite in retea un nou token care sa-l inlocuiasca pe cel pierdut. Ce se intampla insa daca calculatorul care indeplineste functia de monitor activ este oprit? Functia de monitor activ nu ridica cerinte hardware speciale pentru calculatorul care o indeplineste. Deci oricare din statiile din retea poate prelua aceasta functie daca calculatorul care o indeplineste la un moment dat este oprit. Astfel sunt definite in retea calculatoarele care vor inlocui monitorul activ in cazul deconectarii acestuia. Statiile din aceasta “rezerva de cadre” se numesc monitoare standby

Page 197: programare in Java

294 CURS 23 (Standby Monitors). Pentru ca monitoarele standby sa poata detecta disparitia sa, atunci cand inretea nu circula un token continand date, monitorul activ genereaza periodic (de exemplu la fiecare 7 secunde) un pachet de control numit AMP (Active Monitor Present). Monitoarele standby receptioneaza pe rand acest pachet care le confirma ca monitorul activ functioneaza. La randul lor, monitoarele standby isi confirma prezenta in retea emitand periodic pachete SMP (Standby Monitor Present). Daca monitorul activ intarzie sa trimeata pachetul AMP, unul din monitoarele standby se va sesiza si va solicita preluarea postului de monitor activ prin trimiterea periodica a unor pachete de control CL_TK (Claim Tocken). Monitorul standby continua sa trimeata acest pachet pana cand :

va primi un pachet CL_TK cu adresa expeditorului mai mare decat adresa proprie. Criteriul de promovare in functia de monitor activ este adresa. Va deveni activ monitorul standbay cu adresa cea mai mare. Deci daca un monitror standby a receptionat un pachet CL_TK cu adresa expeditorului mai mare decat adresa proprie, el se va retrage din cursa, existand un candidat mai potrivit.

va primi un pachet de control PURGE emis de monitorul standby care a castigat concursul constatand ca nu exista in retea un alt monitor standby cu adresa mai mare si s-a auto-promovat in consecinta monitor activ.

Interconectarea a doua retele locale diferite Analizand cele doua exemple de retele vom putea trage urmatoarea concluzie. Comunicatia intre calculatoarele unei retele se desfasoara pe baza unui set de reguli foarte precise. Asa cum nu este de imaginat traficul autovehiculelor intr-un oras aglomerat fara respectarea regulilor de circulatie, tot asa nu este posibila comunicatia intre calculatoarele unei retea fara existenta si respectarea acestui set de reguli care includ definirea structurii unui pachet de date, modul de adresare al pachetelor, modalitatile de acces la canalul de date, mecanismul de validare al datelor, modul de rezolvare al avariilor cum ar fi coliziunile, pierderea tokenului, etc. Acest ansamblu de reguli se numeste Protocol de comunicatie. Protocoalele de comunicatie sunt standardizate pentru a permite producatorilor de hardware diferiti sa produca module compatibile, care sa poata comunica intre ele daca respecta standardul respectiv. Astfel protocolul Ethernet este definit in standardul IEEE 802.3 iar protocolul Token Ring in IEEE 802.5 Se pune problema urmatoare: O institutie dispune in aceeasi cladire de doua retele A si B de tipuri diferite. Ce poate fi facut pentru a asigura comunicatia dintre o statie oarecare a retelei A cu o alta statie a retelei B? La prima vedere rezolvarea este destul de simpla. Se va prevedea un nod comun intre cele doua retele – un calculator care va fi echipat cu doua interfete de retea – una de tip A si celalta de tip B. Pe acest calculator va rula un program specializat care va avea sarcina sa detecteze pachetele de date din reteaua A sa le despacheteze din “plicul” original acceptat de protocolul din reteaua A si sa le impacheteze in formatul acceptat de protocolul retelei B si sa le depuna pe canalul de date al retelei B. Va face acelasi lucru pentru pachetele din reteaua B facandu-le sa ajunga in reteaua A. Un astfel de calculator se numeste Bridge – jucand intr-adevar rolul de punte pentru circulatia mesajelor intre cele doua retele (figura 24.3).

Page 198: programare in Java

PROGRAMARE IN JAVA - Note de curs 295

Fig.24.3 – intrconectarea a doua retele locale diferite printr-un bridge Sa analizam acum cum se modifica problema daca vrem sa interconectam doua retele locale aflate in cladiri diferite aflate orase diferite. De data aceasta va trebui sa apelam la serviciile unei retele de comunicatii (cel mai frecvent publice dar sunt cazuri cand este folosita o retea de comunicatii privata – cazul sistemelor de transfer bancar sau al sistemelor informatice militare). In acest caz legatura intre retele trebuie facuta printr-un modem ca in figura 24.4

Fig.24.4 – Interconectarea a doua retele locale la distanta daca dorim ca legatura intre cele doua retele sa fie permanenta, ea trebuie realizata printr-o linie telefonica inchiriata. Aici calculatoarele echipate cu modem, numite calculatoare gazda (host), stabilesc prin linia de comunicatie o legatura punct-la-punct si pot juca rolul de bridge intre cele doua retele. Totusi aceasta solutie devine greoaie si nesigura in cazul interconectarii mai multor retele locale. Daca una din liniile de comunicatie se defecteaza reteaua formata prin interconectarea mai multor retele locale este fragmentata in doua subretele astfel incat pachetele de date dintr-o subretea nu mai ajung si in cealalta. Rezolvarea problemei a fost inspirata din functionarea sistemelor postale cu transmiterea din host in host a pachetelor pe o anumita ruta stabilita in functie de adresa destinatarului. Daca una din rute nu este disponibila,

Page 199: programare in Java

296 CURS 23 hostul care detine la un moment dat pachetul, va alege o alta ruta, poate mai lunga, dar in schimb disponibila. Un calculator host care realizeaza functia de rutare a pachetelor de date se numeste dupa cum banuiti ruter (router). Ruterele formeaza o retea de hosturi ca cea din figura 24.5. Ele sunt interconectate prin canale de comunicatie inchiriate de mare viteza care asigura posibilitatea transferului rapid a unui volum urias de date (cabluri cu fibre optice, canale satelit, etc).

Fig.24.5 Transmiterea pachetelor cu de date cu ajutorul ruterelor In cazul din aceasta figura, daca un calculator din reteaua A trebuie sa transmita un pachet de date unui calculator din reteaua B, hostul A va hotara ca pachetul sa fie transmis la destinatie pe ruta cea mai scurta, respectiv transmitandu-l hostului B. Daca insa comunicatia intre hostul A si hostul B nu functioneaza, hostul A va trimite pachetul pe ruta a doua, spre hostul B. Acesta va ruta pachetul spre C care il va transmite in reteaua locala C spre calculatorul de destinatie. Problema care apare la rutarea pachetelor consta in determinarea rutei spre reteaua de destinatie pe baza adresei destinatarului. Adresa hardware a acestuia specificata in

Page 200: programare in Java

PROGRAMARE IN JAVA - Note de curs 297

campul MAC (Media Access Control) corespunzator al pachetului este, dupa cum am mai spus, un numar arbitrar care desi identifica unic calculatorul intr-o retea locala nu contine informatie care sa permita ruterului sa stabileasca despre care dintre cele 4 posibile este vorba. Solutia este de a organiza calculatoarele din retelele locale interconectate intr-o retea logica globala, instituind un sistem unitar de adresare a acestora. Astfel, fiecare calculator va primi o adresa unica in reteaua logica globala constituita prin interconectarea retelelor locale. Deoarece ruterul “poarta de iesire” (gateway) din reteaua locala a pachetului il va despacheta, extragind blocul de date ceea, ce se transmite de la o retea la alta de la ruter la ruter este doar acest blocul de date. Pentru ca blocul de date sa poata fi rutat el trebuie sa contina adresele logice ale expeditorului si destinatarului precum si alte informatii cum ar fi lungimea sa in octeti. In concluzie blocul insusi va avea o structura de genul celei din figura 24.6. Un astfel de bloc il vom numi in continuare Datagrama.

Fig. 24.6 Structura unui bloc de date tip datagrama

Datagrama incapsuleaza deci alaturi de informatia utila adresele logice ale expeditorului si destinatarului. Adresele logice se stabilesc tinand cont de urmatoarele considerente:

adresa logica este un numar reprezentat pe 32 de biti (4 octeti). adresa logica are doua componente: {netid,hostid}. Componenta netid este un

numar reprezentat pe N biti care identifica reteaua iar hostid este un numar reprezentat pe restul de 32-N biti si identifica calculatorul gazda (host) din reteaua respectiva.

In conditiile punctului de mai sus vom avea patru clase de adrese: - clasa A in care netid este reprezentat pe 8 biti (1 octet). In acest caz vom

dispune de restul de 24 de biti pentru a defini adresele calculatoarelor din retea. Vom putea astfel asigna, intr-o singura retea, adrese diferite la 224 = 16.777.216 calculatoare. Adrese din clasa A se folosesc pentru retele mari (cu numar foarte mare de calculatoare).

- clasa B in care netid este reprezentat pe 16 biti (2 octeti). In acest caz vom dispune de restul de 16 biti pentru a defini adresele calculatoarelor din retea. Vom putea astfel asigna, intr-o singura retea, adrese diferite la 216 = 65.536 calculatoare. Adrese din clasa B se folosesc pentru retele medii (cu numar mediu de calculatoare).

- clasa B in care netid este reprezentat pe 24 biti (2 octeti). In acest caz vom dispune de restul de 8 biti pentru a defini adresele calculatoarelor din retea. Vom putea astfel asigna, intr-o singura retea, adrese diferite la 28 = 256 calculatoare. Adrese din clasa C se folosesc pentru retelele mici (cu numar mic de calculatoare).

Dispunand de un numar pe 32 de biti nu putem sa delimitam partea care desemneaza netid de partea care desemneaza hostid daca nu exista un indicator din care sa ne dam seama carei clase de retele apartine adresa respectiva. S-a adoptat urmatoarea conventie: - Daca cel mai semnificativ bit al adresei (bitul 31) este 0 inseamna ca este

vorba de o retea mare din clasa A. Deci urmatorii 7 biti vor identifica reteaua iar restul de 24 claculatorul. Vom putea astfel interconecta intr-o retea logica 27 = 128 de retele mari, a cate 16.777.216 calculatoare fiecare.

Page 201: programare in Java

298 CURS 23

- Daca bitul 31 este 1 si bitul 30 este 0 este vorba despre o retea medie din clasa B cu netid- ul reprezentat pe 16 biti. Daca primii doi biti din cei 16 au valoarea 10 desemnand clasa retelei, ceilalti 14 biti vor identifica reteaua. Restul de 16 biti ai adresei vor contine hostid-ul claculatorului. Vom putea astfel interconecta intr-o retea logica, alaturi de cele maximum 128 de retele mari, 214 = 16.384 de retele medii, a cate 65.536 calculatoare fiecare.

- Daca bitii 31 si 30 au valoarea 11 iar bitul 29 este 0 este vorba despre o retea mica din clasa C cu netid- ul reprezentat pe 24 de biti. Daca primii trei biti din cei 24 au valoarea 110 desemnand o retea din clasa C, ceilalti 21 de biti vor identifica reteaua. Restul de 8 biti ai adresei vor contine hostid-ul claculatorului. Vom putea astfel interconecta intr-o retea logica, alaturi de cele maximum 128 de retele mari si cele 16.384 retele medii, inca 221 = 2.097.152 de retele mici, a cate 256 de calculatoare fiecare.

Pe baza unei astfel de adrese ruterul va putea sa identifice reteaua de destinatie a datagamei. El dispune de o tabela de rutare ale carei intrari contin fiecare cate o ruta. O ruta asociaza unui identificator netid al unei retele cu ruterul catre care trebuie trimisa datagrama ca sa ajunga la reteaua de destinatie. Datagrama adresata unui calculator din reteaua cu identificatorul netid va fi trimisa catre ruterul asociat in tabela de rutare. Acesta la riandul sau va consulta tabela proprie de rutare, va determina catre ce ruter trebuie transmisa datagrama in continuare si o va transmite acestuia. Procesul continua pana cand datagrama va ajunge la ruterul poarta de intrare in reteaua local careia ii apartine calculatorul destinatar. Cand datagrama ajunge la ruterul poarta de intrare in reteaua de destinatie, acesta o va impacheta in formatul specific retelei locale respective si va transmite pachetul spre calculatorul de destinatie. Cu aceasta inca problema nu este definitiv rezolvata. Faptul ca am asociat fiecarui calculator din retea o adresa unica nu inseamna ca am raspuns la intrebarea – ce va inscrie in campurile de adresa hardware (campurile MAC– Media Acess Control) ale noilor pachete la reimpchetarea blocului de date? Este clar ca in campul expeditorului, ruterul va inscrie propria sa adresa hardware din reteaua respectiva. Pentru destinatar insa ruterul va trebui sa determine adresa hardware a acestuia din reteaua locala pe baza adresei globale din datagrama. In acest scop el detine o tabela de corespondenta a adreselor in care sunt definite perechile adresa globala – adresa hardware. Aceasta tabela poate fi definita manual – de exempolu editata intr-un fisier. La initializarea ruterului, acesta va citi tabela de rezolutie a adreselor din fisier in memorie si o va accesa ori de cate ori detecteaza un pachet de date care trebuie transbordat. In aceste conditii activitatea ruteruluiului se va desfasura astfel:

Ori de cate ori receptioneaza o datagrama, el citeste din aceasta adresa logica a destinatarului.

Dupa componenta netid a adresei, ruterul dtermina daca destinatarul este in reteaua locala deservita de el sau daca trebuie sa ruteze datagrama mai departe.

Daca datagrama nu este adresata retelei locale deservite de ruter el va consulta tbela de rutare, va determina carui ruter trebuie sa o trimita mai departe si o va transmite acestuia. Daca datagrama este adresata retelei deservite de el, va cauta in tabela de rezolutie a adresei adresa hardware asociata adresei logice a destinatarului.

Impacheteaza datagrama conform protocolului retelei deastrvite si o expediaza in reteaua locala de destinatie pe adresa hardware gasita la pasul anterior. La adresa hardware a expeditorului va figura adresa interfetei sale cu aceasta retea.

Page 202: programare in Java

PROGRAMARE IN JAVA - Note de curs 299

Aceasta solutie prezinta un dezavantaj. Ori de cate ori este adaugat un nou calculator in retea sau se inlocuieste interfata de retea a unui calculator (cu una mai performanta sau in urma defectarii interfetei vechi) tabelul de rezolutie a adresei trebuie actualizat. S-a gasit o solutie care elimina acest inconvenient. Ruterul trimite in reteaua locala deservita de el un pachet de control de un tip special, reprezentand o cerere de rezolutie de adresa (pachetul este numit ARP request – Address Resolution Protocol request). Pachetul contine in blocul de date adresa logica a calculatorului a a carui adresa fizica ruterul doreste sa o afle. In campul de adresa hardware, ruterul trece adresa hardwre a interfetei sale de retea. Pachetul este adresat tuturor calculatoarelor din reteaua locala (broadcast). Sunt posibile trei situatii. 1. Calculatorul cu adresa logica specificata in pachet isi recunoaste adresa logica si

raspunde cu un pachet ARP Reply, trimis pe adresa hardware a ruterului (aflata din campul de adresa hardware a expeditorului din pachetul ARP Request).

2. Calculatorul cu adresa logica specificata in pachet sa fie oprit. In acest caz ruterul va astepta un interval de timp prestabilit si daca nu primeste in acest interval de timp raspunsul abandoneaza pachetul.

3. Sa existe doua sau mai multe calculatoare in retea care sa aiba aceiasi adresa logica. Situatia este ilegala dar este posibila. Ruterul va primi mai multe raspunsuri ARP Reply.

Ruterul va extrage din campul de adresa expeditor al pachetului ARP Reply primit adresa hardware cautata si o va inscrie in campul de adresa destinatar al pachetului generat si il va expedia in reteaua locala. Analizand mecanismul de comunicatie descris mai sus constatam ca si aceasta se desfasoara dupa un set de reguli precis care defineste o structura a datagramei, un mod de adresare in reteaua logica, niste reguli precise de rutare a datagramelor si de rezolutie a adreselor hardware. Acest set de reguli constitue si el un protocol de comunicatie. Acest protocol se bazeaza insa pe protocoalele de comunicatie de la nivelul legaturii de date cum ar fi Ethernet, Token Ring sau PPP (Point to Point Protocol) care descriu regulile de comunicatie intre interfetele de retea. Protocolul discutat mai sus descrie structurile si regulile de manipulare a datelor pentru transmiterea lor in reteaua logica constituita din interconectarea retelelor fizice de tipuri diferite situate la distanta. Modulele software si programele care implementeaza acest protocol nu acceseaza direct interfetele de retea ci interactioneaza cu ele prin intermediul driverelor acestor interfete. Astfel acelasi modul software care implementeaza acest protocol va putea fi rulat si pe un PC intr-o retea Ethernet si pe un PC intr-o retea Token Ring, si pe un PC conectat pe o linie telefonica la un ruter printr-o legatura PPP, deoarece driverele acestor module de retea diferite ca constructie si principiu de functionare asigura o interfata standardizata cu programele utilizatoare ascunzand particularitatile hardware specifice fiecarui tip in parte. (Este situatia similara celei de la automobile unde interfata este aceiasi si la un trabant si la Lamborghini – un volan, trei pedale si o maneta pentru schimbarea vitezelor. Oricand, un sofer care a condus toata viata numai automobile Lamborghini, poate pilota un Trabant si invers. Cu toate acestea hardware-ul de sub capota celor doua masini este foarte diferit.) Spunem despre acest protocol ca este definit la nivelul retea logica (Network) si il putem amplasa pe o scara ierarhica ca fiind deasupra protocoalelor de la nivelul legaturii de date (Data Link). La acest nivel, un program care are de transmis date unui alt program care ruleaza pe un alt calculator va solicita modulului software care implementeaza protocolul IP sa transmita mesajul respectiv specificandu-i doar adresa logica a destinatarului, fara sa se preocupe de pozitia acestuia si de tipul retelei careia ii apartine. Destinatarul poate sa fie calculatorul pe

Page 203: programare in Java

300 CURS 23 care ruleaza el insusi, calculatorul invecinat de pe aceiasi masa, un calculator aflat intr-un birou in aceiasi cladire sau un calculator aflat intr-o cladire situata in cu totul alt oras la 60.000 de kilometri distanta. Cu atat mai putin trebuie sa se preocupe programul expeditor de tipul placii de retea cu care este echipat calculatorul destinatar sau calculatorul propriu. Pe motivul ca protocolul descris mai sus asigura comunicatia intre retele de tipuri diferite organizand calculatoarele din componenta acestora intr-o singura retea logica, el s-a numit Internetwork Protocol sau mai scurt Intrenet Protocol sau si mai scurt IP. Protocolul IP asa cum am vazut, are doar sarcina sa ascunda caracterul eterogen din punct de vedere hardware si software al retelelor interconectate, transformandu-le intr-o singura retea logica prin introducerea unui sistem de adresare uniforma a calculatoarelor acestor retele. El nu ofera insa nici un mecanism de validare al integritatii datagramelor la receptie si nu rezolva nici accesul simultan la canalul de date al mai multor aplicatii concurente ruland pe acelasi calculator in regim multitasking. De fapt scopul inerconectarii calculatoarelor in retea este de a asigura comunicatia nu intre calculatoare ci intre aplicatii executate pe calculatoare diferite ca in figura 24.7.

Fig.24.7 Comunicatia intre aplicatii concurente in reteaua IP

In aceasta figura sunt reprezentata doua aplicatii “baieti” A1 si A2 care ruleaza concurent pe un calculator A si doua aplicatii “fete” B1 si B2 care ruleaza concurent pe un calculator B. Aplicatia A1 trimite mesaje aplicatiei B1 si A2 lui B2. Calculatoarele sunt interconectate printr-o retea IP. Protocolul IP asigura doar un singur canal de comunicatie intre A si B. Mesajul de la una din aplicatiile A1 sau A2 trebuie transmis de la calculatorul A la calculatorul B. Modulul software transmitator care implementeaza protocolul IP impacheteaza datele ce urmeaza sa plece de la calculatorul A intr-o datagrama si o paseaza prin intermediul driverului de de retea la nivelul legaturii de date. La acest nivel datagrama este la randul sau impachetata de interfata de retea conform protocolului specific si depusa la nivelul fizic – perechi de fire torsadate, cablu coaxial sau fibra optica – sub forma de semnale electrice sau optice. Daca statia B se gaseste in reteaua locala pachetul de date va fi expediat pe adresa sa hardware gasita cu ajutorul protocolului de rezolutie a adresei. Daca nu, adresa hardware de destinatie va fi cea a ruterul-gateway al retelei locale care va prelua pachetul, va extrage datagrama incapsulata in acesta, va citi din datagrama identificatorul retelei de destinatie (componenta netid a adresei IP), va cauta in tabela de rutare identificand ruta optima pe care va expedia datagrama. Din ruter in ruter, datagrama va ajunge la ruterul-gateway al retelei de

Page 204: programare in Java

PROGRAMARE IN JAVA - Note de curs 301

destinatie. Acesta va sesiza ca datagrama este adresata retelei sale locale si va face rezolutia adresei hardware dupa care va impacheta datagrama conform protocolului de la nivelul legaturii de date expediind-o spre calculatorul B. Aici pachetul de date receptionat va fi despachetat extragandu-se datagrama care se paseaza modulului software receptor care implementeaza protocolul IP. Datagrama este despachetata extragandu-se din ea informatia utila continand mesajul trimis de pe calculatorul A. In tot acest timp aplicatiile B1 si B2 asteapta mesaje de la baietii din A. Datele receptionate trebuie sa ajunga la aplicatia B1 daca sunt expediate de A1 sau la B2 daca expeditorul este A2. Din experienta acumulata deja din descrierea protocoalelor probabil ca intuiti ca blocul de date care a fost receptionat si extras din datagrama trebuie sa fie la randul sau un “plic” avand inscriptionate pe el adresele expeditorului (A1 sau A2) si al destinatarului (B1 sau B2) si continand in interiorul sau mesajul transmis. Partial aveti dreptate dar apare aici o mare problema – aplicatiile, spre deosebire de calculatoarele din retea nu pot fi caracterizate printr-o “adresa” fixa. Nu se poate prevedea la scrierea unor rutine de serviciu ale sistemului de operare cum sunt modulele care implementeaza protocoalele de comunicatie in retea ce aplicatii vor apela la serviciile lor pentru transmiterea sau receptia unor mesaje. Pentru a iesi din acest impas s-a optat pentru solutia urmatoare. Canalul de date IP va fi interfatat cu un modul software prevazut cu un numar de “prize” numite porturi fiecare avand asociat un numar intreg ca identificator. Aplicatiile pot sa se conecteze la un astfel de port pentru a transmite sau receptiona date din canalul de comunicatie IP. Aceste porturi se comporta ca niste streamuri de intrare la receptia si de iesire la transmisia de date. O aplicatie poate inscrie octeti in portul respectiv sau extrage octeti din port ( numai daca sunt disponibili, in caz contrar trebuind sa astepte sosirea pe canalul IP a unei noi datagrame adresate portului). La scrierea de octeti intr-un port, interfata nu ii expediaza imediat ci ii cumuleaza intr-un buffer de date. In momentul in care buffer-ul s-a umplut, ea il va goli, transmitand continutul lui incapsulat intr-un pachet in care este specificat numarul portului expeditor si numarul portului destinatar. Acest pachet este pasat canalului IP care il va incapsula la randul lui intr-o datagrama si il va expedia pe canalul IP la destinatie. Aici pacetul va fi extras din datagrama si pasat interfetei care va extrage din pachet datele si le va directa spre bufferul portul cu numarul specificat pe pachet la care asteapta aplicatia destinatar. Aceasta va citi din buffer octetii mesajului din port. Daca mesajul este mai lung decat capacitatea bufferului, el va fi trimis la destinatie sub forma mai multor pachete, fiecare pachet continand numai o secventa din mesaj. Lungimea secventei este data de capacitatea bufferului portului. Datorita rutarii (datagramele continand secvente ale mesajului pot ajunge la destinatie pe rute diferite si deci cu intarzieri diferite)ordinea de sosire a pachetelor poate sa fie diferita. Astfel datagrama continand prima secventa, desi a fost expediata prima, poate sa ajunga la destinatie a treia, a doua sa ajunga prima iar a treia sa ajunga a doua mergand pe o ruta mai scurta decat prima. In aceste conditii pachetele continand secventele de mesaj trebuie sa contina un camp care sa specifice numarul secventei din mesaj continute. Pe baza acestui numar secventele sunt “reasamblate” de interfata in ordinea corecta. Astfel a secventa 2, desi sosita prima nu va fi transmisa spre port pana cand nu este citita de aplicatia destinatar secventa 1 sosita a treia. Pe langa multiplexarea porturilor si reasamblarea pachetelor de date, interfata cu canalul IP mai implementeaza un mecanism care asigura o fiabilitate ridicata a canalului virtual de comunicatie intre doua porturi. Astfel, interfata care expediaza un pachet, il completeaza la sfarsit cu un camp CRC calculat pe baza continutaului

Page 205: programare in Java

302 CURS 23

pachetului. Interfata care receptioneaza pachetul, calculeaza CRC-ul acestuia si il compara cu valoarea din campul CRC primita. Daca cele doua valori sunt egale, inseamna ca pachetul este valid si in consecinta, interfata receptoare va genera si va trimite spre interfata expeditor un pachet de control care confirma primire pachetului de date. In caz contrar va genera si trimite catre catre interfata expeditor un pachet de control prin care solicita retransmiterea pachetului de date. Pe de alta parte, dupa ce a trimis un pachet de date, interfata expeditor asteapta receptia pachetului de confirmare a primirii sau cerere de retransmitere. Daca dupa un interval de time-out nu primeste un astfel de pachet, considera ca acesta s-a pierdut pe traseu (de exemplu prin oprirea unui ruter care primise pachetul de date pentru rutare dar nu a mai apucat sa-l expedieze mai departe). In acest caz, interfata ia decizia de retransmitere a pachetului pe care il considera pierdut. Din descrierea de mai sus a interfetei care creaza prin multiplexarea canalului IP si controleaza canale virtuale multiple pentru transportul datelor de la oaplicatie la alta constatam ca aceasta implementeaza o structura de date si un set de proceduri de comunicatie care constituie si ele un protocol. Acest protocol actioneaza la nivelul transportului de date fiind situat ierarhic deasupra protocolului IP. Protocolul descris a fost denumit Transmision Control Protocol sau mai scurt TCP. In figura 24.8 este reprezentata stiva protocoalelor descrise mai sus si procesarile aplicate mesajului la transmiterea acestuia de la aplicatia expeditor la aplicatia destinatar.

Fig.24.8 - Stiva protocoalelor TCP/IP

Page 206: programare in Java

PROGRAMARE IN JAVA - Note de curs 303

Curs 25

Reprezentarea zecimala a adreselor IP Am vazut ca o adresa IP este un numar intreg pe 4 octeti memorat de calculator in forma binara, de exemplu 10001101.01010101.00101100.00000001. Daca ar fi reprezentat in zecimal ar fi dificil sa determinam pe baza valorii 2371169281 carei clase apartine reteaua locala, si pornind de la aceasta sa determinam identificatorul retelei sau a calculatorului. Desi s-ar fi putut reprezenta adresa in hex 8D552C01 s-a optat pentru reprezentarea zecimala a valorii fiecarui octet, aceste valori fiind separate prin puncte: 141.85.44.1. Stiind adresele IP ale calculatoarelor din retelele mari din clasa A au bitul cel mai semnificativ 0 si deci adresele lor vor incepe cu un numar cuprins intre 1 ( primul octet al adresei este 00000001) si 126 (primul octet al adresei este 0111110) valorile 0 si 127 fiind rezervate, avand o semnificatie speciala. Adresele IP ale calculatoarelor din retelele medii din clasa B incep cu bitii 10 si in consecinta primul numar zecimal al adresei va fi cuprins intre 128 (10000000) si 191(10111111). Adresele IP ale calculatoarelor din retelele mici din clasa C incep cu bitii 110 si in consecinta primul numar zecimal al adresei va fi cuprins intre 192(11000000) si 223(110111111). Astfel, avand adresa IP 141.85.44.1 vom trage imediat concluzia ca reteaua careia ii apartine calculatorul cu aceasta adresa de IP este o retea de dimensiune medie din clasa B. Deoarece identificatorul unei retele din clasa B este reprezentat pe doi octeti, vom putea obtine acest identificator: 141.85. Rezulta de aici ca identificatorul calculatorului cu aceasta adresa de retea este 44.1. Sa luam un alt exemplu in care adresa IP a calculatorului este 195.65.4.2. Dupa primul numar al adresei deducem ca este vorba de o retea din clasa C, avand identificatorul reprezentat pe 3 octeti: 195.65.4. Rezulta ca identificatorul calculatorului este 2.

Adrese de IP cu semnificatie speciala O serie de adrese de IP sunt rezervate si nu desemneaza un anumit calculator dintr-o retea locala. Ele au o semnificatie speciala. Astfel s-au definit adresele IP speciale:

{netid 0} – de exemplu 141.85.0.0 sau 195.65.4.0. Aceasta adresa desemneaza reteaua locala si nu un calculator anume din retea. (Aceasta inseamna ca nici un calculator nu poate avea identificatorul 0, acesta fiind rezervat pentru acest caz.)

{netid -1} – de exemplu 141.85.255.255 sau 195.65.4.255. Aceasta adresa IP desemneaza toate calculatoarele din reteaua locala. Un mesaj trimis pe o astfel de adresa va fi difuzat (broadcast) in reteaua locala specificata de netid, fiind adresat tuturor calculatoarelor care fac parte din ea. (Aceasta inseamna ca nici un calculator nu poate avea identificatorul -1, acesta fiind rezervat pentru acest caz.)

{-1 -1} – adica 255.255.255.255. O astfel de adresare determina o difuzare limitata doar la aria retelei locale. Mesajul trimis la o astfel de adresa de IP nu va depasi ruterul-gateway al retelei fiind adresat implicit tuturor calculatoarele retelei locale.

Page 207: programare in Java

304 CURS 25

{0 0} – adica 0.0.0.0. Calculatorul care emite mesajul pe o astfel de adresa se adreseaza pe sine. O astfel de adresare este utila daca calculatorul vrea sa –si afle propria adresa de IP. La receptionarea mesajului, el va gasi in campul de adresa al expeditorului din datagrama propria adresa pe care nu o cunostea.

{0 hostid} – de exemplu 0.0.44.1 sau 0.0.0.2. Mesajul transmis pe aceasta adresa 0.0.44.1 intr-o retea locala din clasa B se adreseaza calculatorului cu numarul 44.1 din aceasta retea. Daca calculatorul din reteaua 141.85.44.1 primeste un mesaj cu adresa destinatarului 0.0.44.1 el il va recunoaste si accepta stiind ca ii este destinat.

{127 orice} – Orice mesaj expediat pe adresa IP 127.X.X.X, unde X este orice numar cuprins intre 0 si 255, este destinat calculatorului emitent, intorcandu-se la acesta fara a fi transmis pe canalul de comunicatie la nivel fizic (cablu). Pachetul este pur si simplu copiat din bufferul de emisie in bufferul de receptie ale aceluiasi calculator.

Retele din clasa D. Multicasting Pe langa modalitatea de difuzare a mesajelor catre toate calculatoarele unei retele locale descrisa mai sus (prin adresarea cu 255.255.255.255 sau {hostid –1}) mai exista o forma de difuzare catre un anumit grup variabil de calculatoare numita multicasting. In acest caz calculatoarele care vor sa primeasca sau sa transmita mesaje “de la unul la toti ceilalti membri ai grupului” trebuie sa se alature unui grup-retea din clasa D, identificat de o adresa IP speciala avand primii biti 1110. Adresa unui astfel de grup-retea din clasa D are in primul octet un numar cuprinse intre 224 (11100000) si 239(11101111). Adresa de baza 224.0.0.0 este rezervata. Unele adrese sunt alocate standard unor grupuri de conferinta sau altor scopuri. De exemplu: - 224.0.0.1 grupul toturor calculatoarele din subretea - 224.0.0.2 grupul tuturor ruterelor din subretea Sunt nealocate adresele: - 224.0.0.3 - 224.0.0.12 - 224.0.0.255 - 224.0.1.27 - 224.0.1.255 - 224.0.5.128 - 224.0.5.255 - 224.0.6.128 - 224.0.6.255 - 233.0.0.0 - ... Orice calculator din reteaua IP poate sa se alature unui grup. In consecinta, ruterele retelelor locale trebuie sa stie daca primind un mesaj adresat unui grup sa-l difuzeze sau nu in reteaua lor locala. In acest scop el tine o tabela interna cu evidenta grupurilor din reteaua locala subordonata. Petru actualizarea tabelei, ruterul trebuie sa afle daca in reteaua sa locala exista calculatoare care s-au alaturat unui grup. Ruterele afla despre alaturarea sau parasirea unui grup de catre un calculator din reteaua subordonata prin interogarea periodica a tuturor calculatoarelor din aceasta subretea. Interogarea se face transmitand un mesaj multicast pe adresa 244.0.0.1 catrea toate calculatoarele din subretea. Toate calculatoarele din subretea care s-au alaturat unor grupuri raspund cu mesaje adresate ruterului. Din aceste mesaje ruterul afla adresele grupurilor la care s-au alaturat calculatoarele din subretea, actualizand tabela interna. La randul lor, ruterele comunica intre ele pentru a-si actualiza tabelele de rutare a mesajelor multicast transmise prin reteaua IP. Mecanismul de comunicare descris mai sus implementeaza protocolul Internet Group Management Protocol (IGMP).

Page 208: programare in Java

PROGRAMARE IN JAVA - Note de curs 305

Protocolul UDP Exista aplicatii care nu necesita robustetea (fiabilitatea) protocolului TCP. Pentru aceste aplicatii este suficient un protocol la nivelul transport care sa asigure comunicatia intre aplicatii rulate pe calculatoare diferite intr-o retea IP si un mecanism minimal de validare a integritatii mesajelor receptionate. Protocolul UDP (User Datagram Protocol) satisface aceste cerinte. Spre deosebire de protocolul TCP bazat pe stabilirea unui canal virtual de comunicatie intre aplicatii, protocolul UDP opereaza intr-un mod orientat pe transmiterea datagramelor. El nu incearca sa stabileasca o legatura intre aplicatii ci doar incapsuleaza datele intr-un pachet UDP avand structura din figura 25.1 si il paseaza nivelului IP. La nivelul IP pachetul UDP este ambalat intr-o datagrama IP si transmis nivelului inferior pentru expeditie.

Fig.25.1 – Structura pachetului UDP In aceasta etapa poate apare un fenomen deranjant. Dimensiunea blocului de date ce poate fi incapsulat intr-un pachet la nivelul legaturii de date este limitat de dimensiunea bufferelor de emisie/receptie ale interfetei de retea. De exemplu dimensiunea maxima a blocului de date intr-un pachet Ethernet este de 1500 de octeti. Daca dimensiunea datagramei depaseste dimensiunea maxima admisa de protocolul de la nivelul legaturii de date, pachetul UDP este fragmentat si incapsulat in mai multe datagrame transmise succesiv. Desi ordinea de transmitere a datagramelor este cea corecta, ele s-ar putea, datorita rutarii, sa ajunga la destinatie intr-o ordine diferita. Spre deosebire de protocolul TCP care asigura asamblarea secventelor mesajului in ordinea corecta, protocolul UDP nu mai face acest lucru. Astfel blocul de date expediat ar putea ajunge la destinatie un pic “amestecat”. In aceste conditii aplicatiile care apeleaza la protocolul UDP pentru comunicatie trebuie sa asigure ele dezasamblarea/asamblarea mesajelor inainte/dupa transmisie/receptie. Probabilitatea unei sosiri in ordine diferita a mesajelor la comunicatia intr-o retea locala este nula, pachetele nefiind rutate. O alta problema care poate sa apara la utilizarea protocolului UDP este absenta confirmarii de primire a mesajului. Spre deosebire de protocolul UDP nu garanteaza ajungerea la destinatie a mesajului. Astfel daca pe traseu, datagrama este pierduta (prin oprirea unui ruter chiar in momentul in care datagrama se gasea la el), protocolul UDP nu se va sesiza de acest lucru deoarece nu prevede confirmarea de primire din partea destinatarului. Acelasi lucru se va intampla daca calculatorul de la adresa de destinatie nu exista sau este oprit. Protocolul TCP retransmite mesajul de mai multe ori daca nu primeste dupa un timp fixat raspunsul de confirmare. Dupa un numar de retransmiteri renunta si avertizeaza aplicatia expeditor despre esecul stabilirii legaturii. Protocolul UDP nu face acest lucru, misiunea sa limitandu-se doar la multiplexarea porturilor - sa preia datele de la porturi, sa le incapsuleze in pachete UDP si sa plaseze aceste pachete in canalul de comunicatie comun IP, respectiv sa extraga datele din pachetele UDP receptionate prin acest canal si sa le dirijeze spre porturile de destinatie specificate in antetul pachetului UDP primit.

Page 209: programare in Java

306 CURS 25 Aceste caracteristici fac ca protocolul UDP sa fie mai simplu si mai eficient decat protocolul TCP cu singurul dezavantaj ca este mai putin sigur. El este recomandat pentru aplicatii care lucreaza in cooperare, pe principiu cerere-raspuns. Este bine ca atat cererea cat si raspunsul trebuie sa incapa intr-o singura datagrama. Este sarcina aplicatiei sa repete cererea daca nu a primit raspunsul intr-un timp dat sau sa renunte daca nu primeste raspuns la mai multe cereri succesive. De asemenea protocolul UDP este util in cazul aplicatiilor care difuzeaza un mesaj in retea simultan la mai multe calculatoare. De exemplu daca o aplicatie are de trimis acelasi mesaj la 1000 de aplicatii ruland pe alte calculatoare, daca va folosi protocolulea va trebui sa stabileasca 1000 de conexiuni prin canale virtuale. Dupa terminarea difuzarii mesajului prin transmiterea lui pe fiecare conexiune in parte, aplicatia va trebui sa inchida aceste 100 de conexiuni. Costul in resurse ale sistemului pentru crearea si mentinerea conexiunilor pe perioada difuzarii mesajelor este mare. Folosind protocolul UDP aplicatia poate expedia cele 1000 de mesaje cu costuri mult mai mici si intr-un timp mult mai scurt. De asemenea cele 1000 de calculatoare de destinatie pot fi grupate intr-o retea de tip D iar mesajul poate fi transmis o singura data, in regim de multicasting, pe adresa de IP a grupului. In pus, o aplicatie poate plasa o singura datagrama cu adresa de IP de forma 255.255.255.255 cerand distribuirea acesteia in regim de broadcasting in reteaua locala sau de forma {netid - 1} pentru difuzarea catre toate calculatoarele din reteaua netid.

Adrese simbolice si serviciul DNS Folosirea adreselor IP este greoaie deoarece acestea sunt greu de memorat. Din acest motiv s-a prevazut mecanismul de adresare a calculatoarelor dintr-o retea IP printr-un nume simbolic de forma <nume host>.<domeniu> cum ar fi de exemplu big_crash.hacker.ro. in loc de 195.65.4.1. Un mecanism simplu de rezolutie a adresei IP pe baza unei adrese simbolice se bazeaza pe existenta unui fisier hosts in care sunt editate asocierile {adresa simbolica – adresa IP}, cate una pe fiecare rand ca in exemplul de mai jos: # Copyright (c) 1998 Microsoft Corp. # # This is a sample HOSTS file used by Microsoft TCP/IP stack for # Windows98 # # This file contains the mappings of IP addresses to host names. Each # entry should be kept on an individual line. The IP address should # be placed in the first column followed by the corresponding host # name. # The IP address and the host name should be separated by at least # one space. # # Additionally, comments (such as these) may be inserted on # individual # lines or following the machine name denoted by a '#' symbol. # # For example: # # 102.54.94.97 rhino.acme.com # source server # 38.25.63.10 x.acme.com # x client host 127.0.0.1 localhost 195.65.4.1 big_crash.hacker.ro

Page 210: programare in Java

PROGRAMARE IN JAVA - Note de curs 307

In momentul in care o aplicatie solicita transmiterea unei datagrame la adresa simbolica big_crash.hacker.ro modulul care implementeaza protocolul IP deschide fisierul hosts si cauta in el adresa IP asociata numelui simbolic respectiv. Acest mecanism este insa ineficient deoarece nu este practic posibil sa tii evidenta tuturor calculatoarelor din reteaua IP al caror numar se poate ridica la zeci de milioane. Un astfel de fisier ar trebui actualizat pe fiecare calculator din reteaua IP ori de cate ori apare un nou calculator in retea sau un calculator existent doreste sa-si schimbe numele. Din acest motiv s-a gasit un mecanism mai eficient de rezolutie. Pentru ca acest mecanism sa functioneze adresa simbolica are doua componente – nume_host si domeniu. Prima componenta desemneaza numele atribuit calculatorului gazda (host) – in exemplul nostru, big_crash. Cea de a doua componenta desemneaza domeniul careia ii apartine calculatorul big_crash(hacjker.ro). Toate calculatoarele din reteaua IP sunt impartite pe domenii, fiecare domeniu fiind gestionat de un calculator pe care ruleaza o aplicatie server ce gestioneaza numele calculatoarelor din domeniul sau. Cea mai raspandita astfel de aplicatie este Barkley Internet Domain Name (BIND) server. Pe calculatoarele functionand sub sistemul de operare Unix, aplicatia BIND este numita named. Calculatorul pe care se executa o astfel de aplicatie se numeste server de nume – Domain Name Server sau mai scurt DNS.De exemplu reteaua IP formata din 6 calculatoare organizate in doua subretele locale (195.65.4 si 196.23.75) de cate trei calculatoare. Reteaua IP are un singur domeniu: root. In domeniul root sunt inscrise cele trei calculatoare ale retelei 195.65.4 sub numele x, y si z si celalalte trei calculatoare ale retelei 196.23.75 avand numele p, q si r. Domeniul net este gestionat de exemplu de calculatorul x avand adresa IP 195.65.4.1 Acasta adresa IP a serverului de nume DNS, este inregistrata de softwareul TCP/IP la configurare. Astfel la configurarea modulului TCP/IP a tuturor calculatoarelor din reteaua IP cu exceptia lui x DNS calculatorul cu adresa IP 195.65.4.1 In cazul serverului de nume x, se va dezactiva DNS-ul. In fisierul hosts al serverului de nume x se vor inregistra numele si adresele de IP ale calculatoarelor din domeniul root deservit. Sa presupunem ca unul din calculatoarele din retea trimite un mesaj pe adresa r.root. Modulul sau IP va cauta in fisierul hosts al calculatorului propriu pentru a gasi adresa IP a lui r.root. Daca expeditorul este calculatorul x care joaca rolul de DNS in reteaua IP, el va gasi in acest fisier inregistrarea : ... r.root ... Va extrage adresa IP 196.23.75.3 asociata numelui r.root, va incapsula mesajul in datagrama si o va trimite pe aceasta adresa de IP. Daca expeditorul este un alt calculator din retea, si nu gaseste inregistrarea lui r.root in fisierul hosts propriu, modulul sau IP va transmite o cerere DNS-ului adresata la adresa IP 195.65.4.1, pe nivelul UDP la portul 53. La acest port asculta aplicatia server care ruleaza pe DNS. Primind cererea ea va cauta adresa IP a lui r.root in fisierul hosts al calculatorului propriu, unde va gasi randul: ... r.root ... Va extrage adresa IP 196.23.75.3 asociata numelui r.root si o va trimite ca raspuns modulului IP al solicitantului. Acesta va incapsula mesajul in datagrama si o va trimite pe aceasta adresa de IP. Daca subretelei 195.65.4 i se adauga un nou calculator w, acesta nu va putea fi adresat prin adresa simbolica pana nu este inregistrat in fisierul hosts a calculatorului DNS x. Gestiunea unui numar prea mare de calculatoare este dificila iar la un numar foarte

Page 211: programare in Java

308 CURS 25 mare imposibila. Solutia consta in impartirea domeniului in subdomenii, fiecare gestionat de un DNS propriu, subordonat DNS-ului domeniului root. In exemplul nostru vom imparti domeniul root in doua subdomenii netA si netB. In subdomeniul netA vom incadra calculatoarele w si z ale retelei 195.65.4 iar in subdomeniul netB vom incadra calculatoarele q si r ale retelei 196.23.75. Calculatorul y va fi desemnat DNS-ul retelei 195.65.4 iar p DNS-ul retelei 196.23.75. In fisierul hosts a lui y vom inregistra pe w, y si z iar in fisierul hosts a lui p vom inregistra pe p, q si r. La configurarea modulelor IP ale calculatoarelor w si z se va inregistra ca DNS adresa lui y iar a lui pe q si r adresa lui p. La configurarea modulelor IP ale calculatoarelor y si p se va inregistra ca DNS adresa lui x. Sa presupunem ca unul din calculatoarele din reteaua 195.65.4 trimite un mesaj pe adresa r.netB.root. Modulul sau IP va cauta in fisierul hosts al calculatorului propriu pentru a gasi adresa IP a lui r.netB.root. Negasind-o va emite o cerere catre DNS-ul sau adica catre y. Acesta stiind ca gestioneaza subdomeniul netA o va pasa spre rezolvare DNS-ului sau ierarhic superior adica lui x. Acesta observand ca subdomeniul careia ii apartine adresa simbolica este netB o va redirecta catre DNS-ul subordonat p care gestioneaza acest subdomeniu. Acesta la randul sau va constata ca domeniul adresei simbolice este in jurisdictia lui, o va gasi inregistrata in fisierul sau hosts si va extrage de acolo adresa de IP asociata 196.23.75.3. Aceasta adresa IP o va returna lui x care la randul sau o va returna lui y care la randul sau o va expedia ca raspuns solicitantului. In figura 25.2 este prezentat acest mecanism de servire a cererilor rezolvare a adreselor simbolice.

Fig.25.2 – Servirea de catre DNS a cererilor de rezolvare a adreselor simbolice

Se observa ca gasirea de catre DNS a adreselor IP asociate adreselor simbolice se bazeaza pe organizarea ierarhica a domeniilor. Specificarea in cadrul adresei simbolice a numelui radacinii nu este obligatorie cautarea facandu-se implicit si acolo.

Page 212: programare in Java

PROGRAMARE IN JAVA - Note de curs 309

De exemplu in loc sa se specifice adresa simbolica completa completa p.netB.root este suficient sa se specifice doar subdomeniul p.netB. Similar, pentru a adresa calculatorul w de pe un calculator din acelasi domeniu, nu mai este nevoie sa fie specificat subdomeniul netA. Adresarea se poate face numai prin numele w. Subdomeniul trebuie specificat numai daca calculatorul adresat se gaseste in alt subdomeniu. de exemplu pentru a adresa calculatorul r din z este necesar sa specificam atat numele calculatorului cat si subdomeniul: r.netB. In caz contrar DNS-ul domeniului lui z va considera ca este cautat un calculator cu numele r aflat in domeniul sau (r.netA) pe care evident ca nu-l vagasi in fisierul sau hosts si va da un mesaj de eroare. In Internet exista definita o ierarhie de subdomenii deservite de servere de nume ca cea reprezentata in figura 25.3.

Fig.25.3 – Ierarhizarea domeniilor in Internet Domeniul com este folosit de organizatii comerciale, int – organizatii internationale, edu de institutii de invatamant, net de furnizorii de servicii Internet, mil – armata SUA iar gov – institutii guvernamentale ale SUA. De asemenea sunt definite domeniile regionale cum ar fi ro pentru Romania, us pentru SUA, ca pentru Canada, uk pentru Anglia, de pentru germania, se pentru suedia, fr pentru Franta, in pentru India, cn pentru china, ja pentru japonia, etc. Avantajul unei astfel de organizari este ca fiecare subdomeniu poate sa-si creeze la randul sau in mod independent propriile subdomenii fara ale anunta in vre-un fel nivelului ierarhic superior. Astfel aparitia retele noi X in Romania, apartinand unei intreprinderi X poate fi inregistrata ca subdomeniu x al domeniului ro prin simpla inregistrare a serverului de nume a acestei retele in serverul de nume al domeniului ro. va aparea astfel un nou subdomeniu x.ro. Apoi, intreprinderea va putea sa-si defineasca singura subdomenii ca servicii.x.ro, clienti.x.ro, cotabilitate.x.ro etc. inregistrandu-le doar pe serverul de nume al domeniului x.ro.

Socluri (Sockets) In cadrul nucleului unui sistem de operare este contine de module API – Application Programming Interface care ofera accesul programatorului la o serie de servicii ale sistemului de operare. Unul din aceste module asigura servicii de comunicatie in retea TCP/IP. Ca standard pentru serviciile oferite de acest modul a fost adoptat cel prevazut in S.O. BSD Unix. S.O. Windows ofera astfel servicii de comunicatie in retea TCP/IP printr-un modul - biblioteca cu legare dinamica DLL (Dinamic Link Library) cunoscut sub numele WinSock API (fisierul WINSOCK.DLL in cazul versiunilor Windows pe 16 bit si WSOCK32.DLL pentru versiunile pe 32 de bit). Aplicatiile acceseaza serviciile de retea oferite de sistem (trimitere si primire de date intr-o retea TCP/IP) prin intermediul interfetei oferite de WinSock API. Aplicatia dialogheaza cu modulul WinSock API apeland procedurile oferite de acesta iar

Page 213: programare in Java

310 CURS 25 WinSock API la randul sau stie sa dialogheaze cu implementarea interna a protocolului TCP/IP preluand asupra sa operatiile de rutina necesare crearii si gestionarii unor canale de comunicatie virtuale. Aceasta simplifica in mare masura programarea aplicatiilor de retea punand la dispozitia programatorului proceduri de comunicatie mai puternice si mai usor de folosit. Aplicatiile de retea comunica dupa schema client – server. Aplicatia server ofera servicii aplicatiilor client. Pentru a obtine un serviciu de la o aplicatie server, aplicatia client trebuie sa se conecteze la aplicatia server, intre cele doua stabilindu-se un canal virtual de comunicatie. Aplicatia server asteapta cereri de conectare de la aplicatiile client “ascultand” un port TCP prestabilit n. In momentul in care apare o cerere de conectare din partea unei aplicatii pe portul n al calculatorului pe care ruleaza aplicatia server, aceasta receptioneaza cererea si poate sa o accepte deschizand un canal de comunicatie duplex pe portul n. Odata creat acest acest canal, aplicatia client poate sa-i transmita serverului mesaje-cereri de servicii. Serverul raspunde aplictiei client rezultatul procesarilor solicitate prin mesajele-cerere. La terminarea servirii serverul sau clientul pot inchide canalul de comunicatie virtuel. Elementul central in crearea si gestionarea unui canal virtual de comunicatie intre doua aplicatii este soclul (socket). Acesta este asemanator unui specificator (handler) de fisier fiind un numar intreg pozitiv care identifica unul din cele doua puncte terminale al unui canal de comunicatie virtuale. Datele transmise pe acest canal sunt bufferizate atat de aplicatia server care transmite datele cat si de aplicatia client care le receptioneaza. Soclul nu este tot una cu portul TCP. Mai curand este un specificator de acces la date care include atat adresa de IP cat si portul prin care se realizeaza comunicatia, caracterizand canalul de comunicatie virtula. De exemplu daca o aplicatie client aflata pe calculatorul 195.65.4.1 comunica cu o aplicatie server primind date de la acesta prin portul 3000, atunci socketul (capatul dinspre client al canalului de comunicatie stabilit cu serverul) ar puatea fi descris prin combinarea adresei IP cu numarul portului: 195.65.4.1.3000. Datele transmise pe canalul virtual al carui capat dinspre client este soclul, pot fi sub forma de stream bidirectional sau de datagrama. Pentru transmisia datagramelor WinSock API va apela la UDP iar pentru date de forma unui stream la TCP. Ca sa clarificam lucrurile sa urmarim urmatorul scenariu. La firma de comercializare a echipamentelor de calcul condusa de mine exista un reprezentant permanent al unei firme producatoare de echipamente de calcul. Eu ii solicit sa imi prezinte o specificatie de preturi si caracteristici tehnice ale unor sisteme in configuratia dorita de mine aceasta fiindu-mi necesara pentru intocmirea unei oferte. Pentru aceasta el trebuie sa ia legatura telefonica cu firma pe care o reprezinta. Eu ii indic unul din telefoanele aflate pe biroul meu. Acesta telefoneaza la un numar de telefon unde raspunde in permanenta un operator a carui sarcina este sa raspunda la solicitarile reprezentantilor comerciali ai firmei. Dupa ce este recunoscut de operator, acesta ii face legatura telefonica cu functionarul de la serviciul desfacere care ii poate furniza datele solicitate de mine. Dupa aceasta operatorul inchide, asteptand noi telefoane de la alti reprezentanti comerciali. In acest timp, dupa ce este pus in tema de reprezentantul firmei, functionarul de la desfacere ii transmite acestuia prin faxul telefonului documentatia solicitata dupa care inchide telefonul. Reprezentantul firmei imi inmaneaza datele solicitate.

In acest exemplu biroul meu este calculatorul pe care ruleaza aplicatia client. Reprezentantul comercial este soclul client, iar numarul de telefon este portul

TCP. Operatia de legatare (bind) a soclului la port s-a facut in momentul in care i-

Page 214: programare in Java

PROGRAMARE IN JAVA - Note de curs 311

am indicat reprezentantului un anumit telefon particular de pe biroul meu. Portul lui este numarul special de telefon de serviciu pe care numai el il poate folosi pentru a-l contacta pe operator.

Operatorul este un “soclu server” care accepta apelul pe acest numar si creaza un alt soclu – functionarul de la desfacere. In acest timp, soclul server este disponibil asteptand noi cereri de conectare.

Eu sunt apelatia client care pentru a solicita un serviciu de la aplicatia server, apeleaza la soclu.

Datele primite sunt faxul receptionat de la soclul functionar. Actiunile de creere a unui canal de comunicatie virtual pot fi grupate in doua categorii: 1 Actiuni executate de aplicatia client:

- creaza un soclu - legarea sa la un anumit port TCP si adresa IP a calculatorului client - se conecteaza la server - transmite/primeste date prin soclu - inchide soclul-conexiune

2 Actiuni executate de aplicatia server: - creaza un soclu – server - leaga soclul la adresa IP si portul sau - astepta un apel de la client - dupa conectarea clientului, serverul accepta conectarea creind un nou soclu

pentru servirea clientului - Soclul server revine la asteptarea unei alte conectari - Soclul nou creat executa operatiile de primire/trimitere a datelor cu clientul

dupa care se inchide. Operatiile de mai sus si starile soclului sunt reprezentate in graful din figura 25.4.

Fig.25.4 Operatiile de conectare prin socluri ale aplicatiilor client-server

Functiile reprezentate in acest graf sunt proceduri ale WinSock API care pot fi apelate de catre aplicatiile client-server pentru realizarea comunicatiei intr-o retea IP.

Page 215: programare in Java

312 CURS 25

Clasele Java pentru aplicatiile de retea TCP/IP Limbajul Java ofera in pachetul java.net o serie de clase necesare realizarii aplicatiilor de retea bazate pe socluri. Una din acestea este clasa Socket. Ea are constructorii:

public Socket(String host, int port) throws UnknownHostException, IOException public Socket(InetAddress address, int port) throws UnknownHostException,

IOException public Socket(String host, int port, InetAddress localAddr, int local Port) throws

UnknownHostException, IOException public Socket(InetAddress address, int port, InetAddress localAddr, int local Port)

throws UnknownHostException, IOException Parametrii constructorilor au urmatoarea semnificatie: String host – un string continand adresa simbolica sub forma nume.domeniu sau IP sub forma W.X.Y.Z a calculatorului la care aplicatia doreste sa se conecteze. int port - portul TCP al aplicatiei de la celalalt capat al canalului de comunicatie. InetAddress address – un obiect din clasa InetAddress care contine adresa IP pe 32 de bit a calculatorului gazda al aplicatiei de la celalalt capat al canalului de comunicatie. InetAddress localAddr– un obiect din clasa InetAddress care contine adresa IP pe 32 de bit a calculatorului gazda local ( pe care ruleaza aplicatia curenta). int local Port- portul TCP al aplicatiei curente. Primii doi constructori sunt folositi pentru a crea un soclu specificand doar adresa si portul aplicatiei cu care se doreste stabilirea legaturii. Ceilalti doi constructori permit construirea soclului specificand in plus si adresa si numarul de port de pe masina locala. Acesti constructori sunt utili in cazul in care calculatorul local are mai multe adrese IP. Adresa poate fi specificata fie sub forma simbolica nume.domeniu fie printr-un obiect al clasei InetAddress care contine adresa IP a calculatorului. Clasa final InetAddress ofera un mecanism unitar de manipulare a adreselor si defineste cateva metode utile pentru obtinerea adresei IP apeland la serviciul DNS:

static InetAddress getLocalHost() throws UnknownHostException – intoarce un obiect dinclasa InetAddress reprezentand calculatorul local

boolean equals(Object otherObject) intoarce true daca celalalt obiect este o instanta InetAddress continand aceeasi adresa.

byte[] getAddress() – intoarce un tablou continand cei patru octeti ai adresei IP. static synchronized InetAddress getByName(String hostName)throws

UnknownHostException – intoarce un obiect InetAddress continand adresa calculatorului specificat prin adresa simbolica.

String toString() – intoarce un string continand numele si adresa de IP. Programul urmator demonstreaza utilizarea unora din aceste metode: import java.net.*; class GetIP { public static void main (String[] args) throws UnknownHostException { System.out.println( InetAddress.getLocalHost().toString()); System.out.println(

InetAddress.getByName("nexus.home.ro").toString()); System.out.println(

InetAddress.getByName("141.85.44.10").toString()); } }

Programul va afisa rezultatele din figura 25.5.

Page 216: programare in Java

PROGRAMARE IN JAVA - Note de curs 313

Fig.25.5 – Obtinerea adresei unui calculator La creerea unui obiect din clasa Socket se deschide un canal virtual de comunicatie duplex intre aplicatia client de pe calculatorul local si aplicatia server de pe calculatorul corespondent. Clasa Socket defineste metode care permit scrierea/citirea in/din canalul virtual de comunicatie asociat. Canalul virtual de comunicatie este constituit din doua streamuri – unul de intrare si altul de iesire. Metodele getInputStream() si getOutputStream() intorc referintele la aceste streamuri. La terminarea comunicatiei cu aplicatia client trebuiesc inchise streamurile de I/O si apoi soclul apeland metoda close(). Programul din exemplul urmator se conecteaza la o aplicatie server de e-mail pe portul TCP dedicat 25 si ii transmite un mesaj pe adresa [email protected] . Dialogul intre aplicatia noastra client si aplicatia server este reprezentat in figura 25.7 si se desfasoara prin transmiterea de catre aplicatia client a unor comenzi aplicatiei server si receptionarea unor raspunsuri de confirmare de la aceasta. Regulile de dialog, setul de comenzi, codurile si mesajele de raspuns sunt definite de protocolul SMTP (Simple Mail Transfer Protocol).

Fig.25.7 – Comunicatia intre o aplicatie client si o aplicatie server SMTP

Page 217: programare in Java

314 CURS 25

import java.net.*;import java.io.*; class SMTP{ public static void main(String[] args){ String command[]={"HELO smtp\r\n", "MAIL FROM: [email protected]\r\n", "RCPT To: [email protected]\r\n","DATA\r\n", "message text\nmessage text\nmesagetext etc.\r\n.\r\n", "QUIT\r\n"}; try{ Socket smtp=new Socket("nexus",25); DataInputStream in=new DataInputStream(

smtp.getInputStream()); DataOutputStream out=new DataOutputStream(

smtp.getOutputStream()); for(int i=0;i<command.length;i++){ System.out.print(in.readLine()+'\n'+command[i]+'\n'); out.writeBytes(command[i]); out.flush(); } System.out.print(in.readLine()+'\n'); in.close();out.close(); smtp.close(); }catch(Exception e){ System.err.println(e.toString());} } } In figura 25.8 sunt prezentate rezultatele afisate de aplicatie la consola si fereastra aplicatiei server MDaemon server 1.1 beta produs de Alt-N Technology.

Fig.25.8 – Protocolul de comunicatie client-server SMTP

Page 218: programare in Java

PROGRAMARE IN JAVA - Note de curs 315

Curs 26

Aplicatii server O aplicatie server trebuie sa poata accepta cereri de servire de la mai multi clienti simultan. Pentru aceasta ea trebuie sa stabileasca cate un canal virtual de comunicatie TCP ori de cate ori un client solicita conectarea. Asa cum am vazut, o aplicatie server care ofera o anumita categorie de servicii este contactata de aplicatia client pe un numar de port unic convenit dinainte. De exemplu am vazut in cursul anterior ca serverul de e-mail (SMTP) accepta conexiuni pe portul 25. Aplicatiile client care au de expediat mesaje e-mail solicita conectarea la server fara sa stie daca acesta este deja in legatura cu o alta aplicatie client sau nu. Chiar daca este deja conectat printr-un canal virtual cu o aplicatie client, serverul trebuie sa detecteze cererea de conectare si sa creeze un al doilea canal virtual de comunicatie cu cel de al doilea client care a solicitat conectarea la serviciile sale. Stim ca un canal virtual de comunicatie are doua capete si are la fiecare capat cate un port. Odata creat un astfel de canal, porturile de la capetele sale devin indisponibile pentru crearea unui alt canal. Se pune intrebarea care este mecanismul prin care mai multe aplicatii client se pot conecta la acelasi port cu un numar fixat al aplicatiei server? Acest mecanism a fost deja prezentat cand am prezentat in cursul trecut modulul WinSock API graful din figura 25.4 explicand functionarea lui. Sa analizam exemplul din figura 26.1.

Fig.26.1 Servirea cererilor de conectare de catre o aplicatie server

Avem o aplicatie server X si mai multe aplicatii client Y,Z,W, etc. Aplicatia server X creaza un soclu pe portul cu numar fixat x (de exemplu 25 - cunoscut de toate aplicatiile client SMTP) si "asculta" la acest port asteptand cereri de conectare. In momentul in care un client Y solicita prin portul sau y conectarea pe portul x al serverului, acesta receptioneaza cererea si poate accepta stabilirea legaturii. In acest caz, el va crea un al doilea soclu cu un numar arbitrar x1 al portului si va stabili legatura cu clientul pe acest soclu. In acest fel, soclul cu numarul de port x ramane

Page 219: programare in Java

316 CURS 26 liber si serverul se poate intoarce la "ascultarea" lui pentru stabilirea altor legaturi cu alti clienti. Canalul virtual de comunicatie stabilit intre clientul Y si serverul X are deci la capatul clientului portul cu numarul y iar la capatul dinspre server portul x1. Daca un alt client Z va cere prin portul sau z conectarea la portul x al serverului, acesta va stabili legatura pe un port cu numar arbitrar x2 diferit de x1 alocat anterior. Serverul va continua sa accepte conexiuni la cerere pentru clientii U,V,W,... alocand la fiecare solicitare un nou port xk si creind canale virtuale de comunicatie cu fiecare client in parte u-x3, v-x4, w-x5, etc.(vezi figura 26.1). Soclul pe care "asculta" serverul este implementat in Java printr-o clasa speciala ServerSocket. Ea permite programatorului sa faca asocieze un soclu de "ascultare" cu un port. Odata creat acest soclu, apeland metoda accept() serverul este blocat, intrand intr-o stare de asteptare a unei cereri de conectare din partea unui client. Serverul revine din executia acestei metode numai atunci cand primeste o cerere de conectare din partea unui client pe portul soclului "ascultat". In momentul in care primeste o cerere de conectare, metoda accept() creeaza un canal virtual de comunicatie cu clientul care a solicitat conectarea pe un port disponibil alocat de S.O. (de exemplu de WinSockAPI) si returneaza soclul (obiect din clasa Socket) de la capatul dinspre server al canalului. Odata revenit din metoda accept() serverul poate sa dialogheze cu clientul prin intermediul soclului returnat de metoda. Clientii ale caror cereri de conectare sosesc in timp ce serverul este ocupat cu servirea unui client se aseaza intr-o coada de asteptare. Cand serverul termina servirea clientului curent si executa din nou metoda accept() va prelua cererea primul client din coada si va crea un canal virtual de comunicatie cu acesta. Clasa ServerSocket are trei constructori:

public ServerSocket(int port) throws IOException – creaza un soclu server care asculta la portul specificat. Soclul admite o coada cu maximum 50 de clienti.

public ServerSocket(int port, int count) throws IOException - creaza un soclu server care asculta la portul specificat. Soclul admite o coada cu maximum count clienti.

public ServerSocket(int port, int count, InetAddress localAddr) throws IOException – Acest constructor se foloseste in cazul aplicatiilor server care ruleaza pe un calculator cu mai multe interfete de retea fiecare cu o adresa IP proprie. El creaza un soclu server care asculta la portul specificat pe adresa IP locala a uneia din interfete. Soclul admite o coada cu maximum count clienti.

In programul urmator este prezentata o aplicatie server care accepta conexiuni pe portul 2222. In momentul in care conexiunea este stabilita, serverul solicita aplicatiei client o parola. Dupa receptionarea parolei, o compara cu stringul "sesam deschide-te". Daca cele doua stringuri sunt egale afiseaza mesaj de bun venit "Bine ai venit Alibaba! Exprima-ti o dorinta (QUIT, EXIT,CLOSE):" asteptand apoi receptia unui sir de caractere din cele trei. Daca sirul receptionat este unul din cele trei va afisa mesajul "Ascult si ma supun stapane!" dupa care daca comanda este "QUIT" sau "EXIT" inchide conexiunea iar daca este "CLOSE" inchide in plus si soclul server si se termina. In caz contrar va relua receptionarea unei comenzi. Daca parola receptionata nu este corecta reia inca de doua ori citirea parolei dupa care inchide conexiunea. Pentru a testa aplicatia server ne vom folosi de aplicatia de sistem telnet. Aceasta emuleaza un terminal VT100 si permite crearea unei conexiuni la un server a carui adresa si port se configureaza (figura 26.2). Terminalul telnet trimite catre server liniile de caractere tastate de utilizator (afisate in ecou si in fereastra aplicatiei) si afiseaza liniile de text primite de la aplicatia server. In figura 26.1 mai sunt redate si mesajele afisate de aplicatia server la consola si cele afisate in fereastra telnet.

Page 220: programare in Java

PROGRAMARE IN JAVA - Note de curs 317

Figura 26.1 – O sesiune telnet de conectare la serverul Gate.class

import java.io.*; net.*; import java.

class Gate{ static void main(String[] args){ public

try{ tring psw,cmd; S

Socket s;

n; DataInputStream i PrintStream out;

cket ss=new ServerSocket(2222); ServerSo close:{ for(;;){

tln("Waiting for connection request..."); System.out.prin s=ss.accept(); System.out.println("New connection...");

; in=new DataInputStream(s.getInputStream())tStream(s.getOutputStream()); out=new Prin disconect:{

k=0; int do{

:"); out.print("password psw=in.readLine(); if(psw.equals("sesam deschide-te")){

ranted..."); System.out.println("Alibaba access g out.print("Bine ai venit Alibaba!"+

" Vre-o dorinta (QUIT/EXIT/CLOSE)?"); for(;;){

Page 221: programare in Java

318 CURS 26 cmd=in.readLine(); System.out.println(cmd); if(cmd.equals("QUIT")||cmd.equals("EXIT")){ out.println("Ascult si ma supun stapine!\r"); break disconect; } if(cmd.equals("CLOSE")) break close; out.print(cmd+"!!!!!(QUIT/EXIT/CLOSE)?"); } } out.println("Wrong password...\r"); k++; }while(k<3); } out.println("Connection closed...\r"); in.close(); out.close(); s.close(); System.out.println("Connection closed..."); } } in.close(); out.close(); s.close(); ss.close(); System.out.println("Connection closed..."); System.out.println("Server shut down..."); } catch(Exception e){ System.err.println(e.toString());} } }

Aplicatii client-server multithread In exemplul anterior serverul actioneaza de asa maniera incat nu apeleaza functia accept() decat atunci cand clientul servit curent inchide sesiunea de lucru. In acest timp ceilalti clienti care au solicitat intre timp conectarea sunt tinuti in coada de asteptare a soclului server. Sunt situatii in care o atare comportare a serverului este inacceptabila. Sa analizam de exemplu cazul urmator: Mai multe aplicatii client trebuie sa converseze intre ei prin schimb de mesaje. Nici unul nu stie cati clienti sunt conectati la un moment dat si cine sunt ei. Sarcina aplicatiei server este sa asigure evidenta clientilor care sunt conecti la un moment dat si transmiterea unui mesaj sosit de la un client la toti ceilalti. In acest caz serverul trebuie sa asigure deci simultan servirea cererilor de conectare, receptionarea mesajelor si retrimiterea mesajelor sosite catre ceilalti clienti conectati. Este evident ca numai threadul principal nu poate face fata acestei sarcini deoarece de exemplu daca asteapta conectarea unui client el este de fapt blocat in executie metodei accept() si deci nu poate sa primeasca mesaje de la ceilalti clienti deja conectati. Similar daca asteapta receptia unui mesaj dintr-un stream de intrare, iarasi este blocat in executia metodei readLine() pana o aplicatie client nu transmite un mesaj. In acest timp nu poate primi cereri de conectare de la alti clienti. Solutia evidenta este ca activitatea sa fie distribuita intre mai multe threduri cu sarcini specifice. Thredul principal ar putea sa se ocupe cu primirea cererilor de conectare si gestiunea conexiunilor. Ori de cate ori se creaza o noua conexiune ea va fi inmanata spre gestionare unui thread care va receptiona mesajele sosite de la clientul conectat la celalalt capat al acestui canal virtual de comunicatie. Acesta va depune mesajul intr-un buffer gestionat de un alt

Page 222: programare in Java

PROGRAMARE IN JAVA - Note de curs 319

thread care se ocupa cu expedierea mesajelor din buffer spre toti ceilalti clienti conectati. Pe de alta parte si clientii trebuie sa fie multithread deoarece un singur thread nu poate gestiona singur si trimiterea si receptia mesajelor. Daca de exemplu el se afla blocat in executia metodei readLine() asteptand sosirea unui mesaj in bufferul streamului de intrare, el nu va putea sa execute in acelasi timp metoda de citire a mesajului introdus de utilizator de la tastatura. De aceea si aplicatia client trebuie sa aibe doua threaduri – unul care sa se ocupe cu receptia mesajelor sosite de la server si altul care sa citeasca o linie de text de la tastatura si sa o expedieze sub forma de mesaj serverului pentru difuzare. In figura 26.3 este reprezentata schematic cooperarea dintre threadurile aplicatiilor client si server pentru realizarea acestei scheme de comunicatie.

Fig.26.3 – Cooperarea threadurilor in aplicatiilor client si server

Aplicatia server, dupa cum se vede din schema, este bazata pe activitatea a trei clase de threaduri, s, Tx si Rx. Threadul s este cel care gestioneaza cererile de conectare. El "asculta" la soclul server x pe portul cu numarul 2222 (ales arbitrar). In momentul in care primeste o cerere de conectare el va crea un "canal" de comunicatie duplex prin soclul intors de metoda accept() a soclului server x, si-l va "implanta" in primul "slot" i liber al tabloului de conexiuni. La creerea canalului de comunicatie duplex se va crea si un thred din clasa Rx care va incepe receptia mesajelor sosite prin soclul canalului din slotul i. Dupa aceste operatii, threadul s va reincepe "ascultarea" portului 2222 asteptand o noua cerere de conectare.

Page 223: programare in Java

320 CURS 26 Threadul Rx asteapta sosirea unui pachet continand un mesaj de la client. In momentul receptiei acestuia, il inscrie intr-o coada de pachete pentru reexpediere dupa care revine la receptionarea unui nou pachet. Daca clientul se deconecteaza inchizand soclul de la capatul sau al canalului de comunicatie threadul Rx receptioneaza un string null. Threadul inchide la randul sau canalul si il extrage din tabloul de conexiuni. Dupa aceasta operatie threadul Rx se "sinucide" eliberand memoria alocata. Threadul Tx are sarcina sa reexpedieze pachetele din coada de pachete. El extrage pachetele din coada si le expediaza pe toate canalele virtuale de comunicatie gasite in sloturile tabloului de conexiuni. Coada de pachete este o lista inlantuita de tip FiFo (First In First Out – primul intrat, primul iesit) ale carei elemente sunt generate dinamic. Ea are structura din figura 26.4.

Fig.26.4 – Operatii de adaugare/extragere de elemente in/din coada Coada are doua variabile head (cap) si tail (coada), referinte la obiecte de tip element al listei. Variabila head refera primul element al listei iar tail pe ultimul. Initial coada este vida si deci cele doua elemente contin valorile null. Elementele listei sunt obiecte care au doua campuri, unul continand informatia utila iar celalalt o "legatura"-referinta la urmatorul element din lista. Daca elementul este ultimul element din lista, aceasta "legatura" este null. La adaugarea elementelor noi in coada se executa metoda pop(obj) care daca lista este vida inscrie referinta obj in ambele variabile head si tail. Daca lista nu este vida, ultimul element din lista se leaga la elementul nou adaugat prin inscrierea in campul sau de legatura a referintei la acesta. Referinta la elementul adaugat se inscrie si in variabila tail, devenind astfel ultimul element din lista. La extragerea elementelor din lista se apeleaza metoda push() care "dezleaga" primul element al listei inscriind in variabila head referinta continuta de campul de legatura al primului elementului din lista (referit de head). Metoda pop() intoarce referinta la obiectul extras din coada. Daca elementul extras este ultimul ( are in campul de legatura referinta null) se inscrie null si in variabila tail, lista devenind din nou vida. Metodele push() si pop() sunt sincronizate, lista fiind accesata de mai multe threaduri executate concurent (Threadurile Rx inscriu pachete in lista iar Tx extrage pachete din lista). In plus, daca lista este vida threadul Tx este blocat la apelul metodei pop() pina la adaugarea de catre un thread Rx a unui pachet nou in lista.

Page 224: programare in Java

PROGRAMARE IN JAVA - Note de curs 321

Pachetele sunt blocuri de octeti de dimensiune variabila construite de metoda statica newPacket(String msg) a clasei Packet. Ea creaza si intoarce referinta la un tablou de octeti construit pe baza argumentului String msg adaugandu-i la sfarsit caracterele de control <CR><LF> care constituie terminatorul de linie. Programul este prezentat mai jos: import java.net.*; import java.io.*; class TCPServer implements Runnable{ private final static int PORT=2222; private final static int MAX_CLIENTS=50; private Channel[] ch; private Thread tx; private ServerSocket ss; private Socket s; private Queue que; public TCPServer(){ ch=new Channel[MAX_CLIENTS]; try{ ss=new ServerSocket(PORT); } catch(IOException ex){ System.err.println(ex.toString()); System.exit(0); } que=new Queue(); } public static void main(String[] args){ int i; TCPServer server=new TCPServer(); server.tx=new Thread(server); server.tx.setDaemon(true); server.tx.start(); System.out.println("server is listening on port nr."+PORT); for(;;){ try{ server.s=server.ss.accept(); } catch(IOException ex){ System.out.println("Server socket error: "+

ex.toString()); } System.out.println("Connection request..."); for(i=0;i<MAX_CLIENTS;i++) if(server.ch[i]==null)break; if(i<MAX_CLIENTS){ System.out.println("New connection on channel "+i); server.ch[i]=new Channel(

server.ch,i,server.s,server.que); server.ch[i].start(); }else{ try{ server.s.close(); } catch(IOException ex){ System.out.println("Error closing socket: "+ ex.toString()); } } } }

Page 225: programare in Java

322 CURS 26 public void run(){ byte[] packet; for(;;){ packet=(byte[])que.pop(); for(int i=0;i<MAX_CLIENTS;i++) if(ch[i]!=null) ch[i].output(packet); } } } class Queue{ public synchronized Object pop() { while(head==null){ try{ wait(); // block until we get an item }catch(InterruptedException e){} } Object obj=head.obj;// pop the first item; head=head.next; if (head==null)tail=null; return obj; } public synchronized void push(Object obj) { if(head==null)head=tail=new QueLink(obj); else{ tail.next=new QueLink(obj); tail=tail.next; } notify(); // wake up blocked threads } private QueLink head=null, tail=null; } class QueLink { public QueLink (Object obj) { this.obj=obj; next=null; } public QueLink next; public Object obj; } class Channel implements Runnable{ private boolean isRunning=false; private Channel[] ch; private int thisChannel; private Socket s; private Queue que; private DataInputStream in; private Thread receiver; public Channel(Channel[] ch,int i,Socket s,Queue q){ this.ch=ch; thisChannel=i; this.s=s; que=q; try{ in=new DataInputStream(s.getInputStream()); } catch(IOException ex){

Page 226: programare in Java

PROGRAMARE IN JAVA - Note de curs 323

System.out.println("Error opening input stream: "+ ex.toString()); } } public void start(){ receiver=new Thread(this); receiver.setDaemon(true); isRunning=true; receiver.start(); } public void finalize(){ receiver.stop(); receiver=null; //System.gc(); } private void stop(){ ch[thisChannel]=null; } public void output(byte[] packet){ try{ s.getOutputStream().write(packet); } catch(IOException ex){ System.out.println("Error writing packet to channel "+ thisChannel+"; ("+ex.toString()+")"); } } public void run(){ String msg=""; while(isRunning){ try{ if((msg=in.readLine())!=null){ System.out.println("Received '"+msg+ "' on channel "+thisChannel); que.push(Packet.newPacket(msg)); }else{ System.out.println("Connection "+thisChannel+ " closed by client."); isRunning=false;stop(); } }catch(IOException ex){ System.out.println("Connection "+thisChannel+ " closed by client."); isRunning=false;stop(); } } } } class Packet{ public static byte[] newPacket(String msg){ byte[] buffer; int length=msg.length(); buffer=new byte[length+2]; buffer[length]='\r'; buffer[length+1]='\n'; msg.getBytes(0,length,buffer,0); return buffer; } } In figura 26.5 sunt prezentate rezultatele afisate de programul TCPServer.class in timpul servirii a trei clienti telnet conectati pe portul 2222.

Page 227: programare in Java

324 CURS 26

Fig.26.5 – Activitatea unui server TCP/IP multithred Aplicatia client este redata mai jos: import java.net.*; import java.io.*; import java.awt.*; class Client extends Frame{ private final static int PORT=2222; private TextField send=new TextField(30), recv=new TextField(30); private Button btn; private boolean btnStat; private Socket s; private DataInputStream in; private DataOutputStream out; public Client(){ super("Aplicatie Client"); btnStat=false; btn=new Button("Start"); setLayout(new FlowLayout()); recv.setEditable(false); add(send);add(recv);add(btn); resize(220,115); show(); } public boolean handleEvent(Event e){ if(e.id==e.WINDOW_DESTROY){ quit(); return true; } return super.handleEvent(e); } public boolean action(Event e, Object o){ String msg; if(e.target==btn) if(btnStat){ try{ in.close(); out.close(); s.close(); s=null; System.gc();

Page 228: programare in Java

PROGRAMARE IN JAVA - Note de curs 325

btnStat=false; btn.setLabel("Start"); }catch(IOException ex){} }else{ try{ s=new Socket("141.85.44.10",PORT); in=new DataInputStream(s.getInputStream()); out=new DataOutputStream(s.getOutputStream()); btnStat=true; btn.setLabel("Stop"); }catch(IOException ex){} } else{ try{ msg=send.getText(); send.setText(""); out.write(Packet.newPacket(msg)); } catch(IOException ex){} } return true; } private void quit(){ System.out.println("Client shut down..."); hide();dispose();System.exit(0); } public static void main(String[] args){ String msg=""; Client client=new Client(); for(;;) if(client.btnStat){ try{ if((msg=client.in.readLine())!=null) client.recv.setText(msg); else{ client.in.close(); client.out.close(); client.s.close(); client.s=null; System.gc(); client.btnStat=false; client.btn.setLabel("Start"); client.recv.setText(""); } } catch(IOException ex){ try{ client.in.close(); client.out.close(); client.s.close(); client.s=null; System.gc(); client.btnStat=false; client.btn.setLabel("Start"); client.recv.setText(""); }catch(IOException e){} } } } }

Page 229: programare in Java

326 CURS 26 In aceasta aplicatie, threadul receptor este chiar thredul principal iar cel emitator este threadul AWT. Ferestrele afisate de trei aplicatii client si mesajele afisate de server sunt prezentate in figura 25.6.

Fig.26.6 – Aplicatii client-server TCP

Socluri UDP Asa cum am vazut, la nivelul transport, pe langa protocolul TCP este definit ca alternativa mai eficienta dar mai putin robusta protocolul UDP. Pachetul java.net ofera instrumentele necesare comunicatiei pe baza acestui protocol. Astfel pentru crearea datagramelor este prevazuta clasa DatagramPacket. Ea permite atat crearea de datagrame UDP cat si extragerea dintr-o datagrama receptionata a datelor mesajului, adresei IP a expeditorului si a meta-informatiei continute. Pentru crearea unei datagrame se foloseste constructorul clasei: public DatagramPacket(byte[] ibuf, int length, InetAddress iaddr, int port) – unde ibuf este un tablou de octeti continand datele mesajului, length - lungimea acestuia, iaddr – adresa IP a destinatarului, port – portul la care trebuie livrata datagrama. Pentru receptionarea unei datagrame UDP trebuie de asemenea creat un obiect din clasa DatagramPacket in care datagrama receptionata sa fie inscrisa. Constructorul folosit in acest caz este: public DatagramPacket(byte[] ibuf, int length) – unde ibuf este un tablou de octeti in care se vor inscrie datele mesajului iar length – numarul de octeti ce trebuiesc copiati din datagrama UDP receptionata in acest buffer. Clasa DatagramPacket prevede in plus metode pentru obtinerea meta-informatiei continute de datagrama UDP receptionata:

public int getLength() – intoarce lungimea in octeti a informatiei utile din datagrama.

public byte[] getData() – intoarce o referinta la tabloul de octeti de date. public InetAddress getAddress() – intoarce adresa IP a expeditorului. public int getPort() – intoarce portul UDP pe care s-a receptionat datagrama.

Operatiile de transmisie si receptie a datagremelor UDP este indeplinita de obiecte de tip soclu UDP, instante ale clasei DatagramSocket. Aceasta defineste urmatorii constructori:

Page 230: programare in Java

PROGRAMARE IN JAVA - Note de curs 327

public DatagramSocket() throws IOException – construieste un soclu UDP pe un port arbitrar. public DatagramSocket(int port) throws IOException – construieste un soclu UDP pe portul cu numarul port. public DatagramSocket(int port, InetAddress localAddr) throws IOException – folosit pentru cazul calculatoarelor cu mai multe interfete de retea, fiecare cu adresa IP diferita, construieste un soclu UDP pe portul cu numarul port si adresa IP a interfetei de retea specificate. Clasa DatagramSocket defineste urmatoarele metode: public void send(DatagramPacket p) ) throws IOException – care expediaza in reteaua IP datagrama transmisa ca parametru. public synchronized void receive(DatagramPacket p) ) throws IOException – care asteapta sa fie receptionata din reteaua IP datagrama transmisa pe adresa IP locala a calculatorului si portul soclului UDP. Datagrama UDP receptionata este inscrisa in obiectul din clasa DatagramPacket p specificat ca parametru. Aplicatia client-server prezentata in continuare exemplifica utilizarea acestor metode: import java.net.*; import java.util.*; class UDPServer{ public final static int PORT=2222; public static void main(String[] args)throws Exception{ DatagramPacket dp; int port; byte[] packet; InetAddress addr=InetAddress.getByName("nexus.home.ro"); DatagramSocket ds=new DatagramSocket(PORT); for(;;){ dp=new DatagramPacket(new byte[2],2); System.out.println("Waiting request..."); ds.receive(dp); addr=dp.getAddress(); port=dp.getPort(); System.out.println("Received request from "+ addr.toString()); packet=Packet.newPacket((new Date()).toString()); dp=new DatagramPacket(packet,packet.length,addr,port); System.out.println("Sending date to "+ addr.toString()+":"+port); ds.send(dp); } } } Programul de mai sus implementeaza aplicatia server UDP. Acesta asteapta sosirea pe portul 2222 a unei cereri din partea unei aplicatii client. Din datagrama UDP primita ca cerere extrage adresa calculatorului pe care ruleaza aplicatia client si portul la care aceasta va astepta raspunsul. Pe aceasta adresa si la acest port trimite o datagrama continand data si ora curenta dupa care reia asteptarea unei noi cereri. Aplicatia client are codul sursa de mai jos: import java.net.*; import java.util.*; class UDPClient{ public final static int PORT=2222; public static void main(String[] args)throws Exception{ DatagramPacket dp; byte[] packet=new byte[64]; InetAddress addr=InetAddress.getByName("nexus.home.ro");

Page 231: programare in Java

328 CURS 26

DatagramSocket ds=new DatagramSocket(); for(;;){ Thread.sleep(10000); dp=new DatagramPacket(Packet.newPacket(""),2,addr,PORT); System.out.println("Sending request to "+

addr.toString()+"..."); ds.send(dp); dp=new DatagramPacket(packet,packet.length); ds.receive(dp); System.out.println("Current date:"+

(new String(dp.getData(),0))); } } } Aplicatia client lanseaza la fiecare 10 secunde o cerere de servire prin transmiterea unui pachet gol pe adresa calculatorului server, la portul 2222. Dupa aceea asteapta sosirea datagramei de la server din care extrage mesajul continand data si ora curenta a serverului. Mesajul primit este afisat la consola. Mesajele afisate la consola de 2 aplicatii client si aplicatia server, sunt redate in figura 26.7

Fig.26.6 – Aplicatii client-server UDP

Page 232: programare in Java

PROGRAMARE IN JAVA - Note de curs 341

Curs 28

Structura documentelor HTML Fie fisierul text3.txt copiat in text4.html: <IMG SRC=javamatic.gif> <P>Javamatic</P> <P>Internet/Intranet Software</P> <P></P> <P><B>SERVICES</B></P> <P><I>- IT Professional Services</P> <P>- System Integration</P> <P>- Software applications</P> <P>- Consulting hw/sw</P> <P>- Networking</P> <P>- Custom Internet/Intranet client-server applications</P> <P><B>Call Now!</B></I> Incarcand cu browserul MSIE fisierul text4.html acesta va afisa in fereastra sa documentul executand procesarile specificate de marcajele prevazute in textul acestuia (figura 28.1).

Fig.28.1 – documentul text4.html afisat de MSIE La afisarea acestui document, browserul a facut exact aceleasi operatii de procesare a textului ca si programul Doc.class elaborat de noi. De fapt marcajele convenite la

Page 233: programare in Java

342 CURS 28 scrierea programului nu au fost alese arbitrar, fiind chiar cele din setul de marcaje HTML. Documentul HTML original, numit si sursa, a carui reprezentare apare in figura 27.9 este redat in listingul 28.1. Listing 28.1 - Cybertyse.html <HTML> <HEAD> <TITLE>Cybertise's Virtual Gallery</TITLE> </HEAD> <BODY BGCOLOR=#FFFFFF TEXT=#000000 LINK=#0000FF VLINK=#008000 ALINK=#FFFF00> <CENTER><img src="images/gallery2.jpg" alt="Virtual Gallery"></CENTER> <br> <center><tt><h2>Welcome to the Cybertise Virtual Gallery.</h2></tt></center> <center>This is a collection of design and artwork done by Cybertise artists for clients, fun, or friends.</center> <center>All of the graphics on this Web site were done by Cybertise (except for obvious icons), and there are more to come in the future.</center> <br> <center><tt>Just click on a designer's name and away you go...</tt></center> <br> <center> <table border=3> <tr> <td> <a href="gallery/index.html"><img border=0 src="images/williams.gif" alt="Ken Williams"></a> </td> <td> <a href="gallery/index2.html"><img border=0 src="images/wing.jpg" alt="Chris Wing"></a> </td> </tr> </table> </center> </body> </html> Se vede ca este vorba de un simplu fisier text. Ceea ce face ca acest document sa fie afisat intr-o forma mult mai atragatoare sunt codurile de marcaj sau tag-urile ( acele portiuni din text scrise intre perechi de paranteze unghiulare < > ) care controleaza formatarea si aspectul reprezentarii sale in fereastra navigatorului. Acestea reprezinta de fapt niste instructiuni, comenzi date browserului privind structura documentului si modul de afisare al textului incadrat de aceste marcaje. Cea mai eficienta cale de a invata cum se construieste o pagina de Web este de a crea una. Pentru aceasta folositi un program de editare de text, de exemplu Notepad din accesoriile Windows. Se editeaza un fisier cu numele Prima.html continand textul din listingul 28.2. Pentru a personaliza intr-un fel exemplul nostru, vom presupune ca fiind conectat la Internet prin sistemul subspatial de comunicatii galactice la mare distanta, capitanul navei spatiale Entreprise, Jean-Luc Picard, s-a hotarat sa creeze pe WWW o pagina Web a acesteia pentru ca toti klingonii, romulanii si ferengii dar si celelalte fiinte la fel de inteligente si conectate la Internet din Galaxie sa aibe informatii despre misiunea echipajului sau .

Page 234: programare in Java

PROGRAMARE IN JAVA - Note de curs 343

Pentru inceput el a editat urmatorul document HTML: Listing 28.2 - Prima.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> Welcome to Galaxy class starship, USS Entreprise, 1701-D. </body> </html> Observati ca nu are importanta daca cuvintele cheie, cum ar fi HTML, TITLE, BODY, ce apar in marcajele HTML < > sunt scrise cu litere mari sau mici. Putem scrie dupa preferinta <HTML> sau <html>, browserul nu va face nici o diferenta la interpretarea acestora. Totusi, folosirea literelor mari este recomandata, marcajele iesind astfel in evidenta in textul documentului HTML, structura acestuia devenind astfel mai evidenta. De asemenea caracterele SPATIU, TAB si ENTER sunt ignorate de browser. data editat fisierul Prima.html, incarcandu-l cu browserul favorit, veti obtine o imagine similara cu cea din figura 28.2.

HEAD / TITLE BODY

Fig.28.2 Prima pagina Web a capitanului Picard

Marcaje de structura Sa analizam un pic documentul HTML creat de noi. Observam ca el, pe langa textul ce va fi afisat de browser, contine diferite marcaje care, in acest caz, definesc structura documentului. Astfel indiferent de continutul documentului HTMl, acesta trebuie sa inceapa cu marcajul <HTML> si sa se termine cu marcajul complementar </HTML>: <HTML>

… Continutul documentului …

Page 235: programare in Java

344 CURS 28 </HTML> Aceste marcaje definesc inceputul respectiv sfarsitul documentului HTML. Majoritatea marcajelor limbajului HTML se folosesc la fel ca si cele de mai sus, perechi. Unul, de exemplu <…>, marcheaza inceputul unui bloc de text iar perechea sa </…>, avind acelasi nume ca si primul dar precedat de un caracter “slash” (/) sfarsitul blocului. Documentele HTML sunt structurate in doua sectiuni - antetul documentului si corpul acestuia. Antetul este un bloc de text delimitat de perechea <HEAD> si </HEAD>. Corpul documentului este delimitat de marcajele <BODY> si </BODY>: <HTML>

<HEAD> … Antetul documentului …

</HEAD> <BODY>

… Corpul documentului … </BODY>

</HTML> Antetul documentului HTML poate sa contina la rindul sau mai multe elemente dar obligatoriu trebuie sa fie prevazut numai titlul acestuia - un text incadrat de marcajele pereche <TITLE> si </TITLE>. Acest text este afisat de majoritatea browserelor in bara de titlu a ferestrei aplicatiei ca in figura 2.1. <HTML>

<HEAD> <TITLE>

… Titlul documentului … <\TITLE>

</HEAD> <BODY>

… Corpul documentului … </BODY>

</HTML> Corpul documentului poate sa contina la rindul sau mai multe elemente pe care le vom analiza in continuare.

Crearea subtitlurilor Limbajul HTML prevede marcaje pentru a crea subtitluri de diverse dimensiuni. Sunt posibile sase niveluri de subtitluri de la cele mai mari marcate la inceput cu <H1> pana la cele mai mici marcate cu <H6>. Ca si marcajele discutate inainte marcajele de subtitluri se termina cu marcajele pereche de incheiere </H1> pana la </H6>. Pe majoritatea sistemelor subtitlurile de nivel 6 au literele foarte mici, fiind dificil de citit. In figura 28.3 se poate observa efectul introducerii subtitlurilor in document. Documentul sursa HTML corespunzator paginii afisate in figura 28.3 este cel din Listingul 28.3. In acest listing se observa ca marcajele de sfirsit de subtitlu au fost

Page 236: programare in Java

PROGRAMARE IN JAVA - Note de curs 345

aliniate prin introducere de caractere TAB. Prin aceasta nu este influentat cu nimic modul de afisare al paginii dar se imbunatateste lizibilitatea programului, marcajele de sfarsit sunt mai usor de vazut in listing. Listing 28.3 - Headings.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <H1> STAR TREK </H1> <H2> THE NEXT GENERATION </H2> <H3> Welcome to Galaxy class starship, USS Entreprise, 1701-D </H3> <H4> TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE. </H4> <H5> Stardate 48030.4581 </H5> <H6> This page is under heavy construction! </H6> The USS Entreprise, NCC -1701-D, is a Galaxy class starship built at the Utopia Plannitia Fleet Yards above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard. This latest starship is Starfleet‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the Federation. </BODY> </HTML>

Fig.28.3 Crearea subtitlurilor

Paragrafe Analizand cu atentie listingul si imaginea afisata, vom constata ca browserul a ignorat la afisarea textului despartirea pe randuri facuta de capitanul Picard si a facut propria sa despartire in functie de latimea ferestrei de afisare. Astfel in listing apare textul privind nava Entreprise pe 8 randuri iar browserul o afiseaza numai pe 5: textul sursa: The USS Entreprise, NCC -1701-D, is a Galaxy class starship

Page 237: programare in Java

346 CURS 28 built at the Utopia Plannitia Fleet Yards above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard. This latest starship is Starfleet‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the Federation. textul afisat de browser: The USS Entreprise, NCC -1701-D, is a Galaxy class starship built at the Utopia Plannitia Fleet Yards above Mars. It was comiissioned in 2363, and is currently under command of Captain Jean-Luc Picard.This latest starship is Starfleet‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the Federation.

Daca fereastra de afisare ar fi fost mai lata, numarul de randuri al textului ar fi fost mai mic iar daca ar fi fost mai ingusta , mai mare. Pentru a putea desparti textele din corpul documentului HTML in paragrafe, fortand browserul sa afiseze textul paragrafului de la inceput de rand, se folosesc marcajele <P> si </P> care delimiteaza continutul unui paragraf. Listingul 28.4 si Fig.28.4 arata modul de introducere si efectul acestor marcaje intr-un document HTML. Listing 28.4 - Paragraphs.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <H1> STAR TREK </H1> <H2> THE NEXT GENERATION </H2> <H3> Welcome to Galaxy class starship, USS Entreprise, 1701-D </H3> <H4> TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE. </H4> <H5> Stardate 48030.4581 </H5> <H6> This page is under heavy construction! </H6> <P>The USS Entreprise, NCC -1701-D, is a Galaxy class starship built at the Utopia Plannitia Fleet Yards above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.</P> <P>This latest starship is Starfleet‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the Federation.</P> </BODY> </HTML>

Fig.28.4 - Despartirea textului in paragrafe

Delimitarea randurilor

Page 238: programare in Java

PROGRAMARE IN JAVA - Note de curs 347

Acum textul arata ceva mai bine. Problema este ca spatiul dintre cele doua fragmente de text este cam mare. Daca vreti ca browserul sa intrerupa linia de text afisata dar sa nu lase spatiu liber pana la linia urmatoare, se foloseste marcajul <BR> (Break line). Marcajul <BR> nu are un marcaj pereche </BR> - este un marcaj vid. Utilizarea acestui marcaj si rezultatul obtinut se vede in listingul 28.5 respectiv in Fig.28.5. Listing 28.5 - LineBreaks.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <H1> STAR TREK </H1> <H2> THE NEXT GENERATION </H2> <H3> Welcome to Galaxy class starship, USS Entreprise, 1701-D </H3> <H4> TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT <BR> NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO <BR> WHERE NO ONE HAS GONE BEFORE. </H4> <H5> Stardate 48030.4581 </H5> <H6> This page is under heavy construction! </H6> The USS Entreprise, NCC -1701-D, is a Galaxy class starship built at the Utopia Plannitia Fleet Yards above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is Starfleet‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the Federation. </BODY> </HTML>

Fig.28.5 - Delimitarea liniilor

Page 239: programare in Java

348 CURS 28

Adaugarea unei linii de separatie Acum ca am lipit intre ele cele doua paragrafe, subtitlurile nu arata prea bine contopindu-se cu textul paginii. O linie orizontala care sa separe vizual subtitlurile de text ar face ca pagina sa arate mai organizat. astfel de linie (rigla) se introduce cu marcajul <HR> (Horizontal Rule). Aceasta nu numai determina browserul sa deseneze o linie orizontala pe ecran dar, in punctul in care este introdus in text, determina o trecere la un rand nou . Ca si <BR>, marcajul <HR>nu are o pereche </HR> (marcaj vid). Utilizarea acestui marcaj si rezultatul obtinut se vede in listingul 28.6 respectiv in Fig.28.6. Listing 28.6 - Rules.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <H1> STAR TREK </H1> <H2> THE NEXT GENERATION </H2> <HR> <H3> Welcome to Galaxy class starship, USS Entreprise, 1701-D </H3> <H4> TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT <BR> NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO <BR> WHERE NO ONE HAS GONE BEFORE. </H4> <HR> <H5> Stardate 48030.4581 </H5> <H6> This page is under heavy construction! </H6> <HR> The USS Entreprise, NCC -1701-D, is a Galaxy class starship built at the Utopia Plannitia Fleet Yards above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is Starfleet‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the Federation. <HR> </BODY> </HTML>

Page 240: programare in Java

PROGRAMARE IN JAVA - Note de curs 349

Fig.28.6 - Utilizarea riglelor

Comentarii Pe langa informatia ce urmeaza a fi afisata, in textul documentului HTML este util sa fie introduse comentarii, adica blocuri de text care nu sunt afisate de browser dar pot servi pentru o mai buna documentare a documentului. Pot fi astfel inscrise informatii privind autorul documentului, data si natura modificarilor aduse in timp acestuia, alte explicatii privind documentul respectiv. Marcajul folosit pentru a indica inceputul comentariului este <!--. Sfirsitul comentariului se indica cu marcajul - ->. In listingul 28.7 se poate vedea utilizarea acestor marcaje.

Stilul textului afisat La procesoarele de texte cum ar fi Microsoft Word for Windows pentru a imbunatati impactul vizual al textelor editate avem posibilitatea de a alege corpuri de litera diferite (fonte) si stiluri de afisare a acestora (Bold/ Italic/Underline) In cazul HTML nu putem alege un anume corp de litera dar putem totusi specifica stilul in care textul va fi afisat. Capitanul Picard a folosit in pagina sa Web imbunatatita marcajele <B></B> pentru stilul Bold(accentuat), <I></I> pentru italic, si <U></U> pentru Underline(subliniat). Fig.28.7 si Listingul 28.7 prezinta aceasta pagina Web si documentul sursa HTML corespunzator. Listing 28.7 - Styles.html <HTML> <!-- This is the new and iprouved HTML page of USS Entreprise 1701-D For klingons and Romulans use only. Created on stardate 48030.4581 by Captain Jen-Luc Picard. - -> <HEAD><TITLE>USS Entreprise - Home Page</TITLE></HEAD> <BODY> <H1> <B><I>STAR TREK</I></B></H1> <H2> <B><I>THE NEXT GENERATION</I></B></H2><HR> <H3> <I>Welcome to Galaxy class starship,USS Entreprise, 1701-D </I></H3> <H4> <I>TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT <BR> NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO <BR> WHERE NO ONE HAS GONE BEFORE.</I> </H4><HR> <H5> <U>Stardate 48030.4581</U></H5> <H6> This page is under heavy construction!</H6><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <I>Utopia Plannitia Fleet Yards</I> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR>This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> </BODY> </HTML>

Page 241: programare in Java

350 CURS 28

Fig.28.7 -Stiluri de litere

Imagini Pentru a face pagina de Web mai atractiva ea poate fi prevazuta cu imagini. O imagine este continuta de un fisier intr-un format dat. Majoritatea browserelor recunosc formatele GIF si JPEG. Pentru a adauga o imagine paginii de Web se foloseste marcajul <IMG> avand sintaxa <IMG SRC=”fisier_imagine”>. In listingul 28.8 se vede cum a folosit capitanul Picard acestui marcaj pentru a adauga la pagina Web a navei sale imaginea continuta de fisierul StarTrek.gif . In acest caz fisierul se gaseste in acelasi director cu fisierul Images.html, marcajul imagine fiind … <IMG SRC="StarTrek.gif"> … Daca fisierul imagine s-ar fi gasit intr-un director diferit de cel al fisierului sursa, marcajul ar fi trebuit sa contina si calea pana la fisier. Astfel daca fisierul se gaseste in directorul C:\IMAGES\GIF_URI, marcajul ar fi fost: … <IMG SRC="C:\Images\Gif_uri\StarTrek.gif"> … Rezultatul obtinut la afisarea acestui document de catre browser este redat in figura 28.8: Listing 28.8 - Images.html <HTML> <!-- This is the new and iprouved HTML page of USS Entreprise 1701-D

Page 242: programare in Java

PROGRAMARE IN JAVA - Note de curs 351

For klingons and Romulans use only. Created on stardate 48030.4581 by Captain Jen-Luc Picard. - -> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <H5> <IMG SRC="StarTrek.gif"><I>Stardate 48030.4581</I></H5><HR> <H3> <B><I>WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise, 1701-D<BR> TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT<BR> NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO<BR> WHERE NO ONE HAS GONE BEFORE.</I></B> </H3><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> </BODY> </HTML>

Fig.28.8 Imagine in pagina Web

Page 243: programare in Java

352 CURS 28

Tabele Desi aduce un plus de culoare in pagina Web, totusi imaginea este prost amplasata in raport cu textul. Ramine un spatiu nefolosit in dreapta imaginii. Lucrurile pot fi ameliorate folosind marcajele pentru tabele. HTML foloseste marcajele <TABLE> si </TABLE> pentru a defini un tabel. In cadrul blocului de text incadrat de aceste doua marcaje se definesc optional antetul si celulele tabelului cu ajutorul marcajelor <TH>,</TH> pentru antet (Table Heading), <TR> ,</TR> pentru delimitarea unui rand al tabelului (Table Row) si <TD>,</TD> pentru celulele unui rand (Table Data). Pentru ca tabelul sa aibe margini vizibile marcajul de inceput de tabel va fi <TABLE BORDER> De exemplu pentru a introduce intr-o pagina Web tabelul 28.1, documentul HTML corespunzator va fi cel din listingul 28.9

Tabel 28.1 Warp speed table Speed miles per

hour Times speed of light

Earth to moon

Across solar system

Between two nearby stars

Across one sector

Across Federa-tion

To nearby Galaxy

250.000 miles

7,440,000,000 miles

5 light years 20 light years

10,000 light years

2,000,000 light years

Warp factor 6

263 Bilion 382 0.00342634 seconds

2 minutes 5 days 18 days 25 years 5,096 years

Listing 28.9 - WarpSp.html <HTML> <!-- Warp speed table with borders Created on stardate 48030.4581 by Captain Jen-Luc Picard.--> <HEAD> <TITLE>Warp speed table</TITLE> </HEAD> <BODY> <TABLE BORDER> <!-- Antetul tabelului - -> <TH>Warp speed table</TH> <!-- Randul 1 - -> <TR> <TD>Speed</TD><TD>miles per hour</TD><TD>Times speed of light</TD> <TD>Earth to moon</TD><TD>Across solar system</TD> <TD>Between two nearby stars</TD><TD>Across one sector</TD> <TD>Across Federation</TD><TD>To nearby Galaxy</TD> </TR> <!-- Randul 2 - -> <TR> <TD></TD><TD></TD><TD></TD><TD>250,000 miles</TD> <TD>7,440,000,000 miles</TD><TD>5 light years</TD> <TD>20 light years</TD><TD>10,000 light years</TD> <TD>2,000,000 light years</TD> </TR> <!-- Randul 3 - -> <TR> <TD>Warpfactor 6</TD><TD>263 Bilion</TD><TD>382</TD> <TD>0.00342634 seconds</TD><TD>2 minutes</TD><TD>5 days</TD> <TD>18 days</TD><TD>25 years</TD><TD>5,096 years</TD> </TR> </TABLE> </BODY> </HTML>

Page 244: programare in Java

PROGRAMARE IN JAVA - Note de curs 353

Rezultatul incarcarii acestui document de catre browser este cel din figura 28.9

Fig.28.9 - Tabel cu margini vizibile

In celulele tabelului poat fi introduse blocuri de text sau imagini. Folosind acest mecanism poate fi aliniata o imagini cu un bloc de text in celule de tabel alaturate, procedeu la care a apelat si capitanul navei Entreprise in listingul 28.10 pentru a alinia imaginea cu mesajul de salut, rezultand pagina Web din figura 28.10. Listing 28.10 - Tables.html <HTML> <!-- This is the last release of HTML page, using tables and images, of USS Entreprise 1701-D, Created on stardate 48030.4653 by Captain Jen-Luc Picard. - -> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <TABLE> <TR><!-- Tabelul are un singur rand cu doua celule si este fara margini vizibile --> <TD> <IMG SRC="StarTrek.gif"></TD> <!-- prima celula contine imaginea --> <TD> <!-- a doua celula contine textul de bun venit--> <H3><B><I>WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3> </TD> </TR> </TABLE> <HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of

Page 245: programare in Java

354 CURS 28

exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> </BODY> </HTML>

Fig.28.10 Utilizarea tabelelor pentru alinierea imaginii cu textul

Page 246: programare in Java

PROGRAMARE IN JAVA - Note de curs 355

Curs 29

Ancore si legaturi Spatiul de afisare in fereastra browserului este limitat. Daca informatia continuta in document este mare, regasirea unui anume subiect prin cautare in toata pagina este dificila. Este ca si cand am avea o carte fara cuprins pe care am dori sa o deschidem la un anumit capitol. Pentru aceasta ar trebui sa o rasfoim cautand titlul capitolului dorit. HTML permite organizarea documentului prin definirea a hiperlegaturilor, puse in evidenta in textul paginii printr-o culoare distincta. Utilizatorul poate “naviga” prin document clicaind cu mouseul pe hiperlegaturi, browserul deruland automat pagina pana cind informatia asociata acestora apare in fereastra. In plus culoarea hiperlegaturii se schimba pentru ca utilizatorul sa stie ca a “vizitat-o” deja cel putin odata. Hiperlegaturile se bazeaza pe marcajul HTML tip ancora <A>,</A>(Anchor) avand sintaxa: … <A HREF =”#zona”>Text_evidentiat</A> … Aceasta este o trimitere catre o zona din document marcata la randul sau cu o ancora avand sintaxa: … <A NAME=”zona”> … Text_evidentiat este textul care va apare in pagina colorat distinct si subliniat. Acest mecanism este cel care transforma documentele HTML din simple texte in hipertexte - pagini interactive organizate cu ajutorul hiperlegaturilor, folosite de utilizator pentru a se “teleporta” de la o zona la alta. Listingul 29.1 si figura 29.1 arata modul de folosire a hiperlegaturilor in cadrul documentului HTML. Listing 29.1 - Links.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <TABLE> <TR><!-- Tabelul are un singur rand cu doua celule si este fara margini vizibile --> <A NAME="TOP"> <TD> <IMG SRC="StarTrek.gif"></TD> <!-- prima celula contine imaginea --> <TD> <!-- a doua celula contine textul de bun venit--> <H3><B><I>WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO

Page 247: programare in Java

356 CURS 29 WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE><HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> <A HREF="#MISSION_OBJ"> MISSION OBJECTIVES</A><BR> <A HREF="#SPECS">SPECIFICATIONS</A><HR> <A NAME="MISSION_OBJ"> <H3> MISSION OBJECTIVES FOR GALAXY CLASS PROJECT</H3> <P>Starfleet has long been charged with a broad spectrum of responsibilities to the citizens of the Federation and to the lifeforms of the galaxy at large. As the volume of explored space continues to grow, and with it the Federation itself, so do Starfleet's duties.</P> <P>These duties range from relatively mundane domestic and civil missions, to cultural contact and diplomacy, to defense, to our primary mission of exploration and research. Many of these responsibilities are best carried out with relatively small, specialized ships. Yet there continues to be an ongoing need for a small number of larger, multimission vehicles that are capable of implementing the complete range of Starfleet's objectives. This need has in fact grown as the volume of relatively unexplored space within Federation influence continues to expand.</P> <P>The Galaxy class starship represents Starfleet's most sophisticated achievement in multimission ship systems design.</P> <A HREF="#TOP">Go to top</A><HR> <A NAME="SPECS"> <H3>SPECIFICATIONS</H3> <A HREF="#PROP"> PROPULSION</A><BR> <A HREF="#MISS"> MISSION</A><BR> <A HREF="#ENVC"> ENVIRONMENT/CREW </A><BR> <A HREF="#TACT"> TACTICAL </A><BR> <A HREF="#DSGN"> DESIGN LIFE </A><BR> <A HREF="#TOP">Go to top</A><HR> <A NAME="PROP"> <H4>PROPULSION</H4> <P>Sustainable cruise velocity of Warp Factor 9.2. Ability to maintain speeds of up to Warp 9.6 for periods of up to twelve hours. Fifth-phase dilithium controlled matter/antimatter reactor primary power.Sustainable field output to exceed 1,650 cochranes, peak transitional surge reserve to exceed 4,225% of nominal output (170 ns phase). Warp driver coils efficiency to meet or exceed 88% at speeds up to Warp 7.0. Minimum efficiency of 52% to be maintained through Warp 9.1. Life cycle of all primary coil elements to meet or exceed 1,200,000 cochrane-hours between neutron purge refurbishment. Secondary coil elements to meet or exceed 2,000,000 cochrane-hours between neutron purge refurbishment. warp field geometry to incorporate modified 55¡ Z-axis compression characteristics on forward warp lobe for increased peak transitional efficiency. Warp nacelle center-lines to conform to 2.56:1 ratio of separation to maximum field strength. Secondary (impulse) propulsion system to provide sublight velocities up to and including 0.92 lightspeed (c). Engine systems of choice to include but are not limited to at least two YPS 8063 fusion drive motors. All units to be equipped with subspace driver accelerators, field output not less than 180 millicochranes at 1.02 x 10¦K. Reactor modules to be field-replaceable. Independent impulse propulsion system of choice for primary hull to include but not be limited to YPS 8055 fusion drive motors.</P> <A HREF="#TOP">Go to top</A><HR> <A NAME="MISS"> <H4>MISSION</H4> <P> Ability to operate independent of starbase refurbishment for extended periods. Independent exploration mode capability of seven Standard years at nominal Warp 6 velocity for docked configuration.

Page 248: programare in Java

PROGRAMARE IN JAVA - Note de curs 357

Ability to execute deep- space exploration missions including charting and mapping, first cultural contact scenarios, and full biologic and ecologic studies. </P> <P>Space allocation for mission-specific facilities: Habitable area to include 800,000 m® for mission-adaptable facilities including living quarters for mission-specific attached personnel.</P> <P>Ability to support a wide range of mission-related ongoing research and other projects (including sufficient habitable volume and power generation for facilities and operations) without impact on primary mission operations. <P>Full spectrum EM, optical, subspace flux, gravimetric, particle, and quark population analysis sensor capability. Multimode neutrino interferometry instrumentation. Wide-band life sciences analysis capability pursuant to Starfleet life contact policy directive. Two-meter diameter gamma ray telescope. Upgradable experiment and sensor array design. Ability to support both on-board and probe-mounted science instrumentation.</P> <P>Support facilities for auxiliary spacecraft and instrumented probes needed for short-range operations to include at least two independent launch, resupply, and repair bays.</P> <A HREF="#TOP">Go to top</A><HR> <A NAME="ENVC"> <H4> ENVIRONMENT/CREW</H4> <P>Environmental systems to conform to Starfleet Regulatory Agency (SFRA)- standard 102.19 for Class M compatible oxygen-breathing personnel. All life- critical systems to be triply redundant. Life support modules to be replaceable at major starbase layover to permit vehiclewide adaptation to Class H, K, or L environmental conditions.</P> <P>Ability to support up to 5,000 non-crew personnel for mission-related operations.</P> <P>Facilities to support Class M environmental range in all individual living quarters, provisions for 10% of quarters to support Class H, K, and L environmental conditions. Additional 2% of living quarters volume to be equipped for Class N and N(2) environmental adaptation.</P> <P>All habitable volumes to be protected to SFRA-standard 347.3(a) levels for EM and nuclear radiation. Subspace flux differential to be maintained within 0.02 millicochranes.</P> <A HREF="#TOP">Go to top</A><HR> <A NAME="TACT"> <H4> TACTICAL</H4> <P>Defensive shielding systems to exceed 7.3 x 10° kW primary energy dissipation rate. All tactical shielding to have full redundancy, with auxiliary system able to provide 65% of primary rating. </P> <P>Tactical systems to include full array of Type X phaser bank elements on both primary and stardrive (battle) sections capable of 5.1MW maximum single emitter output. Two photon torpedo launchers required for battle section, one auxiliary launcher in primary hull.</P> <P> Ability to separate into two autonomous spacecraft comprising a battle section, capable of warp flight and optimized for combat, and a primary section capable of impulse flight and defensive operations</P> <P>Full independent sublight operational capability for command section in Separated Flight Mode.</P> <A HREF="#TOP">Go to top</A><HR> <A NAME="DSGN"> <H4>DESIGN LIFE</H4> <P>Spaceframe design life of approximately one hundred years, assuming approximately five major shipwide system swapouts and upgrades at average intervals of twenty years. Such upgrades help insure the continuing usefulness of the ship even though significant advances in technology are anticipated during that time. Minor refurbishment and upgrade to occur at approximately one-to five-year intervals, depending on specific mission requirements and hardware availability. </P> <A HREF="#TOP">Go to top</A><HR></BODY></HTML>

Page 249: programare in Java

358 CURS 29

Fig.29.1a

Fig.29.1b

Fig.29.1c

Page 250: programare in Java

PROGRAMARE IN JAVA - Note de curs 359

In figura 29.2 este prezentata diagrama legaturilor implementate in documentul HTML din exemplul nostru. Legaturile pot face trimiteri nu numai in cadrul aceluiasi document HTML ci pot referi documente HTML amplasate in alte fisiere. Astfel in cazul exemplului precedent este mai rational din punct de vedere structural ca informatiile privind obiectivele misiunii si specificatiile navei stelare Entreprise 1701-D sa fie inscrise in doua fisiere distincte denumite MissionObj.HTML si Specs.HTMLaccesul la acestea facandu-se prin legaturi din documentul principal continut intr-un fisier denumit de exemplu Index.HTML care joaca un rol similar cu cuprinsului unei carti. Sintaxa pentru o astfel de legatura la un document extern este : … <A HREF =”document_tinta”>Text_evidentiat</A> … Legaturile pot sa refere nu numai un anumit document ci si o pozitie specifica din acesta. In acest caz sintaxa legaturii este: … <A HREF =”document_tinta#zona”>Text_evidentiat</A> … Zona referita de o astfel de legatura se marcheaza cu o ancora avand sintaxa: … <A NAME =”zona”>Text_evidentiat</A> … Structura ansamblului de documente HTML interconectate prin legaturi este redata in figura 29.3. Continutul documentului Index.HTML este cel redat in Listingul 29.2 iar al fisierelor MissionObj.HTML si Specs.HTML in Listingul 29.3 respectiv 29.4. Explorarea cu navigatorul a acestui pachet de pagini Web este prezentata in figurile 29.4a si 29.4b.

Page 251: programare in Java

360 CURS 29

Fig.29.2 Legaturi in documentul Links.HTML

Fig.29.3 Structura unui ansamblu de documente HTML interconectate prin legaturi

Page 252: programare in Java

PROGRAMARE IN JAVA - Note de curs 361

Listing 29.2 - Index.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <TABLE> <TR> <TD> <IMG SRC="StarTrek.gif"></TD> <TD> <H3><B><I> WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE><HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> <A HREF="MissionObj.HTML"> MISSION OBJECTIVES</A><BR> <A HREF="Specs.HTML">SPECIFICATIONS</A><BR> <A HREF=" Specs.HTML #PROP"> PROPULSION</A><BR> <A HREF=" Specs.HTML #MISS"> MISSION</A><BR> <A HREF=" Specs.HTML #ENVC"> ENVIRONMENT/CREW </A><BR> <A HREF=" Specs.HTML #TACT"> TACTICAL </A><BR> <A HREF=" Specs.HTML #DSGN"> DESIGN LIFE </A><HR> </BODY> </HTML> Listing 29.3 - MissionObj.html <HTML> <HEAD> <TITLE>USS Entreprise - Mission Objectives</TITLE> </HEAD> <BODY> <H3> MISSION OBJECTIVES FOR GALAXY CLASS PROJECT</H3><HR> <P>Starfleet has long been charged with a broad spectrum of responsibilities to the citizens of the Federation and to the lifeforms of the galaxy at large. As the volume of explored space continues to grow, and with it the Federation itself, so do Starfleet's duties.</P> <P>These duties range from relatively mundane domestic and civil missions, to cultural contact and diplomacy, to defense, to our primary mission of exploration and research. Many of these responsibilities are best carried out with relatively small, specialized ships. Yet there continues to be an ongoing need for a small number of larger, multimission vehicles that are capable of implementing the complete range of Starfleet's objectives. This need has in fact grown as the volume of relatively unexplored space within Federation influence continues to expand.</P> <P>The Galaxy class starship represents Starfleet's most sophisticated achievement in multimission ship systems design.</P><HR> <A HREF="Index.HTML">Go to home page</A> </BODY> </HTML> Listing 29.4 - Specs.html <HTML> <HEAD> <TITLE>USS Entreprise - Specifications</TITLE> </HEAD> <BODY>

Page 253: programare in Java

362 CURS 29 <H3>SPECIFICATIONS</H3><HR> <A NAME="PROP"><H4>PROPULSION</H4> <P>Sustainable cruise velocity of Warp Factor 9.2. Ability to maintain speeds of up to Warp 9.6 for periods of up to twelve hours. Fifth-phase dilithium controlled matter/antimatter reactor primary power.Sustainable field output to exceed 1,650 cochranes, peak transitional surge reserve to exceed 4,225% of nominal output (170 ns phase). Warp driver coils efficiency to meet or exceed 88% at speeds up to Warp 7.0. Minimum efficiency of 52% to be maintained through Warp 9.1. Life cycle of all primary coil elements to meet or exceed 1,200,000 cochrane-hours between neutron purge refurbishment. Secondary coil elements to meet or exceed 2,000,000 cochrane-hours between neutron purge refurbishment. warp field geometry to incorporate modified 55¡ Z-axis compression characteristics on forward warp lobe for increased peak transitional efficiency. Warp nacelle center-lines to conform to 2.56:1 ratio of separation to maximum field strength. Secondary (impulse) propulsion system to provide sublight velocities up to and including 0.92 lightspeed (c). Engine systems of choice to include but are not limited to at least two YPS 8063 fusion drive motors. All units to be equipped with subspace driver accelerators, field output not less than 180 millicochranes at 1.02 x 10¦K. Reactor modules to be field-replaceable. Independent impulse propulsion system of choice for primary hull to include but not be limited to YPS 8055 fusion drive motors.</P> <A HREF="Index.HTML">Go to home page</A><HR> <A NAME="MISS"> <H4>MISSION</H4> <P> Ability to operate independent of starbase refurbishment for extended periods. Independent exploration mode capability of seven Standard years at nominal Warp 6 velocity for docked configuration. Ability to execute deep- space exploration missions including charting and mapping, first cultural contact scenarios, and full biologic and ecologic studies. </P> <P>Space allocation for mission-specific facilities: Habitable area to include 800,000 m® for mission-adaptable facilities including living quarters for mission-specific attached personnel.</P> <P>Ability to support a wide range of mission-related ongoing research and other projects (including sufficient habitable volume and power generation for facilities and operations) without impact on primary mission operations. <P>Full spectrum EM, optical, subspace flux, gravimetric, particle, and quark population analysis sensor capability. Multimode neutrino interferometry instrumentation. Wide-band life sciences analysis capability pursuant to Starfleet life contact policy directive. Two-meter diameter gamma ray telescope. Upgradable experiment and sensor array design. Ability to support both on-board and probe-mounted science instrumentation.</P> <P>Support facilities for auxiliary spacecraft and instrumented probes needed for short-range operations to include at least two independent launch, resupply, and repair bays.</P> <A HREF="Index.HTML">Go to home page</A><HR> <A NAME="ENVC"> <H4> ENVIRONMENT/CREW</H4> <P>Environmental systems to conform to Starfleet Regulatory Agency (SFRA)- standard 102.19 for Class M compatible oxygen-breathing personnel. All life- critical systems to be triply redundant. Life support modules to be replaceable at major starbase layover to permit vehiclewide adaptation to Class H, K, or L environmental conditions.</P> <P>Ability to support up to 5,000 non-crew personnel for mission-related operations.</P> <P>Facilities to support Class M environmental range in all individual living quarters, provisions for 10% of quarters to support Class H, K, and L environmental conditions. Additional 2% of living quarters volume to be equipped for Class N and N(2) environmental adaptation.</P>

Page 254: programare in Java

PROGRAMARE IN JAVA - Note de curs 363

<P>All habitable volumes to be protected to SFRA-standard 347.3(a) levels for EM and nuclear radiation. Subspace flux differential to be maintained within 0.02 millicochranes.</P> <A HREF="Index.HTML">Go to home page</A><HR> <A NAME="TACT"> <H4> TACTICAL</H4> <P>Defensive shielding systems to exceed 7.3 x 10° kW primary energy dissipation rate. All tactical shielding to have full redundancy, with auxiliary system able to provide 65% of primary rating. </P> <P>Tactical systems to include full array of Type X phaser bank elements on both primary and stardrive (battle) sections capable of 5.1MW maximum single emitter output. Two photon torpedo launchers required for battle section, one auxiliary launcher in primary hull.</P> <P> Ability to separate into two autonomous spacecraft comprising a battle section, capable of warp flight and optimized for combat, and a primary section capable of impulse flight and defensive operations</P> <P>Full independent sublight operational capability for command section in Separated Flight Mode.</P> <A HREF="Index.HTML">Go to home page</A><HR> <A NAME="DSGN"> <H4>DESIGN LIFE</H4> <P>Spaceframe design life of approximately one hundred years, assuming approximately five major shipwide system swapouts and upgrades at average intervals of twenty years. Such upgrades help insure the continuing usefulness of the ship even though significant advances in technology are anticipated during that time. Minor refurbishment and upgrade to occur at approximately one-to five-year intervals, depending on specific mission requirements and hardware availability. </P> <A HREF="Index.HTML">Go to home page</A><HR> </BODY> </HTML> Se observa ca acum, cand informatia este distribuita in mai multe documente, structura acestora este mult mai clara. Fisierele de resurse referite intr-un document HTML nu trebuie sa se afle pe acelasi calculator cu acesta putand sa fie amplasate pe alte calculatoare server WWW. In acest caz fisierul tinta referit se specifica prin URL-ul acestuia. De exemplu pentru a prevedea o legatura cu banca de date a Federatiei, aflata pe un server WWW al Guvernului, pe Tera, capitanul Picard a localizat-o in pagina sa de Web prin urmatorul URL: http://www.Fed.guv/DataBank.HTML

Fig.29.4a

Page 255: programare in Java

364 CURS 29

Fig.29.4b

In listingul 29.5 se vede cum a adaugat Picard referirea la aceasta resursa in Index.HTML iar in figura 29.4a si b rezultatul incarcarii acestei resurse de catre navigator. Listing 29.5 - Index.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <TABLE> <TR> <TD> <IMG SRC="StarTrek.gif"></TD> <TD> <H3><B><I> WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE><HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> <A HREF="http://www.Fed.guv/DataBank.HTML">FEDERATION DATABANK </A><BR> <A HREF="MissionObj.HTML"> MISSION OBJECTIVES</A><BR> <A HREF="Specs.HTML">SPECIFICATIONS</A><BR> <A HREF=" Specs.HTML #PROP"> PROPULSION</A><BR> <A HREF=" Specs.HTML #MISS"> MISSION</A><BR> <A HREF=" Specs.HTML #ENVC"> ENVIRONMENT/CREW </A><BR> <A HREF=" Specs.HTML #TACT"> TACTICAL </A><BR> <A HREF=" Specs.HTML #DSGN"> DESIGN LIFE </A><HR> </BODY> </HTML>

Liste Se observa ca in documentul Index.HTML apare o insiruire de legaturi la alte resurse. Limbajul HTML permite gruparea a mai multor blocuri de text in liste formatate. Si in cazul documentului nostru, este rational din punct de vedere structural sa grupam

Page 256: programare in Java

PROGRAMARE IN JAVA - Note de curs 365

legaturile din pagina Web intr-o astfel de lista. HTML asigura mai multe formate diferite pentru liste: • neordonate (marcate cu buline) • ordonate (numerotate secvential) • de tip director (cu elementele aliniate pe orizontala) • de tip meniu (simple fara buline sau numerotare) Listele neordonate se definesc cu marcajele <UL></UL>, cele ordonate cu marcajele <OL></OL>, cele director delimitate cu <DIR>,</DIR> iar cele meniu cu <MENU>,</MENU>. Elementele listei se delimiteaza cu marcajele <LI>,</LI>.

Fig.29.5a

Fig.29.5b

Structura de definire a unei liste este: <TIP> <LI> element 1 </LI> <LI> element 2 </LI> …………………….

Page 257: programare in Java

366 CURS 29 <LI> element n </LI> </TIP> unde TIP este unul din marcajele UL,OL,DIR sau MENU. Elementele listei sunt blocuri de text ce pot contine si marcaje HTML. In listingul 29.6 si figura 29.6 este exemplificata utilizarea listelor ordonate si neordonate. Listing 29.6 - List.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <TABLE> <TR> <TD> <IMG SRC="StarTrek.gif"></TD> <TD> <H3><B><I> WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE><HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></B> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> <OL><!-- Lista ordonata - -> <LI><A HREF="DataBank.HTML">FEDERATION DATABANK </A></LI> <LI><A HREF="MissionObj.HTML"> MISSION OBJECTIVES</A></LI> <LI><A HREF="Specs.HTML">SPECIFICATIONS</A></LI> </OL> <UL><!-- Lista neordonata - -> <LI><A HREF=" Specs.HTML #PROP"> PROPULSION</A></LI> <LI><A HREF=" Specs.HTML #MISS"> MISSION</A></LI> <LI><A HREF=" Specs.HTML #ENVC"> ENVIRONMENT/CREW </A></LI> <LI><A HREF=" Specs.HTML #TACT"> TACTICAL </A><BR></LI> <LI><A HREF=" Specs.HTML #DSGN"> DESIGN LIFE </A></LI> </UL> <HR> </BODY> </HTML>

Page 258: programare in Java

PROGRAMARE IN JAVA - Note de curs 367

Fig.29.6 Liste ordonate si neordonate

Elementele listei pot sa fie tot liste rezultand astfel liste incuibate. Variantele de combinatii utile sunt doua: • liste incuibate neordonate - o lista neordonata ale carei elemente sunt tot liste

neordonate. • liste incuibate mixte ordonate/neordonate - o lista ordonata ale carei elemente sunt

liste neordonate. In listingul 29.7 si in figura 29.7 este prezentata utilizarea unei liste mixte. Listing 29.7 - List.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <TABLE> <TR> <TD> <IMG SRC="StarTrek.gif"></TD> <TD> <H3><B><I> WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE><HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> <!-- Lista mixta - -> <OL><!-- Lista ordonata - ->

Page 259: programare in Java

368 CURS 29 <LI><A HREF="DataBank.HTML">FEDERATION DATABANK </A></LI> <LI><A HREF="MissionObj.HTML"> MISSION OBJECTIVES</A></LI> <LI><A HREF="Specs.HTML">SPECIFICATIONS</A></LI> <UL><!-- Lista neordonata - -> <LI><A HREF=" Specs.HTML #PROP"> PROPULSION</A></LI> <LI><A HREF=" Specs.HTML #MISS"> MISSION</A></LI> <LI><A HREF=" Specs.HTML #ENVC"> ENVIRONMENT/CREW </A></LI> <LI><A HREF=" Specs.HTML #TACT"> TACTICAL </A><BR></LI> <LI><A HREF=" Specs.HTML #DSGN"> DESIGN LIFE </A></LI> </UL></OL> <HR> </BODY> </HTML>

Fig.29.7 Liste mixte

In cadrul sau chiar locul textului acentuat dintr-o legatura, poate fi prevazuta o imagine care poate fi selectata cu mouse-ul. De exemplu primului element al meniului poate sa -i fie adaugata imaginea grafica a comunicatoarelor folosite de echipajul navei pentru a sugera comunicatia la distanta cu computerul Federatiei (listingul 29.8 si figura 29.8). Listing 29.8 - ImageLink.html <HTML> <HEAD> <TITLE>USS Entreprise - Home Page</TITLE> </HEAD> <BODY> <TABLE> <TR> <TD> <IMG SRC="StarTrek.gif"></TD> <TD> <H3><B><I>WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE><HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was

Page 260: programare in Java

PROGRAMARE IN JAVA - Note de curs 369

comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> <OL><LI> <A HREF="DataBank.HTML"><IMG SRC=”sCom.gif”>FEDERATION DATABANK </A></LI> <LI><A HREF="MissionObj.HTML"> MISSION OBJECTIVES</A></LI> <LI><A HREF="Specs.HTML">SPECIFICATIONS</A></LI> <UL><LI><A HREF=" Specs.HTML #PROP"> PROPULSION</A></LI> <LI><A HREF=" Specs.HTML #MISS"> MISSION</A></LI> <LI><A HREF=" Specs.HTML #ENVC"> ENVIRONMENT/CREW </A></LI> <LI><A HREF=" Specs.HTML #TACT"> TACTICAL </A><BR></LI> <LI><A HREF=" Specs.HTML #DSGN"> DESIGN LIFE </A></LI> </UL></OL> <HR> </BODY> </HTML>

Fig.29.8 Imagini in hiprlegaturi

Frame HTML permite (in cazul browserelor care accepta acest lucru) divizarea unei ferestre in sub-ferestre numite frame (cadru). Divizarea se poate face fie pe verticala, pe coloane, fie pe orizontala, pe randuri. Sub-ferestrele se pot si ele diviza la randul lor fiecare pe coloane sau randuri, etc. In fiecare cadru astfel creat poate fi incarcat cate un document HTML distinct. Un document cu frame-uri se aseamana foarte mult cu un document HTML obisnuit cu deosebirea ca dupa sectiunea HEAD, in loc de sectiunea BODY din documentele HTML obisnuite, aparte sectiunea FRAMESET. Aceasta sectiune este delimitata de perechea de marcaje <FRAMESET>,</FRAMESET> care contine definirea sub-ferestrelor sau frame-urilor. Deci structura unui document HTML cu frame-uri este urmatoarea: <HTML> <HEAD> </HEAD> <FRAMESET> … definirea frame-urilor … </FRAMESET> </HTML>

Page 261: programare in Java

370 CURS 29 Marcajul <FRAMESET> poate avea doua atribute – ROWS (randuri) si COLS(coloane). Un document cu frame-uri nu are sectiunea BODY si deci nu poate contine marcajele specifice acestei sectiuni. Daca un astfel de marcaj este introdus in sectiunea FRAMESET, aceasta va fi ignorata de browser. In cadrul sectiunii FRAMESET sunt permise alte marcaje FRAMESET incuibate precum si marcaje FRAME sau NOFRAMES. • Atributul ROWS stabileste divizarea ferestrei pe randuri. El are sintaxa: ROWS="lista_de_inaltimi_ale_randurilor" Lista_de_inaltimi_ale_randurilor este o lista de valori separate prin virgula reprezentand inaltimea fiecarui rand la divizarea ferestrei. Valorile sunt numere intregi reprezentand inaltimea in pixeli a randului sau procente (1..100) sau valori de scalare relativa. Numarul de randuri este dat implicit de numarul de elemente ale listei. Deoarece suma inaltimilor randurilor trebuie sa fie egala cu inaltimea ferestrei browserului, una din valorile din lista poate fi scalata relativ. Astfel daca valoarea din lista este un numar intreg n atunci randul respectiv va avea inaltimea de n pixeli. Daca valoarea este data in procente n% atunci randul va avea inaltimea reprezentand n din inaltimea totala a ferestrei. Daca totalul inaltimilor randurilor este mai mic decat inaltimea ferestrei si exista in lista o inaltime scalata relativ, aceasta va fi dimensionata astfel incat totalul sa fie egal cu inaltimea ferestrei. Daca nu exista in lista o inaltime scalata relativ, toate inaltimile vor fi scalate astfel incat suma lor sa dea 100%. Inaltimea randurilor scalate relativ se defineste sub forma n*. Numarul n este optional. Caracterul * are semnificatia frame_dimensionat_relativ, fiind interpretat ca o cerere de a dimensiona inaltimea frame-ului la tot spatiul ramas disponibil dupa amplasarea celorlalte frame-uri. Dca exista in lista mai multe inaltimi scalate relativ, spatiul disponibil este impartit in mod egal la fiecare dintre ele. Daca inaltimea este specificata de o valoare urmata de caracterul * (n*) frame-ul respectiv va primi de n ori mai mult spatiu decat un frame avand inaltimea specificata numai cu caracterul . De exemplu in cazul listei “ 2*,* ”,inaltimea primului frame va fi de 2/3 din inaltimea ferestrei iar a celui de al doilea 1/3. • Atributul COLS stabileste divizarea ferestrei pe coloane. El are sintaxa: COLS="lista_de_latimi_ale_coloanelor" Lista_de_latimi_ale_coloanelor are aceiasi sintaxa ca si lista_de_inaltimi_ale_randu-rilor. Marcajele FRAMESET pot fi incuibate intre alte marcaje FRAMESET, definind divizarea unui frame. Pentru fiecare frame definit de marcajul <FRAMESET>, in cadrul sectiuni FRAMESET respective trebuie sa apara marcajul <FRAME>. Acest marcaj defineste continutul frame-ului si modul sau de afisare. Poate avea sase atribute: SRC, NAME, MARGINWIDTH, MARGINHEIGHT, SCROLLING, si NORESIZE. Marcajul <FRAME> este un marcaj vid(nu exista perechea </FRAME>). • Atributul SRC are sintaxa: SRC=”url”

Page 262: programare in Java

PROGRAMARE IN JAVA - Note de curs 371

unde url specifica locatia resursei ce va fi afisata in frame. Frame-urile fara atributul SRC sunt afisate fara continut. • Atributul NAME este folosit pentru a asocia un nume frame-ului astfel ca acesta

sa poata fi accesat de legaturi din alte documente (de regula incarcate in alte frame-uri ale aceleiasi ferestre). Sintaxa atributului NAME este:

NAME="nume_fereastra" Numele trebuie sa inceapa cu un caracter alfanumeric sau caracterul “_”. Atributul NAME este optional. Implicit, toate ferestrele sunt fara nume. Numele frameurilor se foloseste pentru a stabili frame-ul de destinatie in care se va incarca documentul specificat de o legatura. Pentru a stabili fereastra tinta intr-o legatura aceasta trebuie specificata prin atributul TARGET al marcajului ancora <A>:

<A HREF="url" TARGET="nume_fereastra">text_legatura</A> Cateva nume rezervate sunt predefinite de browser :

• _blank – la selectarea unei legaturi, documentul se incarca intr-o fereastra noua, fara nume.

• _self – la selectarea legaturii documentul se incarca in frame-ul in care se gaseste legatura.

• _parent – documentul cerut se incarca in frame-ul parinte. Devine _self daca fereastra nu are parinte.

• _top – documentul cerut se incarca in fereastra principala indiferent de numarul de marcaje FRAMESET incuibate si de frame-ul din care s-a facut cererea

• Atributul MARGINWIDTH este folosit pentru dimensionarea marginii frame-ului si are sintaxa

MARGINWIDTH="n" unde n este latimea in pixeli. Aceasta valoare nu poate fi mai mica decat 1. Atributul este optional. Atributul MARGINHEIGHT e asemanator ca sintaxa cu atributul MARGINWIDTH descris mai sus, cu exceptia faptului ca el stbileste marginile superioara si inferioara ale frame-ului in timp ce MARGINWIDTH stabileste dimensiunea marginilor laterale. • Atributul SCROLLING este folosit pentru a specifica trebuie sau nu prevazut cu o bara de defilare. Sintaxa sa este:

SCROLLING="yes|no|auto" Daca atributul primeste valoarea yes, frame-ul va fi prevazut cu o bara de defilare. Daca se specifica no, frame-ul va fi prevazut cu o bara de defilare. Daca

Page 263: programare in Java

372 CURS 29 SCROLLING este setat pe auto, browserul va afgisa bara de defilare numai daca este cazul. Atributul SCROLLING este optional., valoarea implicita fiind auto. • Atributul NORESIZE nu primeste valoare. Daca este specificat, browserul nu ii va permite utilizatorului sa redimensioneze frameul “tragand” cu mouse-ul de marginea acestuia. Daca frame-ul adiacent are atributul NORESIZE specificat atunci nici marginea framului vecin nu poate fi deplasata. Atributul este optional. Daca nu este specificat, implicit frame-ul este redimensionabil. Marcajele <NOFRAMES> ,</NOFRAMES> delimiteaza textul HTML alternativ ce va fi afiosat de browserele care nu implementeaza mecanismul frame-urilor. Aceasta sectiune este ignorata de browserele capabile sa afiseze frame-uri. In figura 29.9 este prezentat un exemplu de pagina Web cu 3 frame-uri, fiecare afisand un alt document (index.html, header.html si info.html). In acest exemplu, se observa ca fereastra a fost divizata in doua coloane. In frame-ul din prima coloana s-a incarcat documentul index.html. Coloana din dreapta a fost la randul ei divizata in doua randuri. In randul de sus al celei de a doua coloane s-a incarcat documentul header.html iar in cel de jos documentul info.html.

Fig.29.9 Fereastra cu frame-uri

Documentul HTML care a generat aceste trei frame-uri este cel din listingul 29.9 Listing 29.9 - Frames.HTML <HTML> <HEAD> <TITLE>"Pagina Web cu frame-uri"</TITLE> </HEAD> <FRAMESET COLS="140,*"> <FRAME SRC="index_.html" NAME=1index MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="AUTO" FRAMEBORDER="YES" FRAMESPACING="1" NORESIZE> <FRAMESET ROWS="65,*"> <FRAME SRC="header.html" NAME=2header SCROLLING="NO" MARGINHEIGHT=0 MARGINWIDTH=0 NORESIZE> <FRAME SRC="info.html" NAME=3info NORESIZE> </FRAMESET> </FRAMESET>

Page 264: programare in Java

PROGRAMARE IN JAVA - Note de curs 373

<NOFRAMES> <H2> Aceasta pagina se vede mai bine cu browserele Netscape sau InternetExplorer <H2> </NOFRAMES> </HTML> In listingul 29.10 este prezentat fisierul MainPage.HTML creat de capitanul Picard pentru a obtine o pagina Web cu framuri. Listing 29.10 - MainPage.HTML <HTML> <HEAD> <TITLE> USS Entreprise - Home Page </TITLE> </HEAD> <FRAMESET ROWS="160,*"> <FRAME SRC="header.html" SCROLLING="NO" NORESIZE> <FRAMESET cols="230,*"> <FRAME SRC="index1.html" NAME=Index SCROLLING="AUTO" NORESIZE> <FRAME SRC="info.html" NAME=Info SCROLLING="AUTO" NORESIZE> </FRAMESET> </FRAMESET> <NOFRAMES> <TABLE> <TR> <TD><IMG SRC="StarTrek.gif"></TD> <TD><H3><B><I>WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE><HR> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>‘s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> <OL><LI> <A HREF="DataBank.HTML"><IMG SRC=”sCom.gif”>FEDERATION DATABANK </A></LI> <LI><A HREF="MissionObj.HTML"> MISSION OBJECTIVES</A></LI> <LI><A HREF="Specs.HTML">SPECIFICATIONS</A></LI> <UL><LI><A HREF=" Specs.HTML #PROP"> PROPULSION</A></LI> <LI><A HREF=" Specs.HTML #MISS"> MISSION</A></LI> <LI><A HREF=" Specs.HTML #ENVC"> ENVIRONMENT/CREW </A></LI> <LI><A HREF=" Specs.HTML #TACT"> TACTICAL </A><BR></LI> <LI><A HREF=" Specs.HTML #DSGN"> DESIGN LIFE </A></LI> </UL></OL> <HR> </NOFRAMES> </HTML> In acest document HTML fereastra browserului este divizata in doua framuri orizontale Primul are o inaltime fixa de 160 de pixeli si incarca documentul din fisierul Header.HTML al carui continut este redat in listingul 29.11.

Page 265: programare in Java

374 CURS 29 Listing 2.19 – Header.HTML <HTML> <HEAD> <TITLE> USS Entreprise - Home Page header</TITLE> </HEAD> <BODY> <TABLE> <TR> <TD><IMG SRC="StarTrek.gif"></TD> <TD><H3><B><I>WELCOME TO GALAXY CLASS STARSHIP,USS Entreprise 1701-D, TO EXPLORE STRANGE, NEW WORLDS, TO SEEK OUT NEW LIFE, AND NEW CIVILISATIONS,TO BOLDLY GO WHERE NO ONE HAS GONE BEFORE.</I></B><H3></TD></TR> </TABLE> </BODY> </HTML> Al doilea frame orizontal este divizat la randul sau in doua coloane. Prima coloana are o latime fixa de 230 de pixeli si este prevazuta cu o bara de defilare daca este cazul. In el se incarca documentul din fisierul Index1.HTML din listingul 29.12 Listing 29.12 – Index1.HTML <HTML> <HEAD> <TITLE> USS Entreprise - Home Page index1</TITLE> </HEAD> <BODY> <MENU> <LI><A HREF="DataBank.HTML" TARGET="_blank"><IMG SRC="sCom.gif">FEDERATION DATABANK </A></LI> <LI><A HREF="Info.html" TARGET="Info">INTRODUCTION</A> <LI><A HREF="MissionObj.HTML" TARGET="Info"> MISSION OBJECTIVES</A></LI> <LI><A HREF="Index2.HTML" TARGET="Index">SPECIFICATIONS</A></LI> </MENU> </BODY> </HTML> A doua coloana are o dimensiune scalata relativ si este prevazuta cu o bara de defilare daca este cazul. In el se incarca documentul din fisierul Info.HTML din listingul 29.13 Listing 29.13 – Info.HTML <HTML> <HEAD> <TITLE> USS Entreprise - Home Page introduction</TITLE> </HEAD> <BODY> <H5><B><I>Stardate 48030.4581</I></B><H5><HR> The <B>USS Entreprise, NCC -1701-D</B>, is a Galaxy class starship built at the <B><I>Utopia Plannitia Fleet Yards</I></> above Mars. It was comissioned in 2363, and is currently under command of Captain Jean-Luc Picard.<BR> This latest starship is <B>Starfleet</B>'s flagship and has already distingueshed in an impressive number of significant missions of exploration, as well as in several crucial incidents defending the security of the <B>Federation</B>.<HR> </BODY> </HTML> In figura 29.10 este prezentata pagina Web afisata de browser la incarcarea documentului MainPage.HTML. Daca browserul nu implementeaza framuri, pagina

Page 266: programare in Java

PROGRAMARE IN JAVA - Note de curs 375

afisata este identica cu cea din figura 29.8, textul din sectiunea NOFRAMES fiind identic cu cel din sectiunea BODY al fisierului ImageLink.HTML din listingul 2.16. Selectand din lista afisata optiunea FEDERATION DATABANK browserul va crea o fereastra noua (TARGET=”_blank”) in care va incarca fisierul afisand aceeasi pagina Web ca cea din figura 2.14b. Selectand optiunea SPECIFICATIONS in continutul frame-ului index va fi inlocuit cu cel specificat de fisierul Index2.HTML din listingul 2.22, incarcat in acest frame. Imaginea afisata va fi cea din figura 2.20.

Fig.29.10 Pgina cu frame-uri si legaturi Listing 29.14 – Index2.HTML <HTML> <HEAD> <TITLE> USS Entreprise - Home Page index2</TITLE> </HEAD> <BODY> <MENU><LI><A HREF=" Specs.HTML #PROP" TARGET="Info"> PROPULSION</A></LI> <LI><A HREF=" Specs.HTML #MISS" TARGET="Info">MISSION</A></LI> <LI><A HREF=" Specs.HTML #ENVC" TARGET="Info">ENVIRONMENT</A></LI> <LI><A HREF=" Specs.HTML #TACT" TARGET="Info">TACTICAL </A><BR></LI><LI><A HREF=" Specs.HTML #DSGN" TARGET="Info">DESIGN LIFE</A></LI> <LI><A HREF=" Index1.HTML" TARGET="Index">BACK TO MAIN INDEX</A></LI> </MENU> </BODY> </HTML> Selectand optiunea BACK TO MAIN INDEX se revine la continutul frame-ului index din figura 29.10 prin reincarcarea documentului Index1.HTML. Selectand legatura MISSION OBJECTIVES din indexul din figura 29.10 se incarca in frame-ul info documentul MissionObj.HTML. Selectand optiunea INTRODUCTION se reface continutul frame-ului Info prin incarcarea fisierului Info.HTML. In cazul din figura 29.11, selectand una din optiunile PROPULSION, MISSION, ENVIRONMENT, TACTICAL sau DESIGN LIFE in frame-ul Info se va incarca fisierul Specs.HTML, pozitionand la afisare portiunea de text corespunzatoare ancorei specificate de legatura.

Page 267: programare in Java

376 CURS 29

Fig.29.11 Schimbarea continutului frame-ului index

Astfel, selectand de exemplu optiunea MISSION, fereastra browserului va fi ce din figura 29.12. Textul din fereastra Info va fi pozitionat pe ancora <A NAME=”MISS”>.

Fig.29.12 Selectarea unei legaturi cu afisare in frame-ul Info

Formulare HTML Formularele permit colectarea datelor de la utilizator ceea ofera posibilitatea de a asigura documentelor HTML un feedback, datele preluate putand fi transmise unor programe de pe calculatorul server HTTP pentru prelucrari diverse ( generare de comenzi, actualizarea unor baze de date, evaluare/testare de cunostinte, etc.). Marcajele folosite pentru realizarea formularelor sunt:

<FORM> ... </FORM> - delimiteaza un formular in cadrul unui document HTML. <INPUT ...> ... </INPUT> - definesc un camp de editare. <OPTION> - defineste o optiune selectabila. <SELECT> ... <SELECT> - definesc un grup de optiuni selectabile <TEXTAREA ...> ... </TEXTAREA> - definesc un camp de editare pe mai multe randuri.

Page 268: programare in Java

PROGRAMARE IN JAVA - Note de curs 377

In listingul 29.14 este prezentat un exemplu de folosire a acestor marcaje pentru realizarea unui chestionar. In figura 29.13 este aratata pagina de web afisata de browser la incarcarea fisierului Form.HTML care contine chestionarul. Listing 29.14 – Form.HTML <HTML> <HEAD> <TITLE>Sample Questionnaire</TITLE> </HEAD> <BODY> <H3>Model Formular</H3> <P>Va rugam completati chestionarul urmator: </P> <FORM METHOD="GET" ACTION="http://www.host.org/sample.cgi"> <P>Nume: <INPUT NAME="nume" size="48"></P> <P>Studii: </P> <P>Superioare<INPUT NAME="studii" TYPE=RADIO VALUE="superioare"></P> <P>Medii <INPUT NAME="studii" TYPE=RADIO VALUE="medii"></P> <P>Vechime: <INPUT NAME="vechime" TYPE=text></P> <P>Limbi straine cunoscute: </P> <UL> <LI>Engleza <INPUT NAME="limbi" TYPE=checkbox VALUE="en"> <LI>Franceza <INPUT NAME="limbi" TYPE=checkbox VALUE="fr"> <LI>Germana <INPUT NAME="limbi" TYPE=checkbox VALUE="do"> <LI>Rusa <INPUT NAME="limbi" TYPE=checkbox VALUE=”ru"> <LI>Altele <TEXTAREA NAME="altele" cols=48 rows=4></textarea> </UL> Adresa: <INPUT NAME="adresa" SIZE="42"> <P>Va multumim ca ati completat acest chestionar.</P> <P><INPUT TYPE=SUBMIT> <INPUT TYPE=RESET></P> </FORM> </BODY> </HTML>

Fig.29.13 - model formular In acest exemplu marcajele de paragrafe <P>,<\P> si de liste <UL>,<\UL>,<LI><\LI> s-au folosit pentru amplasarea in pagina a elementelor formularului. Se observa ca elementele interactive ale formularului din exemplul nostru sunt butoane cu interblocare (radio button) folosite la introducerea informatiei privind

Page 269: programare in Java

378 CURS 29 studiile, casete de validare (check box) folosite pentru specificarea limbilor straine cunoscute, casete de editare pe un rand folosite pentru specificarea vechimii sau pe mai multe randuri folosite pentru specificarea altor limbi straine in afara celor explicitate in formular. De asemenea formularul contine doua butoane inscriptionate “Submit” “si Reset”. Actionarea butonului "Submit” are ca efect expedierea datelor inscrise in formular iar apasarea butonului “Reset” duce la stergerea informatiilor introduse si reluarea editarii formularului. • marcajele <FORM>,</FORM> Aceste marcaje delimiteaza textul de definire al formularului de colectare de la utilizator a datelor. Atributele marcajului <FORM> specifica unde, cum si sub ce forma sa fie expediate datele colectate.

· atributul ACTION este un URL care specifica locatia unde va fi expediata informatia inscrisa in formular pentru a fi prelucrata si se va obtine raspunsul. daca atributul ACTION este omis, atunci implicit se considera URL-ul documentului care contine formularul.

· atributul METHOD selecteaza protocolul folosit la transmiterea informatiei. In cazul in care atributul ACTION specifica un URL HTTP, protocoalele de transmitere a datelor din formular pot fi GET sau POST. In cazul in care atributul METHOD nu este specificat, protocolul implicit este GET.

Daca METHOD=”GET” atunci datele codificate sub forma unui sir de interogare (query string)se adauga la URL -ul specificat de ACTION. De exemplu daca ACTION=”http://www.host.org/procesare.pl”, atunci la apasarea butonului “Submit”, browserul va acesa locatia

http://www.host.org/procesare.plsir_de_interogare

“procesare.pl” este numele unui program aflat pe calculatorul server HTTP care va prelucra sirul de interogare primit ca parametru. Serverul HTTP separa locatia programului de sirul de interogare, lanseaza programul “procesare.pl” si ii transmite sirul de interogare ca parametru. Daca METHOD=”POST”, sirul de interogare este expediat de browser catre serverul HTTP intr-un bloc de date separat de URL-ul care contine cererea de prelucrare. Sirul de interogare contine informatia introdusa de utilizator in formular, codata cu ajutorul unor caractere speciale de separare. aceste caractere speciale sunt caracterul Spatiu, Procent(%), semnul intrebarii (?), ampersand (&), egal (=) si plus(+). Revenind la exemplu nostru se vede ca formularul este de fapt o grupare de “controale”. Fiecare control are asociat la definire un nume. Dupa completarea formularului, fiecarui control ii este atribuita o “valoare” constind din textul introdus de utilizator in cazul casetelor de editare sau in textul setat la definirea controlului in document in cazul casetelor de validare si abutoanelor interblocate. Codificarea informatiilor inscrise in formular se face astfel:

1. datele se grupeaza in perechi nume=valoare. In cazul casetelor de validare si a butoanelor interblocate se iau in considerare numai controalele validate de utilizator. Spatiile din textul valorii sunt inlocuite cu semnul +. O serie de caractere care nu au voie sa apara in sirul de interogare fiind rezervate se inlocuesc cu valoarea lor codata in HEX si prefixata de semnul Procent(%) conform tabelului 29.1.

2. perechile nume=valoare se separa inter ele prin caracterul &. 3. sirul astfel obtinut se prefixeaza cu semnul ?.

Tabel 29.1

Page 270: programare in Java

PROGRAMARE IN JAVA - Note de curs 379

Caracter Cod Caracter Cod Caracter Cod Tab %09 : %3A ‘ %60

“ %22 < %3C { %7B ( %28 > %3E | %7C ) %29 [ %5B } %7D , %2C \ %5C ~ %7E . %2E ] %5D + %2B ; %3B ^ %5E CR,LF %0D%0A

Astfel in cazul exemplului din figura 29.13, browserul va transmite sirul de interogare cu cererea de acces: http://www.host.org/sample.cgi?nume=BigCrash&studii=superioare&vechime=20&limbi=en&limbi=fr&limbi=ru&altele=C%0D%0AC%2B%2B%0D%0AJava%0D%0APascal%0D%0ABasic&adresa=Starbase+3%2C+Level+23

• marcajele <INPUT>,</INPUT > Marcajul <INPUT> defineste un control Input reprezentand un camp al carui continut poate fi editat de utilizator. Atributele elementului Input sunt:

ALIGN stabileste alinierea pe verticala a imaginii controlului. Se foloseste numai in combinatie cu atributul TYPE=IMAGE

CHECKED Arata ca o caseta de validare sau un buton interblocat se afiseaza validat.

MAXLENGTH Indica numarul maxim de caractere ce pot fi intrioduse intr-o caseta de editare text. Acest numar poate fi mai mare decat latimea casetei specificata cu atributul SIZE. Daca textul introdus este mai lung decat spatiul vizibil stabilit de SIZE, textul din campul de editare va fi defilat corespunzator. Daca MAXLENGTH este omis, numarul implicit de caractere este nelimitat

NAME Stabileste numele folosit la codificarea informatiei inscrise in sirul de interogare. Symbolic name used when transferring the form's contents.

SIZE Specifica in functie de tipul controlului dimensiunea sau precizia campului de editare. De exemplu pentru a defini o caseta de editare cu latimea de 24 de caractere se specifica INPUT TYPE=text SIZE="24".

SRC Specifica un URL ce refera un fisier imagine. Se foloseste numai cu TYPE=IMAGE.

TYPE Defineste tipul de date acceptate de control. Implicit este text. Mai multe tipuri de controale pot fi definite cu acest atribut:

CHECKBOX : folosit pentru casete de validare. Daca o astfel de caseta este validata, numele ei este inscris in sirul de interogare imperecheat cu valoarea specificata cu atributul VALUE. Pot fi definite mai multe controale de acest tip, avand acelasi nume. Toate controalele validate vor genera perechi nume/valoare ce se vor inscrie in sirul de interogare. Daca atributul VALUE este omis, valoarea implicita este “on”

Page 271: programare in Java

380 CURS 29

HIDDEN : controlul nu este vizibil pentru utilizator dar valoarea continuta este inscrisa in sirul de interogare. Aceasta valoare poate fi folosita pentru a transmite informatii privind interactiunea client-server.

IMAGE : Un control imagine pe care utilizatorul sa il poata clicai cu mouse-ul determinand expedierea imediata a formularului. Coordonatele punctului de pe imagine selectat de utilizator sunt inscrise in sirul de interogare sub forma a doua prechi nume/valoare, una pentru coordonata x, numele fiind nume_control.x si cealalta pentru y, numele fiind nume_control.y. Coordonatele sunt masurate in pixeli relativ la coltul din stanga sus al imaginii. Fisierul care contine imaginea este specificat cu atributul SRC.

PASSWORD: este identic cu atributul TEXT cu exceptia faptului ca sirul de caractere introdus de utilizator nu este vizibil.

RADIO: este folosit pentru butoane interblocate. Mai multe astfel de controale avand acelasi nume formeaza un grup.Utilizatorul nu poate selecta decat unul din controalele grupului. Numai valoarea butonul selectat este imperecheata cu numele si adaugata sirului de interogare. Valoarea acestor controale trebuie specificata explicit cu atributul VALUE.

RESET: este butonul care daca este apasat restaureaza formularul setand toate controalele cu valorile implicite. Eticheta implicita de pe buton este Reset si poate fi modificata cu atributul VALUE. Daca butonului i s-a asociat un nume cu atributul NAME, la expedierea formularului, perechea nume/valoare este adaugata sirului de interogare.

SUBMIT: este butonul care apasat, determina expedierea formularului. Eticheta implicita de pe buton este dependenta de browser si poate fi modificata cu atributul VALUE. Daca butonului i s-a asociat un nume cu atributul NAME, la expedierea formularului, perechea nume/valoare este adaugata sirului de interogare.

TEXT: este o caseta de editare text pe un singur rand. Se foloseste impreuna cu atributele SIZE si MAXLENGTH.

TEXTAREA: este o o caseta de editare text pe mai multe randuri. Se foloseste impreuna cu atributele SIZE si MAXLENGTH.

VALUE: Valoarea initiala afisata de controalele text sau starea la casetele de validare si butoanele interblocate.

• marcajele <SELECT>,</SELECT > si <OPTION>,</OPTION> Aceste marcaje delimiteaza o lista de optiuni din care utilizatorul poate selecta unul sau mai multe elemente. Elementele listei sunt etichete text delimitate de marcaje le <OPTION>, </OPTION>. Atributele marcajului <SELECT> sunt:

MULTIPLE: Pentru a permite selectarea a mai multor elemente din lista, in marcajul <SELECT> trebuie specificat atributul MULTIPLE: <SELECT MULTIPLE>. Controlul afisat de browser va fi o lista tip MENU sau MENU DERULANT daca numarul de elemente ale listei este mai mare decat cel de elemente vizibile specificatcu atributul SIZE.

NAME: specifica numele ce va intra in perechea nume/valoare inscrisa in sirul de interogare.

SIZE: specifica numarul de elemente vizibile ale listei. Daca este 1, controlul afisat de browser va fi o lista tip caseta de selectie.

Exemplude folosire a marcajului select:

Page 272: programare in Java

PROGRAMARE IN JAVA - Note de curs 381

<SELECT NAME="aroma"> <OPTION>Vanilie</OPTION> <OPTION>Capsuni</OPTION> <OPTION>Rom</OPTION> <OPTION>Piersica</OPTION> </SELECT> Elementul implicit se specifica cu atributul SELECTED in marcajul <OPTION>. In caz contrar, elementul selectat implicit este primul element din lista. Valoarea asociata elementului selectat este implicit chiar textul etichetei delimitat de marcajele <OPTION>,</OPTION> . Aceasta valoare poate fi stabilita si explicit cu atributul VALUE putand fi diferita in acest caz de textul etichetei elementului.

Page 273: programare in Java

PROGRAMARE IN JAVA - Note de curs 383

Curs 30

JavaScript Sub MS DOS daca vrem sa automatizam o serie de operatii vom edita un fisier de comenzi cu extensia .BAT (de la Batch file) Comenzile din fisier vor fi citite interpretate si executate pe rand, una cate una, de catre interpretorul de comenzi command.com In listingul 30.1 este prezentat un exemplu al unui astfel de fisier de comenzi care afiseaza un meniu, si in functie de optiunea utilizatorului lanseaza in executie un program sau altul. Listing 30.1 - start.bat @echo off cls echo. echo A Microsoft Editor echo B Microsoft Anti-Virus echo C Microsoft Backup echo. choice /c:abc Selectati o optiune if errorlevel 3 goto MSBAckup if errorlevel 2 goto MSAv if errorlevel 1 goto Edit :Edit edit goto End :MSAv msav goto End :MSBackup msbackup goto End :End Fisierul de comenzi start.bat este un exemplu de script. El adauga setului de comenzi DOS o noua comanda (comanda start) cu functionaliate complexa pe care producatorul sistemului de operare nu a prevazut-o si nici nu putea sa o prevada. Dar a prevazut sistemul cu facilitatea ca utilizatorul sa-si poata construi dupa plac propriile comenzi care sa-i satisfaca cerintele. Este imposibil pentru un program sa satisfaca toate exigentele ale tuturor utilizatorilor. Producatorii de software se straduiesc din rasputeri sa raspunda cerintelor utilizatorilor dar nu pot anticipa totul. Pentru a-si face programele mai flexibile ei le prevad cu facilitatea ca utilizatorul sa le poata extinde sau modifica functionalitatea prin intermediul unor script-uri scrise chiar de el ca in cazul exemplului de mai sus. Un script nu este altceva decat o succesiune de comenzi (numite instructiuni) pe care programul le parcurge una cate una executand ceea ce scriptul ii cre sa faca. Scrierea script-urilor este cam acelasi lucru cu programarea cu exceptia faptului ca limbajul de comanda este adeseori mult mai simplu si mai usor de invatat decat un limbaj de programare.

Page 274: programare in Java

384 CURS 30 Un script este deci o succesiune de instructiuni date unui program, pe care acesta le interpreteaza si le executa. Din acest punct de vedere si documentele HTML sunt niste script-uri, marcajele fiind niste comenzi date browserului cum sa afiseze blocul de text pe care il delimiteaza sau ce alta resursa sa incarce ca raspuns la selectarea de catre utilizator a unei hiperlegaturi. Asa cum am vazut, posibilitatile oferite de HTML sunt limitate. Pentru a le imbunatati functionalitatea, Compania Netscape Comunications si-a inzestrat browserele cu un interpretor al unor comenzi mai complexe decat cele HTML. Aceste comenzi se prezinta sub forma unor instructiuni scrise intr-un limbaj botezat de Netscape LiveScript in prima sa versiune iar mai tarziu JavaScript. Instructiunile scrise in acest limbaj sunt grupate in cadrul unui document HTML intre perechea de marcaje <SCRIPT>,</SCRIPT> constituind un asa numit script.Atunci cand este cazul, browserul interpreteaza si executa una cate una instructiunile scriptului delimitat de aceste marcaje. Nu toate browserele au implementat interpretorul de script-uri JavaScript, mai precis numai Netscape Navigator produs Netscape Comunications de si Internet Explorer produs de Microsoft si integrat in sistemul de operare Windows 95 prevad acest mecanism. De exemplu chiar browserul HotJava, produs de SUN Microsystems- compania care a elaborat limbajul Java, nu interpreteaza scripturile JavaScript. Totusi, statisticile arata ca 90% din utilizatori folosesc pe calculatoarele lor fie Netscape Navigator fie Internet Explorer. Limbajul JavaScript este destinat scrierii unor aplicatii simple, fiind destinat celor care doresc sa adauge paginilor lor de Web elemente interactive fara a fi programatori avansati. Din punctul nostru de vedere, in contextul acestei carti, JavaScript va fi o punte ce va va netezi trecerea de la HTML la programarea in Java.

Marcajul <SCRIPT> Intr-un document HTML, un script Java se incadreaza intre marcajele <SCRIPT>,</SCRIPT> avand sintaxa: <SCRIPT LANGUAGE=”JavaScript”> … codul JavaScript … </SCRIPT> este necesara mentiunea LANGUAGE=”JavaScript” deoarece unele browsere accepta scripturi scrise si in alte limbaje decat JavaScript. Browserul Netscape cu versiunea 3.0 sau mai mare accepta si sintaxa: <SCRIPT SRC=”URL - ul unui fisier script”> ………….. </SCRIPT> Fisierul script referit in marcaj prin URL contine codul scriptuli Java ce va fi incarcat de browser si executat. Acest fisier are extensia .JS ca in exemplul urmator: <SCRIPT SRC=”http://www.host.com/program.js”> … </SCRIPT>

Page 275: programare in Java

PROGRAMARE IN JAVA - Note de curs 385

In listingul 30.2 este prezentat un prim exemplu de introducere a unui script Java intr-un document HTML iar in figura 30.1a si 30.1b rezultatul incarcarii si executiei acestuia de catre browser. Listing 30.2 - HalloWorld.HTML <HTML> <HEAD> <TITLE> JavaScript Hallo World </TITLE> </HEAD> <BODY> <H1>Primul program JavaScript va saluta:</H1> <HR> <SCRIPT LANGUAGE="JavaScript"> <!--

window.status="Hallo World!"; window.alert("Uitati-va in bara de stare !");

//--> </SCRIPT> </BODY></HTML>

Fig.30.1 a executia scriptului Java

Fig.30.1 b afisarea documentului HTML

In acest exemplu scriptul Java face urmatoarele: • inscrie in bara de stare a ferestrei sirul de caractere “Hallo World!”. • creaza o fereastra cu mesajul “Uitativa in bara de stare!” si un buton de confirmare. • astepta apasarea butonului. • dupa apasarea butonului, programul se termina.

Page 276: programare in Java

386 CURS 30 Se observa ca desi documentul HTML a fost incarcat (titlul sau apare in bara de titlu a ferestrei si scriptul Java se executa )el nu a fost afisat pana cind nu s-a terminat executia scriptului. Scripturile Java pot fi amplasate oriunde in document. Putem sa folosim oricate perechi de marcaje <SCRIPT>,</SCRIPT> in cadrul documentului.cu urmatoarele observatii: • Definitiile de functii JavaScript (vom vedea mai tarziu ce sunt astea) se plaseaza

obligatoriu in antetul documentului: <HTML> <HEAD> <TITLE> Pagina Web cu functii JavaScript</TITLE> <SCRIPT LANGUAGE=”JavaScript”> function func1(a,b,c){ … instructiuni JavaScript ale functiei func1 … } function func2(x,y,z){ … instructiuni JavaScript ale functiei func2 … } ……………………………………………….. function funcN(u,v,w){ … instructiuni JavaScript ale functiei funcN … } </SCRIPT> </HEAD> <BODY> … corpul documentului HTML … </BODY> • Functiiile trebuiesc definite inainte sa fie apelate. Functiile sunt sectiuni de cod

JavaScript care realizeaza la executie o anumita prelucrare a datelor transmise ca argument (a,b,c in cazul functiei func1 sau x,y,z, in cazul lui func2 sau u,v,w in cazul lui funcN) ca si cand ar fi un fel de mini-programe. Ulterior, in alte sectiuni ale codului JavaScript din document, atunci cand este nevoie de o astfel de procesare a unor date, se apeleaza la serviciile functiei corespunzatoare. Apelul functiei se face prin numele acesteia transmitandu-I-se si argumente concrete pe care ea sa le prelucreze(de exemplu func2(3,5,7) este un apel la functia func2. In urma apelului, func2 incepe sa fie executata de browser prelucrand argumentele x, y, si z ale caror valori sunt initializate cu valorile specificate la apelul functiei, respectiv x=3, y=5 si y=7) Conditia ca acest mecanism sa functioneze este ca definitia functiei sa se gaseasca in documentul HTML inainte de sectiunea in care se produce apelul ei ca in exemplul din listimngul 30.3. Rezultatul executiei acestui script este redat in figura 30.2.

Listing 30.3 – JavaLand.HTML <HTML> <HEAD> <TITLE> Welcome to Java Land </TITLE> <SCRIPT LANGUAGE=”JavaScript”> <!—

Page 277: programare in Java

PROGRAMARE IN JAVA - Note de curs 387

function bunVenit(unde){ document.write(“<H1>Bine ati venit”+unde+”!<H1>”); }//--> </SCRIPT> </HEAD> <BODY><HR> <SCRIPT LANGUAGE=”JavaScript”> <!—Ascunde scriptul de browserele non-Java bunVenit(“in Java Land”);// Apelul functiei cu argumentul actual //--> </SCRIPT><HR> </BODY>

Fig.30.2 Apelul functiilor

• Tot codul JavaScript, inclusiv definitiile functiilor exista si sunt vizibile numai in

cadrul paginii in care sunt prevazute. Daca vrem sa folosim acelasi cod si in alte pagini, trebuie sa il transcriem in toate paginile.

• Scripturile Java este bine sa fie incadrate intre marcajele HTML de comentarii <!—si -- > pentru ca sa nu fie afisate ca simplu text de browserele non-JavaScript ca in cazul documentului din listingul 30.4

Listing 30.4 – BunVenit.HTML <HTML> <HEAD> <TITLE> Bun venit </TITLE> <SCRIPT LANGUAGE=”JavaScript”> function bunVenit(unde){ document.write(“<H1>Bine ati venit”+unde+”!<H1>”); } </SCRIPT> </HEAD> <BODY><HR> <SCRIPT LANGUAGE=”JavaScript”> bunVenit(“la noi”); </SCRIPT> <HR> </BODY> Un browser JavaScript va afisa in fereastra sa textul generat de functia bunVenit ca in cazul din figura 30.3.

Fig.30.3 Documentul BunVenit.HTML afisat de un browser JavaScript

Un browser non-JavaScript ar afisa insa in ferseastra sa ceva in genul textului din figura 30.4, fara a interpreta programul nostru.

Page 278: programare in Java

388 CURS 30

Fig.30.4 Documentul BunVenit.HTML afisat de un browser non-JavaScript

Pentru a face invizibil codul JavaScript pentru browserele non-JavaScript, el trebuie incadrat de marcajele de comentariu <!—si -- >. Pe de alta parte, marcajul -- > trebuie precedat de doua caractere “slash” (//) pentru a fi ignorat de interpretorul JavaScript al browserului ce executa codul. Combinatia // desemneaza un comentariu in limbajul JavaScript, interpretorul ignorand textul ce ii urmeaza pana la capatul randului. In caz contrar, combinatia de caractere -- > ar fi semnalata ca eroare deoarece nu face parte din limbaj si nu este “inteleasa” de interpretor. Listingul 3.5 exemplifica cum se ascunde codul scriptului pentru a nu fi afisat de browserele non-JavaScript. Listing 30.5 – Bun_Venit.HTML <HTML> <HEAD> <TITLE> Welcome to Java Land </TITLE> <SCRIPT LANGUAGE=”JavaScript”> <!—Ascunde scriptul de browserele non-Java function bunVenit(unde){ document.write(“<H1>Bine ati venit”+unde+”!<H1>”);// JavaScript }//- -> </SCRIPT> </HEAD> <BODY><HR> <SCRIPT LANGUAGE=”JavaScript”> <!—Ascunde scriptul de browserele non-Java bunVenit(“ la noi”);// JavaScript ( apelul functiei bunVenit) //--> </SCRIPT><HR> </BODY> • Nu conteaza unde sunt amplasate instructiunile scriptului in documentul HTML.

Ele sunt intotdeauna interpretate ( executate ) dupa ce browserul a incarcat tot documentul dar inainte ca acesta sa fie afisat in fereastra.

Obiecte In viata de zi cu zi suntem obisnuiti sa avem de a face cu diferite obiecte, unele avand o structura simpla, altele destul de complexe, cum ar fi diferite echipamente electronice, masini, avioane, etc. sa luam ca obiect de referinta un televizor. Acest obiect cand este in functiune, are o serie de caracteristici, o sa le numim proprietati, cum ar fi starea (pornit/standby), luminozitatea (0..100%), contrastul (0..100%), saturatia de culoare (0..100%), volum sonor(0..100%), ton sonor (0..100%), 40 de canale avand fiecare asociata o frecventa de receptie. Setarea, acestor proprietati nu

Page 279: programare in Java

PROGRAMARE IN JAVA - Note de curs 389

se face umbland cu surubelnita si letconul in maruntaiele sale (desi acest lucru este posibil, nu este recomandabil). Pentru aceasta televizorul nostru este prevazut cu telecomanda sau cu butoane de reglaj. Actionand asupra tastelor telecomenzii sau asupra butoanelor de reglaj, putem activa niste functii interne ale televizorului care actioneaza asupra proprietatilor modificandu-le la valorile dorite. Avind la dispozitie acest mecanism, utilizatorul nu trebuie sa cunoasca schema de principiu si de montaj a televizorului si sa fie un priceput electronist pentru a-l putea folosi. Iata un alt exemplu – un joc electronic ca cel din figura 30.5.

Fig.30.5 – Joc electronic

Jocul este de fapt o cutie despre continutul careia nu stim nimic, o asa numita cutie neagra prevazuta cu o interfata cu butoane de comanda. Aceasta cutie conectata la un televizor, genereaza o imagine reprezentand pe ecran niste obiecte virtuale, de exemplu doua nave cosmice, avand si ele proprietati ca si obiectele reale: coordonatele fiecarei nave, cantitatea de combustibil in rezervoarele acestora, munitia disponibila, gradul de distrugere, directia si viteza de deplasare, etc. Proprietatile acestor obiecte virtuale se modifica actionand asupra comenzilor: accelereaza, franeaza, schimba directia, trage un proiectil, realimenteaza cu combustibil, etc. Apasarea tastelor de comanda determina cutia neagra sa initieze actiuni al caror rezultat consta in modificarea proprietatilor obiectelor virtuale reprezentate pe ecran. Datorita imaginatiei cu care este inzestrat, omul interpreteaza imaginea de pe ecran a navei spatiale ca pe un obiect asemanator celor din lumea reala – doar ca acest obiect evolueaza (isi modifica proprietatile) in spatele ecranului, intr-o lume virtuala. Aceasta nu il impiedica pe utilizator sa foloseasca comenzile pentru a manevra obiectul in fel si chip, ore intregi, ca si cand ar fi o jucarie reala, palpabila. Este drept ca acest obiect nu este intrutotul virtual fiind conectat la lumea reala printr-o interfata reala, pe care utilizatorul o tine strins si o butoneaza cu frenezie.

Page 280: programare in Java

390 CURS 30 In figura 30.6 este reprezentata schematic aceasta legatura intre obiectul virtual si lumea reala prin intermediul interfetei.

Fig.30.6 Legatura dintre lumea reala si obiectul virtual prin interfeta

In mod asemanator, in programarea orientata obiect, programatorul lucreaza cu obiecte virtuale asemanatoare celor prezentate. Sa analizam cazul unui joc pe calculator care simuleaza o batalie aeriana. Utilizatorul conduce avionul virtual de pe ecran cu ajutorul unui joystick similar cu mansa unui avion de vanatoare. Acest dispozitiv este interfata avionului virtual OV1 cu utilizatorul. Pana aici lucrurile stau asemanator cu cazul precedent. Dar cine conduce atunci avioanele inamice? In cazul precedent conducea partenerul de joc. Acum insa partenerul de joc este chiar calculatorul, mai precis un program manager al obiectelor virtuale. De fapt managerul este cel care prin intermediul driverului de interfata cu joystick-ul preia comenzile date de jucator le interpreteaza si le retransmite avionului virtual pilotat de acesta. Pentru celalalte obiecte virtuale ( avioane, rachete lansate)el elaboreaza singur comenzi pe baza unor strategii de joc prin care simuleaza comportamentul unor piloti inamici in cazul avioanelor sau sistemele de ghidarea automata in cazul rachetelor. Relatia utilizator – manager obiecte virtuale este reprezentata schematic in figura 30.7.

Page 281: programare in Java

PROGRAMARE IN JAVA - Note de curs 391

Fig.30.7 Obiecte virtuale conduse de program

Aceasta figura ne sugereaza mai curand o schema de conexiuni hardware decat o schema logica uzitata candva la proiectarea software-ului procedural. Intr-adevar, metodologia programarii orientate pe obiecte foloseste obiecte abstracte software, definite de programator pe care le asambleaza impreuna pentru a realiza un obiect abstract mai complex, exact la fel cum in proiectarea hardware se folosesc circuitele integrate (tot un fel de cutii nergre – nu trebuie sa le cunoastem schema pentru a le folosi – prevazute cu o interfata pentru aplicarea semnalelor de intrare)- pentru realizarea unor module hardware ca in figura 30.8. Modulele la randul lor intra in componenta unui sistem mai complex, cum ar fi un calculator sau un televizor. Acesta este la randul sau un obiect ale carui detalii de realizare sunt ascunse utilizatorului, fiind inchis (incapsulat) intr-o carcasa sigilata.Pentru a putea fi folosit, este prevazut cu o interfata (butoane, telecomanda) pin intermediul careia comunica cu utilizatorul .

Fig.30.8 Interconectarea a doua microchip-uri intr-in modul hardware

Un obiect virtual, cum ar fi imaginea unui avion pe ecranul computerului, este asemanator cu varful unui iceberg. Asa cum icebergul are sub apa cea mai mare parte din volumul sau, si obiectul virtual are in spate, invizibil, un obiect software care incapsuleaza laolalta datele privind proprietatile si logica de comanda a actiunilor de modificare a acestora. Putem reprezenta schematic acest obiect ca in figura 30.9.

Page 282: programare in Java

392 CURS 30

Fig.30.9 Obiectul software si obiectul virtual

Evolutia pe ecran a avionului virtual este rezultatul afisarii imaginii sale in urma modificarii proprietatilor obiectului software (cum ar fi coordonatele ecran) prin executarea unei actiuni comandate prin interfata de catre un modul exterior. Actiunile de care este capabil obiectul se numesc metode asa cum datele sale interne se numesc proprietati. In programarea obiect stricta, proprietatile obiectului nu pot fi modificate direct din exterior (se spune ca sunt proprietati private ale obiectului). Modificarea lor din exteriorul obiectului este permisa numai indirect, prin apel la metodele publice ale obiectului (pot exista si metode private ce pot fi solicitate numai de alte metode ale obiectului - publice sau private – dar nu pot fi apelate din exterior).

Fereastra Browserului Ferestrele afisate de browserele HTML sunt obiecte avand proprietati si metode publice. Fereastra afisata de browser poate fi comandat prin instructiuni JavaScript incluse in documentul HTML incarcat in sensul modificarii proprietatilor si apelarii metodelor oferite de obiectul fereastra. In codul JavaScript, obiectul fereastra este desemnata prin numele window. Sa analizam intai care sunt proprietatile obiectului window. • Proprietatea status In partea de jos a ferestrei se afla bara de stare in care browserul afiseaza diferite mesaje care de regula il informeaza pe utilizator asupra desfasurarii procesului de incarcare a documentului HTML solicitat (vezi figura 30.10). Un astfel de mesaj este de exemplu Document Done inscris de browser in bara de stare pentru a semnala ca a terminat de incarcat si afisat o pagina Web. Ca proprietar al ferestrei in care este afisat documentul in care este incorporat, codul JavaScript are dreptul sa-si afiseze propriile

Page 283: programare in Java

PROGRAMARE IN JAVA - Note de curs 393

Fig.30.10 – fereastra browserului si bara de stare mesaje in bara de stare. Pentru aceasta nu are decat sa seteze proprietatea status a obiectului window astfel ca aceasta sa contina sirul de caractere al mesajului. Sintaxa instructiunii care realizeaza aceasta operatie este: window.status = “mesajul JavaScript trebuie inscris intre ghilimele” ; Ca urmare a atribuirii de mai sus textul mesajului (fara ghilimelele ce il incadreaza in instructiune ) va aparea in bara de stare a ferestrei. • Proprietatea opener La lansarea in executie, browserul deschide pe ecran o prima fereastra. Din aceasta fereastra utilizatorul poate selectind din meniul File optiunea NewWindow (vezi figura 30.11) sa deschida alte noi ferestre, identice cu prima, in fiecare putand sa fie afisata o alta pagina de Web.

Fig.30.11 – Deschiderea unei noi ferestre

Din fiecare fereastra la randul sau se pot deschide alte ferestre, etc. Proprietatea opener a unei ferestre date contine o referinta la fereastra parinte a acesteia. Asa cum vom vedea mai departe o fereastra noua poate fi deschisa si prin program apeland metoda open. Folosind informatia din proprietatea opener, un script java executat de aceasta noua fereastra poate accesa proprietatile si metodele ferestrei parinte. De exemplu un script java dintr-o fereastra copil poate afisa un mesaj in bara de titlu a ferestrei parinte cu instructiunea : window.opener.status = “Salutari, batrane!” ; • Proprietatatile frames

Aceasta proprietate este un tablou de referinte la frame-urile ferestrei. Fiecare intrare in tablou este o referinta la un frame descendent al unui FRAMESET parinte. De

Page 284: programare in Java

394 CURS 30 exemplu daca fereastra este divizata in dou frame-uri, primul este referit de frames[0] si al doilea de frames[1]. Numarul de frame-uri al ferestrei se poate obtine din proprietatea frames.length care contine indexul in tablou al ultimului frame(in cazul exemplului nostru frames.length are valoarea 1). Frame-urile sunt si ele obiecte cu proprietatile si metodele lor. Prin intermediul referintei la frame aceste proprietati si metode pot fi accesate de un script Java. Accesul la o proprietate sau o metoda a frame-ului prin referinta din tabloul frames se face cu apelul: Window.frames[I].proprietate_sau_metoda_obiect_frame Proprietatile si metodele obiectelor frame vor fi discutate ulterior. • Obiectul self Referinta self este un sinonim al referintei window prin care un script Java se poate referi la obiectul fereastra in cadrul careia se executa. Astfel apelurile self.status(“Hallo!”) si window.status(“Hallo!”) vor sunt echivalente. Vom discuta in continuare metodele obiectului fereastra. • Metoda alert( ) Apelul la aceasta metoda a obiectului fereastra determina afisarea unei ferestere de mesaj de avertizare, prevazuta cu un buton OK. Metoda afiseaza textul transmis ca argument si asteapta ca utilizatorul sa dea un clic pe buton confirmand ca a citit mesajul. Astfel in urma executiei instructiunii JavaScript: window.alert(“Datele introduse sunt incorecte”); Browserul va afisa pe ecran fereastra din figura 30.12.

Fig.30.12 Fereastra de avertizare generata de metoda alert

sa pe Reset- atunci intoarce valoarea booleana lse (fals). Apelul metodei se face cu:

fleet.fed”); Fereastra de confirmare afisata de browser este redata in figura 30.13.

• Metoda confirm( ) Aceasta metoda afiseaza un alt tip de fereastra de mesaj prevazuta cu doua butoane, OK si Reset-. Metoda afiseaza in aceasta fereastra textul mesajului si asteapta apasarea unuia dintre butoane. Daca se apasa Butonul OK, metoda intoarce valoarea booleana true (adevarat) iar daca se apafa window.confirm(“Ati introdus adresa de e-mail [email protected]

Page 285: programare in Java

PROGRAMARE IN JAVA - Note de curs 395

Fig.30.13 Fereastra de confirmare generata de metoda confirm

• Metoda prompt ( ) Aceasta metoda afiseaza o fereastra prevazuta cu un camp de editare in care utilizatorul poate tasta un sir de caractere si cu doua butoane - OK si Reset-. Metoda este apelata cu: window.prompt(“mesaj de afisat in fereastra”,”text implicit in campul de editare”); Metoda afiseaza in fereastra prompt mesajul si initializeaza campul de editare cu textul implicit dupa care asteapta apasarea pe unul din butoane. Utilizatorul poate edita textul implicit din campul de editare dupa care apasa pe unul din butoane. Daca apasa pe OK metoda va returna sirul de caractere introdus de utilizator. Daca apasa butonul Reset-, sirul intors este vid - “”, nu contine nici un caracter. Incarcand de exemplu documentul din listingul 30.6 browserul va afisa fereastra prompt din figura 30.14. Listing 30.6 - Prompt.HTML <HTML> <HEAD> <TITLE> JavaScript window.prompt() </TITLE> </HEAD> <BODY> <H1>window.prompt</H1> <HR> <SCRIPT LANGUAGE="JavaScript"> <!-- var adress=window.prompt("Ce adresa de e-mail aveti?","[email protected]"); window.confirm("Ati introdus adresa de e-mail "+adress); //--> </SCRIPT> </BODY></HTML>

Fig.30.14 Fereastra prompt Se editeaza textul din campul de editare astfel incat in locul adresei de e-mail [email protected] sa apara [email protected] ca in figura 30.15.

Page 286: programare in Java

396 CURS 30

Fig.30.15 Editarea textului din campul de editare Daca se apasa pe butonul OK browserul va afisa fereastra de confirmare din figura 30.16.

Fig.30.16 rezultatul apasarii pe butonul OK Daca se apasa pe butonul Reset- fereastra de confirmare afisata va fi cea din figura 30.17.

Fig.30.17 rezultatul apasarii pe butonul Reset

• Metodele focus( ) si blur( ) Metoda focus seteaza fereastra ca tinta a intrarilor de la tastatura.De exemplu instructiunea window.opener.focus( ); activeaza fereastra parinte a ferestrei curente (window.opener)apeland la metode focus a acesteia. Un alt mod de a realiza acelasi efect este apelul la metoda blur aferestrei curente. Aceasta metoda comuta de pe fereastra curenta pe fereastra parinte a acesteia: window.blur( ) • Metoda scroll( ) Fie documentul HTML din listingul 30.7. Incarcand acest document, browserul va afisa imaginea din figura 30.18.

Page 287: programare in Java

PROGRAMARE IN JAVA - Note de curs 397

Listing 30.7 - Orbit1.HTML <HTML> <HEAD> <TITLE> Orbit </TITLE> </HEAD> <BODY> <IMG SRC=”orbit1.jpg”> </BODY> </HTML> Se observa ca imaginea este mai mare decat spatiul ferestrei si deci este vizibila numai partial. Coltul din stanga sus al ferestrei este plasat la coordonatele (0, 0) ale imaginii. Pentru a vedea o alta portiune a imaginii aceasta trebuie “defilata” la coordonate noi. Utilizatorul se poate folosi pentru aceasta de barele de defilare verticala si orizontala cu care este prevazuta fereastra.

Fig.30.18 Imaginea din fisierul Orbit1.jpg incarcata in fereastra browserului

document printr-o instructiune JavaScript care apeleaza

stanga sus al ferestrei care are coordonatele (0,0). structiunea

oate determina defilarea imaginii in fereastra parinte

igura 30.19 este reprezentat efectul instructiunii window.scroll(122,162) constand

le (122,162). • Metodele open( ) si close( )

Defilarea se poate face si dinmetoda scroll ( ) a ferestrei. Aceasta metoda permite defilarea ferestrei la un punct de coordonate (x,y) in pixeli. Ca referinta este luat coltul dinAstfel de exemplu inwindow.scroll(0,0); va face ca fereastra curenta sa arate pagina Web incarcata incepand cu coltul din stanga sus. Fereastra curenta pprin referinta window.opener: window.opener.scroll(0,150); In fin defilarea imaginii la coordonate

Page 288: programare in Java

398 CURS 30 Metoda open( ) permite o noua fereastra a browserului. Cand aceasta metoda este apelata fereastra curenta (in care se executa scriptul Java ramane pe loc si o noua fereastra este deschisa pe ecran. Sintaxa instructiunii de apel a acestei metode este: window.open(“url”,”nume_fereastra”,”caracteristica1, caracteristica2, ….”); unde url desmneaza locatia resursei ce trebuie incarcata in noua fereastra, nume_fereastra, numele intern, asociat ferestrei, care poate fi folosit pentru referirea ulterioara a acesteia.Lista de caracteristici caracteristica1, caracteristica2,… contine specificatii privind atributele ferestrei nou deschise cum ar fi dimensiunea, titlul, butoane de navigatie, etc.

Fig.30.19 Defilarea imaginii cu metoda scroll( ) Elementele acestei liste pot fi: • toolbar = yes/no - specifica daca bara de butoane standard Back/Forward/Home sa

fie vizibila sau nu. • location = yes/no - specifica daca campul de inscriere a locatiei sa fie sau nu

vizibil. • directories = yes/no - specifica daca butoanele din categoria What’s New si What’s

Cool. • status = yes/no - specifica daca bara de stare sa fie vizibila sau nu. • menubar=yes/no - specifica daca bara de menu sa fie vizibila sau nu. • scrollbars=yes/no - specifica daca barele de defilare sa fie vizibile sau nu. • resizable=yes/no - specifica daca noua fereastra poate sau nu sa fie redimensionata

de utilizator. • copyhistory = yes/no specifica daca noua fereastra sa fie mosteneasca de la

fereastra parinte lista ultimelor pagini accesate in cursul sesiunii curente (istoricul).

Page 289: programare in Java

PROGRAMARE IN JAVA - Note de curs 399

• width = nr.pixeli - specifica latimea in pixeli a noii ferestre. • height = nr.pixeli - specifica inaltimea in pixeli a noii ferestre. Metoda close( ) determina atunci cand este apelata inchiderea ferestrei. Sintaxa instructiunii de apel este: window.close( );

Locatia In fereastra afisata de browser apare o caseta de editare unde utilizatorul poate sa inscrie URL-ul resursei Web pe care vrea sa o incarce (figura 30.20). Aceasta caseta este si ea un obiectavand numele location.

Figura 30.20 – Obiectul “location” • Proprietatea location.href Aceasta proprietate contine sirul de caractere al URL-ului resursei accesate de browser. Astfel pentru a determina browserul sa incarce documentul avand URL-ul http://www.server.ro/document.html, un script Java il va memora in proprietatea href a obiectului locatie: location.href=" http://www.server.ro/document.html" Odata inscrisa noua locatie, browserul va incarca documentul specificat in fereastra curenta. Astfel daca browserul incarca documentul href.html din listingul 30.8 va intalni in cadrul acestuia instructiunea JavaScript location.href="http://www.nexus3.org/mydir/location.html"; Executia aceastei instructiuni il va determina sa incarce documentul location.html al carui continut este redat in listingul 30.8, afisandu-l in fereastra imediat ce a terminat de afisat href.html. Fereastra afisata de browser este cea din figura 30.21. Listing 3.7 - Href.HTML <HTML> <HEAD> <TITLE> location href() </TITLE> </HEAD> <BODY> <H1>utilizare location.href</H1> <SCRIPT LANGUAGE="JavaScript"> <!-- location.href="http://www.nexus3.org/mydir/location.html"; //--> </SCRIPT> </BODY> </HTML> Listing 30.8 - Location.HTML

Page 290: programare in Java

400 CURS 30 <HTML> <HEAD> <TITLE>OBIECTUL "location"</TITLE>

ncarcata in urma executiei unei</P>

on.href="http://www.nexus3.org/MYDIR/location.html";</P>

</HTML>

onul "Back" din bara

l ava poate folosi location.href la apelul metodei window.open( ) a ferestrei curente:

indow.open(location.href,"nume_fereastra","”caracteristica1, caracteristica2, ….”);

ne doar subsirul ubliniat care desemneaza calculatorul gazda pentru serverul HTTP:

</HEAD> <BODY LINK="#0000ff" VLINK="#800080"> <FONT SIZE=6><P>OBIECTUL "<I>location</I>"</P> </FONT><P>Aceasta pagina a fost i<P>instructiuni JavaScript:</P> <FONT FACE="Courier,Courier New" SIZE=3 COLOR="#ff0000"> <P>locati</BODY>

Figura 30.21 - location.html Odata incarcat documentul location.html, incercati sa apasati butde butoane a browserului si incercati sa explicati ce se intampla. Pentru a crea o fereastra noua in care sa fie incarcat si afisat documentul dat, scriptuJ w • Proprietatea location.host Aceasta proprietate contine doar o parte din sirul de caractere al URL-ului paginii curente afisate de browser si anume cea care localizeaza calculatorul gazda. Astfel in cazul URL -ului din exemplul precedent, proprietatea host contis http:// http://www.nexus3.org/mydir/location.html

location.host contine sirul de caractere "http://www.server.ro"

lui de conectare. De exemplu daca serverul "asculta" pe portul 001, URL-ul va fi:

ttp://www.server.ro:6001/document.html

Browserul se conecteaza la serverul HTTP prin accesarea unui port "ascultat" de acesta. Valoarea implicita a numarului de port pentru protocolul HTTP este 80. Sunt insa servere HTTP configurate sa "asculte" pe alte numere de port decat cel implicit. Pentru ca browserul sa poata comunica cu un astfel de server in URL trebuie inclusa si specificarea portu6 h

Page 291: programare in Java

PROGRAMARE IN JAVA - Note de curs 401

iar proprietatea location.host va include si portiunea cu numarul portului a URL -ului urent: c

http://www.nexus3.org:6001/mydir/location.html

location.host contine sirul de caractere "http:// www.nexus3.org:6001".

on.hostname au acelasi continut iar location.port contine un sir id de caractere ("").

• Proprietatile location.hostname, location.port si location.protocol Aceste trei proprietati contin, prima locatia calculatorului gazda al serverului HTTP a doua numarul portului pe care "asculta" acesta si ultima subsirul care specifica protocolul folosit la conectare. Daca in URL nu este specificat un numar de port, location.host si locativ http:// http://www.nexus3.org:6001/mydir/location.html

location.host contine sirul de caractere "http:// www.nexus3.org:6001".

location.protocol contine sirul de caractere "http:".

e specifica calea spre documentul accesat in sistemul de fisiere l calculatorul gazda:

location.host contine sirul de caractere "6001".

• Proprietatea location.pathname Aceasta proprietate contine parte din sirul de caractere al URL-ului paginii curente afisate de browser cara http:// http://www.nexus3.org:6001/mydir/location.html

location.pathname contine sirul de caractere "/mydir/location.html ".

ccesa serverul pentru a incarca noul document. Astfel structiunea JavaScript:

e = "/mydir/altele/document.html";

a determina browserul sa incarce documentul document.html cu URL-ul

ttp://www.nexus3.org/mydir/ altele/document.html

flat in subdirectorul altele din directorul mydir.

Proprietatea location.hash

easta roprietate contine numele ancorei respective. Astfel in cazul cand URL-ul este:

cora location.hash contine sirul de caractere "ancora ".

Proprietatea location.search

Atribuind o valoare noua acestei proprietati, de exemplu o cale spre un alt document HTML, browserul va ain location.pathnam v h a • Atunci cand in URL este specificat nu numai documentul ce se incarca ci si o referire la o ancora din cadrul acestuia marcata cu <A NAME="nume_ancora">, acp http://www.nexus3.org/mydir/ altele/document.html#an

Page 292: programare in Java

402 CURS 30 Am vazut in capitolul precedent ca atunci cand intr-o pagina Web care contine un formular, utilizatorul apasa pe butonul submit, browserul emite o cerere catre serverul HTTP de a executa un program specificat de URL-ul transmis. URL-ul contine pe langa adresa programului si un sir de interogare pe care serverul il va pasa ca parametru programului la serviciile caruia a apelat browserul. Proprietatea location.search contine tocmai aceasta componenta din URL. Astfel in cazul cand URL-ul este: http://www.nexus3.org/mydir/program?DateFormular

location.search contine sirul "DateFormular". 3.6 ISTORICUL NAVIGATIEI Browserele tin istoricul navigatiei de la un document la altul intr-un obiect numit hystory. Acest istoric este tinut sub forma unei liste de URL-uri ale paginilor vizitate. In figura 3.22 este prezentata fereastra history la browserul Netscape Navigator iar in figura 3.23 folder-ul history al lui Internet Explorer. Proprietatile obiectului history sunt history.current care contine URL-ul documentului curent - afisat in fereastra browserului - (avand deci acelasi continut cu location.href) si history.length care contine lungimea actuala a listei de pagini Web vizitate (numarul de elemente ale listei). Astfel in cazul exemplului din figura 30.22 acest numar este 4 iar in cazul celui din figura 30.23 este 3. Metodele obiectului history permit navigarea spre documaentele ale caror URL-uri sunt inscrise in lista istoricului navigatiei. • metodele history.back() si history.forward() Aceste doua metode au efecte similare apasarii butoanelor Back si respectiv Forward din bara de butoane a browserului, permitind navigarea inapoi in pagina precedenta si respectiv inainte la pagina care a fost parasita cu Back. • metodele history.go(deplasament_lista) si history.go(sub_sir) Aceste doua metode permit saltul la URL-ul unui element arbitrar din lista istoricului navigatiei. Astfel metoda history.go(deplasament) determina accesarea URL-ului aflat la deplasament-ul transmis ca argument, relativ la pozitia curenta in lista. Argumentul deplasament poate avea valori pozitive sau negative. Daca este pozitiv, navigarea se face inainte (forward) iar daca este pozitiv, inapoi (back). De exemplu instructiunea: history.go(1) este echivalenta cu history.forward( ) iar history.go(-1) este echivalenta cu history.back( ). In cea de a doua varianta, argumentul poate sa fie si un sir de caractere. Browserul va cauta intrarea in lista in care se regaseste partial sirul de caractere transmis ca

Fig.30.22 - Istoricul navigatiei la browserul Netscape Navigator

Page 293: programare in Java

PROGRAMARE IN JAVA - Note de curs 403

Fig.30.23 - Istoricul navigatiei la browserul Internet Explorer argument si va accesa documentul respectiv. De exemplu pentru lista din figura 3.22 apelul history.go("href.html") va determina accesul la documentul href.html avand URL-ul http://www.nexus3.org/mydir/href.html aflat in lista la pozitia 2.

Obiectul Document Documentul incarcat de browser este si el tratat ca un obiect avand proprietati si metode. Numele atribuit de browser paginii Web curente este document. • proprietatile document.bgColor si dogument.fgColor In exemplele discutate pana acum, textul si imaginile documentului erau afisate pe un fondul gri al ferestrei browserului. Este posibila stabilirea unei culori a fondului pe care este afisat documentul. Aceasta se face cu atributul BGCOLOR (de la BackGroundColor)al marcajului <BODY>: <BODY BGCOLOR="# rrggbb "> ….Textul documentului….. </BODY> In sistemul RGB culorile de baza sunt rosu(red), verde(green) si albastru(blue). Celalalte culori se obtin prin combinarea culorilor de baza cu ponderi diferite. Culoarea fundalului se codifica prin combinatia rrggbb unde rr reprezinta ponderea componentei de culoare rosie exprimata cu doua cifre in baza 16 (HEX). Valorile pentru aceasta componenta pot fi cuprinse deci intre 00 si FF ( in zecimal asta corespunde domeniului 0…255). Combinatia de doua cifre HEX gg reprezinta ponderea componentei de culoare verde iar ponderea componentei albastre. Astfel daca specificam culoarea prin combinatiia #000000 fondul pe care va fi afisat documentul este de culoare neagra (0 rosu, 0 verde si 0 albastru). Daca combinatia este #FFFFFF asta inseamna ca componentele de rosu, verde si albastru sunt la maxim si deci culoarea rezultata este alb. Deoarece nu se cunoaste dinnainte ce posibilitati de a afisa culori are calculatorul pe care va fi incarcat si vizualizat documentul, pentru ca acesta sa fie vazut la fel si pe un calculator care poate afisa doar 16 culori si pe unul care afiseaza 16 milioane de culori, browserul prelucreaza codul culorii afisand numai 16 culori solide. Restul combinatiilor le afiseaza sub forma unei texturi care sugereaza culoarea care ar trebui sa rezulte in realitate. Astfel de exemplu combinatia #FFD700 corespunde unei culori de fond aurii dar nu are corespondent in cele 16 culori solide posibile si atunci est afisata de browser sub forma unei o texturi ca cea din figura 30.24.

Page 294: programare in Java

404 CURS 30

Fig.30.24 - BGCOLOR="#FFD700" Culoarea fondului documentului este continuta de proprietatea document.bgColor si poate fi modificata printr-o instructiune JavaScript care atribuie acesteia o alta valoare decat cea initial continuta de aceasta. De exemplu instructiunea document.bgColor = "#008080" determina ca culoarea de fond a documentului sa devina cea din figura 30.25

Fig.30.25 - document.bgColor="#008080" O alta caracteristica a documentului este culoarea de afisare a textului obisnuit din document. Aceasta implicit este culoarea neagra. Se poate specifica o alta culoare tot in cadrul marcajului <BODY> prin atributul TEXT: <BODY TEXT="# rrggbb "> ….Textul documentului….. </BODY> unde combinatia rrggbb specifica culoarea textului obisnuit din document. Proprietatea document.fgColor (de la ForeGroundColor) are valoarea setata de acest atribut al marcajului <BODY>. Ea poate fi modificata prin program prin atribuirea unei alte valori diferite de cea originala. Astfel atribuirea: document.fgColor="#FFFFFF" va face ca textul sa fie afisat cu culoarea alba (trebuie sa fim atenti la alegerea culorii de fond pentru ca textul sa fie vizibil). • proprietatile document.alinkColor, document.vlinkColor si document.linkColor Pe lunga textul obisnuit, in document se afla si texte incadrate de marcaje de legatura de tipul <A HREF="referinta" >text accentuat</A> In pagina afisata, textul accentuat apare evidentiat prin subliniere si culoare distincta de cea a textului obisnuit. Odata afisate in pagina, legaturile se pot gasi in trei stari:

legatura neselectata; legatura activata(selectata); legatura deja vizitata;

Pentru ca utilizatorul sa distinga usor legaturile deja vizitate de cele pe care urmeaza eventual sa le viziteze si sa vada si ce legatura tocmai a activat, starea legaturii este evidentiata printr-o culoare distincta a textului accentuat. Browserul pastreaza informatia privind legaturile deja vizitate un numar de zile prestabilit dupa care le readuce la starea de legaturi nevizitate. Astfel, chiar si atunci

Page 295: programare in Java

PROGRAMARE IN JAVA - Note de curs 405

cand revine la pagina respectiva dupa o perioada mai lunga de timp, utilizatorul va sti ce legaturi a vizitat la accesarile anterioare. Culoarea celor trei tipuri de legaturi este stabilita implicit de browser dar poate fi stabilita si prin atributele LINK, VLINK si ALINK ale marcajului <BODY>: <BODY LINK="#rrggbb" VLINK="#rrggbb" ALINK="#rrggbb"> ….Textul documentului….. </BODY> Atributul LINK stabileste culoarea pentru legaturile nevizitate, VLINK pentru cele vizitate si ALINK pentru legatura activata. Culorile implicite sunt : LINK=blue, VLINK=purple, and ALINK=red. Proprietatile document.linkColor, document.vlinkColor si document.alinkColor au valoarile setata de aceste atribute ale marcajului <BODY>. Culoarea legaturilor poate fi modificata prin program prin atribuirea unei alte valori diferite de cea originala. Astfel atribuirile: document.linkColor="#00FFFF"; document.vlinkColor="#FF0000"; document.alinkColor="#00FF00"; va seta culoarea legaturilor nevizitate pe galben, a celor vizitate pe rosu si a celei activate pe verde. • proprietatea document.title Intr-un document HTML, in sectiunea HEAD, marcajele <TITLE>,</TITLE> incadreaza un text ce reprezinta titlul documentului si este afisat de browser in bara de titlu a ferestrei. Sirul de caractere al titlului este accesibil intr-un script Java prin proprietatea document.title. Textul afisat in bara de titlu a ferestrei poate fi schimbat prin atribuirea unei noi valori acestei proprietati: document.title = "Noul titlu al paginii Web" • proprietatea document.anchors Am vazut in capitolul precedent ca intr-un document HTML, in sectiunea BODY, marcajul <A NAME="nume_ancora"> amplaseaza in textul acestuia o "ancora" ce poate fi referita intr-o legatura din alt document sau din documentul curent. Proprietatea document.anchors este un tablou continand valorile (numele) tuturor "ancorelor" din pagina, in ordinea in care acestea au fost inscrise in textul documentului. accesul la elementele tabloului se face prin intermediul unui index care specifica numarului de ordine al ancorei: document.anchors[0] document.anchors[1] …………………….. document.anchors[6] Fiecare din elementele de mai sus contine numele unei ancore din textul documentului. Astfel daca in document au fost definite in ordine ancorele cu numele "Luni","Marti","Miercuri", "Joi","Vineri","Sambata","Duminica" aceste siruri de

Page 296: programare in Java

406 CURS 30 caractere sunt continute si de elementele tabloului document.anchors[0], document.anchors[2], … , document.anchors[6]. Tablourile in limbajul JavaScript sunt si ele obiecte (pe care le vom discuta in capitolul ulterior). Astfel numarul de elemente din tablou este continut de proprietatea length a acestuia. In cazul nostru, numarul de ancore in textul documentului curent este dat de document.anchors.length. Desi este posibil sa modificam prin atribuire valorile inscrise in tabloul document.anchors acest lucru nu are sens deoarece ancorele joaca doar rolul de puncte de legatura pentru referinte din alte documente sau din documentul curent. Daca aceste referinte sunt facute chiar in documentul curent, ele nu vor mai fi valide, daca nu sunt actualizate corespunzator modificarilor in document.anchors. • proprietatea document.links Aceasta proprietate este similara cu cea precedenta cu deosebirea ca document.links este un tablou ce contine toate legaturile specificate cu marcajul <A HREF="legatura">, in ordinea in care acestea apar in document. Accesul la elementele tabloului se face printr-un index: document.links[0] document. links [1] …………………….. iar numarul de elemente din tablou este dat de proprietatea acestuia document.links.length. Valorile intrarilor din tablou pot fi modificate prin atribuire ca in exemplul de mai jos: document.links[3] = "http://www.nexus3.org/"; • proprietatea document.lastModified Aceasta proprietate contine un sir de caractere care specifica data calendaristica a ultimei modificari aduse documentului curent. Un script poate folosi aceasta informatie pentru a arata utilizatorului cat de "proaspata" este informatia continuta de pagina Web curenta. • proprietatea document.referrer Daca pagina curenta a fost incarcata prin selectarea unei legaturi dintr-o alta pagina, aceasta proprietate contine URL-ul paginii din care s-a facut referinta. Aceasta informatie poate fi folosita pentru a tine o evidenta statistica a locurilor din care a fost accesata pagina. • proprietatea document.URL Aceasta proprietate poate fi folosita pentru a determina browserul sa incarce in locul paginii curente un alt document, prin atribuirea: document.URL = "http://www.nexus3.org/alta_pagina.html"; • proprietatile document.Forms, document.Images si document.Applets Ca si in cazul ancorelor si legaturilor, aceste proprietati sunt tablouri continand referinte la formularele (definite cu marcajele <FORM>,</FORM>), imaginile (definite cu marcajele <IMG SRC=>,</IMG>) si respectiv appleturile Java (definite

Page 297: programare in Java

PROGRAMARE IN JAVA - Note de curs 407

cu marcajele <APPLET CODE=>,</APPLET>) din documentul curent. Amanunte despre aceste elemente ale documentului HTML vor fi discutate la momentul potrivit, pe parcursul capitolelor urmatoare. Pe langa proprietati, obiectului document are si trei metode ce pot fi apelate de un script Java. • metoda document.clear( ) Aceasta metoda sterge continutul ferestrei curente. Aceasta operatie nu afecteaza continutul documentul curent sau a variabilelor si proprietatilor obiectelor. Este o operatie pur cosmetica care doar sterge imaginea afisata in fereastra browserului pregatind locul pentru ca o noua afisare de informatii sa nu se suprapuna peste imaginea anterioara. • metodele document.write( ) si document.writeln( ) Aceaste doua metode permit afisare de text HTML in fereastra curenta. Ca parametru aceste metode primesc un sir de caractere continand textul in format HTML pe care dorim sa il afisam in fereastra. De exemplu daca dorim sa afisam in fereastra textul in format HTML <H1>Hallo World!</H1> instructiunea JavaScript folosita va fi: • document.write("<H1>Hallo World!</H1>"); Diferenta dintre write si writeln consta in faptul ca dupa afisarea textului, metoda writeln adauga la sfarsitul sirului de caractere afisat un terminator de linie new_line (linie noua) de altfel ignorat de browser (daca chiar se doreste trecerea la o linie noua, se va inscrie la sfarsitul sirului de caractere marcajul HTML <BR> ). Aceasta caracteristica a functiei writeln este utila numai in cazul in care se afiseaza text preformatat incadrat de marcajele <PRE>,</PRE>. In acest caz browserul ia in consideratie la afisare si caracterele de formatare ASCII - SPACE, TAB, LINE_FEED si CARRIAGE_RETURN, ignorate in mod normal. In listingul 30.9 este prezentata utilizarea metodei document.write( ) pentru afisarea in fereastra browserului. Imaginea obtinuta este redata in figura 30.26. Listing 30.9 - write.HTML <HTML> <HEAD> <TITLE>document.write()</TITLE> </HEAD> <BODY> <SCRIPT LANGUAGE="JavaScript"> <!-- document.write("<H2>Utilizarea metodei <I>write</I></H2>"); //--> </SCRIPT> </BODY> </HTML>

Fig.3.26 - metoda document.write()

Page 298: programare in Java

408 CURS 30

Obiectele Formular Formularele definite intr-un document cu marcajele <FORM>,</FORM> sunt la randul lor obiecte. Asa cum am vazut, ele sunt referite de proprietatea forms a obiectului document. Accesul la un obiect formular se face prin intermediul referintei document.forms[i] unde i este numarul de ordine al formularului in document. In continuare sunt discutate proprietatile obiectelor formular astfel refrerite. • proprietatea document.forms[i].action Aceasta proprietate contine URL-ul programului apelat pentru prelucrarea sirului de interogare obtinut dupa completarea de catre utilizator a formularului si apasarea butonului Submit. Valoarea ei poate fi modificata prin atribuirea unei noi valori sub forma unui sir de caractere. Astfel atribuirea document.forms[0].action="Alt_URL"; modifica proprietatea action a primului formular din document. • proprietatea document.forms[i].method Aceasta proprietate contine metoda de transfer a sirului de interogare a serverului stabilita cu atributul METHOD din marcajul <FORM>. Am vazut in capitolul precedent ca metodele posibile sunt doua: GET si POST. Metoda stabilita de marcajul <FORM METHOD=…> poate fi modificata prin atribuirea unei noi valori proprietatii method ca in exemplul urmator: document.forms[0].method="get"; Aceasta atribuire stabileste pentru primul formular din document metoda de transfer GET. • proprietatea document.forms[i].elements Proprietatea elements reprezinta un tablou ale carui elemente sunt referinte la componentele formularului (casete de editare, de validare, butoane, etc.) inscrise in ordinea definirii lor in formular. Numarul de elemente ale tabloului este dat de proprietatea length a acestuia. De exemplu numarul componente ale primul formular din document este dat de document.forms[0].elements.length unde document este documentul curent, forms[0] este referinta la primul formular din tabloul forms (proprietate a obiectului document), iar elements este proprietatea obiectului formular referit de forms[0]. Acesta fiind la randul sau un tablou, lungimea sa (numarul de elemente pe care le cuprinde) este data de proprietatea length. Componentele formularului pot fi acesate prin referintele continute de tabloul elements. Astfel prima componenta este referita de elements[0], a doua de elements[1] si asa mai departe.

Obiectele Buton Pe langa butoanele Sumbit si Reset in cadrul unui formular pot fi definite si alte butoane fara o functionalitate precisa. Unui astfel de buton i se poate asocia o functie JavaScript. Apasarea de catre utilizator a unui astfel de buton declanseaza automat executia functiei JavaScript asociate. In cadrul formularului un astfel de buton se defineste tot prin marcajul <INPUT>: <INPUT TYPE="button" NAME="nume_control" VALUE="eticheta" onClick="functia_asociata()">

Page 299: programare in Java

PROGRAMARE IN JAVA - Note de curs 409

In definitie apare un atribut special, onClick = "functia_asociata", care desemneaza functia JavaScript asociata butonului si evenimentul care declanseaza executia acesteia. In listingul 30.10 este exemplificata utilizarea unui astfel de buton. Butonului cu numele intern test si eticheta Test ii este asociata o functie JavaScript numita mesaj. Evenimentul care declanseaza executia functiei este onClick care corespunde momentului cand browserul sesizeaza ca utilizatorul a clicait pe butonul respectiv. La apasarea butonului este automat executata functia JavaScript mesaj() care afiseaza o fereastra continand un mesaj de confirmare a evenimentului. Listing 3.10 - Button.HTML <HTML> <HEAD> <TITLE>Apasare Buton</TITLE> <SCRIPT LANGUAGE="JavaScript"> <!-- function mesaj() {

window.alert("Butonul TEST a fost apasat!"); } //--> </SCRIPT> <FORM> <H2> Apasati butonul Test</H2> <INPUT TYPE="button" NAME="test" VALUE="Test" onClick="mesaj()"> </FORM> </HEAD> <BODY> </BODY> </HTML> In figura 30.27 sunt reprezentate imaginile ferestrei browserului care afiseaza documentul Button.HTML si fereastra de avertizare afisata de functia mesaj() care confirma apasarea butonului Test. Butoanele sunt la randul lor obiecte. Ele sunt accesibile dintr-un script Java prin referinta din tabloul document.forms[i].elements[j], unde i este i este numarul de ordine in document al formularului iar j este numarul de ordine al butonului in formular. Proprietatile unui control de tip button sunt numele acestuia name si valoarea (eticheta inscrisa pe suprafata butonului) value si tipul acestuia type. In cazul formularului din exemplul anterior, butonul Test este referit de document.forms[0].elements[0] deoarece formularul este primul formular din document(si de altfel singurul ceea ce ne este confirmat de faptul ca document.forms[0].length are valoarea 1 ) si deci are indexul i=0 iar butonul este primul si singurul control din formular (document.forms[0].elements.length are valoarea 1) avand deci indexul j=0. Proprietatile nume valoare si tip ale controlului sunt accesibile prin referintele document.forms[0].elements[0].name ("test"), document.forms[0].elements[0].value ("Test") si document.forms[0].elements[0].type ("button"). Obiectul button nu dispune decat de o singura metoda - click(), care emuleaza apasarea butonului de catre utilizator. Apelul prin referinta, dintr-un script Java, document.forms[0].elements[0].click() , la aceasta metoda are un efect similar apasarii de catre utilizatora butonului Test.

Page 300: programare in Java

410 CURS 30

Fig.30.27 Controlul de tip "button" al unui formular

Obiectele Checkbox (Casete de validare) La definirea aceste controale poate fi precizata ca si in cazul butoanelor o functie JavaScript asociata cu evenimentul onClick. Ca obiecte, ele sunt o extensie a tipului button avand aceleasi proprietati si metode ca si acesta. In plus, casetele de validare mai au proprietatea checked care contine starea butonului sub forma unei valori booleene (TRUE - adevarat - daca este validat sau FALSE - fals - in caz contrar). O alta proprietate suplimentara a casetelor de validare este defaultChecked care specifica starea implicita a controlului la incarcarea documentului, stabilita cu atributul CHECKED, inainte ca utilizatorul sau vre-un script Java sa-l fi actionat. In listingul 30.11 este prezentat un exemplu de accesare a proprietatilor obiectului CHECKBOX. iar in figura 30.28 ferestrele afisate de browser dupa actionarea repetata a contrtolului. In fereastra de mesaj este specificata atat starea actuala cat si starea implicita a casetei de validare. Listing 30.11 - Checkbox.HTML <HTML> <HEAD> <TITLE>Caseta de Validare</TITLE> <SCRIPT LANGUAGE="JavaScript"> <!-- function mesaj() { window.alert("Caseta de validare " +document.forms[0].elements[0].name + " cu valoarea asociata " +document.forms[0].elements[0].value+ " a fost actionata.\nStarea actuala a casetei este " +document.forms[0].elements[0].checked+ ".\nStarea implicita a casetei este " +document.forms[0].elements[0].defaultChecked+"."); } //--> </SCRIPT> <FORM> <H2> Validati caseta alaturata <INPUT TYPE="checkbox"

Page 301: programare in Java

PROGRAMARE IN JAVA - Note de curs 411

NAME="TEST" VALUE="Test" CHECKED="true" onClick="mesaj()">

/HTML>

Fig.30.28 Controlul de tip "checkbox" al unui formular

butoane cu interblocare cu acelasi nume care intra in

numeste

ctionat (numai unul dintre

ica definirea si utilizarea unui formular continand ungrup de butoane interblocate.

</H2> </FORM> </HEAD> </BODY> <

Obiectele Radio (Grup de butoane interblocate) Obiectul radio desemneaza un grup de butoane interblocate fiind o extensie a tipului checkbox . Fata de obiectul checkbox, el are in in plus proprietatea length care specifica numarul decomponenta sa. Deoarece din obiectul radio fac parte mai multe butoane, atunci, obiectul este de fapt un tablou de length referinte la butoanele componente. Astfel daca grupul setest, atunci document.forms[i].test.value ne da valoarea butonului i din grup. Proprietatea value contine valoarea asociata unui buton din grupul de butoane interblocate. Similar, document.forms[i].test.checked refera proprietatea checked a butonului i din grup.Proprietatea checked are valoarea booleana true daca butonul este validat si false nu este validat. Proprietatea defaultChecked specifica starea implicita a butonului interblocat la incarcarea documentului, stabilita cu atributul CHECKED, inainte ca utilizatorul sau vre-un script Java sa-l fi abutoanele grupului poate avea atributul CHEKED=true). Listingul 30.12 si figura 30.29 exemplif

Page 302: programare in Java

412 CURS 30

Fig.30.29 Utilizarea obiectului radio

Listing 30.12 - Radio.HTML <HTML> <HEAD> <TITLE>Butoane interblocate</TITLE> <SCRIPT LANGUAGE="JavaScript"> <!-- function mesaj() { window.alert("Grupul de butoane interblocate "+ document.forms[0].elements[0].name+ " are in componenta sa "+ document.forms[0].test.length+ " butoane.\nStarea butoanelor este:\n"+ document.forms[0].test[0].value+" = "+ document.forms[0].test[0].checked+"\n"+ document.forms[0].test[1].value+" = "+ document.forms[0].test[1].checked+"\n"+ document.forms[0].test[2].value+" = "+ document.forms[0].test[2].checked+"\n"); } //--> </SCRIPT> <FORM> <H2> Validati unul dintre butoanele cu interblocare de mai jos:</H2> <P>Buton 1 <INPUT NAME="test" CHECKED=true TYPE=RADIO VALUE="Butonul 1" onClick="mesaj()"></P> <P>Buton 2 <INPUT NAME="test" TYPE=RADIO VALUE="Butonul 2" onClick="mesaj(document.forms[0])"></P> <P>Buton 3 <INPUT NAME="test" TYPE=RADIO VALUE="Butonul 3" onClick="mesaj(document.forms[0])"></P> </FORM> </HEAD> </BODY> </HTML>

Page 303: programare in Java

PROGRAMARE IN JAVA - Note de curs 413

Obiectul Select ( Lista tip Menu sau Menu Derulant) Obiectul select este si el ca si obiectul radio compus din mai multe elemente selectabile. El corespunde listei tip menu declarata in cadrul unui formular astfel: <SELECT NAME="nume lista"> <OPTION>Optiunea 1</OPTION> <OPTION>Optiunea 2</OPTION> <OPTION>Optiunea 3</OPTION> ……………………………………. <OPTION>Optiunea n</OPTION> </SELECT> Proprietatile obiectului sunt name - numele listei, type = "select" - tipul elementului, length care specifica numarul de optiuni din lista, options[] - un tablou ale carui elemente sunt obiecte corespunzand optiunilor definite cu marcajele <OPTION>,</OPTION> si selectedIndex care contine indexul optiunii selectate in tabloul options[] . Elementele tabloului options[] sunt la randul lor obiecte avand proprietatile text - un sir de caractere care contine textul optiunii delimat in document de marcajele <OPTION>,</OPTION>, value - valoarea setata cu atributul VALUE sau implicit textul etichetei optiunii, acelasi cu cel continut de proprietatea text, defaultSelected - continand o valoare booleana true daca optiunea era implicit selectata cu atributul SELECTED sau false in caz contrar. Obiectului i se pot asocia functii JavaScript ce vor fi executate la producerea unor evenimente cum ar fi onFocus - controlul este activat devenind tinta intrarilor de la tastatura, onBlur - controlul este dezactivat (acesta nu mai este tinta intrarilor de la tastatura) si onChange - produs cand controlul este dezactivat (nu mai este tinta intrarilor de la tastatura) si optiunea selectata initial la activare in momentul onFocus s-a schimbat. Listingul 30.13 si figura 30.30 exemplifica definirea si utilizarea unui formular continand o lista de tip menu. Listing 30.13 - Select.HTML <HTML> <HEAD> <TITLE>Butoane interblocate</TITLE> <SCRIPT LANGUAGE="JavaScript"> <!-- function mesaj() { var index=document.forms[0].elements[0].selectedIndex; window.status="Optiunea selectata = "+(index+1); window.alert("Lista Menu "+document.forms[0].test.name+ " are in componenta sa "+document.forms[0].test.length+ " optiuni.\nOptiunea selectata este:\n " +document.forms[0].test.options[index].text+" cu valoarea " +document.forms[0].test.options[index].value); } //--> </SCRIPT> <FORM> <H2> Selectati una din optiunile din lista urmatoare:</H2> <SELECT NAME="test" onChange="mesaj();"> <OPTION SELECTED VALUE="UNU" >Optiunea 1</OPTION> <OPTION VALUE="DOI" >Optiunea 2</OPTION> <OPTION VALUE="TREI" >Optiunea 3</OPTION>

Page 304: programare in Java

414 CURS 30 </SELECT> </FORM> </HEAD> </BODY> </HTML>

Fig.30.30 - Utilizarea obiectului select In exemplul nostru, pentru a simplifica notatiile, am stocat indexul optiunii selectate din meniu dat de document.forms[0].elements[0].selectedIndex intr-o "memorie" pe care am botezat-o mai simplu, pentru a o putea referi in textul scriptului, index . Acest "botez" care consta in alocarea unui bloc de memorie necesar stocarii unor valori si in asocierea unui nume (identificator) blocului alocat pentru a-l putea accesa prin intermediul acestuia. Aceste operatii s-au facut prin instructiunea declarativa: var index=document.forms[0].elements[0].selectedIndex; prin care, suplimentar am si stocat o valoare in blocul de memorie astfel construit. Instructiunea declaratia var provine de la variable - variabila. Un astfel de obiect declarat cu var, capabil sa memoreze diferite valori, se numeste variabila (tocmai pentru ca poate sa stocheze - i se pot atribui - diferite valori, ceea ce il deosebeste de o constanta a carei valoare se stabileste obligatoriu la declarare si nu poate fi modificata sub nici un chip pe parcursul executiei scriptului). Putem face o similitudine cu cazul a doua dischete (figura 30.31) - una protejata la scriere si alta nu. Cea neprotejata la scriere corespunde unei variabile iar cealalta unei constante. Formatarea disketei, lipirea si inscriptionarea etichetei cu un "nume" corespunde declararii variabilei. Textul inscris pe eticheta dischetei ne permite sa o identificam printre alte dischete si sa ne referim ulterior la ea. Acest text de pe eticheta corespunde identificatorului (numelui) variabilei. Inscrierea unor date pe discheta corespunde operatiei de atribuire a unei valori variabilei. Evident ca asa cum nu putem sa inscriem date pe discheta pana nu am formatat-o si etichetat-o, asa nu putem atribui valori unei variabile pana ce aceasta nu a fost declarata.

Page 305: programare in Java

PROGRAMARE IN JAVA - Note de curs 415

Fig.30.31 Variabila si constanta Revenind la obiectul select sa mai analizam un exemplu care de aceasta data creaza un element de formular de tip lista-menu cu defilare. Lista-menu cuprinde ca optiuni mai multe denumiri de culori. Selectarea uneia dintre culori determina executarea functiei JavaScript schimbaCuloarea() care face ca fondul documentului sa capete culoarea aleasa din lista-menu. Folosind attributul SIZE al marcajului <SELECT> lista-menu poate fi afisata ca o lista menu cu defilare. In exemplul din listingul 30.14 lista menu cu defilare astfel construita permite selectarea culorii de fond a documentului afisat. In figura 30.32 este prezentata pagina afisata de browser dupa ce din lista-menu cu defilare s-a selectat optiunea a 3-a (avand textul "bleuciel" si valoarea "#00FFFF"). Listing 30.14 - Menu.HTML <HTML><HEAD> <TITLE>Background Colors</TITLE> <SCRIPT LANGUAGE="JavaScript"> <!-- function schimbaCuloarea(colorList,index) { document.bgColor=colorList.options[index].value; window.status="A fost selectata culoarea Nr."+ (index+1)+" "+colorList.options[index].text+ " = "+colorList.options[index].value); } //--> </SCRIPT> <FORM> <H2> Selectati culoarea de fond din lista urmatoare:</H2> <SELECT NAME="menu" SIZE=3 onChange="schimbaCuloarea(document.forms[0].elements[0], document.forms[0].elements[0].selectedIndex);"> <OPTION SELECTED VALUE="#C0C0C0" >Implicita</OPTION> <OPTION VALUE="#FFFFFF" >alba</OPTION> <OPTION VALUE="#00FFFF" >bleuciel</OPTION> <OPTION VALUE="#00FF00" >verde</OPTION> </SELECT> </FORM> </HEAD> </BODY> </HTML>

Page 306: programare in Java

416 CURS 30

Fig.30.32 - Lista menu cu defilare Si in acest program am simplificat notatiile prevaziand functia cu argumente. Astfel, functia schimbaCuloarea() are argumentele colorList si index. Aceste argumente constitue parametrii formali ai functiei si sunt o varietate de obiecte similare variabilelor. Deosebirea consta in faptul ca pentru ei alocarea de memorie si atribuirea de valori se face automat, inainte de a se incepe executia functiei in timp ce pentru variabile alocarea de memorie se face la comanda (cu instructiunea declarativa var ) in timpul executiei functiei. Valoarea parametrilor formali este actualizata cu valoarea argumentelor folositi la apelul functiei (parametrii actuali). In cazul exemplului nostru, parametrii actuali au fost document.forms[0].elements[0] reprezentand referinta la elementul 0 al formularului 0 din document adica, chiar referinta la lista-menu din formularul nostru (elementul 0 din formular) si document.forms[0].elements[0].selectedIndex reprezentand indexul in lista-menu a optiunii selectate de utilizator. In urma selectarii unei optiuni din lista, parametrilor formali li se atribuie valorile parametrilor actuali adica in cazul functiei schimbaCuloarea() se fac atribuirile: colorList = document.forms[0].elements[0] index = document.forms[0].elements[0].selectedIndex dupa care se trece la executia functiei. Prin acest mecanism, codul sursa al instructiunilor pentru accesarea propriertatilor obiectelor devine mai simplu de scris si mai intuitiv. Astfel, daca nu am fi folosit parametri, accesul la proprietatea value a optiunii selectate s-ar fi facut cu referinta: document.forms[0].elements[0].options[document.forms[0].elements[0].selectedIndex].value Ufffffffffffffffffff….. am obosit scriind asta. Asa, folosind parametrii formali, accesul la proprietatea value a optiunii selectate se face cu referinta: colorList.options[index].value care este mult mai usor de scris dar si mai usor de inteles. Folosind variabile si parametri formali codul sursa al programelor devine mai usor de inteles ceea ce, implicit, usureaza si depistarea unor eventuale erori si aducerea unor modificari ulterioare programului.

Page 307: programare in Java

PROGRAMARE IN JAVA - Note de curs 417

Obiecte Text, TextArea si Password Controalele text si textarea, componente ale unui formular sunt si ele tratate de JavaScript ca obiecte. Proprietatile si metodele acestor obiecte sunt similare. Diferenta dintre ele consta in faptul ca obiectul text permite editarea textului pe un singur rand iar textarea pe mai multe randuri. Proprietatile celor doua tipuri de obiecte sunt name - care specifica numele intern al obiectului, value - continand textul curent continut de caseta de editare, defaultValue - valoarea implicita stabilita de atributul VALUE al marcajului <INPUT> cu care s-a definit controlul de tip text sau textarea in formular si in final type continand tipul controlului ("text" sau "textarea" ). Metodele obiectului sunt focus( )- care determina activarea controlului, acesta devenind tinta intrarilor de la tastatura, blur( ) - determinand dezactivarea controlului (acesta nu mai este tinta intrarilor de la tastatura) si select ( ) care selecteaza textul din campul de editare. Obiectului i se pot asocia functii JavaScript ce vor fi executate la producerea unor evenimente cum ar fi onFocus - controlul este activat devenind tinta intrarilor de la tastatura, onBlur - controlul este dezactivat (acesta nu mai este tinta intrarilor de la tastatura), onSelect - campul de editare este tinta intrarilor de la tastatura si utilizatorul a selectat o portiune din textul continut de acesta si onChange - produs cand controlul este dezactivat (nu mai este tinta intrarilor de la tastatura) si valoarea (textul) continuta initial la activare in momentul onFocus s-a schimbat. Listingul 30.15 si figura 30.33 exemplifica definirea si utilizarea unui formular continand un control de tipul text. Listing 30.15 - Text.HTML <HTML><HEAD> <TITLE>Text</TITLE> <SCRIPT LANGUAGE="JavaScript"> <!-- function mesaj(textBox) { window.alert("Ati introdus adresa de e-mail\n"+ textBox.value); } function indicatii() {

window.status="Inscrieti in caseta de editare adresa dvs.de e-mail.";

} //--> </SCRIPT> <FORM> <H2> Selectati caseta de editare si urmati indicatiile din bara de stare:</H2> <INPUT NAME="email" TYPE=text onFocus="indicatii()" onChange="mesaj(document.forms[0].elements[0])"> </FORM> </HEAD>

Page 308: programare in Java

418 CURS 30 </BODY> </HTML>

Fig.30.33 -utilizarea obiectul text Controlului de tip password din cadrul unui formular este de fapt o caseta de editare asemanatoare cu cea de tip text, cu deosebirea ca in acest caz textul tastat nu este vizibil fiind mascat cu asteriscuri(*). Proprietatile sunt aceleasi ca si la obiectul text

Obiectul This Am vazut ca accesul la valoarea unui element j al formularului i din document se face cu referinta document.forms[i].elements[j].value Am vazut de asemenea ca o astfel de referinta este prea lunga si incomoda, complicand textul sursa al scriptului. In exemplele anterioare am simplificat notatia folosind variabile si parametri formali. Limbajul JavaScript ofera insa si o cale mai naturala de a evita o astfel de constructie complicata. Astfel, atunci cand este plasat intre marcajele <FORM>,</FORM> de delimitare ale unui formular i al documentului curent, identificatorul this (acesta) refera acest formular fiind echivalent cu document.forms[i] si poate deci sa-l inlocuiasca. Pe de alta parte, un element j al formularului poate fi referit nu numai prin proprietatea this.elements[j] dar si prin numele sau intern specificat cu atributul NAME = nume_element al marcajului <INPUT> in care a fost definit: this.nume_element.

Obiectul Navigator Acest obiect este folosit de un script Java pentru a obtine informatii despre browserul care il executa. Proprietatile obiectului sunt: • proprietatea navigator.appCodeName Aceasta contine numele de cod al browserului. de exemplu pentru browserul Netscape aceasta proprietate este mozilla. • proprietatea navigator.appName Aceasta contine c real al browserului.

Page 309: programare in Java

PROGRAMARE IN JAVA - Note de curs 419

• proprietatea navigator.appVersion Aceasta contine numarul de versiune al browserului. • proprietatea navigator.appAgent Aceasta contine informatia completa asupra browserului inclusiv numele de cod, numele, numarul de versiune si platforma (de exemplu Win95). • proprietatea navigator.plugins[] Aceasta proprietate este un tablou ale carui elemente specifica modulele plug-in instalate (de sunet, animatie, etc.). • proprietatea navigator.mimeType[] Aceasta proprietate este un tablou ale carui elemente specifica ce tipuri MIME de date este capabil sa afiseze browserul (text HTML, imagini GIF, JPG, sunet MID/WAVE etc.) Obiectul are si o metoda - navigator.javaEnabled() care daca este apelata, returneaza o valoare booleana true daca browserul are activata interpretarea codului Java si false in caz contrar.

Evenimente La discutarea obiectelor JavaScript discutate in acest capitol in unele cazuri ne-am folosit de termenul eveniment pentru a desemna momentele in care utilizatorul interactioneaza cu obiectul modificandu-i starea (onClick, onFocus, onBlur si onChange). Acestor evenimente le-am asociat in exemplele noastre functii JavaScript activate automat de browser la sesizarea producerii unui eveniment. Pentru unele obiecte cum ar fi ferestrele si documentele, evenimente sunt si incarcarea/creerea (onLoad) sau distrugerea (onUnload) lor ( asa cum si pentru noi nasterea si moartea sunt evenimente majore). Si acestor evenimente li se pot asocia functii JavaScript care sa fie activate la survenirea lor. In tabelul 30.1 sunt enumerate toate obiectele discutate pe parcursul acestui capitol cu proprietatile, metodele si evenimentele lor.

Page 310: programare in Java

420 CURS 30

Tabel 30.1 Obiecte JavaScript OBIECT Proprietati Metode Evenimente

window frames[] frames.length status self parent top opener

alert() confirm() prompt() open() close() blur() focus() scroll()

onLoad onUnload onFocus onBlur onError

history length go() back() forward()

document title location lastModified loadedDate bgColor fgColor linkColor vlinkColor alinkColor forms[] forms.length links[] links.length anchors[] anchors.length

write() writeln() clear() close() open()

onLoad onUnload

form name method action target elements[]

submit() reset()

onSubmit onReset

button, submit, reset name value type

click() onClick

checkbox name value status defaultChecked checked type

click() onClick

radio name value length defaultChecked checked type

click() onClick

select name length selectedIndex type options.length options[] continand aceste subproprietati:

index name selected text value

onFocus onBlur onChange

text, textarea, password name value defaultValue type

blur() focus() select()

onFocus onBlur onChange onSelect