Sunteți pe pagina 1din 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
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
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++.
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.
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 (
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.
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.
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
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
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:
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.
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.
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
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.
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.
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.
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”);
...
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:
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:
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
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


22 CURS 2

Structura de control repetitiva while

Figura 2.6 – structura repetitiva do/while

Sumar

Exercitii
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
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
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
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:
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“);
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
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.
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:
x
y ⋅ z

vom fi tentati sa scriem:

x/y*z

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:

x
⋅ z
y

O varianta de codificarea corecta in program este expresia:


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:

2
⋅ 12
3
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)
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:
...
System.out.println(contor);
contor++;
...
intr-o singura instructiune:
...
System.out.println(contor++);
...
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;
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);
}
}
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.
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: frecventa3++;
break;
case 4: frecventa4++;
break;
case 5: frecventa5++;
break;
case 6: frecventa6++;
break;
}
}
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);
}
}
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);
}
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.
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
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{
static int factorial(int n){
int fact=1;
for(int k=1;k<=10;k++)
fact *= k;
return fact;
}
public static void main(String[] arguments){
System.out.println(“10! =”+factorial(10));
}
}
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:
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 rezultata pentru 3!. Intr-adevar 3!=3*2*1.
Programul Java care implementeaza acest algoritm folosind o structura de
selectie if/else este:
class FactorialRecursiv{
static int factorial(int n){
int fact;
if(n==1)
fact=1;
else
fact=n*factorial(n-1);
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
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 valorile calculate care au fost afisate incepand cu primul factorial calculat
complet (1!) si terminand cu ultimul (10!).
Figura 6.8- Compilarea si executia aplicatiei FactorialRecursiv.java

Pe baza celor doua exemple prezentate putem face o comparatie intre algoritmii
recursivi 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 se realizeaza explicit prin utilizarea unei structuri repetitive. In
cazul 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 depasirea
spatiului de memorie maxim repartizat programului (prin alocari succesive
de memorie - la fiecare recursie - pentru noi seturi de variabile locale).

Din cele de mai sus putem trage concluzii:


¾ Algoritmii recursivi sunt mai lenti (bazandu-se pe apeluri repetate a unei
metode operatie ce necesita un timp relativ lung in comparatie cu executia unei
instructiuni de calcul)
¾ Algoritmii recursivi solicita mai multa memorie decat algoritmii iterativi.
¾ Algoritmii recursivi sunt mai periculosi putand duce la blocarea intregului
sistem prin depasirea spatiului de memorie maxim admis.
PROGRAMARE IN JAVA - Note de curs 69

In aceste conditii, algoritmii iterativi sunt preferabili celor recursivi. La rezolvarea


unei probleme date se va apela la un algoritm recursiv numai in cazul in care un
algoritm 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 identifica
dupa setul de valori transmis ca parametri (parametrii actuali ai metodei)catre care din
metodele cu acelasi nume sa directeze apelul. Supra definirea se foloseste de regula
pentru a declara un grup de metode care efectueaza aceeasi proocesare dar asupra
unor date de tipuri diferite.
In programul din exemplul urmator cele doua metode numite patrat realizeaza
operatia de ridicare la puterea a 2-a a unei valori. Una din aceste metode are ca
parametru o valoare de tip int si intoarce un rezultat de tip int. A doua metoda patrat
are ca parametru o valoare de tip double si intoarce un rezultat de tip double.
class Supradefinire{
static int patrat(int x){
return x*x;
}
static double patrat(double x){
return x*x;
}

public static void main(String[] arguments){


int a=5;
float b=2.5;
System.out.println(a ”^2 =”+patrat(a));
System.out.println(b ”^2 =”+patrat(b));
}
}
Programul va fi editat in fisierul Supradefinire.java. Comenzile de compilare si
executie a programului continut de acest fisier precum si rezultatele afisate sunt redate
in figura 6.9.

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
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
diferit) se numeste structura de date. Ea modeleaza in program, cu precizia necesara,
caracteristicile obiectului de inventar, materialului sau a persoanei. In Java gruparea
unor date intr-o structura se poate face in cadrul unui modul separat prin declaratiei
unei clase ca in exemplul de mai jos:
public class Automobil{
String tip;
int capacitate;
int nr_usi;
String culoare;
String 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 unui obiect real intr-o data de acest tip este nevoie de un bloc de memorie
suficient de mare pentru a putea stoca toata informatia privind obiectul.
Dimensionarea acestui bloc se face pe baza descrierii facute in declaratia clasei (prin
sumarea dimensiunii locatiilor de memorie necesare memorarii tuturor componentelor
structurii).
In cazul claselor, declararea unei variabile de tipul respectiv nu duce si la
alocarea memoriei necesare stocarii informatiilor privind atributele obiectului. Astfel
declaratia:

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
null deoarece inca nu refera nici un bloc de memorie. Alocarea unui ploc de memorie
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
memorie dimensionat pe baza tipului specificat ca argument. Un astfel de mod de
alocare a memoriei se numeste alocare dinamica. Deci pentru a aloca memorie pentru
pastrarea atributelor autoturismului meu, trebuie folosita secventa :

Automobil masinaMea= new Automobil();

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


operatorul deacces si numele atributului accesat:

...
masinaMea.tip = “Dacia”;
masinaMea.culoare=”Alb13”;
masinaMea.capacitate = 1300;
...

Alocarea dinamica de memorie asigura folosirea rationala a acesteia. Astfel, in


momentul in care blocul de memorie nu mai este necesar, prin simpla atribuire a
valorii 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 utilizarea structurilor de date este prezentat in
continuare. El este alcatuit din doua module. Primul modul consta din clasa Automobil
definita in fisierul Automobil.java:
public class Automobil{
String marca;
int capacitate;
String culoare;
}

Al doilea modul este constituit din clasa Structura care contine metoda main(). Codul
acestei clase este continut de fisierul Structura.java:

class Structura{
public static void main(String[] arguments){
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);

System.out.println("capacitate:"+masinaMea.capacitate);
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 observa 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.
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.


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];
...
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.
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.
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.
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).
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();
}
}
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
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();
}
}
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
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
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.
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
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:
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];
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{
public abstract void push(double val);
public abstract double pop();
public abstract double top();
}
Vom defini clasa Stack ca subclasa derivata din StackInterface:
class Stack extends StackInterface{
private double reg[];
public Stack(){// constructor stiva
... implementare interna ...
}
public void push(double val){// ridica stiva
... 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 ... }
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


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.
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
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.
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:
...
String culoare = “Rosu”;
...
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;
s = new String();
...
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”;
s = new String(s1);
...
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:
...
char charArray[] = new char[10];
...
String s;
s = new String(charArray);
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.
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);
...
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]+" ");
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);
}
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("}");
}
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);
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.
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
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){
p=Integer.parseInt(s1=in1.getText());
in1.setText("");
q=Integer.parseInt(s2=in2.getText());
in2.setText("");
out.setText(s1+" / "+s2+" = "+div(p,q));
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
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
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);
}
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)
throw new ArithmeticException( “Rezultat nedefinit”);
else if(x<0)
throw new ArithmeticException( ”Eroare: Infinit negativ”):
else
throw new ArithmeticException( ”Eroare: Infinit pozitiv”);
return String.valueOf(x/y);
}
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 :
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("");
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.
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();
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
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();}
}
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.
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{
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
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;
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.isRe
ady()));
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;
}
}
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.isRe
ady()));
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
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.
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();
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.
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
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;
}
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(
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


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
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
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
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);
}
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;
}
}
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
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
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.
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.*;
class MyFrame extends Frame{
private Button b;
public MyFrame(){
super("Test AWT thread");
b=new Button("Procesare");
setLayout(new FlowLayout());
add(b);resize(300,100);show();
}
public boolean handleEvent(Event e){
if(e.id==e.WINDOW_DESTROY){
hide();
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
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
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


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


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


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


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

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


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
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
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
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
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);
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!",
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);
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);
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
}
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];}
// verificare daca userul cu identificatorul usrID exista
try{// deschide fisierele bazei de date
ndx=new RandomAccessFile("usr.ndx","rw");
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 bExit=new Button("Exit");// butonul “Exit”
public Panel2(){
setLayout(new BorderLayout());// managerul de amplasare
add("Center",dsp); // adaugare componente
add("South",bExit);
}
/*******************************************************************\
* Metoda startSesiune() initiaza operatiile de incepere a unei sesi-*
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


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);
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);
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
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);
}
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
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();
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;
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
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
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;
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);
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);}
...
}
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:
280 CURS 23

img.width
kx =
img.width
img.height
ky =
img.height

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:
x′′ = x′ ⋅ k x
y ′′ = img.height − y ′ ⋅ k y
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”):
x′′ = (int)(( x − x0 ) ⋅ k x )
y ′′ = img.height − (int)(( y − y0 ) ⋅ k y )

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,
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)
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)
{
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
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++){
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
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{
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),
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);}
}
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
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.
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.
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.
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
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).
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,
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
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.
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.
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
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
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
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


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.
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).
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.
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
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
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.
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
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-
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.
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.
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 user@nexus.home.ro .
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


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: teral@k.ro\r\n",
"RCPT To: teral@nexus.home.ro\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


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
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.
PROGRAMARE IN JAVA - Note de curs 317

Figura 26.1 – O sesiune telnet de conectare la serverul Gate.class

import java.io.*;
import java.net.*;
class Gate{
public static void main(String[] args){
try{
String psw,cmd;

Socket s;
DataInputStream in;
PrintStream out;
ServerSocket ss=new ServerSocket(2222);
close:{
for(;;){
System.out.println("Waiting for connection request...");
s=ss.accept();
System.out.println("New connection...");
in=new DataInputStream(s.getInputStream());
out=new PrintStream(s.getOutputStream());
disconect:{
int k=0;
do{
out.print("password:");
psw=in.readLine();
if(psw.equals("sesam deschide-te")){
System.out.println("Alibaba access granted...");
out.print("Bine ai venit Alibaba!"+
" Vre-o dorinta (QUIT/EXIT/CLOSE)?");
for(;;){
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
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.
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.
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());
}
}
}
}
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){
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.
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();
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){}
}
}
}
}
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:
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");
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


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


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


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 Times Earth to Across solar Between two Across Across To nearby
hour speed of moon system nearby stars one Federa- Galaxy
light sector tion
250.000 7,440,000,000 5 light years 20 light 10,000 2,000,000
miles miles years light light years
years
Warp 263 Bilion 382 0.00342634 2 minutes 5 days 18 days 25 years 5,096
factor 6 seconds 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>
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
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


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
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.
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>
358 CURS 29

Fig.29.1a

Fig.29.1b

Fig.29.1c
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.
360 CURS 29

Fig.29.2 Legaturi in documentul Links.HTML

Fig.29.3 Structura unui ansamblu de documente HTML interconectate prin legaturi


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>
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>
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
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
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>
…………………….
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>
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 - ->
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
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>
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”
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
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>
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.
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
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.
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.
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
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
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&vechim
e=20&limbi=en&limbi=fr&limbi=ru&altele=C%0D%0AC%2B%2B%0D%0AJava%0D%0A
Pascal%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”
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:
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.
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.
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>
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.
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”>
<!—
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.
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
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.
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.
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.
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
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
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

• 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 apasa pe Reset- atunci intoarce valoarea booleana
false (fals). Apelul metodei se face cu:

window.confirm(“Ati introdus adresa de e-mail picard@uss1701D.starfleet.fed”);


Fereastra de confirmare afisata de browser este redata in figura 30.13.
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?","picard@uss1701D.strafleet.fed");
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
picard@uss1701D.starfleet.fed sa apara worf@uss1701D.starfleet.fed ca in figura
30.15.
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.
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

Defilarea se poate face si din document printr-o instructiune JavaScript care apeleaza
metoda scroll ( ) a ferestrei.
Aceasta metoda permite defilarea ferestrei la un punct de coordonate (x,y) in pixeli.
Ca referinta este luat coltul din stanga sus al ferestrei care are coordonatele (0,0).
Astfel de exemplu instructiunea
window.scroll(0,0);
va face ca fereastra curenta sa arate pagina Web incarcata incepand cu coltul din
stanga sus. Fereastra curenta poate determina defilarea imaginii in fereastra parinte
prin referinta window.opener:
window.opener.scroll(0,150);
In figura 30.19 este reprezentat efectul instructiunii window.scroll(122,162) constand
in
defilarea imaginii la coordonatele (122,162).
• Metodele open( ) si close( )
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).
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
400 CURS 30

<HTML>
<HEAD>
<TITLE>OBIECTUL "location"</TITLE>
</HEAD>
<BODY LINK="#0000ff" VLINK="#800080">
<FONT SIZE=6><P>OBIECTUL "<I>location</I>"</P>
</FONT><P>Aceasta pagina a fost incarcata in urma executiei unei</P>
<P>instructiuni JavaScript:</P>
<FONT FACE="Courier,Courier New" SIZE=3 COLOR="#ff0000">
<P>location.href="http://www.nexus3.org/MYDIR/location.html";</P>
</BODY>
</HTML>

Figura 30.21 - location.html


Odata incarcat documentul location.html, incercati sa apasati butonul "Back" din bara
de 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, scriptul
Java poate folosi location.href la apelul metodei window.open( ) a ferestrei curente:

window.open(location.href,"nume_fereastra","”caracteristica1, caracteristica2, ….”);

• 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 contine doar subsirul
subliniat care desemneaza calculatorul gazda pentru serverul HTTP:

http:// http://www.nexus3.org/mydir/location.html
¾ location.host contine sirul de caractere "http://www.server.ro"

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 portului de conectare. De exemplu daca serverul "asculta" pe portul
6001, URL-ul va fi:

http://www.server.ro:6001/document.html
PROGRAMARE IN JAVA - Note de curs 401

iar proprietatea location.host va include si portiunea cu numarul portului a URL -ului


curent:

http://www.nexus3.org:6001/mydir/location.html
¾ location.host contine sirul de caractere "http:// www.nexus3.org:6001".

• 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 location.hostname au acelasi continut iar location.port contine un sir
vid de caractere ("").

http:// http://www.nexus3.org:6001/mydir/location.html
¾ location.host contine sirul de caractere "http:// www.nexus3.org:6001".
¾ location.host contine sirul de caractere "6001".
¾ location.protocol contine sirul de caractere "http:".

• Proprietatea location.pathname
Aceasta proprietate contine parte din sirul de caractere al URL-ului paginii curente
afisate de browser care specifica calea spre documentul accesat in sistemul de fisiere
al calculatorul gazda:

http:// http://www.nexus3.org:6001/mydir/location.html
¾ location.pathname contine sirul de caractere "/mydir/location.html ".

Atribuind o valoare noua acestei proprietati, de exemplu o cale spre un alt document
HTML, browserul va accesa serverul pentru a incarca noul document. Astfel
instructiunea JavaScript:

location.pathname = "/mydir/altele/document.html";

va determina browserul sa incarce documentul document.html cu URL-ul

http://www.nexus3.org/mydir/ altele/document.html

aflat in subdirectorul altele din directorul mydir.

• Proprietatea location.hash

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">, aceasta
proprietate contine numele ancorei respective. Astfel in cazul cand URL-ul este:

http://www.nexus3.org/mydir/ altele/document.html#ancora
¾ location.hash contine sirul de caractere "ancora ".

• Proprietatea location.search
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


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


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


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.
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"
PROGRAMARE IN JAVA - Note de curs 411

NAME="TEST"
VALUE="Test"
CHECKED="true"
onClick="mesaj()">
</H2>
</FORM>
</HEAD>
</BODY>
</HTML>

Fig.30.28 Controlul de tip "checkbox" al unui formular

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 de butoane cu interblocare cu acelasi nume care intra in
componenta 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 se numeste
test, 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 actionat (numai unul dintre
butoanele grupului poate avea atributul CHEKED=true).
Listingul 30.12 si figura 30.29 exemplifica definirea si utilizarea unui formular
continand ungrup de butoane interblocate.
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>
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>
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.
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>
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].s
electedIndex].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.
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>
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.
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.
420 CURS 30

Tabel 30.1 Obiecte JavaScript


OBIECT Proprietati Metode Evenimente
window frames[] alert() onLoad
frames.length confirm() onUnload
status prompt() onFocus
self open() onBlur
parent close() onError
top blur()
opener focus()
scroll()
history length go()
back()
forward()
document title write() onLoad
location writeln() onUnload
lastModified clear()
loadedDate close()
bgColor open()
fgColor
linkColor
vlinkColor
alinkColor
forms[]
forms.length
links[]
links.length
anchors[]
anchors.length
form name submit() onSubmit
method reset() onReset
action
target
elements[]
button, submit, reset name click() onClick
value
type
checkbox name click() onClick
value
status
defaultChecked
checked
type
radio name click() onClick
value
length
defaultChecked
checked
type
select name onFocus
length onBlur
selectedIndex onChange
type
options.length
options[] continand
aceste subproprietati:
index
name
selected
text
value
text, textarea, password name blur() onFocus
value focus() onBlur
defaultValue select() onChange
type onSelect

S-ar putea să vă placă și