You are on page 1of 168

www.cartiaz.

ro – Carti si articole online gratuite de la A la Z

Introducere
Scurt istoric

Limbajul Java împreună cu mediul său de dezvoltare şi execuţie au fost proiectate pentru a
rezolva o parte dintre problemele actuale ale programării. Proiectul Java a pornit cu scopul declarat de
a dezvolta un software performant pentru aparatele electronice de larg consum. Aceste echipamente se
definesc ca: mici, portabile, distribuite şi lucrând în timp real. De la aceste aparate, ne-am obişnuit să
cerem fiabilitate şi uşurinţă în exploatare.
Limbajul luat iniţial în considerare a fost C++. Din păcate, atunci când s-a încercat crearea unui
mediu de execuţie care să respecte toate aceste condiţii s-a observat că o serie de trăsături ale C++ sunt
incompatibile cu necesităţile declarate. În principal, problema vine din faptul că C++ este prea
complicat, foloseşte mult prea multe convenţii şi are încă prea multe elemente de definiţie lăsate la
latitudinea celor care scriu compilatoare pentru o platformă sau alta.
În aceste condiţii, firma Sun a pornit proiectarea unui nou limbaj de programare asemănător cu
C++ dar mult mai flexibil, mai simplu şi mai portabil. Aşa s-a născut Java. Părintele noului limbaj a
fost James Gostling care este cunoscut ca autor al editorului emacs şi al sistemului de ferestre grafice
NeWS. Proiectul a început încă din 1990 dar Sun a făcut publică specificaţia noului limbaj abia în 1995
la SunWorld în San Francisco.
Numele iniţial al limbajului a fost Oak, numele unui copac care creşte în faţa biroului lui James
Gostling. Ulterior, s-a descoperit că numele fusese deja folosit în trecut pentru un alt limbaj de
programare aşa că a fost abandonat şi înlocuit cu Java, spre deliciul programatorilor care iubesc
cafenelele şi aromele exotice.

Ce este Java?

În primul rând, Java încearcă să rămână un limbaj simplu de folosit chiar şi de către
programatorii neprofesionişti, programatori care doresc să se concentreze asupra aplicaţiilor în
principal şi abia apoi asupra tehnicilor de implementare a acestora. Această trăsătură poate fi
considerată ca o reacţie directă la complexitatea considerabilă a limbajului C++.
Au fost îndepărtate din Java aspectele cele mai derutante din C++ precum supraîncărcarea
operatorilor şi moştenirea multiplă. A fost introdus un colector automat de gunoaie care să rezolve
problema dealocării memoriei în mod uniform, fără intervenţia programatorului. Colectorul de gunoaie
nu este o trăsătură nouă, dar implementarea acestuia în Java este făcută inteligent şi eficient folosind un
fir separat de execuţie, pentru că Java are încorporate facilităţi de execuţie pe mai multe fire de
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

execuţie. Astfel, colectarea gunoaielor se face de obicei în timp ce un alt fir aşteaptă o operaţie de
intrare-ieşire sau pe un semafor.
Limbajul Java este independent de arhitectura calculatorului pe care lucrează şi foarte portabil.
În loc să genereze cod nativ pentru o platformă sau alta, compilatorul Java generează o secvenţă de
instrucţiuni ale unei maşini virtuale Java (numită bytecod). Execuţia aplicaţiilor Java este interpretată.
Singura parte din mediul de execuţie Java care trebuie portată de pe o arhitectură pe alta este mediul de
execuţie cuprinzând interpretorul şi o parte din bibliotecile standard care depind de sistem. În acest fel,
aplicaţii Java compilate pe o arhitectură SPARC de exemplu, pot fi rulate fără recompilare pe un sistem
bazat pe procesoare Intel.
Una dintre principalele probleme ale limbajelor interpretate este viteza de execuţie, considerabil
scăzută faţă de cea a limbajelor compilate. Dacă nu vă mulţumeşte viteza de execuţie a unei astfel de
aplicaţii, puteţi cere mediului de execuţie Java să genereze automat, plecând de la codul maşinii
virtuale, codul specific maşinii pe care lucraţi, obţinându-se astfel un executabil nativ care poate rula la
viteză maximă. De obicei însă, în Java se compilează doar acele părţi ale programului mari
consumatoare de timp, restul rămânând interpretate pentru a nu se pierde flexibilitatea. Mediul de
execuţie însuşi este scris în C, ceea ce îl face extrem de portabil.
Interpretorul Java este gândit să lucreze pe maşini mici, precum ar fi procesoarele cu care sunt
dotate aparatele casnice. Interpretorul plus bibliotecile standard cu legare dinamică nu depăşesc 300
Kocteţi. Chiar împreună cu interfaţa grafică totul rămâne mult sub 1 Moctet.
Limbajul Java este în totalitate orientat obiect. Cu el se pot crea clase de obiecte şi instanţe ale
acestora, se pot încapsula informaţiile, se pot moşteni variabilele şi metodele de la o clasă la alta, etc.
Singura trăsătură specifică limbajelor orientate obiect care lipseşte este moştenirea multiplă, dar pentru
a suplini această lipsă, Java oferă o facilitate mai simplă, numită interfaţă, care permite definirea unui
anumit comportament pentru o clasă de obiecte, altul decât cel definit de clasa de bază. În Java orice
element este un obiect, în afară de datele primare. Din Java lipsesc funcţiile şi variabilele globale. Ne
rămân desigur metodele şi variabilele statice ale claselor.
Java este distribuit, având implementate biblioteci pentru lucrul în reţea care ne oferă TCP/IP,
URL şi încărcarea resurselor din reţea. Aplicaţiile Java pot accesa foarte uşor reţeaua, folosindu-se de
apelurile către un set standard de clase.
Java este robust. În Java legarea funcţiilor se face în timpul execuţiei şi informaţiile de
compilare sunt disponibile până în momentul rulării aplicaţiei. Acest mod de lucru face ca sistemul să
poată determina în orice moment neconcordanţa dintre tipul referit la compilare şi cel referit în timpul
execuţiei evitându-se astfel posibile intruziuni răuvoitoare în sistem prin intermediul unor referinţe
falsificate. În acelaşi timp, Java detectează referinţele nule dacă acestea sunt folosite în operaţii de
acces. Indicii în tablourile Java sunt verificaţi permanent în timpul execuţiei şi tablourile nu se pot
parcurge prin intermediul unor pointeri aşa cum se întâmplă în C/C++. De altfel, pointerii lipsesc
complet din limbajul Java, împreună cu întreaga lor aritmetică, eliminându-se astfel una din
principalele surse de erori. În plus, eliberarea memoriei ocupate de obiecte şi tablouri se face automat,
prin mecanismul de colectare de gunoaie, evitându-se astfel încercările de eliberare multiplă a unei
zone de memorie.
Java este un limbaj cu securitate ridicată. El verifică la fiecare încărcare codul prin mecanisme
de CRC şi prin verificarea operaţiilor disponibile pentru fiecare set de obiecte. Robusteţea este şi ea o
trăsătură de securitate. La un al doilea nivel, Java are incorporate facilităţi de protecţie a obiectelor din
sistem la scriere şi/sau citire. Variabilele protejate într-un obiect Java nu pot fi accesate fără a avea
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

drepturile necesare, verificarea fiind făcută în timpul execuţiei. În plus, mediul de execuţie Java poate fi
configurat pentru a proteja reţeaua locală, fişierele şi celelalte resurse ale calculatorului pe care rulează
o aplicaţie Java.
Limbajul Java are inclus suportul nativ pentru aplicaţii care lucrează cu mai multe fire de
execuţie, inclusiv primitive de sincronizare între firele de execuţie. Acest suport este independent de
sistemul de operare, dar poate fi conectat, pentru o performanţă mai bună, la facilităţile sistemului dacă
acestea există.
Java este dinamic. Bibliotecile de clase în Java pot fi reutilizate cu foarte mare uşurinţă.
Cunoscuta problemă a fragilităţii superclasei este rezolvată mai bine decât în C++. Acolo, dacă o
superclasă este modificată, trebuie recompilate toate subclasele acesteia pentru că obiectele au o altă
structură în memorie. În Java această problemă este rezolvată prin legarea târzie variabilelor, doar la
execuţie. Regăsirea variabilelor se face prin nume şi nu printr-un deplasament fix. Dacă superclasa nu a
şters o parte dintre vechile variabile şi metode, ea va putea fi refolosită fără să fie necesară
recompilarea subclaselor acesteia. Se elimină astfel necesitatea actualizării aplicaţiilor, generată de
apariţia unei noi versiuni de bibliotecă aşa cum se întâmplă, de exemplu, cu MFC-ul Microsoft (şi toate
celelalte ierarhii C++).

Reprezentarea informaţiilor cu obiecte


Obiecte

Informaţiile pe care le reprezentăm în memoria calculatorului sunt rareori atât de simple precum
culorile sau literele. În general, dorim să reprezentăm informaţii complexe, care să descrie obiectele
fizice care ne înconjoară sau noţiunile cu care operăm zilnic, în interiorul cărora culoarea sau o
secvenţă de litere reprezintă doar o mică parte. Aceste obiecte fizice sau noţiuni din lumea reală
trebuiesc reprezentate în memoria calculatorului în aşa fel încât informaţiile specifice lor să fie păstrate
la un loc şi să se poată prelucra ca un tot unitar. Să nu uităm însă că, la nivelul cel mai de jos,
informaţia ataşată acestor obiecte continuă să fie tratată de către compilator ca un şir de numere binare,
singurele informaţii reprezentabile direct în memoria calculatoarelor actuale.
Putem să extindem cerinţele noastre mai departe, spunând că, atunci când analizăm un obiect
fizic sau o noţiune pentru a le reprezenta în calculator, trebuie să analizăm nu numai proprietăţile
acestora dar şi modul în care acestea pot fi utilizate şi care sunt operaţiile care pot fi executate asupra
lor sau cu ajutorul lor. Uneori, setul de operaţii specifice unui obiect împreună cu modul în care acesta
reacţionează la stimuli exteriori se numeşte comportamentul obiectului.
De exemplu, dacă dorim să construim un obiect care reprezintă o minge de formă sferică în
spaţiu, este necesar să definim trei numere care să reprezinte coordonatele x, y şi z relativ la un sistem
de axe dat, precum şi o valoare pentru raza sferei. Aceste valori numerice vor face parte din setul de
proprietăţi ale obiectului minge. Dacă mai târziu vom dori să construim o operaţie care să reprezinte
mutarea în spaţiu a obiectului minge, este suficient să ne folosim de operaţiile cu numere pentru a
modifica valorile coordonatelor x, y şi z.
Desigur, obiectul minge este insuficient descris prin aceste coordonate şi, pentru a simula în
calculator obiectul real este nevoie de multe proprietăţi suplimentare precum şi de multe operaţii în
plus. Dar, dacă problema pe care o avem de rezolvat nu necesită aceste proprietăţi şi operaţii, este
preferabil să nu le definim în obiectul folosit pentru reprezentare. Rezultatul direct al acestui mod de
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

abordare este acela că vom putea defini acelaşi obiect real în mai multe feluri pentru a-l reprezenta în
memoria internă. Modul de definire depinde de problema de rezolvat şi de programatorul care a gândit
reprezentarea. De altfel, aceste diferenţe de percepţie ale unui obiect real există şi între diverşi
observatori umani.
Din punctul de vedere al programării, un obiect este o reprezentare în memoria calculatorului a
proprietăţilor şi comportamentului unei noţiuni sau ale unui obiect real.

Figura 1 Modelul de reprezentare al unui obiect în memorie. Stratul exterior reprezintă doar operaţiile care oferă
calea de a interacţiona cu proprietăţile obiectului şi nu are corespondent direct în zona de memorie ocupată de obiect.

Încapsularea informaţiilor în interiorul obiectelor

Există situaţii în care accesul din exterior la proprietăţile unui obiect poate să pună probleme
acestuia. Din aceste motive, este preferabil să lăsăm modificarea acestor parametri în sarcina exclusivă
a unor operaţii definite de către obiect, operaţii care vor verifica noile valori înainte de a le schimba în
interiorul obiectului. În lipsa acestui filtru, putem să stricăm coerenţa valorilor memorate în interiorul
unui obiect, făcându-l inutilizabil.
Din acest punct de vedere, putem privi obiectul ca pe un set de valori care formează miezul
obiectului şi un set de operaţii care îmbracă aceste valori, protejându-le. Vom spune că proprietăţile
obiectului sunt încapsulate în interiorul acestora. Mai mult, obiectul încapsulează şi modul de
funcţionare a operaţiilor lui specifice, din exterior neputându-se observa decât modul de apelare a
acestor operaţii şi rezultatele apelurilor. Cu alte cuvinte, procesul de încapsulare este procesul de
ascundere a detaliilor neimportante sau sensibile de construcţie a obiectului.
Dar nu numai proprietăţile unui obiect trebuiesc protejate ci şi operaţiile definite de către acesta.
Unele dintre operaţiile definite pentru un obiect sunt periculos de lăsat la dispoziţia oricui. Este
preferabil să putem controla foarte exact cine ce operaţii poate apela pentru un anumit obiect.
Această protejare şi încapsulare a proprietăţilor şi operaţiilor ce se pot executa cu ajutorul unui
obiect are şi o altă consecinţă şi anume aceea că utilizatorul obiectului respectiv este independent de
detaliile constructive ale obiectului respectiv. Structura internă a obiectului poate fi astfel schimbată şi
perfecţionată în timp fără ca funcţionalitatea de bază să fie afectată.
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

Clase de obiecte

În lumea reală se pot identifica uşor familii de obiecte. Este greu să descriem într-un limbaj de
programare fiecare minge din lume dar, pentru a putea folosi orice minge din lume, este suficient să
descriem o singură dată care sunt proprietăţile unei mingi în general, precum şi operaţiile care pot fi
executate cu aceasta. Aceasta nu înseamnă că toate obiectele minge din lume sunt identice. Diferenţa
dintre ele se află reprezentată în primul rând în valorile proprietăţilor lor care diferă de la un obiect de
acelaşi fel la altul. De exemplu, în fiecare obiect minge vom avea un număr natural care reprezintă
culoarea mingii. Acest număr poate să difere de la o minge la alta exact aşa cum, în realitate, culoarea
diferă de la o minge la alta. La fel coordonatele poziţiei mingii la un moment dat sau raza mingii
precum şi materialul din care este confecţionată au valori care variază de la o minge la alta.
Cu alte cuvinte, fiecare minge din lume are acelaşi set de proprietăţi, dar valorile acestora pot să
difere de la o minge la alta. Modelul de reprezentare în memorie a unui obiect este întotdeauna acelaşi,
dar valorile memorate în locaţiile corespunzătoare proprietăţilor sunt în general diferite.
În ceea ce priveşte operaţiile, acestea sunt întotdeauna aceleaşi dar rezultatul aplicării lor poate
să difere în funcţie de valorile proprietăţilor obiectului asupra căruia au fost aplicate. De exemplu,
atunci când aruncăm o minge spre pământ ea va ricoşa din acesta ridicându-se din nou în aer. Înălţimea
la care se va ridica însă, este dependentă de dimensiunile şi materialul din care a fost confecţionată
mingea. Cu alte cuvinte, noua poziţie în spaţiu se va calcula printr-o operaţie care va ţine cont de
valorile memorate în interiorul obiectului. Se poate întâmpla chiar ca operaţia să hotărască faptul că
mingea va străpunge podeaua în loc să fie respinsă de către aceasta.
Să mai observăm că operaţiile nu depind numai de proprietăţile obiectului ci şi de unele valori
exterioare acestuia. Atunci când aruncăm o minge spre pământ, înălţimea la care va ricoşa aceasta
depinde şi de viteza cu care a fost aruncată mingea. Această viteză este un parametru al operaţiei de
aruncare. Nu are nici un rost să transmitem ca parametrii ai unei operaţii valorile proprietăţilor unui
obiect pentru că acestea sunt întotdeauna disponibile operaţiei. Nici o operaţie nu se poate aplica asupra
unui obiect fără să ştim exact care este obiectul respectiv şi ce proprietăţi are acesta. Este absurd să ne
gândim la ce înălţime se va ridica o minge în general, fără să facem presupuneri asupra valorilor
proprietăţilor acesteia. Să mai observăm însă că, dacă toate mingile ar avea aceleaşi valori pentru
proprietăţile implicate în operaţia descrisă mai sus, am putea să calculăm înălţimea de ricoşeu în
general, fără să fim dependenţi de o anumită minge.
În concluzie, putem spune că obiectele cu care lucrăm fac parte întotdeauna dintr-o
familie mai mare de obiecte cu proprietăţi şi comportament similar. Aceste familii de obiecte le
vom numi în continuare clase de obiecte sau concepte în timp ce obiectele aparţinând unei anumite
clase le vom numi instanţe ale clasei de obiecte respective. Putem vorbi despre clasa de obiecte minge
şi despre instanţele acesteia, mulţimea tuturor obiectelor minge care există în lume. Fiecare instanţă a
clasei minge are un loc bine precizat în spaţiu şi în timp, un material şi o culoare. Aceste proprietăţi
diferă de la o instanţă la alta, dar fiecare instanţă a aceleiaşi clase va avea întotdeauna aceleaşi
proprietăţi şi aceleaşi operaţii vor putea fi aplicate asupra ei. În continuare vom numi variabile aceste
proprietăţi ale unei clase de obiecte şi vom numi metode operaţiile definite pentru o anumită clasă de
obiecte.
Pentru a clarifica, să mai reluăm încă o dată: O clasă de obiecte este o descriere a
proprietăţilor şi operaţiilor specifice unui nou tip de obiecte reprezentabile în memorie. O
instanţă a unei clase de obiecte este un obiect de memorie care respectă descrierea clasei. O
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

variabilă a unei clase de obiecte este o proprietate a clasei respective care poate lua valori diferite
în instanţe diferite ale clasei. O metodă a unei clase este descrierea unei operaţii specifice clasei
respective.
Să mai precizăm faptul că, spre deosebire de variabilele unei clase, metodele acesteia sunt
memorate o singură dată pentru toate obiectele. Comportarea diferită a acestora este dată de faptul că
ele depind de valorile variabilelor.
O categorie aparte a claselor de obiecte este categoria acelor clase care reprezintă concepte care
nu se pot instanţia în mod direct, adică nu putem construi instanţe ale clasei respective, de obicei pentru
că nu avem destule informaţii pentru a le putea construi. De exemplu, conceptul de om nu se poate
instanţia în mod direct pentru că nu putem “construi” un om despre care nu ştim exact dacă este bărbat
sau femeie. Putem în schimb instanţia conceptul de bărbat şi conceptul de femeie care sunt nişte
subconcepte ale conceptului om.
Clasele abstracte, neinstanţiabile, servesc în general pentru definirea unor proprietăţi sau
operaţii comune ale mai multor clase şi pentru a putea generaliza operaţiile referitoare la
acestea. Putem, de exemplu să definim în cadrul clasei de obiecte om modul în care acesta se
alimentează ca fiind independent de apartenenţa la conceptul de bărbat sau femeie. Această definiţie va
fi valabilă la amândouă subconceptele definite mai sus. În schimb, nu putem decât cel mult să precizăm
faptul că un om trebuie să aibă un comportament social. Descrierea exactă a acestui comportament
trebuie făcută în cadrul conceptului de bărbat şi a celui de femeie. Oricum, este interesant faptul că,
indiferent care ar fi clasa acestuia, putem să ne bazăm pe faptul că acesta va avea definit un
comportament social, specific clasei lui.
Cele două metode despre care am vorbit mai sus, definite la nivelul unui superconcept, sunt
profund diferite din punctul de vedere al subconceptelor acestuia. În timp ce metoda de alimentaţie este
definită exact şi amândouă subconceptele pot să o folosească fără probleme, metoda de comportament
social este doar o metodă abstractă, care trebuie să existe, dar despre care nu se ştie exact cum trebuie
definită.
Fiecare dintre subconcepte trebuie să-şi definească propriul său comportament social pentru a
putea deveni instanţiabil. Dacă o clasă de obiecte are cel puţin o metodă abstractă, ea devine în
întregime o clasă abstractă şi nu poate fi instanţiată, adică nu putem crea instanţe ale unei clase de
obiecte abstracte.
Altfel spus, o clasă abstractă de obiecte este o clasă pentru care nu s-au precizat suficient
de clar toate metodele astfel încât să poată fi folosită în mod direct.

Derivarea claselor de obiecte

O altă proprietate interesantă a claselor de obiecte este aceea de ierarhizare. Practic, ori de câte
ori definim o nouă clasă de obiecte care să reprezinte un anumit concept, specificăm clasa de obiecte
care reprezintă conceptul original din care provine noul concept împreună cu diferenţele pe care le
aduce noul concept derivat faţă de cel original. Această operaţie de definire a unei noi clase de
obiecte pe baza uneia deja existente o vom numi derivare. Conceptul mai general se va numi
superconcept iar conceptul derivat din acesta se va numi subconcept. În acelaşi mod, clasa originală
se va numi superclasă a noii clase în timp ce noua clasă de obiecte se va numi subclasă a clasei
derivate.
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

Uneori, în loc de derivare se foloseşte termenul de extindere. Termenul vine de la faptul că o


subclasă îşi extinde superclasa cu noi variabile şi metode.
În spiritul acestei ierarhizări, putem presupune că toate clasele de obiecte sunt derivate dintr-o
clasă iniţială, să-i spunem clasa de obiecte generice, în care putem defini proprietăţile şi operaţiile
comune tuturor obiectelor precum ar fi testul de egalitate dintre două instanţe, duplicarea instanţelor
sau aflarea clasei de care aparţine o anumită instanţă.
Ierarhizarea se poate extinde pe mai multe nivele, sub formă arborescentă, în fiecare punct
nodal al structurii arborescente rezultate aflându-se clase de obiecte. Desigur, clasele de obiecte de pe
orice nivel pot avea instanţe proprii, cu condiţia să nu fie clase abstracte, imposibil de instanţiat.

Figura 2 O ierarhie de clase de obiecte în care clasele sunt reprezentate în câmpuri eliptice iar instanţele acestora în
câmpuri dreptunghiulare. Clasele abstracte de obiecte au elipsa dublată.

Desigur, este foarte dificil să construim o ierarhie de clase de obiecte completă, care să conţină
clase de obiecte corespunzătoare fiecărui concept cunoscut. Din fericire, pentru o problemă dată,
conceptele implicate în rezolvarea ei sunt relativ puţine şi pot fi uşor izolate, simplificate şi definite.
Restrângerea la minimum a arborelui de concepte necesar rezolvării unei anumite probleme fără a se
afecta generalitatea soluţiei este un talent pe care fiecare programator trebuie să şi-l descopere şi să şi-l
cultive cu atenţie. De alegerea acestor concepte depinde eficienţa şi flexibilitatea aplicaţiei.
O clasă de obiecte derivată dintr-o altă clasă păstrează toate proprietăţile şi operaţiile acesteia
din urmă aducând în plus proprietăţi şi operaţii noi. De exemplu, dacă la nivelul clasei de obiecte om
am definit forma bipedă a acestuia şi capacitatea de a vorbi şi de a înţelege, toate acestea vor fi
moştenite şi de către clasele derivate din clasa om, şi anume clasa bărbaţilor şi cea a femeilor. Fiecare
dintre aceste clase de obiecte derivate îşi vor defini propriile lor proprietăţi şi operaţii pentru a descrie
diferenţa dintre ele şi clasa originală.
Unele dintre proprietăţile şi operaţiile definite în superclasă pot fi redefinite în subclasele de
obiecte derivate. Vechile proprietăţi şi operaţii sunt disponibile în continuare, doar că pentru a le putea
accesa va trebui să fie specificată explicit superclasa care deţine copia redefinită. Operaţia de redefinire
a unor operaţii sau variabile din interiorul unei clase în timpul procesului de derivare o vom numi
rescriere.
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

Această redefinire ne dă de fapt o mare flexibilitate în construcţia ierarhiei unei probleme date
pentru că nici o proprietate sau operaţie definită într-un punct al ierarhiei nu este impusă definitiv
pentru conceptele derivate din acest punct direct sau nu.
Revenind pentru un moment la protejarea informaţiilor interne ale unui obiect să precizăm
faptul că gradul de similitudine de care vorbeam mai sus este mărit în cazul în care vorbim de două
clase derivate una din cealaltă. Cu alte cuvinte, o subclasă a unei clase are acces de obicei la mult mai
multe informaţii memorate în superclasa sa decât o altă clasă de obiecte oarecare. Acest lucru este
firesc ţinând cont de faptul că, uneori, o subclasă este nevoită să redefinească o parte din
funcţionalitatea superclasei sale.

Interfeţe spre obiecte

Un obiect este o entitate complexă pe care o putem privi din diverse puncte de vedere. Omul de
exemplu poate fi privit ca un mamifer care naşte pui vii sau poate fi privit ca o fiinţă gânditoare care
învăţă să programeze calculatoare sau poate fi privit ca un simplu obiect spaţio-temporal care are
propria lui formă şi poziţie în funcţie de timp.
Această observaţie ne spune că trebuie să dăm definiţii despre ce înseamnă cu adevărat faptul că
un obiect poate fi privit ca un mamifer sau ca o fiinţa gânditoare sau ca un obiect spaţio-temporal.
Aceste definiţii, pe care le vom numi în continuare interfeţe, sunt aplicabile nu numai clasei de obiecte
om dar şi la alte clase de obiecte derivate sau nu din acesta, superclase sau nu ale acesteia. Putem să
găsim o mulţime de clase de obiecte ale căror instanţe pot fi privite ca obiecte spaţio-temporale dar care
să nu aibă mare lucru în comun cu omul. Practic, atunci când construim o interfaţă, definim un set
minim de operaţii care trebuie să aparţină obiectelor care respectă această interfaţă. Orice clasă
de obiecte care declară că respectă această interfaţă va trebui să definească toate operaţiile.
Operaţiile însă, sunt definite pe căi specifice fiecărei clase de obiecte în parte. De exemplu,
orice obiect spaţial trebuie să definească o operaţie de modificare a poziţiei în care se află. Dar această
operaţie este diferită la un om, care poate să-şi schimbe singur poziţia, faţă de o minge care trebuie
ajutată din exterior pentru a putea fi mutată. Totuşi, dacă ştim cu siguranţă că un obiect este o instanţă a
unui clase de obiecte care respectă interfaţa spatio-temporală, putem liniştiţi să executăm asupra
acestuia o operaţie de schimbare a poziţiei, fără să trebuiască să cunoaştem amănunte despre modul în
care va fi executată această operaţie. Tot ceea ce trebuie să ştim este faptul că operaţia este definită
pentru obiectul respectiv.
În concluzie, o interfaţă este un set de operaţii care trebuiesc definite de o clasă de obiecte
pentru a se înscrie într-o anumită categorie. Vom spune despre o clasă care defineşte toate operaţiile
unei interfeţe că implementează interfaţa respectivă.
Cu alte cuvinte, putem privi interfeţele ca pe nişte reguli de comportament impuse claselor de
obiecte. În clipa în care o clasă implementează o anumită interfaţă, obiectele din clasa respectivă pot fi
privite în exclusivitate din acest punct de vedere. Interfeţele pot fi privite ca nişte filtre prin care putem
privi un anumit obiect, filtre care nu lasă la vedere decât proprietăţile specifice interfeţei, chiar dacă
obiectul în vizor este mult mai complicat în realitate.
Interfeţele crează o altă împărţire a obiectelor cu care lucrăm. În afară de împărţirea normală pe
clase, putem să împărţim obiectele şi după interfeţele pe care le implementează. Şi, la fel cu situaţia în
care definim o operaţie doar pentru obiectele unei anumite clase, putem defini şi operaţii care lucrează
doar cu obiecte care implementează o anumită interfaţă, indiferent de clasa din care acestea fac parte.
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

Structura lexicală Java

Setul de caractere

Limbajului Java lucrează în mod nativ folosind setul de caractere Unicode. Acesta este un
standard internaţional care înlocuieşte vechiul set de caractere ASCII. Motivul acestei înlocuiri a fost
necesitatea de a reprezenta mai mult de 256 de caractere. Setul de caractere Unicode, fiind reprezentat
pe 16 biţi are posibilităţi mult mai mari.
Vechiul standard ASCII este însă un subset al setului Unicode, ceea ce înseamnă că vom regăsi
caracterele ASCII cu exact aceleaşi coduri ca şi mai înainte în noul standard.
Java foloseşte setul Unicode în timpul rulării aplicaţiilor ca şi în timpul compilării acestora.
Folosirea Unicode în timpul execuţiei nu înseamnă nimic altceva decât faptul că o variabilă Java de tip
caracter este reprezentată pe 16 biţi iar un şir de caractere va ocupa fizic în memorie de două ori mai
mulţi octeţi decât numărul caracterelor care formează şirul.
În ceea ce priveşte folosirea Unicode în timpul compilării, compilatorul Java acceptă la intrare
fişiere sursă care pot conţine orice caractere Unicode. Se poate lucra şi cu fişiere ASCII obişnuite în
care putem introduce caractere Unicode folosind secvenţe escape. Fişierele sursă sunt fişiere care
conţin declaraţii şi instrucţiuni Java. Aceste fişiere trec prin trei paşi distincţi la citirea lor de către
compilator:
1. Şirul de caractere Unicode sau ASCII, memorat în fişierul sursă, este transformat într-un şir de
caractere Unicode. Caracterele Unicode pot fi introduse şi ca secvenţe escape folosind doar
caractere ASCII.
2. Şirul de caractere Unicode este transformat într-un şir de caractere în care sunt evidenţiate
separat caracterele de intrare faţă de caracterele de sfârşit de linie.
3. Şirul de caractere de intrare şi de sfârşit de linie este transformat într-un şir de cuvinte ale
limbajului Java.
În primul pas al citirii fişierului sursă, sunt generate secvenţe escape. Secvenţele escape sunt
secvenţe de caractere ASCII care încep cu caracterul backslash \. Pentru secvenţele escape Unicode, al
doilea caracter din secvenţă trebuie să fie u sau U. Orice alt caracter care urmează după backslash va fi
considerat ca fiind caracter nativ Unicode şi lăsat nealterat. Dacă al doilea caracter din secvenţa escape
este u, următoarele patru caractere ASCII sunt tratate ca şi cifre hexazecimale (în baza 16) care
formează împreună doi octeţi de memorie care reprezintă un caracter Unicode.
Se pot folosi la intrare şi fişiere ASCII normale, pentru că ASCII este un subset al Unicode. De
exemplu, putem scrie:
int f\u0660 = 3;
Numele variabilei are două caractere şi al doilea caracter este o cifră codificată Unicode.
Exemple de secvenţe Unicode:
\uaa08 \U0045 \u6abe
În al doilea pas al citirii fişierului sursă, sunt recunoscute ca şi caractere de sfârşit de linie
caracterele ASCII CR şi ASCII LF. În acelaşi timp, secvenţa de caractere ASCII CR-ASCII LF este
tratată ca un singur sfârşit de linie şi nu două. În acest mod, Java suportă în comun standardele de
terminare a liniilor folosite de diferite sisteme de operare: MacOS, Unix şi DOS.
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

Este important să separăm caracterele de sfârşit de linie de restul caracterelor de intrare pentru a
şti unde se termină comentariile de o singură linie (care încep cu secvenţa //) precum şi pentru a raporta
odată cu erorile de compilare şi linia din fişierul sursă în care au apărut acestea.
În pasul al treilea al citirii fişierului sursă, sunt izolate elementele de intrare ale limbajului Java,
şi anume: spaţii, comentarii şi unităţi lexicale.
Spaţiile pot fi caracterele ASCII SP (spaţiu), FF (avans de pagină) sau HT (tab orizontal) precum şi
orice caracter terminator de linie.

Unităţi lexicale

Unităţile lexicale sunt elementele de bază cu care se construieşte semantica programelor Java. În
şirul de cuvinte de intrare, unităţile lexicale sunt separate între ele prin comentarii şi spaţii. Unităţile
lexicale în limbajul Java pot fi:

• Cuvinte cheie
• Identificatori
• Literali
• Separatori
• Operatori

Cuvinte cheie

Cuvintele cheie sunt secvenţe de caractere ASCII rezervate de limbaj pentru uzul propriu. Cu
ajutorul lor, Java îşi defineşte unităţile sintactice de bază. Nici un program nu poate să utilizeze aceste
secvenţe altfel decât în modul în care sunt definite de limbaj. Singura excepţie este aceea că nu există
nici o restricţionare a apariţiei cuvintelor cheie în şiruri de caractere sau comentarii.
Cuvintele cheie ale limbajului Java sunt:
abstract for public
boolean future rest
break generic return
byte goto short
case if static
cast implements super
catch import switch
char inner synchronized
class instanceof this
const intinterface throw
continue long throws
default native transient
do new try
double null var
else operator void
extends outer volatile
final package while
finally private byvalue
float protected
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

Dintre acestea, cele îngroşate sunt efectiv folosite, iar restul sunt rezervate pentru viitoare extensii ale
limbajului.

Identificatori

Identificatorii Java sunt secvenţe nelimitate de litere şi cifre Unicode, începând cu o literă.
Identificatorii nu au voie să fie identici cu cuvintele rezervate.
Cifrele Unicode sunt definite în următoarele intervale:
Reprezentare Unicode Caracter ASCII Explicaţie
\u0030-\u0039 0-9 cifre ISO-LATIN-1

\u0660-\u0669 cifre Arabic-Indic

\u06f0-\u06f9 cifre Eastern Arabic-Indic

\u0966-\u096f cifre Devanagari

\u09e6-\u09ef cifre Bengali

\u0a66-\ u0a6f cifre Gurmukhi

\u0ae6-\u0aef cifre Gujarati

\u0b66-\u0b6f cifre Oriya

\u0be7-\u0bef cifre Tamil

\u0c66-\u0c6f cifre Telugu

\u0ce6-\u0cef cifre Kannada

\u0d66-\u0d6f cifre Malayalam

\u0e50-\u0e59 cifre Thai

\u0ed0-\u0ed9 cifre Lao

\u1040-\u1049 cifre Tibetan

Tabelul 1 Cifrele Unicode.

Un caracter Unicode este o literă dacă este în următoarele intervale şi nu este cifră:
Reprezentare Unicode Caracter ASCII Explicaţie
\u0024 $ semnul dolar (din motive istorice)

\u0041-\u005a A-Z litere majuscule Latin


www.cartiaz.ro – Carti si articole online gratuite de la A la Z

\u005f _ underscore (din motive istorice)

\u0061-\u007a a-z litere minuscule Latin

\u00c0-\u00d6 diferite litere Latin cu diacritice

\u00d8-\u00f6 diferite litere Latin cu diacritice

\u00f8-\u00ff diferite litere Latin cu diacritice

\u0100-\u1fff alte alfabete şi simboluri non-CJK

\u3040-\u318f Hiragana, Katakana, Bopomofo, şi Hangul

\u3300-\u337f cuvinte pătratice CJK

\u3400-\u3d2d simboluri Hangul coreene

\u4e00-\u9fff Han (Chinez, Japonez, Corean)

\uf900-\ufaff compatibilitate Han

Tabelul 2. Literele Unicode.

Literali

Un literal este modalitatea de bază de exprimare în fişierul sursă a valorilor pe care le pot lua
tipurile primitive şi tipul şir de caractere. Cu ajutorul literalilor putem introduce valori constante în
variabilele de tip primitiv sau în variabilele de tip şir de caractere.
În limbajul Java există următoarele tipuri de literali:

• literali întregi
• literali flotanţi
• literali booleeni
• literali caracter
• literali şir de caractere

Literali întregi

Literalii întregi pot fi reprezentaţi în baza 10, 16 sau 8. Toate caracterele care se folosesc pentru
scrierea literalilor întregi fac parte din subsetul ASCII al setului Unicode.
Literalii întregi pot fi întregi normali sau lungi. Literalii lungi se recunosc prin faptul că se
termină cu sufixul l sau L. Un literal întreg este reprezentat pe 32 de biţi iar unul lung pe 64 de biţi.
Un literal întreg în baza 10 începe cu o cifră de la 1 la 9 şi se continuă cu un şir de cifre de la 0
la 9. Un literal întreg în baza 10 nu poate să înceapă cu cifra 0, pentru că acesta este semnul folosit
pentru a semnaliza literalii scrişi în baza 8.
Exemple de literali întregi în baza 10:
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

12356L 234871 2345678908


Exemplul al d oilea şi al patrulea sunt literali întregi lungi.
Pentru a exprima un literal întreg în baza 16 trebuie să definim cifrele de la 10 la 15. Convenţia
va fi următoarea:
10 - a, A 13 - d, D
11 - b, B 14 - e, E
12 - c, C 15 - f, F
În plus, pentru că un literal întreg în baza 16 poate începe cu o literă, vom adăuga prefixul 0x
sau 0X. Dacă nu am adăuga acest sufix, compilatorul ar considera că este vorba despre un identificator.
Exemple de literali întregi în baza 16:
0xa34 0X123 0x2c45L 0xde123abccdL
Ultimele două exemple sunt literali întregi lungi.
Pentru a reprezenta un literal întreg în baza 8, îl vom preceda cu cifra 0. Restul cifrelor pot fi
oricare între 0 şi 7. Cifrele 8 şi 9 nu sunt admise în literalii întregi în baza 8.
Exemple de literali întregi în baza 8:
0234500123001234567712345677L
Valoarea maximă a unui literal întreg normal este de 2147483647 (231-1), scrisă în baza 10. În
baza 16, cel mai mare literal pozitiv se scrie ca 0x7fffffff iar în baza 8 ca 017777777777. Toate trei
scrierile reprezintă de fapt aceeaşi valoare, doar că aceasta este exprimată în baze diferite.
Cea mai mică valoare a unui literal întreg normal este -2147483648 (-231), respectiv 0x80000000 şi
020000000000. Valorile 0xffffffff şi 037777777777 reprezintă amândouă valoarea -1.
Specificarea în sursă a unui literal întreg normal care depăşeşte aceste limite reprezintă o eroare de
compilare. Cu alte cuvinte, dacă folosim în sursă numărul: 21474836470 de exemplu, fără să punem
sufixul de număr lung după el, compilatorul va genera o eroare la analiza sursei.
Valoarea maximă a unui literal întreg lung este, în baza 10, 9223372036854775807L (2 63-1). În
octal, asta înseamnă 0777777777777777777777L iar în baza 16 0x7fffffffffffffffL. În mod asemănător,
valoarea minimă a unui literal întreg lung este -9223372036854775808L (-263-1), în octal această
valoare este 0400000000000000000000L iar în baza 16 este 0x8000000000000000L.
La fel ca şi la literalii întregi normali, depăşirea acestor limite este o eroare de compilare.

Literali flotanţi

Literalii flotanţi reprezintă numere reale. Ei sunt formaţi dintr-o parte întreagă, o parte
fracţionară, un exponent şi un sufix de tip. Exponentul, dacă există, este introdus de litera e sau E
urmată opţional de un semn al exponentului.
Este obligatoriu să existe măcar o cifră fie în partea întreagă fie în partea zecimală şi punctul
zecimal sau litera e pentru exponent.
Sufixul care indică tipul flotantului poate fi f sau F în cazul în care avem o valoare flotantă
normală şi d sau D dacă avem o valoare flotantă dublă. Dacă nu este specificat nici un sufix, valoarea
este implicit dublă.
Valoarea maximă a unui literal flotant normal este 3.40282347e+38f iar valoarea cea mai mică
reprezentabilă este 1.40239846e-45f, ambele reprezentate pe 32 de biţi.
Valoarea maximă reprezentabilă a unui literal flotant dublu este de
1.79769313486231570e+308 iar valoarea cea mai mică reprezentabilă este 4.94065645841246544e-
324, ambele reprezentate pe 64 de biţi.
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

La fel ca şi la literalii întregi, este o eroare să avem exprimat în sursă un literal mai mare decât
valoarea maximă reprezentabilă sau mai mic decât cea mai mică valoare reprezentabilă.
Exemple de literali flotanţi:
1.0e45f -3.456f 0. .01e-3
Primele două exemple reprezintă literali flotanţi normali, iar celelalte literali flotanţi dubli.

Literali booleeni

Literalii booleeni nu pot fi decât true sau false, primul reprezentând valoarea booleană de
adevăr iar celălalt valoarea booleană de fals. True şi false nu sunt cuvinte rezervate ale limbajului Java,
dar nu veţi putea folosi aceste cuvinte ca identificatori.

Literali caracter

Un literal de tip caracter este utilizat pentru a exprima caracterele codului Unicode.
Reprezentarea se face fie folosind o literă, fie o secvenţă escape. Secvenţele escape ne permit
reprezentarea caracterelor care nu au reprezentare grafică şi reprezentarea unor caractere speciale
precum backslash şi însăşi caracterul apostrof.
Caracterele care au reprezentare grafică pot fi reprezentate între apostrofe, ca în exemplele:
'a' 'Ş' ','
Pentru restul caracterelor Unicode trebuie să folosim secvenţe escape. Dintre acestea, câteva sunt
predefinite în Java, şi anume:

Secvenţă escape Caracterul reprezentat

'\b' caracterul backspace BS \u0008

'\t' caracterul tab orizontal HT \u0009

'\n' caracterul linefeed LF \u000a

'\f' caracterul formfeed FF \u000c

'\r' caracterul carriage return CR \u000d

'\"' caracterul ghilimele \u0022

'\'' caracterul apostrof \u0027

'\\' caracterul backslash \u005c

Tabelul 3 Secvenţe escape predefinite în Java.

În formă generală, o secvenţă escape se scrie sub una din formele:


'\o' '\oo' '\too'
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

unde o este o cifră octală iar t este o cifră octală între 0 şi 3.


Nu este corect să folosiţi ca valori pentru literale caracter secvenţa '\u000d' (caracterul ASCII
CR), sau altele care reprezintă caractere speciale, pentru că acestea fiind secvenţe escape Unicode sunt
transformate foarte devreme în timpul procesării sursei în caractere CR şi sunt interpretate ca
terminatori de linie.
Exemple de secvenţe escape:
'\n' '\u23a' '\34'
dacă după caracterul backslash urmează altceva decât: b, t, n, f, r, ", ', \, 0, 1, 2, 3, 4, 5, 6, 7 se va
semnala o eroare de compilare.
În acest moment secvenţele escape Unicode au fost deja înlocuite cu caractere Unicode native. Dacă u
apare după \, se semnalează o eroare de compilare.

Literali şir de caractere

Un literal şir de caractere este format din zero sau mai multe caractere între ghilimele.
Caracterele care formează şirul de caractere pot fi caractere grafice sau secvenţe escape ca cele definite
la literalii caracter.
Dacă un literal şir de caractere conţine în interior un caracter terminator de linie va fi semnalată
o eroare de compilare. Cu alte cuvinte, nu putem avea în sursă ceva de forma:
"Acesta este
greşit! "
chiar dacă aparent exprimarea ar reprezenta un şir format din caracterele A, c, e, s, t, a, spaţiu, e, s, t, e,
linie nouă, g, r, e, ş, i, t, !. Dacă dorim să introducem astfel de caractere terminatoare de linie într-un şir
va trebui să folosim secvenţe escape ca în:
Acesta este\ngreşit
Dacă şirul de caractere este prea lung, putem să-l spargem în bucăţi mai mici pe care să le concatenăm
cu operatorul +.
Fiecare şir de caractere este în fapt o instanţă a clasei de obiecte String declarată standard în
pachetul java.lang.
Exemple de şiruri de caractere:
"" "\"""Şir de caractere" "unu" + "doi"
Primul şir de caractere din exemplu nu conţine nici un caracter şi se numeşte şirul vid. Ultimul
exemplu este format din două şiruri distincte concatenate.

Separatori

Un separator este un caracter care indică sfârşitul unei unităţi lexicale şi începutul alteia.
Separatorii sunt necesari atunci când unităţi lexicale diferite sunt scrise fără spaţii între ele. Acestea se
pot totuşi separa dacă unele dintre ele conţin caractere separatori. În Java separatorii sunt următorii:
(){}[];,.
Exemple de separare:
a[i] sin(56)
În primul exemplu nu avem o singură unitate lexicală ci patru: a, [, i, ]. Separatorii [ şi ] ne dau
această informaţie. În al doilea exemplu, unităţile lexicale sunt tot 4 sin, (, 56, ).
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

Atenţie, separatorii participă în acelaşi timp şi la construcţia sintaxei limbajului. Ei nu sunt identici cu
spaţiile deşi, ca şi acestea, separă unităţi lexicale diferite.

Operatori

Operatorii reprezintă simboluri grafice pentru operaţiile elementare definite de limbajul Java.
Lista tuturor operatorilor limbajului Java este:
= > < ! ~ ?:
= = <= >= != && || ++ --
+ - * / & | ^ % << >> >>>
+= -= *= /= &= |= ^= %= <<= >>= >>>=
Să mai precizăm deocamdată că toţi operatorii joacă şi rol de separatori. Cu alte cuvinte, din secvenţa
de caractere:
vasile+gheorghe
putem extrage trei unităţi lexicale, vasile, + şi gheorghe.

Comentarii

Un comentariu este o secvenţă de caractere existentă în fişierul sursă dar care serveşte doar
pentru explicarea sau documentarea sursei şi nu afectează în nici un fel semantica programelor.
În Java există trei feluri de comentarii:

• Comentarii pe mai multe linii, închise între /* şi */. Toate caracterele dintre cele două secvenţe
sunt ignorate.
• Comentarii pe mai multe linii care ţin de documentaţie, închise între /** şi */. Textul dintre cele
două secvenţe este automat mutat în documentaţia aplicaţiei de către generatorul automat de
documentaţie.
• Comentarii pe o singură linie care încep cu //. Toate caracterele care urmează acestei secvenţe
până la primul caracter sfârşit de linie sunt ignorate.

În Java, nu putem să scriem comentarii în interiorul altor comentarii. La fel, nu putem introduce
comentarii în interiorul literalilor caracter sau şir de caractere. Secvenţele /* şi */ pot să apară pe o linie
după secvenţa // dar îşi pierd semnificaţia. La fel se întâmplă cu secvenţa // în comentarii care încep
cu /* sau /**.
Ca urmare, următoarea secvenţă de caractere formează un singur comentariu:
/* acest comentariu /* // /* se termină abia aici: */
Variabile
Declaraţii de variabile

O variabilă în limbajul Java este o locaţie de memorie care poate păstra o valoare de un anumit
tip. În ciuda denumirii, există variabile care îşi pot modifica valoarea şi variabile care nu şi-o pot
modifica, numite în Java variabile finale. Orice variabilă trebuie să fie declarată pentru a putea fi
folosită. Această declaraţie trebuie să conţină un tip de valori care pot fi memorate în locaţia rezervată
variabilei şi un nume pentru variabila declarată. În funcţie de locul în sursa programului în care a fost
declarată variabila, aceasta primeşte o clasă de memorare locală sau statică. Această clasă de memorare
defineşte intervalul de existenţă al variabilei în timpul execuţiei.
În forma cea mai simplă, declaraţia unei variabile arată în felul următor:
Tip NumeVariabilă [, NumeVariabilă];

Tipul unei variabile

Tipul unei variabile poate fi fie unul dintre tipurile primitive definite de limbajul Java fie o
referinţă. Creatorii limbajului Java au avut grijă să definească foarte exact care sunt caracteristicile
fiecărui tip primitiv în parte şi care este setul de valori care se poate memora în variabilele care au
tipuri primitive. În plus, a fost exact definită şi modalitatea de reprezentare a acestor tipuri primitive în
memorie. În acest fel, variabilele Java devin independente de platforma hardware şi software pe care
lucrează.
În acelaşi spirit, Java defineşte o valoare implicită pentru fiecare tip de dată, în cazul în care
aceasta nu a primit nici o valoare de la utilizator. În acest fel, ştim întotdeauna care este valoarea cu
care o variabilă intră în calcul. Este o practică bună însă aceea ca programele să nu depindă niciodată
de aceste iniţializări implicite.

Numele variabilelor

Numele variabilei poate fi orice identificator Java. Convenţia nescrisă de formare a numelor
variabilelor este aceea că orice variabilă care nu este finală are un nume care începe cu literă minusculă
în timp ce variabilele finale au nume care conţin numai majuscule. Dacă numele unei variabile care nu
este finală conţine mai multe cuvinte, cuvintele începând cu cel de-al doilea se scriu cu litere minuscule
dar cu prima literă majusculă. Exemple de nume de variabile care nu sunt finale ar putea fi:
culoarea numărulDePaşi următorulElement
Variabilele finale ar putea avea nume precum:
PORTOCALIUVERDEALBASTRUDESCHIS
Iniţializarea variabilelor

Limbajul Java permite iniţializarea valorilor variabilelor chiar în momentul declarării acestora.
Sintaxa este următoarea:
Tip NumeVariabilă = ValoareIniţială;
Desigur, valoarea iniţială trebuie să fie de acelaşi tip cu tipul variabilei sau să poată fi
convertită într-o valoare de acest tip.
Deşi limbajul Java ne asigură că toate variabilele au o valoare iniţială bine precizată, este
preferabil să executăm această iniţializare în mod explicit pentru fiecare declaraţie. În acest fel
mărim claritatea propriului cod.
Regula ar fi deci următoarea: nici o declaraţie fără iniţializare.

Tipuri primitive
Tipul boolean

Tipul boolean este folosit pentru memorarea unei valori de adevăr. Pentru acest scop, sunt
suficiente doar două valori: adevărat şi fals. În Java aceste două valori le vom nota prin literalii true
şi respectiv false. Aceste valori pot fi reprezentate în memorie folosindu-ne de o singură cifră binară,
adică pe un bit.
Valorile booleene sunt foarte importante în limbajul Java pentru că ele sunt valorile care se
folosesc în condiţiile care controlează instrucţiunile repetitive sau cele condiţionale. Pentru a
exprima o condiţie este suficient să scriem o expresie al cărui rezultat este o valoare booleană,
adevărat sau fals.
Valorile de tip boolean nu se pot transforma în valori de alt tip în mod nativ. La fel, nu
există transformare nativă dinspre celelalte valori înspre tipul boolean. Cu alte cuvinte, având o
variabilă de tip boolean nu putem memora în interiorul acesteia o valoare întreagă pentru că
limbajul Java nu face pentru noi nici un fel de presupunere legată de ce înseamnă o anumită valoare
întreagă din punctul de vedere al valorii de adevăr. La fel, dacă avem o variabilă întreagă, nu îi
putem atribui o valoare de tip boolean.
Orice variabilă booleană nou creată primeşte automat valoarea implicită false. Putem
modifica această comportare specificând în mod explicit o valoare iniţială true.
Pentru a declara o variabilă de tip boolean, în Java vom folosi cuvântul rezervat boolean ca în
exemplele de mai jos:
boolean terminat;
boolean areDreptate;
Rândurile de mai sus reprezintă declaraţia a două variabile de tip boolean numite terminat
respectiv areDreptate. Cele două variabile au, după declaraţie, valoarea false.

Tipul caracter

Orice limbaj de programare ne oferă într-un fel sau altul posibilitatea de a lucra cu caractere
grafice care să reprezinte litere, cifre, semne de punctuaţie, etc. În cazul limbajului Java acest lucru
se poate face folosind tipul primitiv numit tip caracter.
O variabilă de tip caracter poate avea ca valoare coduri Unicode reprezentate pe 16 biţi,
adică doi octeţi. Codurile reprezentabile astfel sunt foarte multe, putând acoperi caracterele de bază
din toate limbile scrise existente.
În Java putem combina mai multe caractere pentru a forma cuvinte sau şiruri de caractere
mai lungi. Totuşi, trebuie să precizăm că aceste şiruri de caractere nu trebuiesc confundate cu
tablourile de caractere pentru că ele conţin în plus informaţii legate de lungimea şirului.
Codul nu este altceva decât o corespondenţă între numere şi caractere fapt care permite
conversii între variabile întregi şi caractere în ambele sensuri. O parte din aceste transformări pot să
altereze valoarea originală din cauza dimensiunilor diferite ale zonelor în care sunt memorate cele
două tipuri de valori. Convertirea caracterelor în numere şi invers poate să fie utilă la prelucrarea în
bloc a caracterelor, cum ar fi trecerea tuturor literelor minuscule în majuscule şi invers.
Atunci când declarăm un caracter fără să specificăm o valoare iniţială, el va primi automat
ca valoare implicită caracterul null al codului Unicode, \u0000.
Pentru a declara o variabilă de tip caracter folosim cuvântul rezervat char ca în exemplele
următoare:
char primaLiteră;
char prima, ultima;
În cele două linii de cod am declarat trei variabile de tip caracter care au fost automat iniţializate cu
caracterul null.

Tipuri întregi
Tipul octet

Între tipurile întregi, acest tip ocupă un singur octet de memorie, adică opt cifre binare. Într-
o variabilă de tip octet sunt reprezentate întotdeauna valori cu semn, ca de altfel în toate variabilele
de tip întreg definite în limbajul Java. Această convenţie simplifică schema de tipuri primitive care,
în cazul altor limbaje include separat tipuri întregi cu semn şi fără.
Fiind vorba de numere cu semn, este nevoie de o convenţie de reprezentare a semnului.
Convenţia folosită de Java este reprezentarea în complement faţă de doi. Această reprezentare este
de altfel folosită de majoritatea limbajelor actuale şi permite memorarea, pe 8 biţi a 256 de numere
începând de la -128 până la 127. Dacă aveţi nevoie de numere mai mari în valoare absolută, apelaţi la
alte tipuri întregi.
Valoarea implicită pentru o variabilă neiniţializată de tip octet este valoarea 0 reprezentată pe un
octet.
Iată şi câteva exemple de declaraţii care folosesc cuvântul Java rezervat byte:
byte octet;
byte eleviPeClasa;

Tipul întreg scurt

Tipul întreg scurt este similar cu tipul octet dar valorile sunt reprezentate pe doi octeţi, adică
16 biţi. La fel ca şi la tipul octet, valorile sunt întotdeauna cu semn şi se foloseşte reprezentarea în
complement faţă de doi. Valorile de întregi scurţi reprezentabile sunt de la -32768 la 32767 iar
valoarea implicită este 0 reprezentat pe doi octeţi.
Pentru declararea variabilelor de tip întreg scurt în Java se foloseşte cuvântul rezervat short,
ca în exemplele următoare:
short i, j;
short valoareNuPreaMare;
Tipul întreg

Singura diferenţă dintre tipul întreg şi tipurile precedente este faptul că valorile sunt
reprezentate pe patru octeţi adică 32 biţi. Valorile reprezentabile sunt de la -2147483648 la 2147483647
valoarea implicită fiind 0. Cuvântul rezervat este int ca în:

int salariu;

Tipul întreg lung

În fine, pentru cei care vor să reprezinte numerele întregi cu semn pe 8 octeţi, 64 de biţi,
există tipul întreg lung. Valorile reprezentabile sunt de la -9223372036854775808 la
9223372036854775807 iar valoarea implicită este 0L.
Pentru cei care nu au calculatoare care lucrează pe 64 de biţi este bine de precizat faptul că
folosirea acestui tip duce la operaţii lente pentru că nu există operaţii native ale procesorului care să
lucreze cu numere aşa de mari.
Declaraţia se face cu cuvântul rezervat long.

Tipuri flotante

Acest tip este folosit pentru reprezentarea numerelor reale sub formă de exponent şi cifre
semnificative. Reprezentarea se face pe patru octeţi, 32 biţi, aşa cum specifică standardul IEEE

Tipul flotant

Valorile finite reprezentabile într-o variabilă de tip flotant sunt de forma:


sm2e
unde s este semnul +1 sau -1, m este partea care specifică cifrele reprezentative ale numărului,
numită şi mantisă, un întreg pozitiv mai mic decât 224 iar e este un exponent întreg între -149 şi 104.
Valoarea implicită pentru variabilele flotante este 0.0f. Pentru declararea unui număr flotant, Java
defineşte cuvântul rezervat float. Declaraţiile se fac ca în exemplele următoare:
float procent;
float x,y;

Tipul flotant dublu

Dacă valorile reprezentabile în variabile flotante nu sunt destul de precise sau destul de
mari, puteţi folosi tipul flotant dublu care foloseşte opt octeţi pentru reprezentare, urmând acelaşi
standard IEEE
Valorile finite reprezentabile cu flotanţi dubli sunt de forma:
sm2e
unde s este semnul +1 sau -1, m este mantisa, un întreg pozitiv mai mic decât 253 iar e este un
exponent întreg între -1045 şi 1000. Valoarea implicită în acest caz este 0.0d.
Pentru a declara flotanţi dubli, Java defineşte cuvântul rezervat double ca în:
double distanţaPânăLaLună;
În afară de valorile definite până acum, standardul IEEE defineşte câteva valori speciale
reprezentabile pe un flotant sau un flotant dublu.

Reali speciali definiţi de IEEE


Prima dintre acestea este NaN (Not a Number), valoare care se obţine atunci când efectuăm
o operaţie a cărei rezultat nu este definit, de exemplu 0.0 / 0.0.
În plus, standardul IEEE defineşte două valori pe care le putem folosi pe post de infinit pozitiv şi
negativ. Şi aceste valori pot rezulta în urma unor calcule.
Aceste valori sunt definite sub formă de constante şi în ierarhia standard Java, mai precis în clasa
java.lang.Float şi respectiv în java.lang.Double. Numele constantelor este POSITIVE_INFINITY,
NEGATIVE_INFINITY, NaN.
În plus, pentru tipurile întregi şi întregi lungi şi pentru tipurile flotante există definite clase
în ierarhia standard Java care se numesc respectiv java.lang.Integer, java.lang.Long, java.lang.Float
şi java.lang.Double. În fiecare dintre aceste clase numerice sunt definite două constante care
reprezintă valorile minime şi maxime care se pot reprezenta în tipurile respective. Aceste două
constante se numesc în mod uniform MIN_VALUE şi MAX_VALUE.

Tipuri referinţă

Tipurile referinţă sunt folosite pentru a referi un obiect din interiorul unui alt obiect. În acest
mod putem înlănţui informaţiile aflate în memorie.
Tipurile referinţă au, la fel ca şi toate celelalte tipuri o valoare implicită care este atribuită
automat oricărei variabile de tip referinţă care nu a fost iniţializată. Această valoare implicită este
definită de către limbajul Java prin cuvântul rezervat null.
Puteţi înţelege semnificaţia referinţei nule ca o referinţă care nu trimite nicăieri, a cărei
destinaţie nu a fost încă fixată.
Simpla declaraţie a unei referinţe nu duce automat la rezervarea spaţiului de memorie pentru
obiectul referit. Singura rezervare care se face este aceea a spaţiului necesar memorării referinţei în
sine. Rezervarea obiectului trebuie făcută explicit în program printr-o expresie de alocare care
foloseşte cuvântul rezervat new.
O variabilă de tip referinţă nu trebuie să trimită pe tot timpul existenţei sale către acelaşi
obiect în memorie. Cu alte cuvinte, variabila îşi poate schimba locaţia referită în timpul execuţiei.

Tipul referinţă către o clasă

Tipul referinţă către o clasă este un tip referinţă care trimite către o instanţă a unei clase de
obiecte. Clasa instanţei referite poate fi oricare clasă validă definită de limbaj sau de utilizator.
Clasa de obiecte care pot fi referite de o anumită variabilă de tip referinţă la clasă trebuie
declarată explicit. De exemplu, pentru a declara o referinţă către o instanţă a clasei Minge, trebuie să
folosim următoarea sintaxă:
Minge mingeaMea;
Din acest moment, variabila referinţă de clasă numită mingeaMea va putea păstra doar
referinţe către obiecte de tip Minge sau către obiecte aparţinând unor clase derivate din clasa Minge.
De exemplu, dacă avem o altă clasă, derivată din Minge, numită MingeDeBaschet, putem memora în
referinţa mingeaMea şi o trimitere către o instanţă a clasei MingeDeBaschet.
În mod general însă, nu se pot păstra în variabila mingeaMea referinţe către alte clase de
obiecte. Dacă se încercă acest lucru, eroarea va fi semnalată chiar în momentul compilării, atunci
când sursa programului este examinată pentru a fi transformată în instrucţiuni ale maşinii virtuale
Java.

Tipul referinţă către o interfaţă


Tipul referinţă către o interfaţă permite păstrarea unor referinţe către obiecte care respectă o
anumită interfaţă. Clasa obiectelor referite poate fi oricare, atâta timp cât clasa respectivă
implementează interfaţa cerută.
Declaraţia se face cu următoarea sintaxă:
ObiectSpaţioTemporal mingeaLuiVasile;
în care tipul este chiar numele interfeţei cerute. Dacă clasa de obiecte Minge declară că
implementează această interfaţă, atunci variabila referinţă mingeaLuiVasile poate lua ca valoare
referinţa către o instanţă a clasei Minge sau a clasei MingeDeBaschet.
Prin intermediul unei variabile referinţă către o interfaţă nu se poate apela decât la
funcţionalitatea cerută în interfaţa respectivă, chiar dacă obiectele reale oferă şi alte facilităţi, ele
aparţinând unor clase mai bogate în metode.

Tipul referinţă către un tablou

Tipul referinţă către un tablou este un tip referinţă care poate păstra o trimitere către locaţia
din memorie a unui tablou de elemente. Prin intermediul acestei referinţe putem accesa elementele
tabloului furnizând indexul elementului dorit.
Tablourile de elemente nu există în general ci ele sunt tablouri formate din elemente de un
tip bine precizat. Din această cauză, atunci când declarăm o referinţă către un tablou, trebuie să
precizăm şi de ce tip sunt elementele din tabloul respectiv.
La declaraţia referinţei către tablou nu trebuie să precizăm şi numărul de elemente din
tablou.
Iată cum se declară o referinţă către un tablou de întregi lungi:
long numere[];
Numele variabilei este numere. Un alt exemplu de declaraţie de referinţă către un tablou:
Minge echipament[];
Declaraţia de mai sus construieşte o referinţă către un tablou care păstrează elemente de tip referinţă
către o instanţă a clasei Minge. Numele variabilei referinţă este echipament. Parantezele drepte de
după numele variabilei specifică faptul că este vorba despre un tablou.

Clasa de memorare

Fiecare variabilă trebuie să aibă o anumită clasă de memorare. Această clasă ne permite să
aflăm care este intervalul de existenţă şi vizibilitatea unei variabile în contextul execuţiei unui
program.
Este important să înţelegem exact această noţiune pentru că altfel vom încerca să referim variabile
înainte ca acestea să fi fost create sau după ce au fost distruse sau să referim variabile care nu sunt
vizibile din zona de program în care le apelăm.

Variabile locale

Aceste variabile nu au importanţă prea mare în contextul întregii aplicaţii, ele servind la
rezolvarea unor probleme locale. Variabilele locale sunt declarate, rezervate în memorie şi utilizate
doar în interiorul unor blocuri de instrucţiuni, fiind distruse automat la ieşirea din aceste blocuri.
Aceste variabile sunt vizibile doar în interiorul blocului în care au fost create şi în subblocurile
acestuia.
Variabile statice

Variabilele statice sunt în general legate de funcţionalitatea anumitor clase de obiecte ale
căror instanţe folosesc în comun aceste variabile. Variabilele statice sunt create atunci când codul
specific clasei în care au fost declarate este încărcat în memorie şi nu sunt distruse decât atunci când
acest cod este eliminat din memorie.
Valorile memorate în variabile statice au importanţă mult mai mare în aplicaţie decât cele
locale, ele păstrând informaţii care nu trebuie să se piardă la dispariţia unei instanţe a clasei. De
exemplu, variabila în care este memorat numărul de picioare al obiectelor din clasa Om nu trebuie
să fie distrusă la dispariţia unei instanţe din această clasă. Aceasta din cauză că şi celelalte instanţe
ale clasei folosesc aceeaşi valoare. Şi chiar dacă la un moment dat nu mai există nici o instanţă a
acestei clase, numărul de picioare ale unui Om trebuie să fie accesibil în continuare pentru
interogare de către celelalte clase.
Variabilele statice nu se pot declara decât ca variabile ale unor clase şi conţin în declaraţie
cuvântul rezervat static. Din cauza faptului că ele aparţin clasei şi nu unei anumite instanţe a clasei,
variabilele statice se mai numesc uneori şi variabile de clasă.

Variabile dinamice

Un alt tip de variabile sunt variabilele a căror perioadă de existenţă este stabilită de către
programator. Aceste variabile pot fi alocate la cerere, dinamic, în orice moment al execuţiei
programului. Ele vor fi distruse doar atunci când nu mai sunt referite de nicăieri.
La alocarea unei variabile dinamice, este obligatoriu să păstrăm o referinţă către ea într-o
variabilă de tip referinţă. Altfel, nu vom putea accesa în viitor variabila dinamică. În momentul în
care nici o referinţă nu mai trimite către variabila dinamică, de exemplu pentru că referinţa a fost o
variabilă locală şi blocul în care a fost declarată şi-a terminat execuţia, variabila dinamică este
distrusă automat de către sistem printr-un mecanism numit colector de gunoaie.
Colectorul de gunoaie poate porni din iniţiativa sistemului sau din iniţiativa programatorului
la momente bine precizate ale execuţiei.
Pentru a rezerva spaţiu pentru o variabilă dinamică este nevoie să apelăm la o expresie de
alocare care foloseşte cuvântul rezervat new. Această expresie alocă spaţiul necesar pentru un
anumit tip de valoare. De exemplu, pentru a rezerva spaţiul necesar unui obiect de tip Minge, putem
apela la sintaxa:
Minge mingeaMea = new Minge( );
iar pentru a rezerva spaţiul necesar unui tablou de referinţe către obiecte de tip Minge putem folosi
declaraţia:
Minge echipament[] = new Minge[5];
Am alocat astfel spaţiu pentru un tablou care conţine 5 referinţe către obiecte de tip Minge. Pentru
alocarea tablourilor conţinând tipuri primitive se foloseşte aceeaşi sintaxă. De exemplu, următoarea
linie de program alocă spaţiul necesar unui tablou cu 10 întregi, creând în acelaşi timp şi o variabilă
referinţă spre acest tablou, numită numere:
int numere[] = new int[10];
Tablouri de variabile

Tablourile servesc, după cum s-a văzut, la memorarea secvenţelor de elemente de acelaşi tip.
Tablourile unidimensionale au semnificaţia vectorilor de elemente. Se poate întâmpla să lucrăm şi
cu tablouri de referinţe către tablouri, în acest caz modelul fiind acela al unei matrici
bidimensionale, sau putem extinde definiţia şi pentru mai mult de două dimensiuni.

Declaraţia variabilelor de tip tablou

Pentru a declara variabile de tip tablou, trebuie să specificăm tipul elementelor care vor
umple tabloul şi un nume pentru variabila referinţă care va păstra trimiterea către zona de memorie
în care sunt memorate elementele tabloului.
Deşi putem declara variabile referinţă către tablou şi separat, de obicei declaraţia este făcută
în acelaşi timp cu alocarea spaţiului ca în exemplele din paragraful anterior.
Sintaxa Java permite plasarea parantezelor drepte care specifică tipul tablou înainte sau după
numele variabilei. Astfel, următoarele două declaraţii sunt echivalente:
int[ ] numere;
int numere[ ];
Dacă doriţi să folosiţi tablouri cu două dimensiuni ca matricile, puteţi să declaraţi un tablou de
referinţe către tablouri cu una dintre următoarele trei sintaxe echivalente:
float [ ][ ] matrice;
float[ ] matrice[];
float matrice[][];
De precizat că şi în cazul dimensiunilor multiple, declaraţiile de mai sus nu fac nimic altceva
decât să rezerve loc pentru o referinţă şi să precizeze numărul de dimensiuni. Alocarea spaţiului
pentru elementele tabloului trebuie făcută explicit.
Pentru tablourile cu mai multe dimensiuni, rezervarea spaţiului se poate face cu următoarea sintaxă:
byte [][]octeti = new byte[23][5];
În expresia de alocare sunt specificate în clar numărul elementelor pentru fiecare dimensiune a
tabloului.

Iniţializarea tablourilor.

Limbajul Java permite şi o sintaxă pentru iniţializarea elementelor unui tablou. Într-un astfel
de caz este rezervat automat şi spaţiul de memorie necesar memorării valorilor iniţiale. Sintaxa
folosită în astfel de cazuri este următoarea:
char []caractere = { ’a’, ’b ’, ’c ’, ’d ’ };
Acest prim exemplu alocă spaţiu pentru patru elemente de tip caracter şi iniţializează aceste
elemente cu valorile dintre acolade. După aceea, creează variabila de tip referinţă numită caractere
şi o iniţializează cu referinţa la zona de memorie care păstrează cele patru valori.
Iniţializarea funcţionează şi la tablouri cu mai multe dimensiuni ca în exemplele următoare:
int [][]numere = {
{ 1, 3, 4, 5 },
{ 2, 4, 5 },
{ 1, 2, 3, 4, 5 }
};
double [][][]reali = {
{ { 0.0, -1.0 }, { 4.5 } },
{ { 2.5, 3.0 } }
};
După cum observaţi numărul iniţializatorilor nu trebuie să fie acelaşi pentru fiecare element.
Lungimea tablourilor

Tablourile Java sunt alocate dinamic, ceea ce înseamnă că ele îşi pot schimba dimensiunile pe
parcursul execuţiei. Pentru a afla numărul de elemente dintr-un tablou, putem apela la următoarea
sintaxă:
float []tablou = new float[25];
int dimensiune = tablou.length; // dimensiune primeşte valoarea 25
sau
float [][]multiTablou = new float[3][4];
int dimensiune1 = multiTablou[2].length; // dimensiune1 primeşte valoarea 4
int dimensiune2 = multiTablou.length; // dimensiune2 primeşte valoarea 3

Referirea elementelor din tablou

Elementele unui tablou se pot referi prin numele referinţei tabloului şi indexul elementului
pe care dorim să-l referim. În Java, primul element din tablou este elementul cu numărul 0, al doilea
este elementul numărul 1 şi aşa mai departe.
Sintaxa de referire foloseşte parantezele pătrate [ şi ]. Între ele trebuie specificat indexul elementului
pe care dorim să-l referim. Indexul nu trebuie să fie constant, el putând fi o expresie de
complexitate oarecare.
Iată câteva exemple:
int []tablou = new int[10];
tablou[3] = 1;
// al patrulea element primeşte valoarea 1
float [][]reali = new float[3][4];
reali[2][3] = 1.0f;
// al patrulea element din al treilea tablou
// primeşte valoarea 1
În cazul tablourilor cu mai multe dimensiuni, avem în realitate tablouri de referinţe la tablouri. Asta
înseamnă că dacă considerăm următoarea declaraţie:
char [][]caractere = new char [5][];

Elementele tabloului sunt de tip referinţă, iniţializate implicit la valoarea null.

Variabila referinţă numită caractere conţine deocamdată un tablou de 5 referinţe la tablouri de


caractere. Cele cinci referinţe sunt iniţializate cu null. Putem iniţializa aceste tablouri prin atribuiri
de expresii de alocare:
caractere[0] = new char [3];
caractere[4] = new char [5];
Noile tablouri sunt referite din interiorul tabloului original. Elementele noilor tablouri sunt caractere.

La fel, putem scrie:


char []tablouDeCaractere = caractere[0];

Variabilele de tip referinţă caractere[0] şi tablouDeCaractere trimit spre acelaşi tablou rezervat în memorie.

Variabila tablouDeCaractere trimite către acelaşi tablou de caractere ca şi cel referit de


primul element al tabloului referit de variabila caractere.
Să mai precizăm că referirea unui element de tablou printr-un index mai mare sau egal cu lungimea
tabloului duce la oprirea execuţiei programului cu un mesaj de eroare de execuţie corespunzător.

Alocarea şi eliberarea tablourilor

În cazul în care nu avem iniţializatori, variabilele sunt iniţializate cu valorile implicite


definite de limbaj pentru tipul corespunzător. Aceasta înseamnă că, pentru tablourile cu mai multe
dimensiuni, referinţele sunt iniţializate cu null.
Pentru eliberarea memoriei ocupate de un tablou, este suficient să tăiem toate referinţele către
tablou. Sistemul va sesiza automat că tabloul nu mai este referit şi mecanismul colector de gunoaie
va elibera zona. Pentru a tăia o referinţă către un tablou dăm o altă valoare variabilei care referă
tabloul. Valoarea poate fi null sau o referinţă către un alt tablou.
De exemplu:
float []reali = new float[10];

reali = null; // eliberarea tabloului


sau
reali = new float[15]; // eliberarea în alt fel
sau
{
float []reali = new float[10];

Conversii

Operaţiile definite în limbajul Java au un tip bine precizat de argumente. Din păcate, există
situaţii în care nu putem transmite la apelul acestora exact tipul pe care compilatorul Java îl
aşteaptă. În asemenea situaţii, compilatorul are două alternative: fie respinge orice operaţie cu
argumente greşite, fie încearcă să convertească argumentele către tipurile necesare. Desigur, în
cazul în care conversia nu este posibilă, singura alternativă rămâne prima.
În multe situaţii însă, conversia este posibilă. Să luăm de exemplu tipurile întregi. Putem să
convertim întotdeauna un întreg scurt la un întreg. Valoarea rezultată va fi exact aceeaşi. Conversia
inversă însă, poate pune probleme dacă valoarea memorată în întreg depăşeşte capacitatea de
memorare a unui întreg scurt.
În afară de conversiile implicite, pe care compilatorul le hotărăşte, există şi conversii
explicite, pe care programatorul le poate forţa la nevoie. Aceste conversii efectuează de obicei
operaţii în care există pericolul să se piardă o parte din informaţii. Compilatorul nu poate hotărî de
unul singur în aceste situaţii.
Conversiile implicite pot fi un pericol pentru stabilitatea aplicaţiei dacă pot să ducă la
pierderi de informaţii fără avertizarea programatorului. Aceste erori sunt de obicei extrem de greu
de depistat.
În fiecare limbaj care lucrează cu tipuri fixe pentru datele sale există conversii imposibile,
conversii periculoase şi conversii sigure. Conversiile imposibile sunt conversiile pe care limbajul nu
le permite pentru că nu ştie cum să le execute sau pentru că operaţia este prea periculoasă. De
exemplu, Java refuză să convertească un tip primitiv către un tip referinţă. Deşi s-ar putea imagina o
astfel de conversie bazată pe faptul că o adresă este în cele din urmă un număr natural, acest tip de
conversii sunt extrem de periculoase, chiar şi atunci când programatorul cere explicit această
conversie.

Conversii de extindere a valorii

În aceste conversii valoarea se reprezintă într-o zonă mai mare fără să se piardă nici un fel de
informaţii. Iată conversiile de extindere pe tipuri primitive:

• byte la short, int, long, float sau double


• short la int, long, float sau double
• char la int, long, float sau double
• int la long, float sau double
• long la float sau double
• float la double

Să mai precizăm totuşi că, într-o parte din aceste cazuri, putem pierde din precizie. Această
situaţie apare de exemplu la conversia unui long într-un float, caz în care se pierd o parte din cifrele
semnificative păstrându-se însă ordinul de mărime. De altfel această observaţie este evidentă dacă
ţinem cont de faptul că un long este reprezentat pe 64 de biţi în timp ce un float este reprezentat doar
pe 32 de biţi.
Precizia se pierde chiar şi în cazul conversiei long la double sau int la float pentru că, deşi
dimensiunea zonei alocată pentru cele două tipuri este aceeaşi, numerele flotante au nevoie de o
parte din această zonă pentru a reprezenta exponentul.
În aceste situaţii, se va produce o rotunjire a numerelor reprezentate.

Conversii de trunchiere a valorii

Convenţiile de trunchiere a valorii pot produce pierderi de informaţie pentru că ele convertesc
tipuri mai bogate în informaţii către tipuri mai sărace. Conversiile de trunchiere pe tipurile
elementare sunt următoarele:

• byte la char
• short la byte sau char
• char la byte sau short
• int la byte, short sau char
• long la byte, short char, sau int
• float la byte, short, char, int sau long
• double la byte, short, char, int, long sau float.
În cazul conversiilor de trunchiere la numerele cu semn, este posibil să se schimbe semnul
pentru că, în timpul conversiei, se îndepărtează pur şi simplu octeţii care nu mai încap şi poate
rămâne primul bit diferit de vechiul prim bit. Copierea se face începând cu octeţii mai puţin
semnificativi iar trunchierea se face la octeţii cei mai semnificativi.

Conversii pe tipuri referinţă

Conversiile tipurilor referinţă nu pun probleme pentru modul în care trebuie executată
operaţia din cauză că, referinţa fiind o adresă, în timpul conversiei nu trebuie afectată în nici un fel
această adresă. În schimb, se pun probleme legate de corectitudinea logică a conversiei. De
exemplu, dacă avem o referinţă la un obiect care nu este tablou, este absurd să încercăm să
convertim această referinţă la o referinţă de tablou.
Limbajul Java defineşte extrem de strict conversiile posibile în cazul tipurilor referinţă pentru a
salva programatorul de eventualele necazuri care pot apare în timpul execuţiei. Iată conversiile
posibile:

• O referinţă către un obiect aparţinând unei clase C poate fi convertit la o referinţă către un
obiect aparţinând clasei S doar în cazul în care C este chiar S sau C este derivată direct sau
indirect din S.
• O referinţă către un obiect aparţinând unei clase C poate fi convertit către o referinţă de
interfaţă I numai dacă clasa C implementează interfaţa I.
• O referinţă către un tablou poate fi convertită la o referinţă către o clasă numai dacă clasa
respectivă este clasa Object.
• O referinţă către un tablou de elemente ale cărui elemente sunt de tipul T1 poate fi
convertită la o referinţă către un tablou de elemente de tip T2 numai dacă T1 şi T2 reprezintă
acelaşi tip primitiv sau T2 este un tip referinţă şi T1 poate fi convertit către T2.

Conversii la operaţia de atribuire

Conversiile pe care limbajul Java le execută implicit la atribuire sunt foarte puţine. Mai
exact, sunt executate doar acele conversii care nu necesită validare în timpul execuţiei şi care nu pot
pierde informaţii în cazul tipurilor primitive.
În cazul valorilor aparţinând tipurilor primitive, următorul tabel arată conversiile posibile. Pe
coloane avem tipul de valoare care se atribuie iar pe linii avem tipurile de variabile la care se
atribuie:
boolean char byte short int long float double
boolean Da Nu Nu Nu Nu Nu Nu Nu
char Nu Da Da Da Nu Nu Nu Nu
byte Nu Da Da Nu Nu Nu Nu Nu
short Nu Da Da Da Nu Nu Nu Nu
int Nu Da Da Da Da Nu Nu Nu
long Nu Da Da Da Da Da Nu Nu
float Nu Da Da Da Da Da Da Nu
double Nu Da Da Da Da Da Da Da

Conversiile posibile într-o operaţie de atribuire cu tipuri primitive. Coloanele reprezintă tipurile care se atribuie
iar liniile reprezintă tipul de variabilă către care se face atribuirea.

După cum se observă, tipul boolean nu poate fi atribuit la o variabilă de alt tip.
Valorile de tip primitiv nu pot fi atribuite variabilelor de tip referinţă. La fel, valorile de tip
referinţă nu pot fi memorate în variabile de tip primitiv. În ceea ce priveşte tipurile referinţă între
ele, următorul tabel defineşte situaţiile în care conversiile sunt posibile la atribuirea unei valori de
tipul T la o variabilă de tipul S: S=T
T este o clasă care T este o clasă care T este o interfaţă T = B[] este un tablou cu
nu este finală este finală elemente de tipul B

S este o clasă care T trebuie să fie T trebuie să fie o eroare la S trebuie să fie Object
nu este finală subclasă a lui S subclasă a lui S compilare

S este o clasă care T trebuie să fie T trebuie să fie eroare la eroare la compilare
este finală aceeaşi clasă ca şi S aceeaşi clasă ca şi S compilare

S este o interfaţă T trebuie să T trebuie să T trebuie să fie o eroare la compilare


implementeze implementeze subinterfaţă a lui
interfaţa S interfaţa S S

S = A[] este un eroare la compilare eroare la compilare eroare la A sau B sunt acelaşi tip
tablou cu compilare primitiv sau A este un tip
elemente de tipul referinţă şi B poate fi atribuit
A lui A

Conversiile posibile la atribuirea unei valori de tipul T la o variabilă de tipul S.


Conversii explicite

Conversiile de tip cast, sau casturile, sunt apelate de către programator în mod explicit.
Sintaxa pentru construcţia unui cast este scrierea tipului către care dorim să convertim în paranteze
în faţa valorii pe care dorim să o convertim. Forma generală este:

( Tip ) Valoare

Conversiile posibile în acest caz sunt mai multe decât conversiile implicite la atribuire pentru că în
acest caz programatorul este prevenit de eventuale pierderi de date el trebuind să apeleze conversia
explicit. Dar, continuă să existe conversii care nu se pot apela nici măcar în mod explicit.
În cazul conversiilor de tip cast, orice valoare numerică poate fi convertită la orice valoare
numerică.
În continuare, valorile de tip boolean nu pot fi convertite la nici un alt tip.
Nu există conversii între valorile de tip referinţă şi valorile de tip primitiv.
În cazul conversiilor dintr-un tip referinţă într-altul putem separa două cazuri. Dacă compilatorul
poate decide în timpul compilării dacă conversia este corectă sau nu, o va decide. În cazul în care
compilatorul nu poate decide pe loc, se va efectua o verificare a conversiei în timpul execuţiei. Dacă
conversia se dovedeşte greşită, va apare o eroare de execuţie şi programul va fi întrerupt.
Iată un exemplu de situaţie în care compilatorul nu poate decide dacă conversia este posibilă sau nu:

Minge mingeaMea;
....
MingeDeBaschet mingeaMeaDeBaschet; // MingeDeBaschet este o clasă derivată din clasa
Minge
mingeaMeaDeBaschet=(MingeDeBaschet)mingeaMea;

În acest caz, compilatorul nu poate fi sigur dacă referinţa memorată în variabila mingeaMea este de
tip MingeDeBaschet sau nu pentru că variabilei de tip Minge i se pot atribui şi referinţe către instanţe de
tip Minge în general, care nu respectă întru totul definiţia clasei MingeDeBaschet sau chiar referinţă
către alte tipuri de minge derivate din clasa Minge, de exemplu MingeDePolo care implementează
proprietăţi şi operaţii diferite faţă de clasa MingeDeBaschet.
Iată şi un exemplu de conversie care poate fi decisă în timpul compilării:

Minge mingeaMea;
MingeDeBaschet mingeaMeaDeBaschet;
....
mingeaMea = ( Minge ) mingeaMeaDeBaschet;

În următorul exemplu însă, se poate decide în timpul compilării imposibilitatea conversiei:

MingeDeBaschet mingeaMeaDeBaschet;
MingeDePolo mingeaMeaDePolo;
....
mingeaMeaDePolo = ( MingeDePolo ) mingeaMeaDeBaschet;

În fine, tabelul următor arată conversiile de tip cast a căror corectitudine poate fi stabilită în timpul
compilării. Conversia încearcă să transforme printr-un cast o referinţă de tip T într-o referinţă de tip
S.
T este o clasă care T este o clasă care T este o interfaţă T = B[] este un tablou cu
nu este finală este finală elemente de tipul B

S este o clasă care T trebuie să fie T trebuie să fie o Totdeauna corectă S trebuie să fie Object
nu este finală subclasă a lui S subclasă a lui S la compilare

S este o clasă care S trebuie să fie T trebuie să fie S trebuie să eroare la compilare
este finală subclasă a lui T aceeaşi clasă ca şi implementeze
S interfaţa T

S este o interfaţă Totdeauna corectă T trebuie să Totdeauna corectă eroare la compilare


la compilare implementeze la compilare
interfaţa S

S = A[] este un T trebuie să fie eroare la eroare la compilare A sau B sunt acelaşi tip
tablou cu Object compilare primitiv sau A este un tip
elemente de tipul referinţă şi B poate fi
A convertit cu un cast la A

Cazurile posibile la convertirea unei referinţe de tip T într-o referinţă de tip S.


Conversii de promovare aritmetică

Promovarea aritmetică se aplică în cazul unor formule în care operanzii pe care se aplică un
operator sunt de tipuri diferite. În aceste cazuri, compilatorul încearcă să promoveze unul sau chiar
amândoi operanzii la acelaşi tip pentru a putea fi executată operaţia.
Există două tipuri de promovare, promovare aritmetică unară şi binară.
În cazul promovării aritmetice unare, există un singur operand care în cazul că este byte sau
short este transformat la int altfel rămâne nemodificat.
La promovarea aritmetică binară se aplică următorul algoritm:

1. Dacă un operand este double, celălalt este convertit la double.


2. Altfel, dacă un operand este de tip float, celălalt operand este convertit la float.
3. Altfel, dacă un operand este de tip long, celălalt este convertit la long
4. Altfel, amândoi operanzii sunt convertiţi la int.

De exemplu, în următoarea operaţie amândoi operanzii vor fi convertiţi la double prin promovare
aritmetică binară:
float f;
double i = f + 3;
După efectuarea operaţiei, valoarea obţinută va fi convertită implicit la double.
În următorul exemplu, se produce o promovare unară la int de la short.
short s, r;
...
int min = ( r < -s ) ? r : s;
În expresia condiţională, operandul -s se traduce de fapt prin aplicarea operatorului unar - la
variabila s care este de tip short. În acest caz, se va produce automat promovarea aritmetică
unară de la short la int, apoi se va continua evaluarea expresiei.
Obiecte Java
În primul rând să observăm că, atunci când scriem programe în Java nu facem altceva decât să
definim noi şi noi clase de obiecte. Dintre acestea, unele vor reprezenta însăşi aplicaţia noastră în
timp ce altele vor fi necesare pentru rezolvarea problemei la care lucrăm. Ulterior, atunci când dorim
să lansăm aplicaţia în execuţie nu va trebui decât să instanţiem un obiect reprezentând aplicaţia în
sine şi să apelăm o metodă de pornire definită de către aplicaţie, metodă care de obicei are un nume
şi un set de parametri bine fixate. Totuşi, numele acestei metode depinde de contextul în care este
lansată aplicaţia noastră.
Această abordare a construcţiei unei aplicaţii ne spune printre altele că vom putea lansa oricâte
instanţe ale aplicaţiei noastre dorim, pentru că fiecare dintre acestea va reprezenta un obiect în memorie
având propriile lui valori pentru variabile. Execuţia instanţelor diferite ale aplicaţiei va urma desigur
căi diferite în funcţie de interacţiunea fiecăreia cu un utilizator, eventual acelaşi, şi în funcţie de unii
parametri pe care îi putem defini în momentul creării fiecărei instanţe.

Declaraţia unei noi clase de obiecte


Pasul 1: Stabilirea conceptului reprezentat de clasa de obiecte
Să vedem ce trebuie să definim atunci când dorim să creăm o nouă clasă de obiecte. În primul
rând trebuie să stabilim care este conceptul care este reprezentat de către noua clasă de obiecte şi să
definim informaţiile memorate în obiect şi modul de utilizare a acestuia. Acest pas este cel mai
important din tot procesul de definire al unei noi clase de obiecte. Este necesar să încercaţi să respectaţi
două reguli oarecum antagonice. Una dintre ele spune că nu trebuiesc create mai multe clase de obiecte
decât este nevoie, pentru a nu face dificilă înţelegerea modului de lucru al aplicaţiei la care lucraţi. Cea
de-a doua regulă spune că nu este bine să mixaţi într-un singur obiect funcţionalităţi care nu au nimic în
comun, creând astfel clase care corespund la două concepte diferite.
Medierea celor două reguli nu este întotdeauna foarte uşoară. Oricum, vă va fi mai uşor dacă
păstraţi în minte faptul că fiecare clasă pe care o definiţi trebuie să corespundă unui concept real bine
definit, necesar la rezolvarea problemei la care lucraţi. Şi mai păstraţi în minte şi faptul că este inutil să
lucraţi cu concepte foarte generale atunci când aplicaţia dumneavoastră nu are nevoie decât de o
particularizare a acestora. Riscaţi să pierdeţi controlul dezvoltării acestor clase de obiecte prea generale
şi să îngreunaţi dezvoltarea aplicaţiei.
Pasul 2: Stabilirea numelui clasei de obiecte
După ce aţi stabilit exact ce doriţi de la noua clasă de obiecte, sunteţi în măsură să găsiţi un
nume pentru noua clasă, nume care trebuie să urmeze regulile de construcţie ale identificatorilor
limbajului Java definite în capitolul anterior.
Stabilirea unui nume potrivit pentru o nouă clasă nu este întotdeauna un lucru foarte uşor.
Problema este că acest nume nu trebuie să fie exagerat de lung dar trebuie să exprime suficient de bine
destinaţia clasei. Regulile de denumire ale claselor sunt rezultatul experienţei fiecăruia sau al unor
convenţii de numire stabilite anterior. De obicei, numele de clase este bine să înceapă cu o literă
majusculă. Dacă numele clasei conţine în interior mai multe cuvinte, aceste cuvinte trebuie de
asemenea începute cu literă majusculă. Restul caracterelor vor fi litere minuscule.
De exemplu, dacă dorim să definim o clasă de obiecte care implementează conceptul de motor Otto
vom folosi un nume ca MotorOtto pentru noua clasă ce trebuie creată. La fel, vom defini clasa
MotorDiesel sau MotorCuReacţie. Dacă însă avem nevoie să definim o clasă separată
pentru un motor Otto cu cilindri în V şi carburator, denumirea clasei ca
MotorOttoCuCilindriÎnVSiCarburator nu este poate cea mai bună soluţie. Poate că în
acest caz este preferabilă o prescurtare de forma MotorOttoVC. Desigur, acestea sunt doar câteva
remarci la adresa acestei probleme şi este în continuare necesar ca în timp să vă creaţi propria
convenţie de denumire a claselor pe care le creaţi.
Pasul 3: Stabilirea superclasei
În cazul în care aţi definit deja o parte din funcţionalitatea de care aveţi nevoie într-o altă
superclasă, puteţi să derivaţi noua clasă de obiecte din clasa deja existentă. Dacă nu există o astfel
de clasă, noua clasă va fi automat derivată din clasa de obiecte predefinită numită Object. În Java,
clasa Object este superclasă direct sau indirect pentru orice altă clasă de obiecte definită de
utilizator.
Alegerea superclasei din care derivaţi noua clasă de obiecte este foarte importantă pentru că
vă ajută să refolosiţi codul deja existent. Totuşi, nu alegeţi cu uşurinţă superclasa unui obiect pentru
că astfel puteţi încărca obiectele cu o funcţionalitate inutilă, existentă în superclasă. Dacă nu există
o clasă care să vă ofere doar funcţionalitatea de care aveţi nevoie, este preferabil să derivaţi noua
clasă direct din clasa Object şi să apelaţi indirect funcţionalitatea pe care o doriţi.
Pasul 4: Stabilirea interfeţelor pe care le respectă clasa
Stabilirea acestor interfeţe are dublu scop. În primul rând ele instruiesc compilatorul să
verifice dacă noua clasă respectă cu adevărat toate interfeţele pe care le-a declarat, cu alte cuvinte
defineşte toate metodele declarate în aceste interfeţe. A doua finalitate este aceea de a permite
compilatorului să folosească instanţele noii clase oriunde aplicaţia declară că este nevoie de un
obiect care implementează interfeţele declarate.
O clasă poate să implementeze mai multe interfeţe sau niciuna.
Pasul 5: Stabilirea modificatorilor clasei
În unele cazuri trebuie să oferim compilatorului informaţii suplimentare relative la modul în
care vom folosi clasa nou creată pentru ca acesta să poată executa verificări suplimentare asupra
descrierii clasei. În acest scop, putem defini o clasă ca fiind abstractă, finală sau publică folosindu-
ne de o serie de cuvinte rezervate numite modificatori. Modificatorii pentru tipurile de clase de mai
sus sunt respectiv: abstract, final şi public.
În cazul în care declarăm o clasă de obiecte ca fiind abstractă, compilatorul va interzice
instanţierea acestei clase. Dacă o clasă este declarată finală, compilatorul va avea grijă să nu putem
deriva noi subclase din această clasă. În cazul în care declarăm în acelaşi timp o clasă de obiecte ca
fiind abstractă şi finală, eroarea va fi semnalată încă din timpul compilării pentru că cei doi
modificatori se exclud.
Pentru ca o clasă să poată fi folosită şi în exteriorul contextului în care a fost declarată ea trebuie
să fie declarată publică. Orice clasă de obiecte care va fi instanţiată ca o aplicaţie trebuie
declarată publică.
Pasul 6: Scrierea corpului declaraţiei
În sfârşit, după ce toţi ceilalţi paşi au fost efectuaţi, putem trece la scrierea corpului
declaraţiei de clasă. În principal, aici vom descrie variabilele clasei împreună cu metodele care
lucrează cu acestea. Tot aici putem preciza şi gradele de protejare pentru fiecare dintre elementele
declaraţiei. Uneori numim variabilele şi metodele unei clase la un loc ca fiind câmpurile clasei.
Forma generală a unei declaraţii de clasă
Sintaxa exactă de declarare a unei clase arată în felul următor:
{ abstract | final | public } class NumeClasă
[ extends NumeSuperclasă ]
[ implements NumeInterfaţă [ , NumeInterfaţă ] ]
{ [ CâmpClasă ] }

Variabilele unei clase


În interiorul claselor se pot declara variabile. Aceste variabile sunt specifice clasei
respective. Fiecare dintre ele trebuie să aibă un tip, un nume şi poate avea iniţializatori. În afară de
aceste elemente, pe care le-am prezentat deja în secţiunea în care am prezentat variabilele,
variabilele definite în interiorul unei clase pot avea definiţi o serie de modificatori care alterează
comportarea variabilei în interiorul clasei, şi o specificaţie de protecţie care defineşte cine are
dreptul să acceseze variabila respectivă.
Modificatori
Modificatorii sunt cuvinte rezervate Java care precizează sensul unei declaraţii. Iată lista acestora:
static
final
transient
volatile
Dintre aceştia, transient nu este utilizat în versiunea curentă a limbajului Java. Pe viitor
va fi folosit pentru a specifica variabile care nu conţin informaţii care trebuie să rămână persistente
la terminarea programului.
Modificatorul volatile specifică faptul că variabila respectivă poate fi modificată
asincron cu rularea aplicaţiei. În aceste cazuri, compilatorul trebuie să-şi ia măsuri suplimentare în
cazul generării şi optimizării codului care se adresează acestei variabile.
Modificatorul final este folosit pentru a specifica o variabilă a cărei valoare nu poate fi
modificată. Variabila respectivă trebuie să primească o valoare de iniţializare chiar în momentul
declaraţiei. Altfel, ea nu va mai putea fi iniţializată în viitor. Orice încercare ulterioară de a seta
valori la această variabilă va fi semnalată ca eroare de compilare.
Modificatorul static este folosit pentru a specifica faptul că variabila are o singură
valoare comună tuturor instanţelor clasei în care este declarată. Modificarea valorii acestei variabile
din interiorul unui obiect face ca modificarea să fie vizibilă din celelalte obiecte. Variabilele statice
sunt iniţializate la încărcarea codului specific unei clase şi există chiar şi dacă nu există nici o
instanţă a clasei respective. Din această cauză, ele pot fi folosite de metodele statice.
Protecţie
În Java există patru grade de protecţie pentru o variabilă aparţinând unei clase:
• privată
• protejată
• publică
• prietenoasă
O variabilă publică este accesibilă oriunde este accesibil numele clasei. Cuvântul rezervat
este public.
O variabilă protejată este accesibilă în orice clasă din pachetul căreia îi aparţine clasa în
care este declarată. În acelaşi timp, variabila este accesibilă în toate subclasele clasei date, chiar
dacă ele aparţin altor pachete. Cuvântul rezervat este protected.
O variabilă privată este accesibilă doar în interiorul clasei în care a fost declarată. Cuvântul
rezervat este private.
O variabilă care nu are nici o declaraţie relativă la gradul de protecţie este automat o
variabilă prietenoasă. O variabilă prietenoasă este accesibilă în pachetul din care face parte clasa în
interiorul căreia a fost declarată, la fel ca şi o variabilă protejată. Dar, spre deosebire de variabilele
protejate, o variabilă prietenoasă nu este accesibilă în subclasele clasei date dacă aceste sunt
declarate ca aparţinând unui alt pachet. Nu există un cuvânt rezervat pentru specificarea explicită a
variabilelor prietenoase.
O variabilă nu poate avea declarate mai multe grade de protecţie în acelaşi timp. O astfel de
declaraţie este semnalată ca eroare de compilare.
Accesarea unei variabile
Accesarea unei variabile declarate în interiorul unei clasei se face folosindu-ne de o expresie
de forma:
ReferinţăInstanţă.NumeVariabilă
Referinţa către o instanţă trebuie să fie referinţă către clasa care conţine variabila. Referinţa
poate fi valoarea unei expresii mai complicate, ca de exemplu un element dintr-un tablou de
referinţe.
În cazul în care avem o variabilă statică, aceasta poate fi accesată şi fără să deţinem o referinţă către
o instanţă a clasei. Sintaxa este, în acest caz:
NumeClasă.NumeVariabilă
Vizibilitate
O variabilă poate fi ascunsă de declaraţia unei alte variabile cu acelaşi nume. De exemplu,
dacă într-o clasă avem declarată o variabilă cu numele unu şi într-o subclasă a acesteia avem
declarată o variabilă cu acelaşi nume, atunci variabila din superclasă este ascunsă de cea din clasă.
Totuşi, variabila din superclasă există încă şi poate fi accesată în mod explicit. Expresia de referire
este, în acest caz:
NumeSuperClasă.NumeVariabilă
sau
super.NumeVariabilă
în cazul în care superclasa este imediată.
La fel, o variabilă a unei clase poate fi ascunsă de o declaraţie de variabilă dintr-un bloc de
instrucţiuni. Orice referinţă la ea va trebui făcută în mod explicit. Expresia de referire este, în acest
caz:
this.NumeVariabilă

Variabile predefinite: this şi super


În interiorul fiecărei metode non-statice dintr-o clasă există predefinite două variabile cu
semnificaţie specială. Cele două variabile sunt de tip referinţă şi au aceeaşi valoare şi anume o
referinţă către obiectul curent. Diferenţa dintre ele este tipul.
Prima dintre acestea este variabila this care are tipul referinţă către clasa în interiorul
căreia apare metoda. A doua este variabila super al cărei tip este o referinţă către superclasa
imediată a clasei în care apare metoda. În interiorul obiectelor din clasa Object nu se poate folosi
referinţa super pentru că nu există nici o superclasă a clasei de obiecte Object.
În cazul în care super este folosită la apelul unui constructor sau al unei metode, ea acţionează ca
un cast către superclasa imediată.
Metodele unei clase
Fiecare clasă îşi poate defini propriile sale metode pe lângă metodele pe care le moşteneşte
de la superclasa sa. Aceste metode definesc operaţiile care pot fi executate cu obiectul respectiv. În
cazul în care una dintre metodele moştenite nu are o implementare corespunzătoare în superclasă,
clasa îşi poate redefini metoda după cum doreşte.
În plus, o clasă îşi poate defini metode de construcţie a obiectelor şi metode de eliberare a
acestora. Metodele de construcţie sunt apelate ori de câte ori este alocat un nou obiect din clasa
respectivă. Putem declara mai multe metode de construcţie, ele diferind prin parametrii din care
trebuie construit obiectul.
Metodele de eliberare a obiectului sunt cele care eliberează resursele ocupate de obiect în
momentul în care acesta este distrus de către mecanismul automat de colectare de gunoaie. Fiecare
clasă are o singură metodă de eliberare, numită şi finalizator. Apelarea acestei metode se face de
către sistem şi nu există nici o cale de control al momentului în care se produce acest apel.
Declararea metodelor
Pentru a declara o metodă, este necesar să declarăm numele acesteia, tipul de valoare pe care
o întoarce, parametrii metodei precum şi un bloc în care să descriem instrucţiunile care trebuiesc
executate atunci când metoda este apelată. În plus, orice metodă are un număr de modificatori care
descriu proprietăţile metodei şi modul de lucru al acesteia.
Declararea precum şi implementarea metodelor se face întotdeauna în interiorul declaraţiei
de clasă. Nu există nici o cale prin care să putem scrie o parte dintre metodele unei clase într-un
fişier separat care să facă referinţă apoi la declaraţia clasei.
În formă generală, declaraţia unei metode arată în felul următor:
[Modificator] TipRezultat Declaraţie [ClauzeThrows] CorpulMetodei
Modificatorii precum şi clauzele throws pot să lipsească.
Numele şi parametrii metodelor
Recunoaşterea unei anumite metode se face după numele şi tipul parametrilor săi. Pot exista
metode cu acelaşi nume dar având parametri diferiţi. Acest fenomen poartă numele de
supraîncărcarea numelui unei metode.
Numele metodei este un identificator Java. Avem toată libertatea în a alege numele pe care îl
dorim pentru metodele noastre, dar în general este preferabil să alegem nume care sugerează
utilizarea metodei.
Numele unei metode începe de obicei cu literă mică. Dacă acesta este format din mai multe
cuvinte, litera de început a fiecărui cuvânt va fi majusculă. În acest mod numele unei metode este
foarte uşor de citit şi de depistat în sursă.
Parametrii metodei sunt în realitate nişte variabile care sunt iniţializate în momentul apelului
cu valori care controlează modul ulterior de execuţie. Aceste variabile există pe toată perioada
execuţiei metodei. Se pot scrie metode care să nu aibă nici un parametru.
Fiind o variabilă, fiecare parametru are un tip şi un nume. Numele trebuie să fie un
identificator Java. Deşi avem libertatea să alegem orice nume dorim, din nou este preferabil să
alegem nume care să sugereze scopul la care va fi utilizat parametrul respectiv.
Tipul unui parametru este oricare dintre tipurile valide în Java. Acestea poate fi fie un tip
primitiv, fie un tip referinţă către obiect, interfaţă sau tablou.
În momentul apelului unei metode, compilatorul încearcă să găsească o metodă în interiorul
clasei care să aibă acelaşi nume cu cel apelat şi acelaşi număr de parametri ca şi apelul. Mai mult,
tipurile parametrilor de apel trebuie să corespundă cu tipurile parametrilor declaraţi ai metodei
găsite sau să poată fi convertiţi la aceştia.
Dacă o astfel de metodă este găsită în declaraţia clasei sau în superclasele acesteia,
parametrii de apel sunt convertiţi către tipurile declarate şi se generează apelul către metoda
respectivă.
Este o eroare de compilare să declarăm două metode cu acelaşi nume, acelaşi număr de
parametri şi acelaşi tip pentru parametrii corespunzători. Într-o asemenea situaţie, compilatorul n-ar
mai şti care metodă trebuie apelată la un moment dat.
De asemenea, este o eroare de compilare să existe două metode care se potrivesc la acelaşi
apel. Acest lucru se întâmplă când nici una dintre metodele existente nu se potriveşte exact şi când
există două metode cu acelaşi nume şi acelaşi număr de parametri şi, în plus, parametrii de apel se
pot converti către parametrii declaraţi ai ambelor metode.
Rezolvarea unei astfel de probleme se face prin conversia explicită (cast) de către
programator a valorilor de apel spre tipurile exacte ale parametrilor metodei pe care dorim să o
apelăm în realitate.
În fine, forma generală de declaraţie a numelui şi parametrilor unei metode este:
NumeMetodă( [TipParametru NumeParametru] [,TipParametru NumeParametru] )
Modificatori de metode
Modificatorii sunt cuvinte cheie ale limbajului Java care specifică proprietăţi suplimentare
pentru o metodă. Iată lista completă a acestora în cazul metodelor:
• static - pentru metodele statice
• abstract - pentru metodele abstracte
• final - pentru metodele finale
• native - pentru metodele native
• synchronized - pentru metodele sincronizate

Metode statice
În mod normal, o metodă a unei clase se poate apela numai printr-o instanţă a clasei
respective sau printr-o instanţă a unei subclase. Acest lucru se datorează faptului că metoda face
apel la o serie de variabile ale clasei care sunt memorate în interiorul instanţei şi care au valori
diferite în instanţe diferite. Astfel de metode se numesc metode ale instanţelor clasei.
După cum ştim deja, există şi un alt tip de variabile, şi anume variabilele de clasă sau
variabilele statice care sunt comune tuturor instanţelor clasei respective şi există pe toată perioada
de timp în care clasa este încărcată în memorie. Aceste variabile pot fi accesate fără să avem nevoie
de o instanţă a clasei respective.
În mod similar există şi metode statice. Aceste metode nu au nevoie de o instanţă a clasei
sau a unei subclase pentru a putea fi apelate pentru că ele nu au voie să folosească variabile care
sunt memorate în interiorul instanţelor. În schimb, aceste metode pot să folosească variabilele
statice declarate în interiorul clasei.
Orice metodă statică este implicit şi finală.
Metode abstracte
Metodele abstracte sunt metode care nu au corp de implementare. Ele sunt declarate numai
pentru a forţa subclasele care vor să aibă instanţe să implementeze metodele respective.
Metodele abstracte trebuie declarate numai în interiorul claselor care au fost declarate
abstracte. Altfel compilatorul va semnala o eroare de compilare. Orice subclasă a claselor abstracte
care nu este declarată abstractă trebuie să ofere o implementare a acestor metode, altfel va fi
generată o eroare de compilare.
Prin acest mecanism ne asigurăm că toate instanţele care pot fi convertite către clasa care
conţine definiţia unei metode abstracte au implementată metoda respectivă dar, în acelaşi timp, nu
este nevoie să implementăm în nici un fel metoda chiar în clasa care o declară pentru că nu ştim pe
moment cum va fi implementată.
O metodă statică nu poate fi declarată şi abstractă pentru că o metodă statică este implicit
finală şi nu poate fi rescrisă.
Metode finale
O metodă finală este o metodă care nu poate fi rescrisă în subclasele clasei în care a fost
declarată. O metodă este rescrisă într-o subclasă dacă aceasta implementează o metodă cu acelaşi
nume şi acelaşi număr şi tip de parametri ca şi metoda din superclasă.
Declararea metodelor finale este utilă în primul rând compilatorului care poate genera
metodele respective direct în codul rezultat fiind sigur că metoda nu va avea nici o altă
implementare în subclase.
Metode native
Metodele native sunt metode care sunt implementate pe o cale specifică unei anumite
platforme. De obicei aceste metode sunt implementate în C sau în limbaj de asamblare. Metoda
propriu-zisă nu poate avea corp de implementare pentru că implementarea nu este făcută în Java.
În rest, metodele native sunt exact ca orice altă metodă Java. Ele pot fi moştenite, pot fi
statice sau nu, pot fi finale sau nu, pot să rescrie o metodă din superclasă şi pot fi la rândul lor
rescrise în subclase.
Metode sincronizate
O metodă sincronizată este o metodă care conţine cod critic pentru un anumit obiect sau
clasă şi nu poate fi rulată în paralel cu nici o altă metodă critică sau cu o instrucţiune
synchronized referitoare la acelaşi obiect sau clasă.
Înainte de execuţia metodei, obiectul sau clasa respectivă sunt blocate. La terminarea
metodei, acestea sunt deblocate.
Dacă metoda este statică atunci este blocată o întreagă clasă, clasa din care face parte
metoda. Altfel, este blocată doar instanţa în contextul căreia este apelată metoda.
Protejarea metodelor
Accesul la metodele unei clase este protejat în acelaşi fel ca şi accesul la variabilele clasei.
În Java există patru grade de protecţie pentru o metodă aparţinând unei clase:
• privată
• protejată
• publică
• prietenoasă
O metodă declarată publică este accesibilă oriunde este accesibil numele clasei. Cuvântul
rezervat este public.
O metodă declarată protejată este accesibilă în orice clasă din pachetul căreia îi aparţine
clasa în care este declarată. În acelaşi timp, metoda este accesibilă în toate subclasele clasei date,
chiar dacă ele aparţin altor pachete. Cuvântul rezervat este protected.
O metodă declarată privată este accesibilă doar în interiorul clasei în care a fost declarată.
Cuvântul rezervat este private.
O metodă care nu are nici o declaraţie relativă la gradul de protecţie este automat o metodă
prietenoasă. O metodă prietenoasă este accesibilă în pachetul din care face parte clasa în interiorul
căreia a fost declarată la fel ca şi o metodă protejată. Dar, spre deosebire de metodele protejate, o
metodă prietenoasă nu este accesibilă în subclasele clasei date dacă aceste sunt declarate ca
aparţinând unui alt pachet. Nu există un cuvânt rezervat pentru specificarea explicită a metodelor
prietenoase.
O metodă nu poate avea declarate mai multe grade de protecţie în acelaşi timp. O astfel de
declaraţie este semnalată ca eroare de compilare.

Apelul metodelor
Pentru a apela o metodă a unei clase este necesar să dispunem de o cale de acces la metoda
respectivă. În plus, trebuie să dispunem de drepturile necesare apelului metodei.
Sintaxa efectivă de acces este următoarea:
CaleDeAcces.Metodă( Parametri )
În cazul în care metoda este statică, pentru a specifica o cale de acces este suficient să
furnizăm numele clasei în care a fost declarată metoda. Accesul la numele clasei se poate obţine fie
importând clasa sau întreg pachetul din care face parte clasa fie specificând în clar numele clasei şi
drumul de acces către aceasta.
De exemplu, pentru a accesa metoda random definită static în clasa Math aparţinând pachetului
java.lang putem scrie:
double aleator = Math.random();
sau, alternativ:
double aleator = java.lang.Math.random();
În cazul claselor definite în pachetul java.lang nu este necesar nici un import pentru că
acestea sunt implicit importate de către compilator.
Cea de-a doua cale de acces este existenţa unei instanţe a clasei respective. Prin această
instanţă putem accesa metodele care nu sunt declarate statice, numite uneori şi metode ale
instanţelor clasei. Aceste metode au nevoie de o instanţă a clasei pentru a putea lucra, pentru că
folosesc variabile non-statice ale clasei sau apelează alte metode non-statice. Metodele primesc
acest obiect ca pe un parametru ascuns.
De exemplu, având o instanţă a clasei Object sau a unei subclase a acesteia, putem obţine o
reprezentare sub formă de şir de caractere prin:
Object obiect = new Object();
String sir = obiect.toString();
În cazul în care apelăm o metodă a clasei din care face parte şi metoda apelantă putem să
renunţăm la calea de acces în cazul metodelor statice, scriind doar numele metodei şi parametrii.
Pentru metodele specifice instanţelor, putem renunţa la calea de acces, dar în acest caz metoda
accesează aceeaşi instanţă ca şi metoda apelantă. În cazul în care metoda apelantă este statică,
specificarea unei instanţe este obligatorie în cazul metodelor de instanţă.
Parametrii de apel servesc împreună cu numele la identificarea metodei pe care dorim să o
apelăm. Înainte de a fi transmişi, aceştia sunt convertiţi către tipurile declarate de parametri ai
metodei, după cum este descris mai sus.
Specificarea parametrilor de apel se face separându-i prin virgulă. După ultimul parametru nu se
mai pune virgulă. Dacă metoda nu are nici un parametru, parantezele rotunde sunt în continuare
necesare. Exemple de apel de metode cu parametri:
String numar = String.valueOf( 12 );
// 12 -> String
double valoare = Math.abs( 12.54 );
// valoare absolută
String prima = numar.substring( 0, 1 );
// prima litera

Valoarea de retur a unei metode


O metodă trebuie să-şi declare tipul valorii pe care o întoarce. În cazul în care metoda doreşte să
specifice explicit că nu întoarce nici o valoare, ea trebuie să declare ca tip de retur tipul void ca în
exemplul:
void a() { … }
În caz general, o metodă întoarce o valoare primitivă sau un tip referinţă. Putem declara acest tip ca
în:
long abs( int valoare ) { … }
Pentru a returna o valoare ca rezultat al execuţiei unei metode, trebuie să folosim instrucţiunea
return, aşa cum s-a arătat în secţiunea dedicată instrucţiunilor. Instrucţiunea return trebuie să
conţină o expresie a cărei valoare să poată fi convertită la tipul declarat al valorii de retur a metodei.
De exemplu:
long abs( int valoare ) {
return Math.abs( valoare );
}
Metoda statică abs din clasa Math care primeşte un parametru întreg returnează tot un întreg. În
exemplul nostru, instrucţiunea return este corectă pentru că există o cale de conversie de la întreg
la întreg lung, conversie care este apelată automat de compilator înainte de ieşirea din metodă.
În schimb, în exemplul următor:
int abs( long valoare ) {
return Math.abs( valoare );
}
compilatorul va genera o eroare de compilare pentru că metoda statică abs din clasa Math care
primeşte ca parametru un întreg lung întoarce tot un întreg lung, iar un întreg lung nu poate fi
convertit sigur la un întreg normal pentru că există riscul deteriorării valorii, la fel ca la atribuire.
Rezolvarea trebuie să conţină un cast explicit:
int abs( long valoare ) {
return ( int )Math.abs( valoare );
}
În cazul în care o metodă este declarată void, putem să ne întoarcem din ea folosind instrucţiunea
return fără nici o expresie. De exemplu:
void metoda() {
…
if( … )
return;
…
}
Specificarea unei expresii în acest caz duce la o eroare de compilare. La fel şi în cazul în care
folosim instrucţiunea return fără nici o expresie în interiorul unei metode care nu este declarată
void.
Vizibilitate
O metodă este vizibilă dacă este declarată în clasa prin care este apelată sau într-una din
superclasele acesteia. De exemplu, dacă avem următoarea declaraţie:
class A {
…
void a() { … }
}
class B extends A {
void b() {
a();
c();
…
}
void c() { .. }
…
}
Apelul metodei a în interiorul metodei b din clasa B este permis pentru că metoda a este declarată în
interiorul clasei A care este superclasă pentru clasa B. Apelul metodei c în aceeaşi metodă b este
permis pentru că metoda c este declarată în aceeaşi clasă ca şi metoda b.
Uneori, o subclasă rescrie o metodă dintr-o superclasă a sa. În acest caz, apelul metodei
respective în interiorul subclasei duce automat la apelul metodei din subclasă. Dacă totuşi dorim să
apelăm metoda aşa cum a fost ea definită în superclasă, putem prefixa apelul cu numele superclasei.
De exemplu:
class A {
…
void a() { … }
}
class B extends A {
void a() { .. }
void c() {
a();// metoda a din clasa B
A.a();// metoda a din clasa A
…
}
…
}

Iniţializatori statici
La încărcarea unei clase sunt automat iniţializate toate variabilele statice declarate în
interiorul clasei. În plus, sunt apelaţi toţi iniţializatorii statici ai clasei.
Un iniţializator static are următoarea sintaxă:
static BlocDeInstrucţiuni
Blocul de instrucţiuni este executat automat la încărcarea clasei. De exemplu, putem defini
un iniţializator static în felul următor:
class A {
static double a;
static int b;
static {
a = Math.random();
// număr dublu între 0.0 şi 1.0
b = ( int )( a * 500 );
// număr întreg între 0 şi 500
}
…
}
Declaraţiile de variabile statice şi iniţializatorii statici sunt executate în ordinea în care apar în clasă.
De exemplu, dacă avem următoarea declaraţie de clasă:
class A {
static int i = 11;
static {
i += 100;
i %= 55;
}
static int j = i + 1;
}
valoarea finală a lui i va fi 1 ( ( 11 + 100 ) % 55 ) iar valoarea lui j va fi 2.
Constructori şi finalizatori
constructori
La crearea unei noi instanţe a unei clase sistemul alocă automat memoria necesară instanţei
şi o iniţializează cu valorile iniţiale specificate sau implicite. Dacă dorim să facem iniţializări
suplimentare în interiorul acestei memorii sau în altă parte putem descrie metode speciale numite
constructori ai clasei.
Putem avea mai mulţi constructori pentru aceeaşi clasă, aceştia diferind doar prin parametrii
pe care îi primesc. Numele tuturor constructorilor este acelaşi şi este identic cu numele clasei.
Declaraţia unui constructor este asemănătoare cu declaraţia unei metode oarecare, cu
diferenţa că nu putem specifica o valoare de retur şi nu putem specifica nici un fel de modificatori.
Dacă dorim să returnăm dintr-un constructor, trebuie să folosim instrucţiunea return fără nici o
expresie. Putem însă să specificăm gradul de protecţie al unui constructor ca fiind public, privat,
protejat sau prietenos.
Constructorii pot avea clauze throws.
Dacă o clasă nu are constructori, compilatorul va crea automat un constructor implicit care
nu ia nici un parametru şi care iniţializează toate variabilele clasei şi apelează constructorul
superclasei fără argumente prin super( ). Dacă superclasa nu are un constructor care ia zero
argumente, se va genera o eroare de compilare.
Dacă o clasă are cel puţin un constructor, constructorul implicit nu mai este creat de către
compilator.
Când construim corpul unui constructor avem posibilitatea de a apela, pe prima linie a
blocului de instrucţiuni care reprezintă corpul constructorului, un constructor explicit. Constructorul
explicit poate avea două forme:
this( [Parametri] );
super( [Parametri] );
Cu această sintaxă apelăm unul dintre constructorii superclasei sau unul dintre ceilalţi constructori
din aceeaşi clasă. Aceste linii nu pot apărea decât pe prima poziţie în corpul constructorului. Dacă
nu apar acolo, compilatorul consideră implicit că prima instrucţiune din corpul constructorului este:
super();
Şi în acest caz se va genera o eroare de compilare dacă nu există un constructor în
superclasă care să lucreze fără nici un parametru.
După apelul explicit al unui constructor din superclasă cu sintaxa super( … ) este executată
în mod implicit iniţializarea tuturor variabilelor de instanţă (non-statice) care au iniţializatori
expliciţi. După apelul unui constructor din aceeaşi clasă cu sintaxa this( … ) nu există nici o altă
acţiune implicită, deci nu vor fi iniţializate nici un fel de variabile. Aceasta datorită faptului că
iniţializarea s-a produs deja în constructorul apelat.
Exemplu:
class A extends B {
String valoare;
A( String val ) {
// aici există apel implicit
// al lui super(), adică B()
valoare = val;
}
A( int val ) {
this( String.valueOf( val ) );// alt constructor
}
}
Finalizatori
În Java nu este nevoie să apelăm în mod explicit distrugerea unei instanţe atunci când nu
mai este nevoie de ea. Sistemul oferă un mecanism de colectare a gunoaielor care recunoaşte
situaţia în care o instanţă de obiect sau un tablou nu mai sunt referite de nimeni şi le distruge în mod
automat.
Acest mecanism de colectare a gunoaielor rulează pe un fir de execuţie separat, de prioritate
mică. Nu avem nici o posibilitate să aflăm exact care este momentul în care va fi distrusă o instanţă.
Totuşi, putem specifica o funcţie care să fie apelată automat în momentul în care colectorul de
gunoaie încearcă să distrugă obiectul.
Această funcţie are nume, număr de parametri şi tip de valoare de retur fixe:
void finalize()
După apelul metodei de finalizare (numită şi finalizator), instanţa nu este încă distrusă până
la o nouă verificare din partea colectorului de gunoaie. Această comportare este necesară pentru că
instanţa poate fi revitalizată prin crearea unei referinţe către ea în interiorul finalizatorului.
Totuşi, finalizatorul nu este apelat decât o singură dată. Dacă obiectul revitalizat redevine candidat
la colectorul de gunoaie, acesta este distrus fără a i se mai apela finalizatorul. Cu alte cuvinte, un
obiect nu poate fi revitalizat decât o singură dată.
Dacă în timpul finalizării apare o excepţie, ea este ignorată şi finalizatorul nu va mai fi apelat din
nou.
Crearea instanţelor
O instanţă este creată folosind o expresie de alocare care foloseşte cuvântul rezervat new. Iată care
sunt paşii care sunt executaţi la apelul acestei expresii:
• Se creează o nouă instanţă de tipul specificat. Toate variabilele instanţei sunt iniţializate pe
valorile lor implicite.
• Se apelează constructorul corespunzător în funcţie de parametrii care sunt transmişi în
expresia de alocare. Dacă instanţa este creată prin apelul metodei newInstance, se
apelează constructorul care nu ia nici un argument.
• După creare, expresia de alocare returnează o referinţă către instanţa nou creată.
Exemple de creare:
A o1 = new A();
B o2 = new B();
class C extends B {
String valoare;
C( String val ) {
// aici există apel implicit
// al lui super(), adică B()
valoare = val;
}
C( int val ) {
this( String.valueOf( val ) );
}
}
C o3 = new C( "Vasile" );
C o4 = new C( 13 );
O altă cale de creare a unui obiect este apelul metodei newInstance declarate în clasa Class.
Iată paşii de creare în acest caz:
• Se creează o nouă instanţă de acelaşi tip cu tipul clasei pentru care a fost apelată metoda
newInstance. Toate variabilele instanţei sunt iniţializate pe valorile lor implicite.
• Este apelat constructorul obiectului care nu ia nici un argument.
• După creare referinţa către obiectul nou creat este returnată ca valoare a metodei
newInstance. Tipul acestei referinţe va fi Object în timpul compilării şi tipul clasei reale în
timpul execuţiei.

Derivarea claselor
O clasă poate fi derivată dintr-alta prin folosirea în declaraţia clasei derivate a clauzei
extends. Clasa din care se derivă noua clasă se numeşte superclasă imediată a clasei derivate.
Toate clasele care sunt superclase ale superclasei imediate ale unei clase sunt superclase şi pentru
clasa dată. Clasa nou derivată se numeşte subclasă a clasei din care este derivată.
Sintaxa generală este:
class SubClasă extends SuperClasă …
O clasă poate fi derivată dintr-o singură altă clasă, cu alte cuvinte o clasă poate avea o singură
superclasă imediată.
Clasa derivată moşteneşte toate variabilele şi metodele superclasei sale. Totuşi, ea nu poate accesa
decât acele variabile şi metode care nu sunt declarate private.
Putem rescrie o metodă a superclasei declarând o metodă în noua clasă având acelaşi nume şi
aceiaşi parametri. La fel, putem declara o variabilă care are acelaşi nume cu o variabilă din
superclasă. În acest caz, noul nume ascunde vechea variabilă, substituindu-i-se. Putem în continuare
să ne referim la variabila ascunsă din superclasă specificând numele superclasei sau folosindu-ne de
variabila super.
Exemplu:
class A {
int a = 1;
void unu() {
System.out.println( a );
}
}
class B extends A {
double a = Math.PI;
void unu() {
System.out.println( a );
}
void doi() {
System.out.println( A.a );
}
void trei() {
unu();
super.unu();
}
}
Dacă apelăm metoda unu din clasa A, aceasta va afişa la consolă numărul 1.
Dacă apelăm metoda unu din clasa B, aceasta va afişa la consolă numărul PI. Apelul îl putem face
de exemplu cu instrucţiunea:
B obiect = new B();
obiect.unu();
Observaţi că în metoda unu din clasa B, variabila referită este variabila a din clasa B. Variabila a
din clasa A este ascunsă. Putem însă să o referim prin sintaxa A.a ca în metoda doi din clasa B.
În interiorul clasei B, apelul metodei unu fără nici o altă specificaţie duce automat la apelul metodei
unu definite în interiorul clasei B. Metoda unu din clasa B rescrie metoda unu din clasa A. Vechea
metodă este accesibilă pentru a o referi în mod explicit ca în metoda trei din clasa B. Apelul acestei
metode va afişa mai întâi numărul PI şi apoi numărul 1.
Dacă avem declarată o variabilă de tip referinţă către o instanţă a clasei A, această variabilă poate să
conţină în timpul execuţiei şi o referinţă către o instanţă a clasei B. Invers, afirmaţia nu este
valabilă.
În clipa în care apelăm metoda unu pentru o variabilă referinţă către clasa A, sistemul va apela
metoda unu a clasei A sau B în funcţie de adevăratul tip al referinţei din timpul execuţiei. Cu alte
cuvinte, următoarea secvenţă de instrucţiuni:
A tablou[] = new A[2];
tablou[0] = new A();
tablou[1] = new B();
for( int i = 0; i < 2; i++ ) {
tablou[i].unu();
}
va afişa două numere diferite, mai întâi 1 şi apoi PI. Aceasta din cauză că cel de-al doilea element
din tablou este, în timpul execuţiei, de tip referinţă la o instanţă a clasei B chiar dacă la compilare
este de tipul referinţă la o instanţă a clasei A.
Acest mecanism se numeşte legare târzie, şi înseamnă că metoda care va fi efectiv apelată este
stabilită doar în timpul execuţiei şi nu la compilare.
Dacă nu declarăm nici o superclasă în definiţia unei clase, atunci se consideră automat că noua clasă
derivă direct din clasa Object, moştenind toate metodele şi variabilele acesteia.

Interfeţe
O interfaţă este în esenţă o declaraţie de tip ce constă dintr-un set de metode şi constante
pentru care nu s-a specificat nici o implementare. Programele Java folosesc interfeţele pentru a
suplini lipsa moştenirii multiple, adică a claselor de obiecte care derivă din două sau mai multe alte
clase.
Sintaxa de declaraţie a unei interfeţe este următoarea:
Modificatori interface NumeInterf [ extends [Interfaţă][, Interfaţă]]
Corp
Modificatorii unei interfeţe pot fi doar cuvintele rezervate public şi abstract. O
interfaţă care este publică poate fi accesată şi de către alte pachete decât cel care o defineşte. În
plus, fiecare interfaţă este în mod implicit abstractă. Modificatorul abstract este permis dar nu
obligatoriu.
Numele interfeţelor trebuie să fie identificatori Java. Convenţiile de numire a interfeţelor le urmează
în general pe cele de numire a claselor de obiecte.
Interfeţele, la fel ca şi clasele de obiecte, pot avea subinterfeţe. Subinterfeţele moştenesc toate
constantele şi declaraţiile de metode ale interfeţei din care derivă şi pot defini în plus noi elemente.
Pentru a defini o subinterfaţă, folosim o clauză extends. Aceste clauze specifică superinterfaţa
unei interfeţe. O interfaţă poate avea mai multe superinterfeţe care se declară separate prin virgulă
după cuvântul rezervat extends. Circularitatea definiţiei subinterfeţelor nu este permisă.
În cazul interfeţelor nu există o rădăcină comună a arborelui de derivare aşa cum există pentru
arborele de clase, clasa Object.
În corpul unei declaraţii de interfaţă pot să apară declaraţii de variabile şi declaraţii de
metode. Variabilele sunt implicit statice şi finale. Din cauza faptului că variabilele sunt finale, este
obligatoriu să fie specificată o valoare iniţială pentru aceste variabile. În plus, această valoare
iniţială trebuie să fie constantă (să nu depindă de alte variabile).
Dacă interfaţa este declarată publică, toate variabilele din corpul său sunt implicit declarate
publice.
În ceea ce priveşte metodele declarate în interiorul corpului unei interfeţe, acestea sunt
implicit declarate abstracte. În plus, dacă interfaţa este declarată publică, metodele din interior sunt
implicit declarate publice.
Iată un exemplu de declaraţii de interfeţe:
public interface ObiectSpatial {
final int CUB = 0;
final int SFERA = 1;
double greutate();
double volum();
double raza();
int tip();
}
public interface ObiectSpatioTemporal extends ObiectSpatial {
void centrulDeGreutate( long moment, double coordonate[] );
long momentInitial();
long momentFinal();
}
Cele două interfeţe definesc comportamentul unui obiect spaţial respectiv al unui obiect
spaţio-temporal. Un obiect spaţial are o greutate, un volum şi o rază a sferei minime în care se poate
înscrie. În plus, putem defini tipul unui obiect folosindu-ne de o serie de valori constante
predefinite precum ar fi SFERA sau CUB.
Un obiect spaţio-temporal este un obiect spaţial care are în plus o poziţie pe axa timpului.
Pentru un astfel de obiect, în afară de proprietăţile deja descrise pentru obiectele spaţiale, trebuie să
avem în plus un moment iniţial, de apariţie, pe axa timpului şi un moment final. Obiectul nostru nu
există în afara acestui interval de timp. În plus, pentru un astfel de obiect putem afla poziţia
centrului său de greutate în fiecare moment aflat în intervalul de existenţă.
Pentru a putea lucra cu obiecte spaţiale şi spaţio-temporale este nevoie să definim diverse
clase care să implementeze aceste interfeţe. Acest lucru se face specificând clauza implements în
declaraţia de clasă. O clasă poate implementa mai multe interfeţe. Dacă o clasă declară că
implementează o anumită interfaţă, ea este obligatoriu să implementeze toate metodele declarate în
interfaţa respectivă.
De exemplu, putem spune că o minge este un obiect spaţial de tip sferă. În plus, mingea are
o poziţie în funcţie de timp şi un interval de existenţă. Cu alte cuvinte, mingea este chiar un obiect
spaţio-temporal. Desigur, în afară de proprietăţile spaţio-temporale mingea mai are şi alte
proprietăţi precum culoarea, proprietarul sau preţul de cumpărare.
Iată cum ar putea arăta definiţia clasei de obiecte de tip minge:
import java.awt.Color;
class Minge extends Jucarie implements ObiectSpatioTemporal {
int culoare = Color.red;
double pret = 10000.0;
double raza = 0.25;
long nastere;
long moarte;
// metodele din ObiectSpatial
double greutate() {
return raza * 0.5;
}
double raza() {
return raza;
}
double volum() {
return ( 4.0 / 3.0 ) * Math.PI * raza * raza * raza;
}
int tip() {
return SFERA;
}
// metodele din interfaţa ObiectSpatioTemporal
boolean centrulDeGreutate( long moment, double coordonate[] ) {
if( moment < nastere || moment > moarte ) {
return false;
}

coordonate[0] = x;
coordonate[1] = y;
coordonate[2] = z;
return true;
}
long momentInitial() {
return nastere;
}
long momentFinal() {
return moarte;
}
int ceCuloare() {
return culoare;
}
double cePret() {
return pret;
}
}
Observaţi că noua clasă Minge implementează toate metodele definite în interfaţa
ObiectSpatioTemporal şi, pentru că aceasta extinde interfaţa ObiectSpatial, şi
metodele definite în cea din urmă. În plus, clasa îşi defineşte propriile metode şi variabile.
Să presupunem în continuare că avem şi o altă clasă, Rezervor, care este tot un obiect spaţio-
temporal, dar de formă cubică. Declaraţia acestei clase ar arăta ca:
class Rezervor extends Constructii implements ObiectSpatioTemporal
{
…
}
Desigur, toate metodele din interfeţele de mai sus trebuiesc implementate, plus alte metode
specifice.
Să mai observăm că cele două obiecte derivă din clase diferite: Mingea din Jucării iar Rezervorul
din Construcţii. Dacă am putea deriva o clasă din două alte clase, am putea deriva Minge din
Jucarie şi ObiectSpatioTemporal iar Rezervor din Constructie şi
ObiectSpaţioTemporal. Într-o astfel de situaţie, nu ar mai fi necesar ca
ObiectSpaţioTemporal să fie o interfaţă, ci ar fi suficient ca acesta să fie o altă clasă.
Din păcate, în Java, o clasă nu poate deriva decât dintr-o singură altă clasă, aşa că este obligatoriu în
astfel de situaţii să folosim interfeţele. Dacă ObiectSpaţioTemporal ar fi putut fi o clasă, am
fi avut avantajul că puteam implementa acolo metodele cu funcţionare identică din cele două clase
discutate, acestea fiind automat moştenite fără a mai fi nevoie de definirea lor de două ori în fiecare
clasă în parte.
Putem crea în continuare metode care să lucreze cu obiecte spaţio-temporale, de exemplu o metodă
care să afle distanţa unui corp spaţio-temporal faţă de un punct dat la momentul său iniţial. O astfel
de metodă se poate scrie o singură dată, şi poate lucra cu toate clasele care implementează interfaţa
noastră. De exemplu:
…
double distanta( double punct[], ObiectSpatioTemporal obiect ) {
double coordonate[] = new double[3];
obiect.centrulDeGreutate( obiect.momentInitial(),coordonate );
double x = coordonate[0] - punct[0];
double y = coordonate[1] - punct[1];
double z = coordonate[2] - punct[2];
return Math.sqrt( x * x + y * y + z * z );
}
Putem apela metoda atât cu un obiect din clasa Minge cât şi cu un obiect din clasa Rezervor.
Compilatorul nu se va plânge pentru că el ştie că ambele clase implementează interfaţa
ObiectSpaţioTemporal, aşa că metodele apelate în interiorul calculului distanţei
(momentInitial şi centruDeGreutate) sunt cu siguranţă implementate în ambele clase. Deci, putem
scrie:
Minge minge;
Rezervor rezervor;
double punct[] = { 10.0, 45.0, 23.0 };
distanţa( punct, minge );
distanţa( punct, rezervor );
Desigur, în mod normal ar fi trebuit să proiectăm şi un constructor sau mai mulţi care să iniţializeze
obiectele noastre cu valori rezonabile. Aceşti constructori ar fi stat cu siguranţă în definiţia claselor
şi nu în definiţia interfeţelor. Nu avem aici nici o cale de a forţa definirea unui anumit constructor
cu ajutorul interfeţei.
Modele de programare
Un nou limbaj de programare nu are şanse să se impună fără să ofere, pe lângă sintaxa propriu-zisă
un set de biblioteci sau o ierarhie de clase coerentă şi cât mai generală. Atunci când limbajul C a fost
prezentat pentru prima dată, împreună cu el a fost prezentată şi biblioteca standard de intrare ieşire. Primul
program C pe care l-am învăţat conţinea deja apelul:
printf( "hello, world!" );
Limbajul Java nu face excepţie de la această regulă. Interfaţa Java pentru programarea aplicaţiilor
(API) oferă o ierarhie de clase care include funcţionalitate pentru lucrul cu mai multe fire de execuţie,
lucrul în reţea, crearea interfeţelor utilizator complexe, grafică, etc.
Există mai multe moduri de a aborda scrierea unui program. Unul dintre acestea este scrierea unui
program care are iniţiativa pe toată perioada rulării. Acest tip de programe execută în permanenţă o
secvenţă de cod, fie şi numai o buclă de aşteptare a cărei condiţie depinde de elemente exterioare precum ar
fi o apăsare de tastă sau sosirea unui pachet de date din reţea.
Alternativa este aceea de a scrie programe care intră în execuţie doar atunci când sunt generate
anumite evenimente în sistem. În clipa în care apar aceste evenimente, programul le analizează şi execută o
secvenţă de cod specifică evenimentului respectiv. După execuţia codului, programul se opreşte din nou
până la apariţia unui nou eveniment.
Aceste două alternative diferite de a aborda scrierea unui program îşi au rădăcinile în moduri
diferite de lucru ale sistemelor de operare şi în moduri diferite de a gândi interfaţa cu utilizatorul.
Java implementează ambele stiluri de programe discutate mai sus. În primul caz, avem o clasă de
pornire care conţine o funcţie publică principală şi care va fi lansată în execuţie la apelarea programului. În
acest caz programul îşi controlează complet execuţia ulterioară. În termenii limbajului Java, aceasta este o
aplicaţie.
În al doilea caz, codul rulează în interiorul unui navigator Internet. Clasa de pornire trebuie să aibă
implementate metode de răspuns la anumite evenimente pe care le generează navigatorul, precum ar fi
iniţializare, pornire, oprire, desenare, etc. Acest al doilea tip de programe Java le vom numi apleturi.
Distincţia dintre cele două moduri de organizare a codului este destul de vagă, din cauză că cele
două moduri de lucru se pot amesteca în realitate, un obiect aplet putând fi în acelaşi timp lansat ca
aplicaţie independentă şi invers. Totul depinde de metodele care au fost definite în interiorul clasei de
pornire a programului.

Aplicaţii Java
Cea mai simplă aplicaţie Java este declaraţia unei clase de pornire conţinând o singură metodă,
main, ca în exemplul următor:
public class HelloWorld {
public static void main( String args[] ) {
System.out.println( "Hello, world!" );
}
}
Acest exemplu defineşte o funcţie principală care afişează un simplu mesaj pe consola aplicaţiei.
Afişarea este lăsată în sarcina clasei java.lang.System care conţine în interior implementarea ieşirii şi intrării
standard precum şi a ieşirii standard de eroare sub forma unor referinţe către obiecte de tip InputStream
pentru in (intrarea standard) respectiv PrintStream pentru out şi err (ieşirea standard şi ieşirea standard de
eroare).
Numele metodei main este obligatoriu, la fel şi parametrul acesteia. Atunci când lansăm
interpretorul Java împreună cu numele unei clase care reprezintă clasa de pornire, interpretorul
caută în interiorul acestei clase definiţia unei metode numite main . Această metodă trebuie să fie
obligatoriu publică şi statică. În acelaşi timp, metoda main trebuie să nu întoarcă nici un rezultat şi
să accepte un singur parametru de tip tablou de şiruri de caractere.
Dacă interpretorul găseşte această metodă în interiorul clasei apelate, el lansează în execuţie
metoda main. Atenţie, metoda main fiind de tip static, nu poate apela decât variabile statice. De
obicei însă, metoda main nu face nimic altceva decât să-şi prelucreze parametrul după care să creeze
o serie de obiecte care vor controla execuţia ulterioară a aplicaţiei.
Singurul parametru al metodei main este un tablou care conţine argumentele aflate pe linia
de comandă în momentul apelului. Nu este necesară transmiterea numărului de argumente care au
fost găsite pe linia de comandă pentru că tablourile Java conţin în interior informaţii relative la
numărul de elemente. Acest număr de elemente se poate obţine prin accesarea variabilei length din
interiorul tabloului ca în exemplul următor care listează parametrii de pe linia de comandă la
lansarea unei clase:
public class Arguments {
public static void main( String args[ ] ) {
for( int i = 0; i < args.length; i++ ) {
System.out.println( args[i] );
}
}
}
Iată un exemplu de rulare a acestei aplicaţii:
>java Arguments unu doi trei
unu
doi
trei
>

Apleturi Java
Apleturile Java rulează într-un document HTML. În acest document, fiecare aplet are
rezervată o fereastră dreptunghiulară prin care comunică cu utilizatorul. Dreptunghiul de încadrare
al ferestrei este definit într-un tag HTML numit APPLET. Această fereastră este în exclusivitate la
dispoziţia apletului care este responsabil de desenarea ei şi de tratarea eventualelor evenimente care
se referă la ea.
Împreună cu definirea interfeţei dintre apleturi şi navigator, Sun a definit şi o sintaxă
specifică noului tag HTML care permite poziţionarea şi dimensionarea ferestrei apletului în
document precum şi specificarea unor parametri care să poată altera modul de lucru al apletului.
Iată un prim exemplu de aplet:
import java.awt.Graphics;
public class HelloWorldApplet extends java.applet.Applet {
public void init() {
resize( 150,25 );
}
public void paint( Graphics g ) {
g.drawString( "Hello world!", 50, 25 );
}
}
În mod minimal, apletul nu defineşte decât două metode şi anume una de iniţializare,
necesară pentru organizarea mediului în care rulează apletul şi una de desenare a spaţiului destinat
apletului în interiorul documentului HTML. Metoda de iniţializare nu face în acest caz decât să
redimensioneze spaţiul alocat în mod corespunzător necesităţilor sale în timp ce metoda de desenare
afişează în acest spaţiu un mesaj de salut.
Pentru a vedea rezultatele rulării acestui aplet trebuie să construim un document minimal HTML,
care poate să arate în felul următor:
<HTML>
<HEAD>
<TITLE> Hello World Applet </TITLE>
</HEAD>
<BODY>
<APPLET CODE="HelloWorldApplet.class" WIDTH=150 HEIGHT=25>
....
</APPLET>
</BODY>
</HTML>
Spre deosebire de o aplicaţie normală Java, apleturile nu pot primi parametri pe linia de
comandă pentru că nu există linie de comandă. Din acest motiv, trebuie să introducem parametrii
apletului în fişierul HTML. De exemplu am putea introduce, imediat după linia de declaraţie a
apletului o linie de forma:
<PARAM NAME=mesaj VALUE="Salutare, lume!">
Şi să modificăm codul apletului după cum urmează:
import java.awt.Graphics;
public class HelloWorldApplet extends java.applet.Applet {
private String sir;
public void init() {
sir=getParameter( "mesaj" );
if( sir == null ) {
sir = "Hello, World!";
}
resize( 150,25 );
}
public void paint( Graphics g ) {
g.drawString( sir, 50, 25 );
}
}
În acest mod putem să controlăm la lansare iniţializarea apletului. În definiţia clasei Applet
există şi două funcţii care permit navigatorului regăsirea unui minim de informaţii despre aplet.
Aceste informaţii reprezintă descrierea apletului şi a parametrilor acestuia. Funcţiile care trebuiesc
definite în noua clasă derivată din Applet sunt getAppletInfo şi getParameterInfo. De exemplu, putem
introduce în clasa HelloWorldApplet două noi funcţii:
public String getAppletInfo() {
return "Applet scris de XXX ";
}
public String [ ][ ] getParameterInfo( ) {
String info[ ][ ] = {
{ "Parametru", "String", "Textul de afisat" }
};
return info;
}
Execuţia unui aplet este marcată de câteva evenimente importante generate de către
navigator. Atunci când navigatorul întâlneşte o etichetă APPLET, porneşte în primul rând
încărcarea codului necesar rulării apletului. Până când acest cod nu a ajuns pe calculatorul client,
apletul nu poate fi pornit.
După încărcarea codului, apletul este apelat pentru iniţializare. Acesta este momentul în care
apletul îşi pregăteşte parametrii şi obţine de la sistem resursele necesare rulării. După ce
iniţializarea a fost terminată, navigatorul trimite către aplet o comandă de pornire. Aceasta este
comanda care pune efectiv apletul în funcţiune deschizând interacţiunea cu utilizatorul.
Un aplet rulează atâta timp cât navigatorul este activ. La schimbarea paginii curente,
apleturile din vechea pagină nu sunt distruse, dar primesc o comandă de oprire temporară (pe care
de altfel pot să o ignore). La reîncărcarea paginii, o altă comandă de pornire este lansată spre aplet
şi acest ciclu se poate relua. În sfârşit, la oprirea navigatorului, apletul primeşte o comandă de oprire
definitivă, caz în care el trebuie să elibereze toate resursele pe care le blochează.
Orice aplet Java reprezintă, din punctul de vedere al limbajului un nou tip de obiect, derivat
din obiectul standard Applet. Atunci când navigatorul lansează un nou aplet el nu face altceva decât
să instanţieze un nou obiect din această clasă. Subrutinele care tratează evenimentele descrise
anterior trebuiesc definite ca metode în interiorul acestui nou tip de obiecte.
În continuare, între două evenimente de pornire şi respectiv de oprire temporară a apletului
navigatorul transmite către aplet evenimente specifice oricărei interfeţe grafice cum ar fi
evenimentul care provoacă redesenarea spaţiului destinat apletului, evenimente legate de apăsarea
unor taste sau a unor butoane ale mausului, etc. Ca răspuns la aceste evenimente, apletul trebuie să
reacţioneze schimbând conţinutul ferestrei, lansând mesaje sonore, etc. Iată şi un exemplu de aplet
care ne arată fazele prin care trece un aplet în timpul existenţei sale:
import java.applet.Applet;
import java.awt.*;

public class Evenimente extends Applet {


public void init() {
// metoda de iniţializare
// este apelată la construcţia noii instanţe de aplet
System.out.println("init");
}

public void paint(Graphics g) {


// metoda de desenare
// este apelată ori de câte ori este necesară
// redesenarea ferestrei apletului
System.out.println("paint");
}

public void start() {


// metoda de lansare in execuţie
// este apelată la pornire
//sau la reîntoarcerea în pagina apletului
System.out.println("start");
}
public void stop() {
// metoda de oprire temporară a execuţiei
System.out.println( "stop" );
}

public void destroy() {


// metoda de oprire definitivă
System.out.println("destroy");
}

public void update(Graphics g) {


// metoda de actualizare a ferestrei apletului
// este apelata atunci când nu este necesară redesenarea
// întregii ferestre. În mod implicit,
// metoda apelează metoda paint.
System.out.println("update");
}

public boolean mouseUp(Event e, int x, int y) {


// S-a ridicat butonul mouse-lui în fereastra apletului.
System.out.println("mouseUp");
return false;
}

public boolean mouseDown(Event e, int x, int y) {


// S-a apăsat butonul mouse-lui în fereastra apletului
System.out.println("mouseDown");
return false;
}

public boolean mouseDrag(Event e, int x, int y) {


// S-a mişcat mouse-ul în fereastra apletului
// cu butonul apăsat
System.out.println("mouseDrag");
return false;
}

public boolean mouseMove(Event e, int x, int y) {


// S-a mişcat mouse-ul în fereastra apletului
System.out.println("mouseMove");
return false;
}

public boolean mouseEnter(Event e, int x, int y) {


// Mouse-ul a pătruns în fereastra apletului
System.out.println("mouseEnter");
return false;
}

public boolean mouseExit(Event e, int x, int y) {


// mouse-ul a ieşit din fereastra apletului
System.out.println("mouseExit");
return false;
}

public void gotFocus() {


// Fereastra apletului a devenit fereastra activă
System.out.println("gotFocus");
}

public void lostFocus() {


// Fereastra apletului nu mai este fereastra activa
System.out.println("lostFocus");
}

public boolean keyDown(Event e, int x) {


// S-a apăsat o tasta şi aceasta
// este destinata apletului
System.out.println("keyDown");
return true;
}
}
Puteţi rula apletul de mai sus pentru a vedea care este ordinea în care sunt apelate aceste
metode de către navigator. Apletul de mai sus produce ieşiri la consolă (o fereastră text) şi nu în
fereastra apletului. Dacă nu vedeţi consola, încercaţi să căutaţi prin meniurile navigatorului opţiunea
de afişare a consolei Java.
Din cele spuse până acum se poate deduce că apleturile Java nu au o viaţă proprie, ele fiind
doar apelate din când în când de navigator. Ceea ce nu este întocmai adevărat pentru că biblioteca
standard de clase Java oferă suport pentru aplicaţii care folosesc mai multe fire de execuţie.
Apleturile pot astfel crea fire de execuţie care să lucreze independent faţă de navigator.

Eticheta (tagul) APPLET


Descrierea tagului pe care Sun l-a definit pentru introducerea unui aplet într-un document
HTML este următoarea.

< APPLET
[CODEBASE = codebaseURL]
CODE = appletFile
[ALT = alternateText]
[NAME = appletInstanceName]
WIDTH = pixels
HEIGHT = pixels
[ALIGN = alignment]
[VSPACE = pixels]
[HSPACE = pixels]
>
[< PARAM NAME = appletParameter1 VALUE = value >]
[< PARAM NAME = appletParameter2 VALUE = value >]
...
[alternateHTML]
</APPLET>
CODEBASE = codebaseURL
Acest atribut opţional specifică URL-ul de bază al appletului – directorul (folderul) care
conţine codul apletului. Dacă acest atribut nu este utilizat atunci se consideră directorul
curent al documentului html.
CODE = appletFile
Acest atribut este obligatoriu şi specifică numele fişierului care conţine forma compilată a
appletului (clasa). Acest atribut este relative la URL – ul de bază.
Dacă codul clasei este în acelaşi director cu documentul HTML este suficient să fie
specificat atributul CODE cu numele fişierului unde este acesta memorat. Dacă este nevoie de un
director diferit, trebuie completat şi atributul CODEBASE în care se menţionează directorul. De
exemplu, dacă fişierele .class sunt într-un subdirector numit /clase al directorului care conţine
documentul HTML, atunci exemplul de mai sus devine:
<APPLET CODE="HelloWorldApplet.class" CODEBASE="clase"
WIDTH=200 HEIGHT=150>
Text care apare dacă navigatorul nu ştie Java
</APPLET>

ALT = alternateText
Acest atribut opţional specifică un text care trebuie să fie afişat dacă browserul înţelege
atributul APPLET dar nu ştie să execute appleturi Java.

NAME = appletInstanceName
Este un atribut opţional care specifică un nume pentru instanţa apletului, care face posibilă
comunicarea între apleturile din aceeaşi pagină.
WIDTH = pixels
HEIGHT = pixels
Aceste două attribute opţionale specifică dimensiunile (în pixeli) ale ferestrei (zonei de
afişare) ale appletului.
ALIGN = alignment
Atributul ALIGN specifică modul în care fereastra destinată apletului va fi aliniată în pagină.
Valorile sale posibile sunt: LEFT, RIGHT, TOP, TEXTTOP, MIDDLE, ABSMIDDLE,
BASELINE, BOTTOM şi ABSBOTTOM. Valoarea implicită este BASELINE.

• LEFT, RIGHT - alinierea va fi în stânga, respectiv dreapta, textului din linie.


• TOP - alinierea se va face cu elementul cel mai înalt din linie, fie el un alt aplet, o imagine
sau textul însuşi.
• TEXTTOP - alinierea se face cu cea mai înaltă poziţie ocupată de textul liniei.
• ABSMIDDLE - mijlocul apletului va fi aliniat cu mijlocul elementului cel mai mare din
linie.
• MIDDLE - mijlocul apletului se va alinia cu mijlocul liniei de bază a textului.
• BASELINE, BOTTOM - baza apletului se va alinia cu linia de bază a textului.
• ABSBOTTOM - baza apletului va fi aliniată cu elementul cel mai de jos din linie.

VSPACE = pixels
HSPACE = pixels
Atributele VSPACE şi HSPACE specifică, în pixeli, spaţiul care desparte apletul de textul care îl
înconjoară. Sunt atribute opţionale.

< PARAM NAME = appletParameter1 VALUE = value >


Eticheta <PARAM> este singura modalitate prin care unui applet i se pot transmite
parametrii. Appletul preia aceşti parametrii cu ajutorul metodei getParameter(), aşa cum
s-a arătat mai sus.
alternateHTML
Dacă pagina html care conţine eticheta APPLET este vizualizată cu ajutorul unui browser
care nu înţelege această etichetă, atunci browserul va ignora cele două etichete APPLET şi
TAG . Browserele compatibile Java vor ignora acest cod.
Atributele obligatorii ale acestei definiţii sunt numele fişierului în care este memorat codul
şi dimensiunea spaţiului rezervat apletului. Minimal, tag APPLET arată în felul următor:
<APPLET CODE="HelloWorldApplet.class" WIDTH=200 HEIGHT=150>
Text care apare dacă navigatorul nu ştie Java
</APPLET>
Atributele opţionale ale tagului descriu locaţia de bază a codului apletului, modul de aliniere
în pagină, spaţierea şi eventualii parametrii care pot fi transmişi apletului din interiorul
documentului HTML.

Structura programelor
Pachete de clase
Clasele Java sunt organizate pe pachete. Aceste pachete pot avea nume ierarhice. Numele de
pachete au forma următoare:

[NumePachet.] NumeComponentăPachet

Numele de pachete şi de componente ale acestora sunt identificatori Java. De obicei, aceste
nume urmează structura de directoare în care sunt memorate clasele compilate. Rădăcina arborelui
de directoare în care sunt memorate clasele este indicată de o variabilă sistem CLASSPATH. În
DOS aceasta se setează în felul următor:
set CLASSPATH=.;c:\java\lib
Din această rădăcină, fiecare pachet are propriul director. În director există codul binar pentru
componentele pachetului respectiv. Dacă pachetul conţine subpachete, atunci acestea sunt
memorate într-un subdirector în interiorul directorului pachetului.
Creatorii Java recomandă folosirea unei reguli unice de numire a pachetelor, astfel încât să nu apară
conflicte. Convenţia recomandată de ei este aceea de a folosi numele domeniului Internet aparţinând
producătorului claselor. Astfel, numele de pachete ar putea arăta ca în:
COM.Microsoft.OLE
COM.Apple.quicktime.v2
şi aşa mai departe.

Importul claselor
Este nevoie ca o clasă să poată folosi obiecte aparţinând unei alte clase. Pentru aceasta,
definiţia clasei respective trebuie să importe codul binar al celeilalte clase pentru a şti care sunt
variabilele şi metodele clasei respective.
Importul se face cu o instrucţiune specială:

import numeClasă ;

unde numele clasei include şi pachetul din care aceasta face parte. De exemplu:
import java.awt.Graphics;
import java.applet.Applet;
Se poate importa şi un pachet întreg, adică toate clasele aparţinând acelui pachet, printr-o
instrucţiune de forma:

import numePachet.*;

De exemplu:

import java.awt.*;

Fişiere sursă
Codul sursă Java trebuie introdus cu un editor într-un fişier text pe care îl vom numi în
continuare fişier sursă. Un fişier sursă poate să conţină declaraţia mai multor clase şi interfeţe, dar
doar una dintre acestea poate fi declarată publică. Utilizarea celorlalte clase este limitată la fişierul
respectiv. Mai mult, nu putem avea în acelaşi timp o interfaţă publică şi o clasă publică declarate în
acelaşi fişier sursă.
Dacă dorim să înregistrăm codul clasei într-un anumit pachet, putem să includem la începutul
fişierului sursă o declaraţie de forma:
package numePachet;

dacă această declaraţie lipseşte, clasa va fi plasată în pachetul implicit, care nu are nume.
Structura generală a unui fişier sursă este următoarea:

[ DeclaraţiePachet ][ InstrucţiuneImport ][ DeclaraţieDeTip ]

unde declaraţia de tip poate fi o declaraţie de clasă sau de interfaţă.

Compilare şi execuţie
Fişierele sursă Java au obligatoriu extensia .java. Numele lor este identic cu numele clasei
sau interfeţei publice declarate în interior. În urma compilării rezultă fişiere cu nume identice cu
numele claselor dar cu extensia .class indiferent dacă este vorba de o clasă sau o interfaţă. Fişierul
.class este generat în directorul local şi nu direct la locaţia pachetului.
Compilarea se face cu o comandă de forma:
javac FişierSursă.java
Comanda aceasta, ca şi celelalte descrise în acest paragraf este specifică mediului de
dezvoltare Java pus la dispoziţie de Sun, numit JDK (Java Development Kit). Există însă şi multe
alte medii de dezvoltare care au propriile lor compilatoare şi interpretoare.
La compilare, variabila sistem CLASSPATH trebuie să fie deja setată pentru că însuşi
compilatorul Java actual este scris în Java.
Pentru lansarea în execuţie a unei aplicaţii Java, trebuie să introduceţi comanda:
java NumeClasă
unde numele clasei este numele aplicaţiei care conţine metoda main . Interpretorul va căuta un fişier
cu numele NumeClasă.class şi va încerca să instanţieze clasa respectivă.
Pentru lansarea unui aplet veţi avea nevoie de un document HTML care conţine tagul APPLET şi
ca parametru al acesteia
name=NumeClasă.class
La lansarea unui aplet, clasele care sunt apelate de clasa principală sunt mai întâi căutate pe
sistemul pe care rulează navigatorul. Dacă nu sunt acolo, ele vor fi transferate în reţea. Asta
înseamnă că transferul de cod este relativ mic, trebuie transferat doar codul specific aplicaţiei.

Eticheta <OBJECT>

Eticheta <APPLET> este o extensie HTML introdusă special pentru a insera programe Java în
paginile Web. In prezent există şi alte tipuri de programe care rulează interactiv într-o pagină, cum
ar fi controale ActiveX. Pentru a trata toate aceste tipuri de programe fără a fi nevoie de câte o
etichetă explicită pentru fiecare, specificaţia HTML a introdus şi eticheta <OBJECT>.
Eticheta <OBJECT> este folosită pentru toate obiectele - programe interactive sau alte elemente
externe - care pot fi prezentate drept parte a paginii Web. Această etichetă este suportată începând
de la versiunile 4.0 ale Netscape Navigator sau Microsoft Internet Explorer. Browserele mai vechi
nu suportă această nouă etichetă, aşa încât în multe cazuri va trebui să folosiţi tot eticheta <APPLET>.
Eticheta <OBJECT> are următoarea formă:

<OBJECT CLASSID="java:Bix.class" CODEBASE="javaclasses* HEIGHT=40 WIDTH=400>


</OBJECT>

Trecerea de la eticheta <APPLET> la <OBJECT> presupune următoarele modificări:


• Eticheta <OBJECT> trebuie folosită în loc de <APPLET>
• Atributul CODE trebuie înlocuit de CLASSID. în plus, înainte de numele clasei applet-ului
trebuie specificat "java:". De exemplu, dacă applet-ul are clasa AppletJoc.class, atunci
atributul CLASSID trebuie să fie java: AppletJoc.class.
În rest, atributele (CODEBASE, HEIGHT, WIDTH, ALIGN) rămân aceleaşi. <OBJECT> poate folosi
şi etichetele opţionale <PARAM>.

Arhive Java
Modalitatea standard de amplasare a unui applet Java într-o pagină Web este de a folosi
etichetele <APPLET> sau <OBJECT> pentru a indica numele clasei primare a applet-ului. Se foloseşte
apoi un browser compatibil Java, care transferă şi execută applet-ul. Orice alte clase sau fişiere
folosite de applet sunt transferate de pe serverul Web.
Problema cu rularea în acest fel a applet-urilor este că fiecare fişier de care are nevoie applet-ul - fie
acesta o altă clasă externă, un fişier imagine, audio, text sau orice altceva -necesită o conexiune
separată de la browserul Web la serverul care conţine fişierul. Deoarece intervalul de timp necesar
pentru a stabili conexiunea nu este neglijabil, acest lucru poate mări timpul total pentru transferul
applet-ului şi al celorlalte fişiere necesare pentru rulare.
Soluţia acestei probleme este crearea unui arhive Java, adică un fişier JAR. 0 arhivă Java reprezintă
o colecţie de clase Java şi alte fişiere, împachetată într-un singur fişier. Folosind o arhivă Java,
browserului îi este suficientă o singură conexiune la serverul Web. Reducând numărul de fişiere
transferate de pe server, applet-ul poate fi încărcat şi rulat mai rapid. Arhivele Java pot fi şi
comprimate, scăzându-le astfel dimensiunea şi micşorându-se timpul de transfer - chiar dacă va mai
dura puţin din partea browserului să le decomprime înainte de a le rula.
Versiunile de Netscape Navigator şi Microsoft Internet Explorer începând cu 4.0 conţin suport
pentru fişiere JAR. Pentru a crea aceste arhive, JDK conţine un utilitar denumit jar, care poate
împacheta sau despacheta fişierele în/din arhive Java. Fişierele JAR pot fi comprimate în format Zip
sau împachetate fără a folosi comprimarea. Următoarea comandă împachetează toate clasele şi
imaginile GIF dintr-un director într-o singură arhivă Java, denumită Animat.jar:
jar cf Animat.jar *.class *.gif
Argumentul cf specifică două opţiuni în linie de comanda, care sunt folosite de programul jar.
Opţiunea c indică faptul că arhiva Java trebuie creată, iar f arată că unul dintre următoarele
argumente din linia de comandă reprezintă numele fişierului arhivă.
Puteţi, de asemenea, adăuga într-o arhivă Java alte fişiere, folosind o comandă de genul:
jar cf Smiley.jar ShowSmiley.class ShowSmiley.html spinhead.gif
Aceasta creează o arhivă Java cu numele Smiley.jar, care conţine trei fişiere:
ShowSmiley.class, ShowSmiley.html şi spinhead.gif.
Rulând utilitarul jar fără argumente, veţi obţine lista de opţiuni care pot fi folosite.
După ce aţi creat arhiva Java, în eticheta <APPLET> se foloseşte atributul ARCHIVE pentru a indica locul
unde se găseşte arhiva. Puteţi folosi arhiva Java în felul următor:
<APPLET CODE="ShowSmiley.class" ARCHIVE="Smiley.jar" WIDTH=45 HEIGHT =42>
</APPLET>
Această etichetă specifică faptul că arhiva numită Smiley.jar conţine fişierele folosite de applet.
Browserele şi utilitarele de navigare care suportă fişiere JAR ştiu să caute în interiorul arhivelor
fişierele necesare pe timpul rulării applet-ului.

Atenţie
Cu toate că o arhivă Java poate conţine fişiere clasă, atributul ARCHIVE nu presupune eliminarea
atributului CODE. Pentru a o încărca, browserul trebuie totuşi să ştie numele clasei principale a
applet-ului.
Transferul de parametri către applet-uri

În aplicaţiile Java puteţi transmite parametri metodei main ( ) specificând argumente în linia de
comandă. Apoi puteţi prelucra aceşti parametri în corpul clasei, aplicaţia comportându-se
corespunzător argumentelor primite.
În schimb, applet-urile nu posedă o linie de comandă. Applet-urile pot obţine diferite date de
intrare din fişierul HTML care conţine eticheta <APPLET> sau <OBJECT>, folosind parametri de applet-
uri. Pentru a defini şi a trata parametri într-un applet aveţi nevoie de două lucruri:
• etichetă specială de parametru în fişierul HTML
• Codul din cadrul applet-ului care să trateze aceşti parametri
Parametrii unui applet sunt compuşi din două părţi: un nume, care este ales de dumneavoastră, şi o
valoare, care determină valoarea respectivului parametru. De exemplu, puteţi indica într-un applet
culoarea unui text folosind un parametru cu numele culoare şi valoarea roşu. Puteţi determina viteza
de derulare a unei animaţii folosind un parametru cu numele viteza şi valoarea 5.
În fişierul HTML care conţine applet-ul, fiecare parametru este indicat folosind eticheta
<PARAM> , care conţine două atribute, pentru nume şi valoare, denumite NAME şi VALUE. Eticheta
<PARAM> se introduce între etichetele <APPLET> de început şi de sfârşit, ca în exemplul:

<APPLET CODE="Exemplu.class" WIDTH=100 HEIGHT=100>


<PARAM NAME=font VALUE="TimesRoman">
<PARAM NAME=dim VALUE="24">
Aici este inserat un applet Java.
</APPLET>

Acest exemplu defineşte doi parametri pentru applet-ul Exemplu: unul, denumit font, care are
valoarea TimesRoman, şi celălalt, denumit dim, care are valoarea 24.
Folosirea etichetei <PARAM> este aceeaşi pentru applet-urile care folosesc eticheta <OBJECT> în loc
de <APPLET>.
Parametrii sunt transmişi applet-ului la încărcarea acestuia. În metoda init() a applet-ului puteţi
obţine aceşti parametri folosind metoda getParameter(). Aceasta preia ca argument un şir ce
reprezintă numele parametrului căutat şi întoarce un şir care conţine valoarea respectivului
parametru. (Ca şi argumentele aplicaţiilor Java, toate valorile parametrilor sunt returnate drept
şiruri.) Pentru a obţine valoarea parametrului font din fişierul HTML, metoda init() ar trebui să
conţină ceva de genul:
String numeleFontului = getParameter("font");

Observaţie

Numele parametrilor specificaţi în eticheta <PARAM> şi numele parametrilor metodei


getParameter () trebuie să fie identice, inclusiv majusculele sau minusculele folosite. Cu alte
cuvinte, <PARAM NAME="meminescu"> este diferit de <PARAM NAME="MEminescu">. Dacă
parametrii dumneavoastră nu sunt transferaţi corect applet-ului, asiguraţi-vă că au folosit acelaşi tip
de caractere (majuscule sau minuscule) în denumirea parametrilor.

Reţineţi că dacă parametrul aşteptat nu a fost specificat in fişierul HTML, metoda getParameter() întoarce
valoarea null. De obicei, ar trebui sa testaţi valoarea null a parametrului si sa oferiţi o valoare implicită, ca in exemplul
următor:
if (numeleFontului == null)
numeleFontului = "Courier";
Ţineţi minte ca metoda getParameter () returnează şiruri; dacă doriţi ca parametrul sa fie alt tip de
obiect sau de data, trebuie sa îl convertiţi singur. De exemplu, sa luam fişierul HTML pentru applet-
ul Exemplu. Pentru a trata parametrul dim si a-l atribui unei variabile întregi, numită dimensiunea,
aţi putea folosi următorul cod:
int dimensiunea;
String s = getParameter("dim");
if (s == null)
dimensiunea = 12;
else dimensiunea = Integer.parseInt(s);
Să cream un exemplu de applet care foloseşte această tehnică. Vom crea un applet Palindrom
pentru a afişa alte texte, cum ar fi ,,Dennis and Edna sinned" sau ,,No, sir, prefer prison". Numele
este transmis applet-ului printr-un parametru HTML. Proiectul va primi numele Palindrom.

import java.awt.Graphics;
import java.awt.Font;
import java.awt.Color;
public class Palindrom extends java.applet.Applet {
Font f = new Font ("TimesRoman", Font.BOLD, 36);
String palindrom;

public void paint(Graphics ecran) {


ecran.setFont(f);
ecran.setColor(Color.red);
ecran.drawString(palindrom, 5, 40);
}

public void init() {


palindrom = getParameter("palindrom");
if (palindrom == null)
palindrom = "Dennis and Edna sinned";
}
}
Fişierul HTML care va conţine acest applet este următorul

<HTML>
<HEAD>
<TITLE>Pagina cu palindrom</TITLE>
</HEAD>
<BODY>
<P>
<APPLET CODE=”Palindrom.class" WIDTH=600 HEIGHT=100>
<PARAM NAME=palindrom VALUE="No, sir, prefer prison">
Browserul dumneavoastră nu suporta Java!
</APPLET>
</BODY>
</HTML>

Remarcaţi eticheta <APPLET>, care desemnează fişierul clasă pentru applet şi valorile pentru
lăţime şi înălţime (600, respectiv 100 de pixeli). Imediat sub această linie (în linia 8) se află eticheta
<PARAM>, care este folosită pentru a transmite parametrul applet-ului. În acest exemplu, numele
parametrului este palindrom, iar valoarea sa este şirul „No, sir, prefer prison".
Dacă nu este specificată nici o valoare pentru parametrul palindrom, textul implicit este „Dennis
and Edna sinned".
Următorul fişier HTML nu conţine nici o etichetă de parametru
<HTML>
<HEAD>
<TITLE>Noua pagina cu palindrom</TITLE>
</HEAD>
<BODY>
<P>
<APPLET CODE="PalindromNou.class" WIDTH=600 HIGHT=100>
Browserul dumneavoastră nu suporta Java!
</APPLET>
</BODY>
</HTML>
Deoarece aici nu a fost specificat nici un parametru, applet-ul va folosi valoarea implicită.
Clasa Graphics
Puteţi să vă imaginaţi applet-ul drept o pânză pe care se vor desfăşura operaţii grafice. Aţi
folosit deja metoda drawString () pentru a desena text într-un applet. Fontul şi culoarea textului
au fost alese anterior desenării textului, în acelaşi fel în care un artist îşi alege culoarea şi pensula
înainte de a picta.
Textul nu este singurul lucru pe care îl puteţi desena în fereastra unui applet. Puteţi desena
şi linii, ovale, cercuri, arcuri, dreptunghiuri şi alte poligoane.
Majoritatea principalelor operaţii de desenare sunt metode definite în clasa Graphics.
Într-un applet nu este nevoie să creaţi un obiect Graphics pentru a putea desena ceva – aşa
cum poate vă mai amintiţi, unul dintre argumentele metodei paint () este un obiect Graphics.
Acest obiect reprezintă fereastra applet-ului, iar metodele sale sunt folosite pentru a desena în
applet.
Clasa Graphics este parte a pachetului java.awt, deci toate applet-urile care desenează ceva
trebuie să folosească instrucţiunea import pentru a putea folosi această clasă.

Sistemul de coordonate grafice


Ca şi metoda drawString(), toate celelalte metode grafice posedă argumente care indică coordonatele x,y.
Unele dintre acestea au mai multe seturi de coordonate, cum ar fi o linie, care posedă un set de coordonate x,y ce
indică punctul de început al liniei şi încă un set de coordonate x,y care corespunde punctului final al liniei.
Sistemul de coordonate Java foloseşte ca unitate de măsură pixelul. Coordonatele
punctului de origine 0,0 sunt situate în colţul din stânga - sus al ferestrei Applet. Valoarea
coordonatei x creşte la dreapta originii 0,0, iar valoarea coordonatei y creşte în jos. Acest lucru
diferă faţă de alte sisteme de coordonate, în care originea 0,0 se află în colţul din stânga -jos şi
valoarea coordonatei y creşte în sus.
Toate valorile de pixeli sunt întregi - nu se pot folosi valori zecimale pentru a afişa ceva
aflat între două valori întregi.

Desenarea şi umplerea
Pentru majoritatea formelor pe care le desenaţi într-un applet sunt disponibile două tipuri de metode: metode
de desenare, care desenează conturul formei, şi metode de umplere, care umplu forma cu culoarea curentă. În fiecare
tip de metodă, conturul obiectului este, de asemenea, desenat cu culoarea curentă.
Linii
Metoda drawLine este folosită pentru a desena o linie între două puncte. Metoda primeşte patru
argumente: coordonatele x,y ale punctului de început şi coordonatele x, y ale punctului final.
drawLine(x1, y1, x2, y2) ;
Această metodă desenează o linie începând cu punctul de coordonate (x1, y1) şi până la punctul de
coordonate (x2, y2). Grosimea liniei este de un pixel.

Dreptunghiuri
Clasa Graphics conţine metode pentru două tipuri de dreptunghiuri: dreptunghiuri normale şi
dreptunghiuri cu colţuri .
Ambele tipuri de dreptunghiuri pot fi desenate sub formă de contur sau umplute cu culoarea curentă.
Pentru a desena un dreptunghi normal se foloseşte metoda drawRect() pentru contururi sau
metoda fillRect() pentru forme umplute. Ambele metode preiau patru argumente:
• Coordonatele x şi y ale colţului din stânga - sus al dreptunghiului
• Lăţimea dreptunghiului
• Înălţimea dreptunghiului
drawRect(x,y,l,h)

Poligoane
Poligoanele pot fi desenate folosind metodele drawPolygon() şi fillPolygon().
Pentru a desena un poligon aveţi nevoie de coordonatele x,y ale fiecărui punct care defineşte colţurile
poligonului. Poligoanele pot fi definite drept o serie de linii conectate una cu cealaltă - se desenează o linie de la un
punct iniţial la un punct final, apoi punctul final este folosit ca punct iniţial pentru o altă linie şi aşa mai departe.
Puteţi specifica aceste coordonate în două moduri:
• Ca o pereche de tablouri cu întregi, dintre care unul păstrează toate valorile coordonatei x şi
celălalt păstrează toate valorile coordonatei y.
• Ca un obiect Polygon, creat folosind un tablou cu valori întregi ale coordonatei x şi un
tablou cu valori întregi ale coordonatei y.
A doua metodă este mai flexibilă, deoarece permite adăugarea individuală a punctelor unui
poligon, înainte de desenarea sa.
În afară de coordonatele x şi y, trebuie să specificaţi numărul de puncte al poligonului. Nu se
pot specifica mai multe coordonate x,y decât numărul de puncte şi nici invers. în oricare din aceste
cazuri, compilatorul va semnala o eroare.
Pentru a crea un obiect polygon, primul pas constă în crearea unui poligon gol, printr-o instrucţiune
new, ca în cazul următor:
Polygon polig = new Polygon();
Mai puteţi crea un poligon pornind de la un set de puncte, folosind tablouri cu valori întregi.
Aceasta necesită un apel către constructorul Polygon (int [], int [], int), unde se specifică tabloul cu
valori pentru coordonata x, tabloul cu valori pentru coordonata y şi numărul de puncte (colţuri). Iată
un exemplu de folosire a acestui constructor:
int x[] = { 10, 20, 30, 40, 50 };
int y[] = { 15, 25, 35, 45, 55 };
int puncte = x.length;
Polygon polig = new Polygon(x, y, puncte);
După ce se creează obiectul Polygon, se pot adăuga puncte folosind metoda addpoint().
Aceasta preia ca argumente coordonatele x, y şi adaugă punctul în poligon. lată un exemplu:
polig.addPoint(60, 65);
Atunci când obiectul Polygon are toate punctele necesare, el poate fi desenat folosind una
dintre metodele drawPolygon() sau fillPolygon() . Aceste metode au drept unic argument obiectul
Polygon, ca în exemplul următor:
ecran.drawPolygon(polig) ;
Dacă folosiţi metoda drawPolygon() în Java 1.02, puteţi închide poligonul stabilind pentru
ultimele coordonate x,y valori identice cu primele. Altfel, poligonul va rămâne deschis pe una
dintre laturi.
Metoda fillPolygon() închide automat forma poligonală, fără a mai fi nevoie de stabilirea
coordonatelor finale.

Ovale
Metodele drawOval() şi fillOval() sunt folosite pentru a desena cercuri şi ovale (elipse).
Metodele preiau patru argumente:
• coordonatele x, y ale ovalului
• lăţimea şi înălţimea ovalului, care în cazul cercurilor iau valori egale
Ovalele sunt tratate în acelaşi mod ca şi colţurile dreptunghiurilor rotunjite. Coordonata x,y va reprezenta
colţul din stânga - sus al zonei în care va fi desenat ovalul, aflându-se de fapt la stânga şi mai sus decât forma ovală
propriu-zisă.

Arce
Un arc este o parte a unui oval, fiind implementat în Java ca o formă eliptică parţial desenată.
Arcele sunt desenate folosind metodele drawArc() şi fillArc(), care preiau şase argumente:
• coordonatele x, y ale ovalului din care face parte arcul;
• lăţimea şi înălţimea ovalului;
• unghiul de unde se începe trasarea arcului;
• numărul de grade al arcului. Primele patru argumente sunt aceleaşi ca în cazul ovalului şi se
comportă identic.
Unghiul de început al arcului ia valori între 0 şi 359 de grade şi creşte în sens trigonometric (invers
acelor de ceasornic).

Copierea şi ştergerea
Clasa Graphics conţine şi câteva funcţii de gen decupează-şi-lipeşte (cut-and-paste),
aplicabile ferestrei Applet:
• Metoda copyArea (), care copiază o regiune dreptunghiulară a ferestrei Applet într-o altă
regiune a ferestrei.
• Metoda clearRect (), care „decupează" o regiune dreptunghiulară din fereastra Applet
Metoda copyArea () preia şase argumente:
• Coordonatele x, y ale regiunii dreptunghiulare de copiat.
• Lăţimea şi înălţimea regiunii.
• Distanţa pe orizontală şi pe verticală, în pixeli, cu care se deplasează copia faţă de regiunea
iniţială, înainte de afişare.
Următoarea instrucţiune copiază o regiune de 100x100 pixeli într-o zonă aflată cu 50 de pixeli
mai la dreapta şi cu 25 de pixeli mai jos:
ecran.copyArea(0, 0, 100, 100, 50, 25);
Metoda clearRect() preia aceleaşi patru argumente ca şi metodele drawRect() sau fillRect(),
umplând regiunea dreptunghiulară astfel definită cu culoarea curentă de fundal a applet-ului. Dacă
doriţi să ştergeţi întreaga fereastră Applet, puteţi determina mai întâi dimensiunea ferestrei folosind
metoda size(). Aceasta returnează un obiect Dimension, care posedă variabilele width (lăţime) şi
height (înălţime); acestea reprezintă dimensiunile applet-ului.
lată un exemplu de folosire a acestei metode:
ecran.clearRect(0, 0, size().width, size().height);

Text şi fonturi
Obiectele clasei java.awt.Font sunt folosite pentru a putea utiliza metoda drawString() cu diferite fonturi.
Obiectele Font conţin numele, stilul şi dimensiunea în puncte a unui font. O altă clasă, FontMetrics, oferă metode
pentru determinarea dimensiunilor şi a caracterelor afişabile cu un anumit font, care pot fi folosite pentru lucruri cum ar
fi formatarea şi centrarea textului.

Crearea obiectelor Font

Un obiect Font este creat prin apelarea constructorului său cu trei argumente:
• Numele fontului
• Stilul fontului
• Dimensiunea în puncte a fontului
Numele poate fi denumirea unui font specific, cum ar fi Arial sau Garamond Old Style, care
va putea fi folosit dacă este prezent (instalat) în sistemul pe care se rulează programul Java.
Există şi alte nume care pot fi folosite pentru selectarea fonturilor interne, proprii Java: TimesRoman,
Helvetica, Courier, Dialog şi DialogInput.
Pot fi selectate trei stiluri de fonturi, folosind constantele Font. PLAIN, Font. BOLD şi Font.
ITALIC. Aceste constante sunt întregi şi pot fi însumate pentru a obţine o combinaţie de efecte.
Ultimul argument al constructorului Font () este dimensiunea fontului. Următoarea
instrucţiune creează un font Dialog de 24 de puncte, cu aldine cursive.
Font f = new Font("Dialog", Font.BOLD + Font.ITALIC, 24);

Desenarea caracterelor şi a şirurilor


Pentru a seta fontul curent se foloseşte metoda setFont() a clasei Graphics, împreună cu un
obiect Font. Următoarea instrucţiune foloseşte un obiect Font denumit ft:
ecran.setFont(ft);
Textul poate fi afişat într-o fereastră Applet folosind metoda drawString(). Această metodă foloseşte fontul
curent selectat; dacă nu a fost selectat nici un font, este folosit unul implicit. Poate fi selectat oricând un nou font cu
ajutorul metodei setFont ().
Următoarea metodă paint () creează un nou obiect Font, stabileşte fontul curent la acest
obiect, după care afişează şirul „Acesta este un font.” la coordonatele 10,100.
public void paint(Graphics ecran) {
Font f = new Font("TimesRoman", Font.PLAIN, 72);
ecran.setFont(f);
ecran.drawString(„Acesta este un font.", 10, 100);
}
Ultimele două argumente ale metodei drawString () sunt coordonatele x, y. Valoarea x reprezintă locul de
început al marginii din stânga a textului, iar y este valoarea la care se afişează linia de bază a şirului de text.

Aflarea de informaţii despre un font

Clasa FontMetrics poate fi folosită pentru obţinerea de informaţii detaliate despre fontul
curent, cum ar fi lăţimea sau înălţimea caracterelor pe care le poate afişa.
Pentru a folosi metodele clasei, trebuie mai întâi creat un obiect FontMetrics prin metoda
getFontMetrics (). Metoda primeşte un singur argument: un obiect Font.
Tabelul prezintă unele informaţii pe care le puteţi obţine despre dimensiunile fontului. Toate aceste metode
pot fi apelate pentru un obiect FontMetrics.
Tabelul Metode FontMetrics.

Nume metodă Acţiune

stringWidth (String) Întoarce lăţimea totală a şirului, în pixeli


charWidth (char) Întoarce lăţimea unui caracter dat
getHeight () Întoarce înălţimea totală a fontului

Culori
Clasele Color şi ColorSpace din pachetul java.awt pot fi folosite pentru a aduce puţină
culoare în applet-urile şi aplicaţiile dumneavoastră. Cu ajutorul acestor clase puteţi stabili culorile
curente folosite în operaţiile de desenare, ca şi culoarea de fundal pentru un applet sau alte ferestre.
De asemenea, puteţi translata o culoare dintr-un sistem de descriere în altul.
În mod prestabilit, Java foloseşte culorile conform unui sistem de descriere denumit sRGB. În acest sistem, o
culoare este descrisă prin cantitatea de roşu, verde şi albastru pe care o conţine - de aici şi iniţialele R(ed), G(reen) şi
B(lue). Fiecare dintre aceste trei componente poate fi reprezentată ca un întreg din gama 0-255. Negrul este 0, 0, 0 -
lipsa completă a tuturor componentelor roşu, verde, albastru. Albul este 255, 255, 255 - valoarea maximă a tuturor
componentelor. Mai puteţi reprezenta valori sRGB folosind trei numere în virgulă mobilă, în domeniul dintre 0 şi 1,0.
Folosind sRGB, Java poate reprezenta milioane de culori aflate între cele două extreme.
Un sistem de descriere a culorilor mai este numit şi paletă sau spaţiu de culori (color space),
iar sRGB este doar unul dintre acestea. Există şi CMYK, un sistem folosit de imprimante şi care
descrie culorile prin procentul de Cyan (azuriu), Magenta (purpuriu), Yellow (galben) şi Black
(negru) pe care îl conţin. Java 2 suportă folosirea oricărui spaţiu de culori doriţi, atât timp cât
folosiţi un obiect ColorSystem care defineşte respectivul sistem de descriere a culorilor. De
asemenea, puteţi converti culorile din sRGB în alt spaţiu de culori şi invers.
Reprezentarea internă a culorilor în Java folosind sRGB reprezintă numai spaţiul de culoare folosit în program.
Dispozitivele de ieşire, cum ar fi monitoarele sau imprimantele, pot avea şi ele propriile spaţii de culoare.
Atunci când afişaţi sau tipăriţi ceva cu o anumită culoare, dispozitivul de ieşire s-ar putea să nu
suporte culoarea respectivă. în acest caz, culoarea va fi înlocuită cu o alta sau cu o culoare impură
(dithered), folosită pentru a aproxima culoarea care nu este disponibilă. Acest lucru se întâmplă
frecvent pe World Wide Web, când o culoare indisponibilă este înlocuită cu una impură, care
aproximează culoarea lipsă.
Practic, culorile definite conform modelului sRGB nu vor putea fi reprezentate pe orice
dispozitiv de ieşire. Dacă aveţi nevoie de un control mai precis al culorii, puteţi folosi ColorSpace
sau alte clase din pachetul java.awt. color, introdus o dată cu Java 2.
Pentru majoritatea programelor, folosirea sRGB, sistemul intern de reprezentare a culorilor, va fi suficientă.

Folosirea obiectelor Color


Pentru a stabili culoarea curentă pentru desenare, trebuie fie să creaţi un obiect Color care să
reprezinte culoarea respectivă, fie să folosiţi una dintre culorile standard existente în clasa Color.
Pentru a crea o culoare, există două metode de apelare a metodei constructor Color;
• Folosirea a trei întregi care să reprezinte valorile sRGB pentru culoarea dorită
• Folosirea a trei numere în virgulă mobilă, care să reprezinte valorile sRGB pentru culoarea
dorită
Puteţi defini deci valoarea sRGB a culorii folosind fie trei variabile int, fie trei variabile float.
lată un exemplu de instrucţiuni de acest gen:
Color c1 = new Color(0.807F, 1F, OF);
Color c2 = new Color(255, 204, 102);
Obiectul c1 defineşte o culoare verde-neon, iar c2 o culoare maro-aurie

Testarea şi stabilirea culorilor curente


Culoarea curentă pentru desenare este desemnată folosind metoda setColor() a clasei
Graphics. Această metodă trebuie apelată pentru obiectul Graphics care reprezintâ zona în care
desenaţi. Într-un applet, acest obiect este cel transmis metodei paint ().
O modalitate de a stabili culoarea de desenare este folosirea uneia dintre culorile standard,
disponibile ca variabilă de clasă în clasa Color.
Aceste culori folosesc următoarele variabile Color (având valorile sRGB indicate între paranteze):
black (negru) (0,0,0) magenta (purpuriu)(255,0,255)
blue (albastru) (0,0,255) orange (portocaliu)(255,200,0)
cyan (azuriu)(0,255,255) pink (roz) (255.175,175)
darkGray(gri închis)(64,64,64) red (rosu) (255,0,0)
gray (gri) (128,128,128) white (alb) (255,255,255)
green (verde) (0,255,0) yellow (galben) (255,255,0)
lightGray(gri deschis)(192,192,192)

Următoarea instrucţiune stabileşte culoarea curentă pentru obiectul ecran folosind una dintre
variabilele de clasă standard:
ecran.setColor(Color.pink) ;
Dacă aţi creat un obiect Color, el poate fi setat într-un mod asemănător:
Color pensula = new Color(255, 204, 102);
ecran.setColor(pensula) ;
După ce aţi stabilit culoarea curentă, toate operaţiile de desenare o vor folosi pe aceasta.
Puteţi stabili culoarea de fundal (background) într-o fereastră Applet folosind metodele
proprii applet-ului, setBackground() şi setForeground(). Acestea sunt moştenite de clasa Applet de
la una dintre superclasele sale, aşa încât toate applet-urile create vor moşteni şi ele aceste metode.
Metoda setBackground() setează culoarea de fundal a ferestrei Applet. Ea primeşte ca singur
argument un obiect color:
setBackground(Color.white) ;
Există şi o metodă setForeground(), care este apelată pentru componentele interfeţei
utilizator, nu pentru obiectele Graphics. Funcţionează la fel ca metoda setColor (), însă schimbă
culoarea unei componente a interfeţei, cum ar fi un buton sau o fereastră.
Deoarece un applet este o fereastră, puteţi folosi metoda setForeground() în metoda init()
pentru a seta culorile pentru operaţiile de desenare. Această culoare va fi folosită până la alegerea
unei alte culori cu una dintre metodele setForeground() sau setColor ().
Dacă doriţi să aflaţi care este culoarea curentă, puteţi folosi metoda getColor() pentru un
obiect grafic, respectiv una dintre metodele getForeground() sau getBackground() pentru clasa
Applet.
Următoarea instrucţiune stabileşte culoarea curentă pentru ecran - un obiect Graphics - ca
fiind aceeaşi cu fundalul applet-ului:
ecran.setColor(getBackground());

Operaţii grafice avansate folosind Java2D


Una dintre îmbunătăţirile aduse în Java2 este Java2D, un set de clase pentru crearea unor imagini şi
texte bidirecţionale de înaltă calitate. Funcţiile Java2D conţin:
• Modele speciale de umplere
• Linii de diferite grosimi
• Anti-aliasing pentru netezirea marginilor obiectelor desenate
• Conversia prin cast a unui obiect Graphics2D
Operaţiile de desenare învăţate până acum sunt apelate pentru obiecte Graphics. Pentru Java2D,
acest obiect se utilizează pentru a crea un nou obiect Graphics2D
public void paint(Graphics ecran) {
Graphics2D ecran2D = (Graphics2D)ecran;
}
Fire de execuţie şi sincronizare
O aplicaţie Java rulează în interiorul unui proces al sistemului de operare. Acest proces
constă din segmente de cod şi segmente de date mapate într-un spaţiu virtual de adresare. Fiecare
proces deţine un număr de resurse alocate de către sistemul de operare, cum ar fi fişiere deschise,
regiuni de memorie alocate dinamic, sau fire de execuţie. Toate aceste resurse deţinute de către un
proces sunt eliberate la terminarea procesului de către sistemul de operare.
Un fir de execuţie este unitatea de execuţie a unui proces. Fiecare fir de execuţie are asociate
o secvenţă de instrucţiuni, un set de regiştri CPU şi o stivă. Atenţie, un proces nu execută nici un fel
de instrucţiuni. El este de fapt un spaţiu de adresare comun pentru unul sau mai multe fire de
execuţie. Execuţia instrucţiunilor cade în responsabilitatea firelor de execuţie. În cele ce urmează
vom prescurta uneori denumirea firelor de execuţie, numindu-le pur şi simplu fire .
În cazul aplicaţiilor Java interpretate, procesul deţine în principal codul interpretorului iar
codul binar Java este tratat ca o zonă de date de către interpretor. Dar, chiar şi în această situaţie, o
aplicaţie Java poate avea mai multe fire de execuţie, create de către interpretor şi care execută,
seturi distincte de instrucţiuni binare Java.
Fiecare dintre aceste fire de execuţie poate rula în paralel pe un procesor separat dacă
maşina pe care rulează aplicaţia este o maşină cu mai multe procesoare. Pe maşinile monoprocesor,
senzaţia de execuţie în paralel a firelor de execuţie este creată prin rotirea acestora pe rând la
controlul unităţii centrale, câte o cuantă de timp fiecare.
Mediul de execuţie Java execută propriul său control asupra firelor de execuţie. Algoritmul
pentru planificarea firelor de execuţie, priorităţile şi stările în care se pot afla acestea sunt specifice
aplicaţiilor Java şi implementate identic pe toate platformele pe care a fost portat mediul de execuţie
Java. Totuşi, acest mediu ştie să profite de resursele sistemului pe care lucrează. Dacă sistemul
gazdă lucrează cu mai multe procesoare, Java va folosi toate aceste procesoare pentru a-şi planifica
firele de execuţie.
În cazul maşinilor multiprocesor, mediul de execuţie Java şi sistemul de operare sunt
responsabile cu repartizarea firelor de execuţie pe un procesor sau altul. Pentru programator, acest
mecanism este complet transparent, neexistând nici o diferenţă între scrierea unei aplicaţii cu mai
multe fire pentru o maşină cu un singur procesor sau cu mai multe. Desigur, există însă diferenţe în
cazul scrierii aplicaţiilor pe mai multe fire de execuţie faţă de acelea cu un singur fir de execuţie,
diferenţe care provin în principal din cauza necesităţii de sincronizare între firele de execuţie
aparţinând aceluiaşi proces.
Sincronizarea firelor de execuţie înseamnă că acestea se aşteaptă unul pe celălalt pentru
completarea anumitor operaţii care nu se pot executa în paralel sau care trebuie executate într-o
anumită ordine. Java oferă şi în acest caz mecanismele sale proprii de sincronizare, extrem de uşor
de utilizat şi înglobate în chiar sintaxa de bază a limbajului.
La lansarea în execuţie a unei aplicaţii Java este creat automat şi un prim fir de execuţie,
numit firul principal. Acesta poate ulterior să creeze alte fire de execuţie care la rândul lor pot crea
alte fire, şi aşa mai departe. Firele de execuţie dintr-o aplicaţie Java pot fi grupate în grupuri pentru
a fi manipulate în comun.
În afară de firele normale de execuţie, Java oferă şi fire de execuţie cu prioritate mică care
lucrează în fundalul aplicaţiei atunci când nici un alt fir de execuţie nu poate fi rulat. Aceste fire de
fundal se numesc demoni şi execută operaţii costisitoare în timp şi independente de celelalte fire de
execuţie. De exemplu, în Java colectorul de gunoaie lucrează pe un fir de execuţie separat, cu
proprietăţi de demon. În acelaşi fel poate fi gândit un fir de execuţie care execută operaţii de
încărcare a unor imagini din reţea.
O aplicaţie Java se termină atunci când se termină toate firele de execuţie din interiorul ei
sau când nu mai există decât fire demon. Terminarea firului principal de execuţie nu duce la
terminarea automată a aplicaţiei.

Crearea firelor de execuţie


Există două căi de definire de noi fire de execuţie: derivarea din clasa Thread a noi clase şi
implementarea într-o clasă a interfeţei Runnable .
În primul caz, noua clasă moşteneşte toate metodele şi variabilele clasei Thread care
implementează în mod standard, în Java, funcţionalitatea de lucru cu fire de execuţie. Singurul lucru
pe care trebuie să-l facă noua clasă este să reimplementeze metoda run care este apelată automat de
către mediul de execuţie la lansarea unui nou fir. În plus, noua clasă ar putea avea nevoie să
implementeze un constructor care să permită atribuirea unei denumiri firului de execuţie.
Dacă firul are un nume, acesta poate fi obţinut cu metoda getName care returnează un obiect
de tip String .
Iată un exemplu de definire a unui nou tip de fir de execuţie:
class FirNou extends Thread {
public FirNou( String nume ) {
// apelează constructorul din Thread
super( nume );
}
public void run() {
while( true ) { // fără sfârşit
System.out.println( getName() +" Tastati ^C" );
}
}
}
Dacă vom crea un nou obiect de tip FirNou şi îl lansăm în execuţie acesta va afişa la infinit
mesajul "Tastaţi ^C". Întreruperea execuţiei se poate face într-adevăr prin tastarea caracterului ^C,
caz în care întreaga aplicaţie este terminată. Atâta timp însă cât noul obiect nu va fi întrerupt din
exterior, aplicaţia va continua să se execute pentru că mai există încă fire de execuţie active şi
indiferent de faptul că firul de execuţie principal s-a terminat sau nu.
Iată şi un exemplu de aplicaţie care foloseşte această clasă:

public class TestFirNou {

public static void main( String[] arg) {

new FirNou( "Primul" ).start();

Metoda start, predefinită în obiectul Thread lansează execuţia propriu-zisă a firului. Desigur
există şi căi de a opri execuţia la nesfârşit a firului creat fie prin apelul metodei stop , prezentată mai
jos, fie prin rescrierea funcţiei run în aşa fel încât execuţia sa să se termine după un interval finit de
timp.
A doua cale de definiţie a unui fir de execuţie este implementarea interfeţei Runnable într-o
anumită clasă de obiecte. Această cale este cea care trebuie aleasă atunci când clasa pe care o creăm
nu se poate deriva din clasa Thread pentru că este important să fie derivată din altă clasă. Desigur,
moştenirea multiplă ar rezolva această problemă, dar Java nu are moştenire multiplă.
Această nouă cale se poate folosi în modul următor:
class Oclasa {

}
class FirNou extends Oclasa implements Runnable {
public void run() {
for( int i = 0; i < 100; i++ ) {
System.out.println( "pasul " + i );
}
}

}
public class TestFirNou {
public static void main( String argumente[ ] ) {
new Thread( new FirNou() ).start();
// Obiectele sunt create şi folosite imediat
// La terminarea instrucţiunii, ele sunt automat
// eliberate nefiind referite de nimic
}
}
După se observă, clasa Thread are şi un constructor care primeşte ca argument o instanţă a
unei clase care implementează interfaţa Runnable. În acest caz, la lansarea în execuţie a noului fir, cu
metoda start , se apelează metoda run din acest obiect şi nu din instanţa a clasei Thread .
Atunci când dorim să creăm un aplet care să ruleze pe un fir de execuţie separat faţă de
pagina de navigator în care rulează pentru a putea executa operaţii în fereastra apletului şi în acelaşi
timp să putem folosi în continuare navigatorul, suntem obligaţi să alegem cea de-a doua cale de
implementare. Aceasta pentru că apletul nostru trebuie să fie derivat din clasa standard Applet .
Singura alternativă care ne rămâne este aceea de a implementa în aplet interfaţa Runnable .

Stările unui fir de execuţie


Un fir de execuţie se poate afla în Java în mai multe stări, în funcţie de ce se întâmplă cu el
la un moment dat.
Atunci când este creat, dar înainte de apelul metodei start, firul se găseşte într-o stare pe care
o vom numi Fir Nou Creat . În această stare, singurele metode care se pot apela pentru firul de
execuţie sunt metodele start şi stop . Metoda start lansează firul în execuţie prin apelul metodei run .
Metoda stop omoară firul de execuţie încă înainte de a fi lansat. Orice altă metodă apelată în această
stare provoacă terminarea firului de execuţie prin generarea unei excepţii de tip
IllegalThreadStateException .
Dacă apelăm metoda start pentru un Fir Nou Creat firul de execuţie va trece în starea Rulează.
În această stare, instrucţiunile din corpul metodei run se execută una după alta. Execuţia poate fi
oprită temporar prin apelul metodei sleep care primeşte ca argument un număr de milisecunde care
reprezintă intervalul de timp în care firul trebuie să fie oprit. După trecerea intervalului, firul de
execuţie va porni din nou.
În timpul în care se scurge intervalul specificat de sleep, obiectul nu poate fi repornit prin
metode obişnuite. Singura cale de a ieşi din această stare este aceea de a apela metoda interrupt.
Această metodă generează o excepţie de tip InterruptedException care nu este interceptată de sleep dar
care trebuie interceptată obligatoriu de metoda care a apelat metoda sleep. De aceea, modul standard
în care se apelează metoda sleep este următorul:

try {
sleep( 1000 ); // o secundă
} catch( InterruptedException ) {

}

Dacă dorim oprirea firului de execuţie pe timp nedefinit, putem apela metoda suspend.
Aceasta trece firul de execuţie într-o nouă stare, numită Nu Rulează. Aceeaşi stare este folosită şi
pentru oprirea temporară cu sleep. În cazul apelului suspend însă, execuţia nu va putea fi reluată
decât printr-un apel al metodei resume. După acest apel, firul va intra din nou în starea Rulează .
Pe timpul în care firul de execuţie se găseşte în starea Nu Rulează , acesta nu este planificat
niciodată la controlul unităţii centrale, aceasta fiind cedată celorlalte fire de execuţie din aplicaţie.
Firul de execuţie poate intra în starea Nu Rulează şi din alte motive. De exemplu se poate
întâmpla ca firul să aştepte pentru terminarea unei operaţii de intrare/ieşire de lungă durată caz în
care firul va intra din nou în starea Rulează doar după terminarea operaţiei.
O altă cale de a ajunge în starea Nu Rulează este aceea de a apela o metodă sau o secvenţă de
instrucţiuni sincronizată după un obiect. În acest caz, dacă obiectul este deja blocat, firul de execuţie
va fi oprit până în clipa în care obiectul cu pricina apelează metoda notify sau notifyAll .
Atunci când metoda run şi-a terminat execuţia, obiectul intră în starea Mort. Această stare
este păstrată până în clipa în care obiectul este eliminat din memorie de mecanismul de colectare a
gunoaielor. O altă posibilitate de a intra în starea Mort este aceea de a apela metoda stop .
Desigur, firul de execuţie poate fi terminat şi pe alte căi, caz în care metoda stop nu este
apelată. În aceste situaţii este preferabil să ne folosim de o clauză finally ca în exemplul următor:

try {
firDeExecutie.start( );

} finally {
..// curăţenie
}
În fine, dacă nu se mai poate face nimic pentru că firul de execuţie nu mai răspunde la
comenzi, puteţi apela la calea disperată a metodei destroy. Din păcate, metoda destroy termină firul
de execuţie fără a proceda la curăţirile necesare în memorie.
Atunci când un fir de execuţie este oprit cu comanda stop, mai este nevoie de un timp până
când sistemul efectuează toate operaţiile necesare opririi. Din această cauză, este preferabil să
aşteptăm în mod explicit terminarea firului prin apelul metodei join:
firDeExecutie.stop( )
try {
firDeExecutie.join( );
} catch( InterruptedException e ) {

}
Excepţia de întrerupere trebuie interceptată obligatoriu. Dacă nu apelăm metoda join pentru
a aştepta terminarea şi metoda stop este de exemplu apelată pe ultima linie a funcţiei main , există
şansa ca sistemul să creadă că firul auxiliar de execuţie este încă în viaţă şi aplicaţia Java să nu se
mai termine rămânând într-o stare de aşteptare. O puteţi desigur termina tastând ^C.

Prioritatea firelor de execuţie


Fiecare fir de execuţie are o prioritate cuprinsă între valorile MIN_PRIORITY şi
MAX_PRIORITY. Aceste două variabile finale sunt declarate în clasa Thread. În mod normal însă,
un fir de execuţie are prioritatea NORM_PRIORITY, de asemenea definită în clasa Thread .
Mediul de execuţie Java planifică firele de execuţie la controlul unităţii centrale în funcţie de
prioritatea lor. Dacă există mai multe fire cu prioritate maximă, acestea sunt planificate după un
algoritm numit round-robin. Firele de prioritate mai mică intră în calcul doar atunci când toate firele
de prioritate mare sunt în starea Nu Rulează .
Prioritatea unui fir de execuţie se poate interoga cu metoda getPriority care întoarce un
număr întreg care reprezintă prioritatea curentă a firului de execuţie. Pentru a seta prioritatea, se
foloseşte metoda setPriority care primeşte ca parametru un număr întreg care reprezintă prioritatea
dorită.
Schimbarea priorităţii unui fir de execuţie este o treabă periculoasă dacă metoda cu
prioritate mare nu se termină foarte repede sau dacă nu are opriri dese. În caz contrar, celelalte
metode nu vor mai putea primi controlul unităţii centrale.
Există însă situaţii în care putem schimba această prioritate fără pericol, de exemplu când
avem un fir de execuţie care nu face altceva decât să citească caractere de la utilizator şi să le
memoreze într-o zonă temporară. În acest caz, firul de execuţie este în cea mai mare parte a
timpului în starea Nu Rulează din cauză că aşteaptă terminarea unei operaţii de intrare/ieşire. În clipa
în care utilizatorul tastează un caracter, firul va ieşi din starea de aşteptare şi va fi primul planificat
la execuţie din cauza priorităţii sale ridicate. În acest fel utilizatorul are senzaţia că aplicaţia
răspunde foarte repede la comenzile sale.
În alte situaţii, avem de executat o sarcină cu prioritate mică. În aceste cazuri, putem seta
pentru firul de execuţie care execută aceste sarcini o prioritate redusă.
Alternativ, putem defini firul respectiv de execuţie ca un demon. Dezavantajul în această
situaţie este faptul că aplicaţia va fi terminată atunci când există doar demoni în lucru şi există
posibilitatea pierderii de date. Pentru a declara un fir de execuţie ca demon, putem apela metoda
setDaemon. Această metodă primeşte ca parametru o valoare booleană care dacă este true firul este
făcut demon şi dacă nu este adus înapoi la starea normală. Putem testa faptul că un fir de execuţie
este demon sau nu cu metoda isDemon.

Grupuri de fire de execuţie


Uneori avem nevoie să acţionăm asupra mai multor fire de execuţie deodată, pentru a le
suspenda, reporni sau modifica prioritatea în bloc. Din acest motiv, este util să putem grupa firele
de execuţie pe grupuri. Această funcţionalitate este oferită în Java de către o clasă numită
ThreadGroup .
La pornirea unei aplicaţii Java, se creează automat un prim grup de fire de execuţie, numit
grupul principal, main. Firul principal de execuţie face parte din acest grup. În continuare, ori de
câte ori creăm un nou fir de execuţie, acesta va face parte din acelaşi grup de fire de execuţie ca şi
firul de execuţie din interiorul căruia a fost creat, în afară de cazurile în care în constructorul firului
specificăm explicit altceva.
Într-un grup de fire de execuţie putem defini nu numai fire dar şi alte grupuri de execuţie. Se
creează astfel o arborescenţă a cărei rădăcină este grupul principal de fire de execuţie.
Pentru a specifica pentru un fir un nou grup de fire de execuţie, putem apela constructorii
obişnuiţi dar introducând un prim parametru suplimentar de tip ThreadGroup . De exemplu, putem
folosi următorul cod:
ThreadGroup tg = new ThreadGroup( "Noul grup" );
Thread t = new Thread( tg, "Firul de executie" );
Acest nou fir de execuţie va face parte dintr-un alt grup de fire decât firul principal. Putem
afla grupul de fire de execuţie din care face parte un anumit fir apelând metoda getThreadGroup , ca
în secvenţa:
Thread t = new Thread( "Firul de Executie" );
ThreadGroup tg = t.getThreadGroup();
Operaţiile definite pentru un grup de fire de execuţie sunt clasificabile în operaţii care
acţionează la nivelul grupului, cum ar fi aflarea numelui, setarea unei priorităţi maxime, etc., şi
operaţii care acţionează asupra fiecărui fir de execuţie din grup, cum ar fi stop, suspend sau resume.
Enumerarea firelor de execuţie
Pentru a enumera firele de execuţie active la un moment dat, putem folosi metoda enumerate
definită în clasa Thread precum şi în clasa ThreadGroup . Această metodă primeşte ca parametru o
referinţă către un tablou de referinţe la obiecte de tip Thread pe care îl umple cu referinţe către
fiecare fir activ în grupul specificat.
Pentru a afla câte fire active sunt în grupul respectiv la un moment dat, putem apela metoda
activeCount din clasa ThreadGroup . De exemplu:
ThreadGroup grup = Thread.currentThread().getThreadGroup();
int numarFire = grup.activeCount();
Thread fire[] = new Thread[numarFire];
grup.enumerate( fire );
for( int i = 0; i < numar; i++ ) {
System.out.println( fire[i].toString() );

Metoda enumerate întoarce numărul de fire memorate în tablou, care este identic cu numărul de fire
active.

Sincronizare
În unele situaţii se poate întâmpla ca mai multe fire de execuţie să vrea să acceseze aceeaşi
variabilă. În astfel de situaţii, se pot produce încurcături dacă în timpul unuia dintre accese un alt fir
de execuţie modifică valoarea variabilei.
Limbajul Java oferă în mod nativ suport pentru protejarea acestor variabile. Suportul este
construit de fapt cu granulaţie mai mare decât o singură variabilă, protecţia făcându-se la nivelul
obiectelor. Putem defini metode, în cadrul claselor, care sunt sincronizate.
Pe o instanţă de clasă, la un moment dat, poate lucra o singură metodă sincronizată. Dacă un
alt fir de execuţie încearcă să apeleze aceeaşi metodă pe aceeaşi instanţă sau o altă metodă a clasei
de asemenea declarată sincronizată, acest al doilea apel va trebui să aştepte înainte de execuţie
eliberarea instanţei de către cealaltă metodă.
În afară de sincronizarea metodelor, se pot sincroniza şi doar blocuri de instrucţiuni. Aceste
sincronizări se fac tot în legătură cu o anumită instanţă a unei clase. Aceste blocuri de instrucţiuni
sincronizate se pot executa doar când instanţa este liberă. Se poate întâmpla ca cele două tipuri de
sincronizări să se amestece, în sensul că obiectul poate fi blocat de un bloc de instrucţiuni şi toate
metodele sincronizate să aştepte, sau invers.
Declararea unui bloc de instrucţiuni sincronizate se face prin:
synchronize ( Instanţă ) {
Instrucţiuni
}
iar declararea unei metode sincronizate se face prin folosirea modificatorului synchronize la
implementarea metodei.

Un exemplu
Exemplul următor implementează soluţia următoarei probleme: Într-o ţară foarte îndepărtată
trăiau trei înţelepţi filozofi. Aceşti trei înţelepţi îşi pierdeau o mare parte din energie certându-se
între ei pentru a afla care este cel mai înţelept. Pentru a tranşa problema o dată pentru totdeauna, cei
trei înţelepţi au pornit la drum către un al patrulea înţelept pe care cu toţii îl recunoşteau că ar fi mai
bun decât ei.
Când au ajuns la acesta, cei trei i-au cerut să le spună care dintre ei este cel mai înţelept.
Acesta, a scos cinci pălării, trei negre şi două albe, şi li le-a arătat explicându-le că îi va lega la ochi
şi le va pune în cap câte o pălărie, cele două rămase ascunzându-le. După aceea, le va dezlega ochii,
şi fiecare dintre ei va vedea culoarea pălăriei celorlalţi dar nu şi-o va putea vedea pe a sa. Cel care
îşi va da primul seama ce culoare are propria pălărie, acela va fi cel mai înţelept.
După explicaţie, înţeleptul i-a legat la ochi, le-a pus la fiecare câte o pălărie neagră şi le-a
ascuns pe celelalte două. Problema este aceea de a descoperi care a fost raţionamentul celui care a
ghicit primul că pălăria lui este neagră.
Programul următor rezolvă problema dată în felul următor: Fiecare înţelept priveşte pălăriile
celorlalţi doi. Dacă ambele sunt albe, problema este rezolvată, a lui nu poate fi decât neagră. Dacă
vede o pălărie albă şi una neagră, atunci el va trebui să aştepte puţin să vadă ce spune cel cu pălăria
neagră. Dacă acesta nu găseşte soluţia, înseamnă că el nu vede două pălării albe, altfel ar fi găsit
imediat răspunsul. După un scurt timp de aşteptare, înţeleptul poate să fie sigur că pălăria lui este
neagră.
În fine, dacă ambele pălării pe care le vede sunt negre, va trebui să aştepte un timp ceva mai
lung pentru a vedea dacă unul dintre concurenţii săi nu ghiceşte pălăria. Dacă după scurgerea
timpului nici unul nu spune nimic, înseamnă că nici unul nu vede o pălărie albă şi una neagră.
Înseamnă că propria pălărie este neagră.
Desigur, raţionamentul pleacă de la ideea că ne putem baza pe faptul că toţi înţelepţii
gândesc şi pot rezolva probleme uşoare. Cel care câştigă a gândit doar un pic mai repede. Putem
simula viteza de gândire cu un interval aleator de aşteptare până la luarea deciziilor. În realitate,
intervalul nu este aleator ci dictat de viteza de gândire a fiecărui înţelept.
Cei trei înţelepţi sunt implementaţi identic sub formă de fire de execuţie. Nu câştigă la
fiecare rulare acelaşi din cauza caracterului aleator al implementării. Înţeleptul cel mare este firul de
execuţie principal care controlează activitatea celorlalte fire şi le serveşte cu date, culoarea
pălăriilor, doar în măsura în care aceste date trebuie să fie accesibile. Adică nu se poate cere propria
culoare de pălărie.
Culoarea iniţială a pălăriilor se poate rescrie din linia de comandă.
import java.awt.Color;
// clasa Filozof implementeaza comportamentul unui concurent
class Filozof extends Thread {
// parerea concurentului despre culoarea palariei sale.
// Null daca înca nu si-a format o parere.
Color parere = null;
Filozof( String nume ) {
super( nume );
}
public void run() {
// concurentii firului curent
Filozof concurenti[] = new Filozof[2];
// temporar
Thread fire[] = new Thread[10];
int numarFire = enumerate( fire );
for( int i = 0, j = 0; i < numarFire && j < 2; i++ ) {
if( fire[i] instanceof Filozof && fire[i] != this ) {
concurenti[j++] = (Filozof)fire[i];
}
}
while( true ) {
Color primaCuloare = Concurs.culoare( this, concurenti[0] );
Color adouaCuloare = Concurs.culoare( this, concurenti[1] );
if( primaCuloare == Color.white && adouaCuloare == Color.white ) {
synchronized( this ) {
parere = Color.black;
}
} else if( primaCuloare == Color.white ){
try{
sleep( (int)( Math.random()*500) );
} catch( InterruptedException e ){
};
if( Concurs.culoare( this, concurenti[1]) != concurenti[1].aGhicit()) {
synchronized( this ) {
parere = Color.black;
};
}
} else if( adouaCuloare == Color.white ) {
try{
sleep( (int)( Math.random()*500));
} catch( InterruptedException e ) {
};
if( Concurs.culoare(this, concurenti[0] ) != concurenti[0].aGhicit()) {
synchronized( this ) {
parere = Color.black;
};
}
} else {
try {
sleep( (int)( Math.random()*500)+500 );
} catch( InterruptedException e ) {
};
if( Concurs.culoare(this, concurenti[0]) != concurenti[0].aGhicit() &&
Concurs.culoare( this, concurenti[1] ) !=concurenti[1].aGhicit() ) {
synchronized( this ) {
parere = Color.black;
};
}
}
}
}
public synchronized Color aGhicit() {
return parere;
}
}

import java.awt.Color;
public class Concurs {
private static Color palarii[] = {
Color.black, Color.black, Color.white
};
private static Filozof filozofi[] = new Filozof[3];

public static void main( String args[] ) {


for( int i = 0; i < args.length && i < 3; i++ ) {
if( args[i].equalsIgnoreCase( "alb" ) ) {
palarii[i] = Color.white;
} else if(args[i].equalsIgnoreCase("negru")) {
palarii[i] = Color.black;
}
}
for( int i = 0; i < 3; i++ ) {
filozofi[i] = new Filozof( "Filozoful " + ( i + 1 ) );
}
for( int i = 0; i < 3; i++ ) {
filozofi[i].start();
}
System.out.println( "Concurenti:" );
for( int i = 0; i < 3; i++ ) {
System.out.println( "\t" +filozofi[i].getName() + " "+ (( palarii[i] == Color.white ) ?"alb":"negru" ) );
}
boolean gata=false;
while( !gata) {
for( int i = 0; i < 3; i++ ) {
if( filozofi[i].aGhicit()==palarii[i] ) {
System.out.println(
filozofi[i].getName() +" a ghicit." );
gata=true;
}
}
}

for( int i = 0; i < 3; i++ ) {


filozofi[i].stop();
try {
filozofi[i].join();
} catch( InterruptedException e ) {};
}
}
public static Color culoare( Filozof filozof, Filozof concurent ) {
if( filozof != concurent ) {
for( int i = 0; i < 3; i++ ) {
if( filozofi[i] == concurent ) {
return palarii[i];
}
}
}
return null;
}
}
Abstract Windowing Toolkit

Abstract Windowing Toolkit, pe scurt AWT, este un set de clase cu ajutorul cărora puteţi
crea o interfaţă grafică utilizator care să reacţioneze la datele de intrare primite de la mouse şi
tastatură.
Deoarece Java este un limbaj independent de platformă, AWT oferă o modalitate de
proiectare a unei interfeţe care să prezinte aceeaşi înfăţişare şi aceleaşi caracteristici pe orice sistem
pe care ar fi rulată.
Folosind AWT, o interfaţă este compusă din următoarele:
• Componente. Orice poate fi plasat pe o interfaţă utilizator, cum ar fi butoane, liste derulante,
meniuri pop-up, casete de validare sau câmpuri de text.
• Containere. Acestea sunt componente care pot conţine alte componente. Aţi folosit deja un
astfel de container - fereastra Applet; alte exemple ar fi panouri, casete de dialog sau ferestre
independente.
• Administratori de dispunere. Obiecte care definesc modul cum sunt aranjate (dispuse)
componentele într-un container. Administratorul de dispunere nu este vizibil într-o interfaţă,
însă sunt vizibile rezultatele „muncii" sale.
Toate clasele AWT fac parte din pachetul java.awt. Pentru a face toate aceste clase
disponibile într-un program, poate fi folosită următoarea instrucţiune de import, introdusă la
începutul codului sursă:
import java.awt.*;
Această instrucţiune are ca rezultat importarea tuturor componentelor, containerelor şi
administratorilor de dispunere pe care îi veţi folosi la proiectarea unei interfeţe. Puteţi folosi şi
instrucţiuni import individuale, numai pentru clasele care vor fi utilizate într-un program.
Clasele AWT, ca oricare alte părţi ale unei biblioteci de clase Java, sunt aranjate ierarhic.

Componentele interfetei utilizator


Componentele sunt poziţionate pe interfaţa utilizator prin adăugarea lor într-un container. Un container este el
însuşi o componentă, deci poate fi adăugat în alte containere. Veţi folosi această caracteristică atunci când veţi începe să
lucraţi cu administratori de dispunere (layout managers) pentru a aranja o interfaţă.
Cea mai uşoară modalitate de a demonstra cum se proiectează o interfaţă este folosirea
containerului cu care aţi lucrat până acum - clasa Applet.

Adăugarea componentelor într-un


container
O componentă este adăugată într-un container astfel:
• Creaţi componenta.
• Apelaţi metoda add() a containerului, pentru componenta respectivă.
Deoarece toate applet-urile sunt containere, puteţi folosi metoda add() într-un applet pentru a
adăuga o componentă direct în fereastra Applet.
Fiecare componentă AWT este o clasă, deci componenta respectivă este creată prin crearea
unui obiect al clasei respective.
Clasa Button reprezintă butoanele din cadrul unei interfeţe (suprafeţe pe care se poate executa
clic). Un buton se creează specificând eticheta sa în metoda constructorului, ca în următorul
exemplu:
Button atentie = new Button(”Atenţie!”) ;
Această instrucţiune creează un buton etichetat cu textul ”Atenţie!”.
O dată creată o componentă, cea mai simplă metodă de a o adăuga într-un container este de
a apela metoda add () a containerului, având ca argument componenta respectivă.
Deoarece un applet este un container, puteţi folosi următoarea instrucţiune pentru a adăuga
obiectul atenţie într-o fereastră Applet:
add(atentie) ;
Adăugarea unei componente nu duce imediat la afişarea acesteia. De fapt, aceasta va fi
afişată numai la apelarea metodei paint() a containerului. De acest lucru se ocupă „în culise" Java,
însă puteţi forţa un apel al metodei paint() a applet-ului folosind metoda repaint() a acestuia.
Atunci când adăugaţi o componentă într-un container, nu se specifică coordonatele x,y ale
locului unde va fi plasată aceasta. De aranjamentul componentelor se ocupă administratorul de
dispunere (layout manager) care aparţine containerului respectiv.

Etichete

Cea mai simplă componentă a unei interfeţe utilizator este eticheta, care este creată din clasa
Label. Etichetele se folosesc de obicei pentru a identifica rolul celorlalte componente aflate pe
interfaţă; acestea nu pot fi modificate direct de utilizator.
Folosirea unei etichete pentru text este preferabilă folosirii metodei drawString(), din
următoarele motive:
• Etichetele sunt desenate automat după creare şi nu trebuie să fie tratate explicit de metoda
paint().
• Etichetele vor fi aranjate corespunzător administratorului de dispunere curent şi nu la o
anumită coordonată x,y cum este cazul şirului.
Pentru a crea o etichetă folosiţi unul dintre următorii constructori:
• Label() creează o etichetă goală (vidă), cu textul aliniat la stânga.
• Label (String) creează o etichetă cu şirul de text dat, aliniat, de asemenea, la stânga.
• Label (String, int) creează o etichetă cu şirul de text dat şi alinierea indicată de argumentul
întreg. Pentru stabilirea alinierii se folosesc următoarele variabile de clasă: Label.RIGHT,
Label.LEFT, Label.CENTER.

Butoane

Butoanele (zone pe care se poate efectua clic) pot fi create folosind clasa Button. Butoanele
sunt folosite într-o interfaţă pentru a declanşa o acţiune, cum este butonul Quit (Terminare) folosit
pentru a părăsi un program.
Pentru a crea un buton, folosiţi unul dintre constructorii:
• Button() creează un buton care nu conţine nici un text pentru explicarea funcţiei sale.
• Button(String) creează un buton pe care este afişat şirul de text primit ca argument.
După crearea unui buton, puteţi să îi modificaţi eticheta folosind metoda setLabel (String) sau
puteţi afla textul scris folosind metoda getLabel()
Casete de validare

Casetele de validare (check boxes) sunt mici casete, etichetate sau nu, care pot fi validate
(„bifate") sau goale. Acestea sunt, de obicei, folosite pentru a selecta sau deselecta anumite opţiuni
într-un program, cum ar fi opţiunile Disable Sound (Dezactivare sunet) sau Password Protected
(Protecţie cu parolă) dintr-o protecţie de ecran (screen saver) Windows.
În mod normal, casetele de validare sunt neexclusive, ceea ce înseamnă că, dacă aveţi cinci
casete de validare într-un container, toate cinci pot fi validate sau nu simultan.
Aceste componente pot fi organizate şi în grupuri de validare, care mai sunt denumite şi
butoane radio (radio buttons). Ele şi-au luat numele de la vechile aparate de radio, la care apăsarea
unui buton ducea la ridicarea altuia care era apăsat până atunci.
Ambele tipuri de casete de validare sunt create folosind clasa Checkbox. Puteţi crea o casetă de
validare neexclusivă folosind unul din următorii constructori:
• Checkbox() creează o casetă de validare neetichetată, care nu este validată.
• Checkbox(String) creează o casetă de validare nevalidată şi care are ca etichetă şirul
dat.
După ce aţi creat un obiect Checkbox, puteţi folosi metoda setState (boolean) pentru a
modifica starea acestuia, astfel: valoarea true pentru a valida caseta şi valoarea false pentru a o
anula. Metoda getState() returnează o valoare Boolean care indică starea de validare a casetei.
Pentru a organiza mai multe casete de validare într-un grup, cu scopul de a nu permite decât
validarea unei singure opţiuni la un moment dat, se creează un obiect CheckboxGroup, printr-o
instrucţiune de genul:
CheckboxGroup radio = new CheckboxGroup();
Obiectul CheckboxGroup păstrează starea tuturor casetelor de validare din grupul său.
Acest obiect va fi folosit ca argument suplimentar pentru constructorul Checkbox.
Checkbox (String, GrupCaseteValidare, boolean) creează o casetă de validare etichetată cu
şirul dat de primul argument şi care aparţine grupului indicat de cel de-al doilea argument. Cel de-al
treilea argument trebuie setat pe true dacă se doreşte validarea casetei de dialog şi false în caz
contrar.

Liste de opţiuni
Listele de opţiuni (choice lists), create din clasa Choice, sunt componente care permit
alegerea unei singure opţiuni dintr-o listă derulantă (pull-down list). Veţi întâlni de multe ori acest
tip de liste atunci când completaţi un formular dintr-o pagină World Wide Web.
Primul pas în crearea unei liste de opţiuni constă în crearea obiectului Choice care va păstra
lista, ca în exemplul următor:
Choice sex = new Choice();
Elementele se adaugă într-o listă de opţiuni folosind metoda addItem(String) a obiectului.
Următoarele instrucţiuni adaugă două elemente în lista de opţiuni sex:
sex.addItem("Masculin");
sex.addItem("Feminin");
Puteţi continua să folosiţi metoda addItem() pentru a adăuga opţiuni în listă chiar şi după ce lista a
fost introdusă într-un container.
Câmpuri de text

Câmpurile de text (text fields) sunt folosite pentru a crea componente în care textul poate fi
modificat de utilizator. Aceste componente sunt create din clasa TextField.
Pentru a crea un câmp de text, folosiţi unul din următorii constructori:
• TextField() creează un câmp gol, fără o lăţime specificată.
• TextField(int) creează un câmp gol, care are o lăţime suficientă pentru a afişa numărul
specificat de caractere.
• TextField(String) creează un câmp completat cu şirul dat şi fără o lăţime specificată.
• TextField (String, int) creează câmp completat cu şirul dat şi cu lăţimea specificata de
argumentul întreg.
Atributul responsabil cu lăţimea câmpului are relevanţă doar în cazul folosirii unor
administratori de dispunere (layout managers) care nu redimensionează componentele, cum ar fi
administratorul FlowLayout.
Următoarea instrucţiune creează un câmp de text gol, care oferă suficient spaţiu pentru 30 de
caractere:
TextField nume = new TextField(30) ;
Următoarea instrucţiune poate fi folosită dacă doriţi să iniţializaţi câmpul cu şirul de text
"Ion I. Ionescu":
TextField nume = new TextField("Ion I. Ionescu", 30);
Puteţi crea şi un câmp de text care să ascundă caracterele tastate, afişând în locul lor un
caracter oarecare. Această facilitate se foloseşte de obicei în câmpurile de introducere a parolelor,
pentru a ascunde parola de priviri indiscrete.
Pentru a defini un astfel de caracter de mascare, în Java 1.02 se foloseşte metoda
setEchoCharacter(char), iar în versiunile următoare, metoda setEchoChar(char). Dacă se
foloseşte un literal, acesta trebuie încadrat între ghilimele simple, ca de exemplu '*'. Java
interpretează orice literal încadrat de ghilimele duble ca fiind un obiect de tip String (şir).

Zone de text
Zonele de text (text areas), create din clasa textArea, sunt câmpuri de text modificabile
care pot conţine mai multe linii de text. Zonele de text posedă bare de defilare orizontale şi
verticale, care permit utilizatorului să parcurgă întreg textul conţinut în componentă.
Pentru a crea o zonă de text puteţi folosi unul din următorii constructori:
• TextArea() creează o zonă de text goală, cu înălţime şi lăţime nespecificate.
• TextArea (int, int) creează o zonă goală, care conţine numărul de rânduri dat de primul
argument şi are lăţimea în caractere dată de al doilea argument.
• TextArea (String) creează o zonă de text care conţine şirul specificat şi are lăţimea şi
înălţimea nespecificate.
• TextArea(String, int, int) creează o zonă de text care conţine şirul specificat, numărul de
rânduri fiind dat de primul argument, iar lăţimea în caractere de al doilea argument.

Liste de derulare
Listele de derulare (scrolling lists), create din clasa List, sunt asemănătoare listelor de
opţiuni, cu două diferenţe semnificative:
• Lista de derulare poate fi configurată aşa încât să poată fi selectate mai multe opţiuni la un
moment dat.
• Listele de derulare se prezintă asemănător unei zone de text în care sunt afişate mai multe
opţiuni. Dacă lista conţine mai multe opţiuni decât pot fi afişate, se foloseşte o bară de
derulare pentru a se parcurge întreaga listă.
O listă de derulare este definită prin crearea unui obiect List şi adăugarea în listă a unor
elemente. Clasa List posedă următorii constructori:
• List() creează o listă de parcurgere vidă care permite selectarea unui singur element la un
moment dat.
• List(int, boolean) creează o listă de derulare care posedă numărul de elemente vizibile
indicat de primul argument (număr care poate fi mai mic decât numărul total de elemente).
Argumentul boolean indică dacă pot fi selectate mai multe elemente (true) sau nu (false).
După crearea unui obiect List se foloseşte metoda addItem(String) pentru a adăuga
elemente în listă. (Nota: Începând cu Java 2, metoda nu se mai recomandă şi a fost înlocuită de
metoda add(String)).

Bare de derulare şi glisoare


Barele de derulare (scrollbars) sunt componente care permit selectarea unei valori prin
deplasarea unei casete între două săgeţi. Există mai multe componente care au înglobate bare de
derulare, cum ar fi zonele de text sau listele de derulare. Clasa Scrollbar este folosită pentru alte
tipuri de bare de derulare. O bară de derulare poate fi orizontală sau verticală.
Barele de derulare sunt folosite în mod normal pentru specificarea valorilor minime şi maxime
care pot fi stabilite prin utilizarea componentei.
Pentru a crea o bară de derulare puteţi folosi următorii constructori:
• Scrollbar() creează o bară de derulare verticală care are valorile maximă şi minimă setate
iniţial la 0.
• Scrollbar(int) creează o bară de derulare care are valorile maximă şi minimă setate la 0, iar
orientarea este dată de valoarea întreagă. Argumentul poate lua următoarele valori, care sunt
variabile de clasă: Scrollbar.HORIZONTAL şi Scrollbar.VERTICAL.
Puteţi folosi, de asemenea, un al treilea constructor, care primeşte cinci argumente:
Scrollbar ( int, int, int, int, int). Argumentele acestei metode sunt, în ordine, următoarele:
• Orientarea, care este Scrollbar.HORIZONTAL sau Scrollbar.VERTICAL.
• Valoarea iniţială a barei de derulare, care trebuie să se afle între valorile minimă şi maximă
ale barei sau să fie egală cu una dintre acestea.
• Lăţimea sau înălţimea generală a casetei folosite pentru modificarea valorii barei de
derulare. Aceasta poate fi egală cu 0 atunci când se foloseşte dimensiunea prestabilită.
• Valoarea minimă a barei de derulare.
• Valoarea maximă a barei de derulare.

Suprafeţe de desenare
Suprafeţele de desenare (canvas) sunt componente folosite, în principal, drept loc de afişare
pentru imagini sau animaţie. Puteţi desena şi pe alte componente, însă obiectele Canvas sunt cele
mai simplu de folosit în acest scop.
Pentru a folosi o suprafaţă de desenare trebuie să creaţi o subclasă a clasei Canvas. Această
subclasă se poate ocupa de toate operaţiunile de desenare care trebuie să aibă loc, în metoda sa
paint().
O dată creată o subclasă Canvas, aceasta poate fi folosită în program prin apelarea
constructorului său şi prin adăugarea noului obiect Canvas într-un container.

Aranjarea componentelor într-o interfaţă utilizator


Puteţi dispune componente pe o interfaţă, însă nu prea veţi avea controlul asupra locului
unde vor fi amplasate.
Pentru a impune o anumită formă interfeţei proiectate cu ajutorul Abstract Windowing
Toolkit trebuie să folosiţi un set de clase denumite administratori de dispunere (layout managers).

Dispunerea componentelor interfeţei

Un administrator de dispunere determină modul cum vor fi aranjate (dispuse) componentele


adăugate într-un container.
Administratorul de dispunere implicit este clasa FlowLayout. Această componentă permite
dispunerea secvenţială a obiectelor, de la stânga la dreapta, în ordinea în care acestea sunt adăugate
în container. Atunci când nu mai este loc pe un rând, celelalte obiecte se dispun în continuare pe
rândul următor, continuând secvenţa de la stânga la dreapta.
Biblioteca AWT conţine cinci administratori de dispunere principali: FlowLayout,
GridLayout, BorderLayout, CardLayout şi GridBagLayout. Pentru a crea un administrator de
dispunere pentru un container, se creează o instanţă a clasei container folosind o instrucţiune de
genul:
FlowLayout flo = new FlowLayout();
După ce aţi creat un administrator de dispunere, îl veţi declara drept administrator de
dispunere pentru container folosind metoda setLayout() a acestuia. Administratorul de dispunere
trebuie stabilit înainte de a adăuga componentele în container. Dacă nu este stabilit nici un
administrator de dispunere, se foloseşte implicit dispunerea în secvenţă (flow layout).
Următoarele instrucţiuni reprezintă punctul de începere pentru un applet în care se creează
un administrator de dispunere şi se foloseşte metoda setLayout() pentru a controla dispunerea
tuturor componentelor ce vor fi adăugate în fereastra Applet:
public class Starter extends java.applet.Applet {
FlowLayout ad = new FlowLayout();
public void init() {
setLayout(ad) ;
}
}
După stabilirea administratorului de dispunere curent, puteţi începe să adăugaţi
componentele în containerul pe care acesta îl controlează. Pentru unii dintre administratorii de
dispunere, cum ar fi FlowLayout, ordinea în care se adaugă componentele este importantă.
Dispunerea secvenţială

FlowLayout este cea mai simplă clasă de administrare a dispunerii. Ea aranjează


componentele într-o manieră asemănătoare dispunerii cuvintelor într-o pagină - de la stânga la
dreapta până la capătul rândului, apoi în continuare pe rândul următor.
În mod prestabilit, dacă folosiţi constructorul FlowLayout() fără argumente, componentele
de pe fiecare rând vor fi centrate. Dacă doriţi ca acestea să fie aliniate la marginea din stânga sau
din dreapta a containerului, trebuie folosite drept argument pentru constructor variabilele de clasă
FlowLayout.LEFT sau FlowLayout.RIGHT:
FlowLayout dreapta = new FlowLayout(FlowLayout.RIGHT);
Variabila de clasă FlowLayout.CENTER este folosită pentru dispunerea pe centru a
componentelor.

Dispunerea tabelară
Administratori de dispunere tabelară (grid) aranjează componentele într-un caroiaj (tabel)
format din rânduri şi coloane.
Componentele sunt adăugate începând cu celula aflată cel mai în stânga pe primul rând al
tabelului şi continuând spre dreapta. După completarea tuturor celulelor de pe primul rând se
continua cu cel de-al doilea rând, de la stânga la dreapta, şi aşa mai departe.
Dispunerea tabelară este stabilită pornind de la clasa GridLayout. Constructorul
GridLayout primeşte două argumente - numărul de rânduri şi numărul de coloane din tabel.
Următoarea instrucţiune creează un administrator de dispunere tabelară pentru 10 rânduri şi 3
coloane:
GridLayout gr = new GridLayout(10,3);
Ca şi în cazul dispunerii secvenţiale, dacă se folosesc două argumente suplimentare, puteţi
specifica spaţiul pe orizontală şi pe verticală care trebuie lăsat între componente. Următoarea
instrucţiune creează un tabel cu 10 rânduri, 3 coloane, un spaţiu pe orizontală de 5 pixeli şi un
spaţiu pe verticală de 8 pixeli:
GridLayout gr2 = new GridLayout(10, 3, 5, 8);
Spaţiul între componentele dispunerii tabelare este implicit 0 pixeli, atât pe orizontală cât si pe
verticală.

Dispunerea marginală

Dispunerile marginale (border layouts), create pornind de la clasa BorderLayout, împart un


container în cinci secţiuni: nord, sud, est, vest şi centru.
Folosind dispunerea marginală, componentele din cele patru puncte cardinale vor ocupa
spaţiul de care au nevoie, iar centrul obţine spaţiul rămas disponibil. De obicei, acest aranjament are
ca rezultat o componentă centrală de dimensiuni mari, înconjurată de patru componente mai
„subţiri".
Dispunerea marginală se obţine folosind unul dintre constructorii Borderlayout() sau
BorderLayout(int, int). Primul constructor creează o dispunere marginală fără nici un spaţiu între
componente. Al doilea constructor specifică spaţiul pe orizontală, respectiv pe verticală.
După ce aţi creat o dispunere marginală şi aţi stabilit-o drept administrator curent de
dispunere pentru un container, componentele sunt adăugate folosind o altă apelare a metodei add()
decât cele folosite anterior:
add(String, componenta)
Al doilea argument al acestei metode este componenta care trebuie adăugată în container.
Primul argument este un şir care indică zona unde va fi plasată componenta. Există cinci valori
posibile: "North" (nord), "South" (sud), "East" (est), "West" (vest) şi "Center" Centru).
Următoarea instrucţiune adaugă un buton, denumit butonTenninare, în porţiunea nordică:
add( "North" , butonTerminare) ;

Combinarea administratorilor de dispunere

Pentru a găsi dispunerea potrivită, de cele mai multe ori trebuie folositi mai multi
administratori pentru aceeaşi interfaţă. Acest lucru se poate face adăugând mai multe containere
într-un container principal, fiecare dispunând de propriul administrator de dispunere.
Aceste containere se numesc panouri (panels) şi sunt derivate din clasa Panel. Panourile sunt
containere folosite pentru a grupa diferite componente. Când lucraţi cu panouri trebuie să ţine cont
de următoarele:
Panoul se umple cu componente înainte de a fi introdus într-un container mai mare;
Panoul posedă propriul administrator de dispunere.
Crearea unui panou:
Panel panou=new Panel();
Panoului i se atribuie o metodă de dispunere prin apelarea metodei setLayout(), ca în exemplul:
BorderLayout b1=new BorderLayout();
panou.setLayout(b1);
Componentele se adaugă apoi folosind metoda add().
Dispuneri complexe

Dispunerea în stivă

O dispunere în stivă (card layout) diferă de celelalte deoarece ascunde vederii unele
componente. O dispunere în stivă este un grup de containere sau de componente afişate câte unul o
dată. Fiecare container din grup este denumit card (cartelă).
În mod normal, dispunerea în stivă foloseşte un panou pentru fiecare cartelă. Mai întâi se introduc
componente în panouri, după care acestea se introduc în containerul pe care s-a stabilit o dispunere
în stivă.
O dispunere în stivă se creează cu ajutorul clasei CardLayout, apelând constructorul acesteia:
CardLayout cc=new CardLayout();
Pentru a stabili administratorul de dispunere pentru un container se foloseşte metoda
setLayout(). După ce aţi stabilit un container se foloseşte metoda add(String, container). Al doilea
argument specifică containerul sau componenta care reprezintă cartela. Dacă este vorba de un
container acesta trebuie să conţină deja toate componentele necesare. Primul argument este un şir
care reprezintă numele cartelei. Acesta poate fi oricare, ca în exemplul:
add(„Cartela de optiuni”, optiuni)
prin care se adaugă în container un panou numit optiuni şi i se atribuie numele „Cartela de optiuni”.
După ce s-a adăugat cartela în containerul principal al programului, se poate folosi metoda show() a
administratorului de dispunere în stivă, metodă ce primeşte două argumente:
• Containerul în care au fost adăugate cartelele. Dacă containerul este chiar fereastra
principală se poate folosi this.
• Numele cartelei
Următorul exemplu apelează metoda show() a administratorului de dispunere în stivă denumir cc:
cc.show(this,”Cartela de date”)
La afişarea unei cartele, cartela afişată anterior este ascunsă.

Dispunerea tabelară neproporţională

O astfel de dispunere diferă de una tabelară simplă prin următoarele:


• O componentă poate ocupa mai multe celule ale tabelului
• Proporţiile dintre diferitele rânduri şi coloane ale tabelului nu trebuie să fie aceleaşi
• Componentele dispuse în cadrul celulelor pot fi aranjate în diverse moduri
Pentru a crea un astfel de mod de dispunere se folosesc clasele GridBagLayout şi o clasă externă
GridBagConstraints. Prima este administratorul de dispunere iar a doua este folosită pentru a
defini restricţiile fiecărei componente.
Se parcurg etapele:
1. crearea unui obiect GridBagLayout şi definirea sa drept administrator curent
2. crearea unei noi instanţe a clasei GridBagConstraints
3. definirea restricţiilor pentru o componentă
4. anunţarea componentei şi a restricţiilor sale în administratorul de dispunere
5. introducerea componentei în container

Exemplu:
GridBagLayout tabel=new GridBagLayout();
GridBagConstraints restrictii = new GridBagConstraints();
setLayout(tabel);
Button b=new Button(„Salvare”);
restrictii.gridx=0;
restrictii.gridy=0;
restrictii.gridwidth=1;
restrictii.gridheight=1;
restrictii.weightx=30;
restrictii.weighty=30;
restrictii.fill= GridBagConstraints.NONE;
restrictii.anchor= GridBagConstraints.CENTER;
tabel.setConstraints(b,restrictii);
add(b);
Etapele care trebuie parcurse la dispunerea tabelara neproporţională

Etapa 1. Proiectarea tabelului


Initial se face o proiectare pe hârtie a tabelului, etichetând celulele tabelului după
coordonatele lor x şi y. Astfel, celula din colţul de stânga sus are coordonatele 0,0, cea alăturată are
coordonatele 0,1 ş.a.m.d.
Etapa a doua. Crearea tabelului
O variantă ar fi să se creeze o metodă ajutătoare, care preia mai multe valori şi stabileşte
restricţiile pentru acestea. Asftel metoda definireRestrictii() preia şapte argumente: un obiect
GridBagConstraints şi şase valori întregi, care reprezintă variabilele de instanţă gridx, gridy,
gridwidth, gridheight, weightx şi weighty. Metoda definireRestrictii este următoarea:
void definireRestrictii(GridBagConstraints gbc, int gx, int gy, int gw, int
gh, int wx, int wy) {
gbc.gridx=gx;
gbc.gridy=gy;
gbc.gridwidth=gw;
gbc.gridheight=gh;
gbc.weightx=wx;
gbc.weighty=wy;
}
Se creează apoi metoda init() , în care se creează de fapt dispunerea.
public void init()
{GridBagLayout tabel=new GridBagLayout();
GridBagConstraints restrictii =new GridBagConstraints();
SetLayout(tabel);
restrictii.fill=GridBagConstraints.BOTH;
}
Se vor defini apoi restrictii corespunzătoare fiecărei componente din interfaţă, ca în
exemplul următor:
definireRestrictii(restrictii, 0,0,1,1,100,100);
Button eticheta1=new Button(„nume”);
tabel.setConstraints(eticheta1,restrictii);
add(eticheta1);
Aceste linii vor stabili restricţiile pentru un obiect, vor crea un buton, vor asocia acestuia restricţiile
şi îl vor adăuga în panou.
Primele două argumente reprezintă valorile gridx şi gridy ale restricţiilor. Acestea
reprezintă chiar coordonatele celulei ce conţine componenta. Dacă o componentă ocupă mai multe
celule se vor introduce coordonatele componentei cea mai din stânga-sus. Următoarele argumente
reprezintă gridwidth şi gridheight. Ele nu reprezintă laţimea şi înălţimea în pixeli, ci reprezintă
numărul de celule pe care le ocupă componenta. Ultimele două argumente sunt proporţiile weightx
şi weighty, folosite pentru a stabili proporţiile rândurilor şi a coloanelor, adică modul de distribuire
al spaţiului suplimentar care rămâne neutilizat după amplasarea componentei. După stabilirea
proporţiilor ele pot fi atribuite unui obiect folosind metoda GridBagConstraints().
Etapa a treia: Determinarea proporţiilor
La determinarea proporţiilor se ţine cont de obiectele care se extind pe mai multe celule.
Acestea trebuie să primească valoarea 0 în direcţia în care se extind.
Etapa a patra. Adăugarea şi aranjarea componentelor
În această etapă se stabilesc restricţiile care arajează componentele în cadrul celulei. Există
două astfel de restricţii: fill (umplere) şi anchor (ancorare).
Restricţia fill determină – pentru componentele care se pot extinde în orice direcţie – în ce direcţie
se face această extindere şi poate avea una dintre următoarele valori:
GridBagConstraints.BOTH, care extinde componenta pentru a umple celula în ambele direcţii
GridBagConstraints.NONE, care determină componenta să fie afişată la cea mai mică dimensiune
a sa
GridBagConstraints.HORIZONTAL, care extinde componenta pe direcţie orizontală
GridBagConstraints.VERTICAL, care extinde componenta pe direcţie verticală.
Implicit restricţia fill este NONE.
A doua restricţie care afectează modul în care apare o componentă într-o celulă este anchor
(ancorare) şi ea se aplică doar componentelor care nu umplu întreaga celulă, indicând funcţiilor
AWT unde să plaseze componenta în cadrul celulei. Valori posibile:
GridBagConstraints.NORTH, GridBagConstraints.SOUTH, GridBagConstraints.NORTHEAST,
GridBagConstraints.SOUTHWEST, GridBagConstraints.EAST, GridBagConstraints.WEST,
GridBagConstraints.SOUTHEAST, GridBagConstraintsNORTHWEST, GridBagConstraints.CENTER.
Valoarea implicită este GridBagConstraints.CENTER.

Tratarea evenimentelor

Unul dintre lucrurile pe care le-aţi învăţat atunci când aţi creat pentru prima dată un applet
este că, atunci când acesta rulează, în fundal se desfăşoară o mulţime de activităţi. Sistemul de
ferestre al Java apelează automat metode cum ar fi paint(), init() sau start(), atunci când este nevoie,
fără nici o intervenţie din partea dumneavoastră.
Ca şi programarea applet-urilor, tratarea evenimentelor presupune apelarea automată a unor
metode atunci când acţiunea unui utilizator duce la apariţia unui eveniment.

Tipuri de evenimente

Un eveniment este generat ca răspuns la aproape orice acţiune pe care o poate întreprinde un
utilizator pe parcursul ciclului de viaţă al unui program Java. O mişcare a mouse-ului, un clic
executat pe un buton, o apăsare de tastă, toate generează un eveniment.
În programele dumneavoastră nu trebuie să trataţi toate evenimentele ce por apărea. De fapt,
vă veţi ocupa de acele evenimente la care vreţi să răspundă programul dumneavoastră; restul vor fi
ignorate. De exemplu, dacă utilizatorul execută un clic cu mouse-ul sau apasă o tastă, puteţi face ca
programul să răspundă la aceste evenimente.
Iată câteva dintre evenimentele care pot fi tratate în programele dumneavoastră:
• Clicuri cu mouse-ul. Apăsarea butonului mouse-ului (mouse down), eliberarea acestuia
(mouse up) sau apăsarea şi eliberarea butonului pe aceeaşi poziţie (mouse clic).
• Mişcări ale mouse-ului. Cursorul mouse-ului care părăseşte sau pătrunde pe suprafaţa
ocupată de o componentă a interfeţei sau tragerea cu mouse-ul (drag - mişcările efectuate de
cursor atunci când butonul mouse-ului este apăsat).
• Apăsări de taste. Apăsarea tastei, eliberarea tastei, tastarea (apăsarea şi eliberarea unei
taste).
• Evenimente ale interfeţei utilizator. Execuţia de clicuri pe butoane, derularea listelor,
afişarea meniurilor derulante şi aşa mai departe.

Metoda handleEvent ()

Tratarea evenimentelor reprezintă domeniul în care au apărut cele mai multe modificări în
trecerea de la Java 1.02 la Java 2. Evenimentele sunt generate şi executate aproximativ în acelaşi
mod, indiferent de versiunea de limbaj pe care o folosiţi; diferenţa constă în modul cum acestea sunt
recepţionate şi procesate.
În Java 1.02, toate evenimentele care apar în ciclul de viaţă al unui program sunt tratate de o
metodă denumită handleEvent(). Aceasta este definită în clasa Component, care este moştenită de
java.applet.Applet, devenind astfel disponibilă tuturor applet-urilor.
Atunci când un eveniment este transmis metodei handleEvent (), aceasta apelează apoi o
metodă specifică de tratare a evenimentului, în funcţie de tipul acestuia. Exemple de astfel de
metode specifice sunt mouseDown(), mouseUp() sau keyDown().
Pentru a trata un eveniment în programele dumneavoastră, va trebui să suprascrieţi una
dintre aceste metode specifice. Apoi, la apariţia evenimentului respectiv, metoda respectivă va fi
apelată. De exemplu, puteţi suprascrie metoda mouseDown() pentru a afişa un mesaj în fereastra
Applet. Atunci când apare un eveniment de acest tip (apăsarea butonului mouse-ului), mesajul va fi
afişat.

Tratarea clicurilor de mouse

Unul dintre cele mai des întâlnite evenimente care v-ar putea interesa este efectuarea unui
clic cu mouse-ul. Evenimentele de acest tip apar atunci când utilizatorul execută un clic cu mouse-
ul undeva, pe interfaţa programului.
Puteţi intercepta clicurile mouse-ului pentru diferite lucruri simple - de exemplu, pentru a
activa sau dezactiva sunetul într-un applet, pentru a trece la următoarea planşă a unei prezentări sau
pentru a şterge ecranul. De asemenea, clicurile cu mouse-ul pot fi folosite împreună cu deplasările
acestuia pentru a realiza o formă de interacţiune mai complexă cu utilizatorul.

Evenimentele mouse down şi mouse up

Atunci când utilizatorul execută un clic cu mouse-ul, se generează două evenimente: un


eveniment mouse down, atunci când se apasă butonul mouse-ului, şi un eveniment mouse up, atunci
când acesta este eliberat. Această divizare permite executarea unor acţiuni diferite în cele două
etape ale clicului.
Tratarea evenimentelor mouse-ului în applet-ul dumneavoastră este simplă; se suprascrie
metoda corespunzătoare, care va fi apelată la apariţia respectivului eveniment. Iată un exemplu de
semnătură de metodă pentru un eveniment mouse down:
public boolean mouseDown(Event evt, int x, int y) {
//. . .
}
Metoda mouseDown() (ca şi metoda mouseUp()) primeşte trei parametri: evenimentul în
sine şi coordonatele x şi y unde a apărut evenimentul respectiv.
Argumentul evt reprezintă o instanţă a clasei Event. Toate evenimentele generează o
instanţă a clasei Event, care conţine informaţii privind locul şi perioada când a avut loc
evenimentul, tipul lui, precum şi alte informaţii. Uneori, existenţa unui astfel de pointer către un
obiect Event este folositoare, aşa cum veţi descoperi mai târziu în această secţiune.
Coordonatele x şi y ale evenimentului, aşa cum au fost ele transmise prin argumentele x şi y
ale metodei mouseDown(), vă permit să determinaţi cu precizie locul unde s-a efectuat , clicul. Deci
de exemplu, dacă evenimentul mouse down a apărut în zona unui buton grafic, puteţi activa acel
buton. Reţineţi că puteţi obţine coordonatele x şi y şi din cadrul obiectului Event; în această metodă,
ele au fost transmise ca argumente separate pentru a se lucra mai uşor cu ele.

Tratarea mişcărilor mouse-ului

Ori de câte ori se deplasează mouse-ul, se generează un eveniment. Deplasarea mouse-ului


de la un capăt la altul ale ferestrei applet-ului poate avea ca rezultat generarea a zeci de evenimente.
AWT defineşte două tipuri distincte de mişcări ale mouse-ului:
"tragerea" mouse-ului (mouse drag), în care mişcările se fac ţinând butonul mouse-ului apăsat, şi
mişcări simple (mouse move), în care butonul nu este apăsat.
În plus, ori de câte ori mouse-ul intră pe sau iese de pe suprafaţa applet-ului sau a oricărei
componente sau container al acestuia, se generează evenimente mouse enter, respectiv mouse exit.
Pentru fiecare dintre aceste evenimente există metode speciale care le interceptează, aşa cum
metodele mouseDown() şi mouseUp() interceptau clicurile mouse-ului.

Evenimentele mouse drag şi mouse move

Pentru interceptarea şi tratarea evenimentelor de deplasare a mouse-ului se folosesc


metodele mouseDrag() şi mouseMove().
Metoda mouseMove(), care tratează mişcările simple de mouse, adică cele când nu este ţinut
apăsat butonul mouse-ului, seamănă foarte mult cu metodele care tratau clicurile de mouse:
public boolean mouseMove(Event evt, int x, int y) {
// ...
}
Metoda mouseDrag() tratează mişcările mouse-ului executate având butonul apăsat (o
mişcare completă de „tragere" - drag - constă dintr-un eveniment mouse down, o serie de
evenimente mouse drag pentru fiecare pixel pe care se deplasează mouse-ul, urmată de un
eveniment mouse up, la eliberarea butonului. Metoda mouseDrag() arată astfel:
public boolean mouseDrag(Event evt, int x, int y) {
// ...
}
De reţinut că în cadrul ambelor metode, mouseMove() şi mouseDrag(), argumentele pentru
coordonatele x şi y reprezintă noua poziţie a mouse-ului, şi nu locul de pornire.
Evenimentele mouse enter şi mouse exit

Metodele mouseEnter() şi mouseExit() sunt apelate când indicatorul mouse-ului pătrunde


sau părăseşte un applet sau o porţiune a acestuia. Atât mouseEnter(), cât şi mouseExit() posedă
semnături similare cu metodele pentru tratarea clicurilor. Acestea primesc trei argumente: obiectul
Event şi coordonatele x şi y ale punctului unde mouse-ul a pătruns în applet sau l-a părăsit.
Următoarele exemple prezintă semnăturile metodelor mouseEnter() şi mouseExit():

public boolean mouseEnter(Event evt, int x, int y) {


// . . .
}
public boolean mouseExit(Event evt, int x, int y) {
// ...
}

Tratarea evenimentelor de tastatură

Un eveniment de tastatură este generat ori de câte ori utilizatorul apasă o tastă. Prin folosirea
acestor evenimente puteţi memora valorile tastelor apăsate de utilizator pentru a executa o anumită
acţiune sau pentru a obţine date de intrare de la utilizatorii applet-ului.
Pentru ca evenimentul de tastatură sa fie recepţionat de o componentă, aceasta trebuie să fie
selectată (focused - „în centrul atenţiei"; cu alte cuvinte, să fie acea componentă a interfeţei
selectată pentru a primi datele de intrare). Veţi învăţa mai târziu despre această selectare a
componentelor, atunci când veţi lucra cu evenimente de selectare (focus events). Selectarea este
mai uşor de înţeles dacă luaţi exemplul unei interfeţe care conţine mai multe câmpuri de text.
Cursorul pâlpâie în câmpul de text selectat, iar utilizatorul poate introduce text în acel câmp
folosind tastatura. Nici un alt câmp nu poate primi date de la tastatură până când nu este selectat.
Toate componentele, inclusiv containerele, pot fi configurate pentru a fi selectate.
Pentru a indica explicit că o componentă este selectată pentru a primi date de intrare (input
focus), poate fi apelată metoda requestFocus() a componentei, fără argumente. Următoarea
instrucţiune selectează un obiect Button, denumit terminare:
terminare.requestFocus() ;
Puteţi selecta fereastra Applet apelând metoda requestFocus() a acesteia.

Evenimentele key down şi key up

Pentru a trata un eveniment de tastatură poate fi folosită metoda keyDown():


public boolean keyDown(Event evt, int tasta) {
// ...
}
Valorile generate de evenimentele key down - tastă apăsată - (şi transmise metodei
keyDown() drept argumentul tasta) sunt întregi care reprezintă valori Unicode, cum ar fi caractere
alfanumerice, taste funcţionale, tabulatori, tasta return (retur de car) şi aşa mai departe. Pentru a le
folosi drept caractere (de exemplu, pentru a le tipări) trebuie să le convertiţi în caractere, astfel:
caracterCrt = (char)tasta;
Iată un exemplu simplu de metodă keyDown(), care nu face altceva decât să afişeze tasta pe
care aţi apăsat-o, atât ca valoare Unicode, cât şi drept caracter :
public boolean keyDown(Event evt, int tasta) {
System.out.println("Valoare UNICODE: " + tasta) ;
System.out.println("Caracter: " + (char) tasta) ;
return true;
}
Ca şi în cazul clicurilor de mouse, fiecare eveniment key down are şi corespondentul key up
– tastă ridicată. Pentru a intercepta evenimentele key up se foloseşte metoda keyUp():
public boolean keyUp(Event evt, int tasta) {
// ...
}
Taste prestabilite

Clasa Event oferă un set de variabile de clasă care reprezintă câteva taste standard ne-
alfanumerice, cum ar fi tastele funcţionale sau tastele direcţionale (cu săgeţi). Dacă interfaţa applet-
ului dumneavoastră foloseşte aceste taste, puteţi crea un cod mai lizibil prin testarea acestora în
metoda keyDown(), în loc să testaţi valorile numerice echivalente (de asemenea, dacă folosiţi aceste
variabile, codul dumneavoastră va funcţiona cu mai mare probabilitate pe alte platforme). De
exemplu, când doriţi să testaţi dacă a fost apăsată tasta direcţională cu săgeata în sus (up arrow),
puteţi folosi următoarea secvenţă de cod:
if (tasta = Event.UP) {
// ...
}
Deoarece valorile acestor variabile de clasă sunt întregi, puteţi folosi pentru testarea lor
instrucţiunea switch.
În tabelul următor se prezintă variabilele de clasă Event pentru diferite taste, precum şi semnificaţia
acestora.

Tastele standard definite în clasa Event.

Variabila de clasă Tasta reprezentată .

Event.HOME Tasta Home


Event.END Tasta End
Event.PGUP Tasta Page Up
Event.PGDN Tasta Page Down
Event.UP Tasta Up
Event.DOWN Tasta Down

Event.LEFT TastaLeft

Event.RIGHT Tasta Right


Event.F1 Tasta F1
Event.F2 Tasta F2
Event.P3 Tasta F3
Event.F4 Tasta F4
Event.F5 TastaF5
Event.F6 Tasta F6
Event.F7 Tasta F7
Event.F8 Tasta F8
Event.F9 Tasta F9
Event.F10 Tasta F10
Event.F11 TastaF11
Event.F12 TastaF12

Tratarea evenimentelor componentelor


Tehnicile de tratare a evenimentelor pe care le-aţi învăţat până acum sau axat pe
interacţiunea cu utilizatorul - execuţia unui clic cu mouse-ul, apăsarea unor taste şi altele de acest
gen. Există şi altfel de evenimente, care au loc pe componente cum ar fi butoane, zone de text sau
alte elemente de interfaţă. De exemplu, butoanele folosesc evenimente declanşate la apăsarea lor.
Nu trebuie să vă intereseze evenimentul mouse down sau mouse up sau locul unde a apărut
interacţiunea respectivă; componenta se va ocupa de toate aceste evenimente în locul
dumneavoastră.
Următoarele evenimente pot fi generate prin interacţiunea cu componentele interfeţei:
• Evenimente de acţiune (action events). Sunt principalele evenimente ale majorităţii
componentelor interfeţei, prin care se indică faptul că acestea au fost "activate".
Evenimentele de acţiune sunt generate atunci când este apăsat un buton, când se selectează
sau se deselectează o casetă de validare sau un buton radio, când se alege o opţiune dintr-un
meniu sau când utilizatorul apasă tasta Return sau Enter într-un câmp de text.
• Evenimente de selectare sau deselectare a listelor (list select/deselect events). Aceste
evenimente sunt generate atunci când se selectează o casetă de validare sau o opţiune dintr-
un meniu (listă). Aceste exemple declanşează, de asemenea un eveniment de acţiune.
• Evenimente de primire sau cedare a selecţiei (got/lost focus events). Aceste evenimente pot
fi generate de orice componentă, fie ca răspuns la un clic de mouse, fie la selecţionarea prin
tasta Tab. Primirea selecţiei (got focus) înseamnă că acea componentă este selecţionată şi
poate fi activată sau poate primi date de intrare. Cedarea selecţiei (lost focus) înseamnă că s-
a selecţionat o altă componentă.

Tratarea evenimentelor de acţiune

Un eveniment de acţiune reprezintă cel mai des întâlnit eveniment al unei interfeţe şi, din
acest motiv, există o metodă specială pentru a-l trata, la fel cum există metode pentru acţiunile de
mouse sau de tastatură.
Pentru a intercepta un eveniment de acţiune generat de o componentă se defineşte în applet o
metodă denumită action(),cu următoarea semnătură:
public boolean action(Event evt, Object arg) {
// ...
}
Această metodă action() ar trebui să arate asemănător cu metodele de tratare a evenimentelor
de mouse sau de tastatură. Ca şi acele metode, aceasta primeşte ca argument un obiect care referă
evenimentul respectiv. Ea mai primeşte, de asemenea, un obiect suplimentar (în acest caz,
parametrul arg), care poate fi de orice tip.
Tipul acestui al doilea argument al metodei de acţiune depinde de componenta interfeţei care
a generat acţiunea. O caracteristică de bază ar fi că este vorba de "orice fel de argument",
determinat de componenta însăşi, care poate transmite informaţiile suplimentare necesare pentru a
procesa acţiunea respectivă. Tabelul următor prezintă argumentele suplimentare pentru fiecare tip
de componentă.

Componente Tip de argument Ce conţine

Butoane String Eticheta butonului


Casete de validare Boolean Întotdeauna true
Butoane radio Boolean Întordeauna true
Meniuri de opţiuni String Eticheta elementului selectat
Câmpuri de text String Textul din cadrul câmpului

În cadrul metodei action(), primul lucru care trebuie făcut este să testaţi care componentă a
generat acţiunea (spre deosebire de evenimentele de mouse sau de tastatură, în care aceasta nu
conta, deoarece toate componentele puteau genera acţiuni). Din fericire, obiectul Event pe care îl
primiţi ca argument în apelul metodei action() conţine o variabilă de instanţă denumită target (ţintă),
care conţine o referinţă către obiectul ce a generat evenimentul. Puteţi folosi operatorul instanceof
pentru a afla ce componentă a generat evenimentul, ca în următorul exemplu:
public boolean action(Event evt, Object arg) {
if (evt.target instanceof textField)
return handleText(evt.target);
else if (evt.target instanceof Choice);
return handleChoice(arg);
// ...
return false;
}
În acest exemplu, metoda action() putea fi generată fie de o componentă TextField (câmp de
text), fie de un meniu cu opţiuni; instrucţiunea if determină care dintre acestea două a generat
evenimentul şi apelează o altă metodă (aici, handleText() sau handlechoice()), care tratează, de fapt,
evenimentul respectiv. (Nici handleText() şi nici handleChoice() nu sunt metode AWT; sunt doar
exemple de nume ce pot fi folosite drept metode ajutătoare. Se obişnuieşte să se creeze astfel de
metode ajutătoare - helper - aşa încât metoda action() să nu devină prea încărcată cu cod.)
Ca şi în cazul altor metode de tratare a evenimentelor, action() returnează o valoare Boolean.
La fel, trebuie să returneze true dacă va trata evenimentul respectiv şi false dacă acesta va fi ignorat
sau tratat în altă parte. În acest exemplu aţi transferat controlul metodelor handleText() sau
handleChoice(), care vor returna true sau false, aşa ca puteţi returna false (reţineţi, veţi returna true
numai daca metoda procesează evenimentul).
Pot apărea complicaţii dacă aveţi mai multe componente aparţinând toate aceleiaşi clase - de
exemplu, o mulţime de butoane. Toate generează acţiuni şi toate sunt instanţe ale clasei Button. Aici
intervine argumentul suplimentar: folosind câteva comparaţii simple de şiruri, puteţi folosi
etichetele, elementele sau conţinutul componentei pentru a determina care dintre ele a generat
evenimentul. (Nu uitaţi să convertiţi prin cast argumentul în obiectul corespunzător.)
public boolean action(Event evt, Object arg) {
if (evt.target instanceof Button) {
String eticheta = (String)arg;
if (eticheta.equals("OK"))
// trateaza butonul OK
else if (eticheta.equals ("Anulare"))
// trateaza butonul Anulare
else if (eticheta.equals("Navigare"))
// trateaza butonul Navigare

// ...
}
}
Tratarea evenimentelor de selectare

Aşa cum am arătat mai devreme, evenimentele de acţiune sunt de departe cele mai des
întâlnite evenimente pe care le veţi trata într-o interfaţă. Totuşi, mai există şi alte elemente pe care
le puteţi folosi în programele dumneavoastră: list select (selecţie listă), list deselect (deselecţie
listă), got focus (primire selecţie) şi lost focus (cedare selecţie). Pentru evenimentele got focus şi
lost focus se pot folosi metodele gotFocus() şi lostFocus(), care se folosesc la fel ca metoda
action(). Iată semnăturile lor:
public boolean gotFocus(Event evt, Object arg) {
// ...
}
public boolean lostFocus(Event evt, Object arg) {
// ...
}
Pentru evenimentele de selectare şi deselectare de liste nu sunt disponibile metode uşor de
suprascris. Pentr a trata aceste evenimente trebuie folosită metoda handleEvent(), ca în exemplul
următor:
public boolean handleEvent(Event evt) {
if (evt.id == Event.LIST_SELECT)
handleSelect(Event);
else if (evt.id == Event.LIST_DESELECT)
handleDeselect(Event) ;
else return super.handleEvent(evt);
}

În acest extras de cod, Event.LIST_SELECT şi Event.LIST_DESELECT reprezintă


identificatorii oficiali ai evenimentelor de selectare/deselectare liste, iar controlul este transferat
către două metode ajutătoare ( handleSelect() şi handleDeselect () ), care se presupune că sunt
definite în altă parte. Remarcaţi, de asemenea, apelul către super.handleEvent() din final; acesta
permite celorlalte evenimente să fie transferate fără probleme câtre metoda handleEvent() originală.

Evenimente ale zonelor de text

Zonele de text prezintă aceleaşi evenimente ca şi câmpurile de text. Puteţi folosi metodele
gotFocus() şi lostFocus() pentru a intercepta evenimentele de primire/cedare a selecţei:
public boolean gotFocus(Event evt, Object arg) {
// . ..
}
public. boolean lostFocus(Event evt, Object arg) {
// ...
}

Evenimente ale listelor derulante


Listele derulante generează trei tipuri de evenimente diferite; selectarea sau deselectarea
unui element individual al listei are ca efect un eveniment de selectare/deselectare listă, iar dublul
clic executat pe un element al listei are ca efect un eveniment de acţiune.
Puteţi suprascrie metoda action() pentru a trata elementul din listă pe care s-a efectuat dublu-
clic. Pentru selecţiile şi deselecţiile din listă, puteţi să suprascrieţi metoda handleEvent() şi să testaţi
identificatorii de eveniment LIST_SELECT şi LIST_DESELECT.

Evenimente ale barelor de derulare

Numai pentru diferitele tipuri de mişcări ale acestora este generată o întreagă gamă de
evenimente. Pentru toate acestea va trebui să folosiţi metoda handleEvent(). Tabelul următor
prezintă identificatorii de evenimente care vă interesează şi mişcările care declanşează aceste
evenimente.

Evenimente ale barelor de derulare

Ce reprezintă
Identificatorul evenimentului

SCROLL_ABSOLUTE Generat la deplasarea casetei barei de derulare


SCROLL_LINE_DOWN Generat la selectarea butonului din capătul de
jos sau din stânga al barei de derulare
SCROLL_LINE_UP Generat la selectarea butonului din capătul de
sus sau din dreapta al barei de derulare
SCROLL_PAGE_DOWN Generat la selectarea spaţiului aflat dedesubtul
(sau la stânga) casetei barei de derulare
SCROLL_PAGE_UP Generat la selectarea spaţiului aflat deasupra
(sau la dreapta) casetei barei de derulare
Dezvoltarea de interfeţe utilizator avansate folosind AWT
Ferestre, cadre şi casete de dialog

În afară de ceea ce vi s-a prezentat până acum, AWT mai oferă posibilitatea creării
de elemente de interfaţă în afara suprafeţei applet-ului sau a ferestrei browserului,
elemente cum ar fi ferestre, cadre sau casete de dialog. Aceste facilităţi vă permit să creaţi
aplicaţii complete, ca parte a applet-ului sau ca aplicaţii Java independente.

Clasele Window
Clasele AWT care produc ferestre sau casete de dialog moştenesc o clasă comună, Window. Clasa Window
moşteneşte clasa Container, ca şi componentele panou şi applet, şi oferă un comportament generic pentru toate
elementele derivate din ferestre.
De obicei nu se folosesc instanţe ale clasei Window, ci ale celor două subclase principale
ale sale: Frame şi Dialog.
Clasa Frame (cadru) oferă o fereastră care conţine o bară de titlu, butoane de
închidere şi alte elemente specifice unei ferestre pentru un anumit sistem. De asemenea,
cadrele permit adăugarea unor bare de meniuri. Clasa Dialog este o variantă mai limitată
a clasei Frame, care, de obicei, nu are un titlu. FileDialog, o subclasă a clasei Dialog,
oferă o casetă de dialog standard pentru alegerea unui fişier (de obicei, aceasta poate fi
folosită doar în cadrul aplicaţiilor Java, deoarece applet-urile impun restricţii de securitate).
Pentru a adăuga o fereastră sau o casetă de dialog în applet-ul sau aplicaţia dumneavoastră
trebuie să creaţi subclase ale claselor Frame sau Dialog.

Cadre
Cadrele (frames) sunt ferestre independente de fereastra applet-ului sau a browserului care
conţine applet-ul; ele sunt ferestre separate, care conţin titluri proprii, butoane de redimensionare
sau de închidere şi bare de meniuri. Puteţi crea cadre, în applet-uri, pentru a produce ferestre sau le
puteţi folosi în aplicaţii pentru a păstra conţinutul acestora.
Un cadru reprezintă o fereastră specifică unei anumite platforme, care conţine un titlu, o
bară de meniuri, butoane de închidere şi de redimensionare şi alte elemente specifice unei ferestre.
Pentru a crea un cadru se foloseşte unul din următorii constructori:
• new Frame () creează un cadru simplu, fără titlu.
• new Frame (String) creează un cadru simplu, cu titlul dat de şirul primit ca argument.
Deoarece cadrele moştenesc clasa Window, care moşteneşte clasa Container, care
moşteneşte clasa Component, cadrele sunt create şi folosite cam în acelaşi fel cu celelalte
componente AWT. Cadrele sunt containere, ca şi panourile, deci puteţi adăuga în ele diferite
componente folosind metoda add (), ca şi în panouri (panels). Dispunerea prestabilită pentru cadre
este BorderLayout. Iată un exemplu care creează un cadru, stabileşte dispunerea componentelor în
acesta şi adaugă două butoane:
win = new Frame("Fereastra mea") ;
win.setLayout(new BorderLayout(10, 20));
win.add("North", new Button("Start")) ;
win.add("Center", new Button("Mutare"));
Pentru a stabili dimensiunea noului cadru se foloseşte metoda resize (), care primeşte ca
argumente lăţimea şi înălţimea acesteia. De exemplu, această linie de cod redimensionează fereastra
la dimensiunile de 100 pixeli lăţime şi 200 pixeli înălţime:
win.resize(100, 200);
Deoarece în sisteme diferite se calculează în mod diferit reprezentările pixelilor şi există
rezoluţii diferite, este dificilă crearea unui cadru de dimensiuni „bune" pentru oricare platformă.
Ferestrele care sunt potrivite într-un sistem pot fi prea mici sau prea mari într-un altul.
O metodă de evitare a acestei probleme este folosirea metodei pack() în locul metodei
resize(). Metoda pack(), care nu primeşte argumente, creează o fereastră de cea mai mică
dimensiune posibilă, date fiind dimensiunile curente ale componentelor pe care le conţine,
administratorul de dispunere şi inserţiile folosite. Următorul exemplu creează două butoane şi le
adaugă într-o fereastră. După aceasta, fereastra este adusă la cele mai mici dimensiuni posibile când
conţine aceste două butoane.
FlowLayout flo = new FlowLayout() ;
Button ok = new Button("OK");
Button anulare = new Button("Anulare");
win.setLayout(flo);
win.add(ok) ;
win.add(anulare);
win.pack() ;
Atunci când creaţi o fereastră, aceasta este invizibilă. Pentru a fi afişată pe ecran trebuie să
folosiţi metoda show(). Pentru a o ascunde din nou puteţi folosi metoda hide(). Exemplu:
win.show() ;
Reţineţi că atunci când o fereastră este afişată de un applet, browserul vă atrage atenţia că
aceasta nu este o fereastră a sa, de obicei, printr-un mesaj în cadrul ferestrei.

Casete de dialog

Casetele de dialog, din punct de vedere funcţional, sunt asemănătoare cadrelor prin faptul că afişează ferestre
pe ecran. Totuşi, ele sunt destinate pentru a fi folosite drept ferestre temporare – adică pentru a vă prezenta mesaje de
avertizare, pentru a vă cere anumite informaţii şi aşa mai departe.
Casetele de dialog nu au, de obicei, bare de titlu sau alte elemente caracteristice unei ferestre
(cu toate că puteţi crea şi casete de dialog cu bare de titlu). Ele pot fi definite aşa încât să nu fie
redimensionabile sau pot fi definite drept modale. (Casetele de dialog modale împiedică accesarea
altor ferestre până în momentul când se renunţă la ele.)
Casetele de dialog sunt ferestre temporare având rolul de a alerta utilizatorul cu privire la un anumit eveniment
sau de a prelua date de la utilizator. Spre deosebire de cadre, casetele de dialog nu posedă, de obicei, bare de titlu sau
butoane de închidere.
O casetă de dialog modală nu permite introducerea de date în alte ferestre până când nu este
închisă. Nu veţi putea aduce în prim plan o altă fereastră şi nici minimiza caseta de dialog modală;
aceasta trebuie închisă explicit pentru a se putea continua lucrul în sistem. De obicei, avertismentele
sunt casete de dialog modale.
AWT oferă două tipuri de casete de dialog: clasa Dialog, care produce o casetă de dialog
generică, şi FileDialog, care produce o casetă de dialog pentru căutarea de fişiere, specifică
platformei respective.

Obiecte dialog

Casetele de dialog se creează şi se folosesc asemănător ferestrelor. Pentru crearea unei casete de dialog
generice se foloseşte unul din următorii constructori:
• Dialog (Frame, boolean) creează o casetă de dialog invizibilă, ataşată cadrului curent, fie
modală (true), fie nemodală (false).
• Dialog(Frame, String, boolean) creează o casetă de dialog invizibilă, cu titlul dat de
argumentul şir, ataşată cadrului curent, modală (true) sau nu (false).
Fereastra de dialog, ca şi cadrul de fereastră (frame), este un panou pe care se pot dispune şi
desena componente de interfaţă sau pe care se pot realiza operaţii grafice, la fel cum s-ar proceda cu
oricare alt panou. Ca şi alte ferestre, şi cea de dialog este iniţial invizibilă, însă o puteţi afişa sau
ascunde folosind metodele show() , respectiv hide().

Ataşarea casetelor de dialog la applet-uri


Casetele de dialog pot fi ataşate numai cadrelor (frames). Pentru a crea o casetă de dialog, trebuie să transmiteţi
o instanţă a clasei Frame uneia dintre metodele constructor ale casetei de dialog. Acest lucru înseamnă că nu puteţi crea
casete de dialog ataşate applet-urilor. Deoarece applet-urile nu sunt cadre, nu le puteţi transmite ca argument unei clase
Dialog. Totuşi, cu puţină îndemânare, puteţi face rost de un obiect cadru care conţine un applet (adesea, chiar browserul
sau fereastra de vizualizare a applet-ului), după care să folosiţi acest obiect drept cadru pentru caseta de dialog.
Codul care face acest lucru foloseşte metoda getParent(), definită pentru toate
componentele AWT. Metoda getParent() returnează obiectul care conţine obiectul curent. Deci
părintele tuturor aplicaţiilor AWT trebuie să fie un cadru. Applet-urile se comportă în acelaşi mod.
Prin apelarea în mod repetat a metodei getParent() veţi ajunge, până la urmă, să obţineţi o instanţă
a clasei Frame. lată codul pe care trebuie să îl introduceţi în applet-ul dumneavoastră:
Object ancora = getParent();
while (! (ancora instanceof Frame))
ancora = ((Component)ancora).getParent();
În prima linie a codului se creează o variabilă locală, denumită ancora, care va memora în final
cadrul acestui applet. Obiectul atribuit variabilei poate fi de mai multe tipuri, aşa încât va fi
declarat de tip Object.
Următoarele două linii de cod definesc un ciclu while care apelează metoda getParent() în
lanţ, pentru fiecare obiect, până când ajunge la un obiect de tip Frame. Se observă aici că, deoarece
metoda getParent() este definită doar pentru obiectele care moştenesc clasa Component, trebuie să
convertiţi prin cast valoarea ancora la tipul Component de fiecare dată când apelaţi metoda
getParent().
După încheierea ciclului, obiectul conţinut de variabila ancora va fi o instanţă a clasei Frame
(sau a uneia dintre subclasele acesteia). Puteţi crea un obiect Dialog ataşat acestui cadru convertind
prin cast variabila ancora, pentru a vă asigura că obţineţi un obiect Frame:
TextDialog dl = new TextDialog((Frame)ancora, "Enter Text",true);
Casete de dialog pentru sistemul de fişiere
Clasa FileDialog permite crearea unei casete de dialog de tip File Open/Save (pentru deschiderea sau salvarea
fişierelor), care vă permite să accesaţi sistemul local de fişiere. Clasa FileDialog este independentă de sistem, însă în
funcţie de platformă este folosită caseta de dialog Open File (Deschidere fişier) sau Save File (Salvare fişier).
Folosirea de instanţe ale clasei FileDialog în applet-uri depinde de browser. Din cauza
restricţiilor de securitate existente în applet-uri, majoritatea browserelor produc o excepţie de
securitate atunci când încercaţi acest lucru. Obiectele FileDialog sunt mai folositoare în aplicaţiile
independente.
Pentru a crea o casetă de dialog pentru fişiere, folosiţi unul din următorii constructori:
• FileDialog(Frame, String) creează o casetă de dialog ataşată unui cadru dat şi cu un titlu
dat, pentru încărcarea unui fişier.
• FileDialog(Frame, String, int) creează tot o casetă de dialog, însă argumentul întreg este
folosit pentru a stabili dacă este vorba despre o casetă de dialog pentru încărcarea sau pentru
salvarea unui fişier. (Singura diferenţă este eticheta butoanelor; de fapt, această casetă de
dialog nu deschide sau nu salvează nimic.) Opţiunile pentru acest argument sunt
FileDialog.LOAD sau FileDialog.SAVE.
După crearea unei instanţe a clasei FileDialog se foloseşte metoda show() pentru a o afişa:
FileDialog fd = new FileDialog(this, "FileDialog=”) ;
fd.show() ;
Atunci când utilizatorul alege un fişier şi închide caseta de dialog, el poate accesa numele fişierului
ales folosind metodele getDirectory() şi getFile(). Ambele metode returnează un şir care conţine
valorile alese de utilizator. După aceasta, puteţi deschide fişierul folosind metodele de lucru cu
fişiere şi fluxuri (streams) de date, apoi puteţi citi sau scrie din/în acest fişier.

Evenimente de fereastră
Aţi ajuns acum la ultimul tip de evenimente pe care le puteţi trata în AWT: evenimentele pentru ferestre şi
casete de dialog. (în ceea ce priveşte evenimentele, caseta de dialog este tot un tip de fereastră.) Evenimentele de
fereastră apar atunci când starea unei ferestre se modifică: de exemplu, când este mutată, redimensionată, minimizată,
restabilită, mutată în prim-plan sau închisă. într-o aplicaţie îngrijit programată, aceste evenimente trebuie tratate - de
exemplu, pentru a opri firele aflate în execuţie atunci când o fereastră este minimizată sau pentru a „face curăţenie" la
închiderea ferestrei.
Puteţi folosi metoda handleEvent() pentru a testa evenimentele prezentate în tabelul
următor folosind instrucţiunea switch pentru variabila de instanţă id (identificator).
Evenimente de fereastră.
Numele evenimentului Când apare
WINDOW_DESTROY Generat atunci când o fereastră este închisă folosind butonul Close sau
opţiunea de meniu Close
WINDOW_EXPOSE Generat atunci când fereastra este adusă în prim-plan din spatele altor ferestre

WINDOW_ICONIFY Generat atunci când fereastra este minimizată (redusă la dimensiunea unei
pictograme)
WINDOW_DEICONIFY Generat atunci când fereastra este restabilită (când se revine de la starea de
pictogramă)
WINDOW_MOVED Generat atunci când fereastra este mutată
Meniuri

O bară de meniuri (menu bar) este o colecţie de meniuri. Un meniu, în schimb, conţine o
colecţie de opţiuni, care pot avea denumiri şi, opţional, prescurtări (shortcuts). Biblioteca AWT
oferă clase pentru toate aceste elemente de meniu, cum ar fi MenuBar, Menu şi MenuItem.

Bare de meniuri şi meniuri

O bară de meniuri (menu bar) reprezintă un set de meniuri care apare în partea de sus a unei
ferestre. Deoarece ele sunt caracteristice ferestrelor, nu puteţi crea bare de meniuri în ferestre applet
(dar, dacă applet-ul afişează o fereastră independentă, aceasta poate conţine o bară de meniuri).
Pentru a crea o bară de meniuri pentru o anumită fereastră veţi crea o instanţă a clasei
MenuBar:
MenuBar mbar = new Menubar();
Acum veţi folosi metoda setMenuBar() (definită în clasa Frame) pentru a asocia această
bară de meniuri cu fereastra:
fereastra.setMenuBar(mbar);
Puteţi adăuga meniuri individuale (File, Edit şi aşa mai departe) în bara de meniuri prin
crearea lor, urmată de adăugarea în bara de meniuri prin metoda add(). Argumentul pentru
constructorul Menu este numele meniului, aşa cum va apărea el în bara de meniuri.
Menu meniulMeu = new Menu("File") ;
mbar.add(meniulMeu) ;
Unele sisteme oferă un meniu Help special, care este afişat în partea din dreapta a barei de meniuri, nu în
partea stângă. Puteţi indica faptul că acest meniu este un meniu Help (de ajutor) prin folosirea metodei setHelpMenu().
Meniul respectiv trebuie adăugat în bara de meniuri înainte de a fi declarat meniu de ajutor.
Menu meniuHelp = new Menu("Help") ;
mbar.add(meniuHelp) ;
mbar.setHelpMenu(meniuHelp) ;
Dacă, pentru un motiv sau altul, doriţi ca un utilizator să nu aibă acces la un meniu, acesta
poate fi dezactivat folosind metoda disable() (şi metoda enable() pentru a-l reactiva):
meniulMeu.disable();

Elemente de meniu
În meniurile individuale puteţi adăuga patru tipuri de elemente (opţiuni):
• Instanţe ale clasei MenuItem, pentru elemente de meniu obişnuite
• Instanţe ale clasei CheckBoxMenuItem, pentru elemente de meniu care au stările
activat/dezactivat
• Alte meniuri, care conţin propriile elemente de meniu
• Separatori, pentru liniile care separă grupuri de elemente de meniu
Crearea elementelor de meniu
Elementele (opţiunile) de meniu obişnuite sunt create şi adăugate într-un meniu folosind
clasa MenuItem. Mai întâi se creează o nouă instanţă a clasei MenuItem, după care se adaugă o
componentă Menu folosind metoda add():
Menu meniulMeu = new Menu("Utilitare");
MenuItem m1= new MenuItem("Info")
meniulMeu.add(m1) ;
meniulMeu.add(new MenuItem("Culori")) ;
Submeniurile pot fi adăugate prin simpla creare a unei noi instanţe a clasei Menu şi
adăugarea în primul meniu. După aceasta, puteţi adăuga elemente în acest meniu:
Menu submeniu = new Menu("Dimensiuni") ;
meniulMeu.add(submeniu) ;
submeniu.add(new Menultem("Mic")) ;
submeniu.add(new Menultem("Mediu")) ;
submeniu.add(new Menultemf"Mare"));
Clasa CheckBoxMenuItem creează un element de meniu care posedă o casetă de validare, permiţând ca
aceasta să fie activată sau dezactivată. (Prin selectarea elementului, acesta devine activat - în dreptul său apare un semn
de validare; o nouă selectare a elementului duce la dispariţia acestui semn şi, implicit, la dezactivarea sa). Puteţi crea şi
adăuga un element de meniu de acest tip în acelaşi fel ca elementele de meniu obişnuite:
CheckBoxMenuItem coord = new CheckBoxMenuItem("Afisare coordonate") ;
meniulMeu.add(coord) ;
Pentru a adăuga un separator într-un meniu (o linie care separă grupurile de opţiuni dintr-un meniu), creaţi şi
adăugaţi un element de meniu care să conţină ca nume o liniuţă (-). Acest element special de meniu va fi reprezentat ca
o linie de separare. Următoarele două linii de cod Java creează un astfel de separator şi îl adaugă în meniul meniulMeu:
MenuItem sep = new MenuItem("-");
meniulMeu.add(sep) ;
Orice element de meniu poate fi dezactivat folosind metoda disable(), după care poate fi
reactivat folosind metoda enable().
Elementele de meniu dezactivate nu pot fi selectate.
Menultem element = new MenuItem("Umplere") ;
meniulMeu.add(element) ;
element.disable() ;

Evenimente de meniu
Acţiunea de selectare a unui meniu cu ajutorul mouse-ului sau a comenzii rapide asociate
generează un eveniment. Puteţi trata acest eveniment folosind metoda action(), aşa cum aţi procedat
şi în aplicatiile anterioare.
În afară de evenimente de acţiune, elementele de tip checkBoxMenuItem generează
evenimente de selectare şi deselectare a listelor, care pot fi tratate cu ajutorul metodei
handleEvent().
Atunci când procesaţi evenimentele generate de elementele de meniu simple sau cele cu
casete de validare, ţineţi cont de faptul că deoarece CheckBoxMenuItem este o subclasă a
MenuItem, nu trebuie să trataţi acel element de meniu drept un caz special. Veţi trata acţiunea sa în
acelaşi mod ca pe alte acţiuni.
Crearea unor aplicaţii AWT independente

Nu există diferenţe prea mari între un applet Java şi o aplicaţie grafică Java. Tot ce aţi învăţat până acum
despre AWT, cum ar fi metodele grafice, tehnicile de animaţie, evenimente, componente ale interfeţei, ferestre şi casete
de dialog, poate fi folosit în aplicaţiile Java în acelaşi fel în care a fost folosit în applet-uri.
Pentru crearea unei aplicaţii Java clasa aplicaţiei principale trebuie să moştenească clasa
Frame. Dacă foloseşte fire de execuţie (pentru animaţie sau alte procesări), trebuie să
implementeze, de asemenea, clasa Runnable:
class AplicatieAWT extends Frame implements Runnable {
//...
}
Pentru aplicaţia dumneavoastră veţi crea în metoda main() o nouă instanţă a clasei; deoarece
clasa moşteneşte clasa Frame, veţi obţine o nouă fereastră AWT, pe care apoi o puteţi
redimensiona şi afişa ca pe orice fereastră AWT.
Caracteristicile AWT normale pentru o fereastră, care se declarau de obicei în cadrul
metodei init() a unui applet, se realizează aici în cadrul metodei constructor a clasei: se stabileşte
titlul, se declară un administrator de dispunere, se creează şi adaugă componente cum ar fi bara de
meniuri sau alte elemente de interfaţă, se lansează fire de execuţie şi aşa mai departe.
Un exemplu de aplicaţie simplă:

import java.awt.* ;
class AplicatieAWT extends Frame {
AplicatieAWT(String titlu) {
super(titlu) ;
setLayout(new FlowLayout()) ;
add(new Button("OK") ;
add (new Button("Reset");
add( new Button("Cancel”) ;
}
public static void main(String args[ ]) {
AplicatieAWT apl = new AplicatieAWT("Prima aplicatie") ;
apl.resize(300, 300);
apl.show() ;
}
}
Pentru controlul şi administrarea unei aplicaţii puteţi folosi aproape oricare dintre metodele
învăţate. Singurele metode care nu pot fi folosite sunt cele specifice applet-urilor (adică cele
definite în java.applet.Applet, cum ar fi cele pentru apelarea informaţiilor de la diferite adrese
URL).
Este bine de cunoscut încă o diferenţă între aplicaţii şi applet-uri: atunci când trataţi un
eveniment de închidere a unei ferestre, în afară de ascunderea sau distrugerea ferestrei trebuie să
mai apelaţi şi metoda System.exit(0), care indică sistemului că aplicaţia dumneavoastră s-a
terminat.
public void închidereFereastra(WindowEvent e) {
fr.hide() ;
fr.destroy() ;
System.exit(0) ;
}

Rolul claselor: pachete, interfeţe şi alte caracteristici


Cu cunoştinţele dobândite până acum se pot scrie programe funcţionale, însă le vor lipsi
câteva dintre caracteristicile avansate în care stă adevărata tărie a acestui limbaj. Vor fi prezentate
următoarele subiecte:
• Controlul accesului la metode şi variabile din afara unei clase
• Finalizarea claselor, metodelor şi variabilelor astfel încât să nu permită derivarea de
subclase, iar valorile sau definiţiile lor să nu poată fi suprascrise
• Crearea de clase şi de metode abstracte pentru gruparea comportamentului comun în
superclase
• Gruparea claselor în pachete
• Folosirea interfeţelor pentru a umple golurile din ierarhia de clase

Modificatori

Tehnicile de programare prezentate în acest capitol folosesc strategii şi modalităţi de abordare


diferite privind organizarea claselor. Singurul lucru pe care îl au în comun este faptul că folosesc
cuvinte cheie speciale, denumite în Java modificatori (modifiers).
Limbajul Java foloseşte mai multe tipuri de modificatori, printre care:
• Modificatori pentru controlul accesului la o clasă, metodă sau variabilă: public,
protected şi private
• Modificatorul static, pentru crearea metodelor şi variabilelor de clasă
• Modificatorul final, pentru finalizarea implementărilor de clase, metode şi variabile
• Modificatorul abstract, pentru crearea de clase şi metode abstracte
• Modificatorii synchronized şi volatile, folosiţi pentru fire de execuţie.
Pentru a folosi un modificator, veţi include cuvântul cheie care îl reprezintă în definiţia clasei,
metodei sau variabilei ce urmează a fi modificată. Modificatorul trebuie dispus înaintea definiţiei
propriu-zise, ca în următoarele exemple:
public class AppletulMeu extends java.applet.Applet { ... }
private boolean oK;
static final double x =9.5;
protected static final int ZILE=42;
public static void main (String argumente [] ) { …}
Dacă folosiţi mai mulţi modificatori într-o instrucţiune, îi puteţi folosi în orice ordine, atât
timp cât aceştia preced elementul pe care îl modifică. Atenţie, nu consideraţi tipul de retur al
metodei - cum ar fi void - drept modificator.
Modificatorii sunt opţionali, însă există destule motive pentru a-i folosi.

Controlul accesului la metode şi variabile


Modificatorii pe care îi veţi folosi cel mai adesea în programele dumneavoastră sunt cei care
controlează accesul la metode şi variabile: public, private şi protected. Aceştia determină ce
variabile şi metode sunt vizibile în alte clase.
Prin controlul accesului puteţi stabili modul cum va fi accesată clasa pe care aţi creat-o din
alte clase. Unele variabile şi metode ale clasei sunt folositoare doar în interiorul acesteia, deci ar
trebui să fie invizibile pentru alte clase care pot interacţiona cu clasa nou creată. Acest proces se
numeşte încapsulare (encapsulation): un obiect poate controla modul cum este văzut şi cum se poate
interacţiona cu el din exterior.

Încapsularea este procesul de prevenire a citirii sau a modificării unor variabile aparţinând
unei clase de către alte clase. Singura metoda de folosire a acestor variabile este prin apelarea
metodelor clasei, dacă acestea sunt disponibile. Limbajul Java oferă patru niveluri de control al
accesului: public, private, protected şi un nivel special, care este specificat prin absenţa
modificatorului.

Accesul prestabilit

Pentru majoritatea exemplelor de până acum nu s-a specificat nici un tip de control al
accesului. Variabilele sau metodele au fost declarate prin instrucţiuni de genul:
String nume = "Popescu Ion";
boolean este ( ) {
return true;
}
O variabilă sau o metodă declarată fără nici un modificator al accesului este disponibilă
tuturor celorlalte clase dintr-un acelaşi pachet. Mai înainte aţi putut vedea cum clasele şi bibliotecile
Java sunt organizate în pachete (packages). Pachetul java. awt este unul dintre ele - un set de clase
cu comportament asemănător, care alcătuiesc Abstract Windowing Toolkit.
Orice variabilă declarată fără modificator poate fi citită sau modificată de orice clasă care
face parte din acelaşi pachet. Orice metodă declarată astfel poate fi apelată de orice clasă din acelaşi
pachet. Alte clase nu pot accesa aceste elemente în nici un mod.
Acest nivel de control de fapt nu controlează prea mult accesul. Atunci când veţi analiza mai
bine modul cum vor fi folosite clasele dumneavoastră de alte clase, veţi folosi în locul controlului
prestabilit al accesului unul dintre cei trei modificatori specializaţi.

Accesul privat

Pentru a ascunde complet o metodă sau o variabilă, în scopul de a nu fi folosită de nici o altă
clasă, se foloseşte modificatorul private. In acest fel, metodele sau variabilele nu pot fi accesate
decât din clasa curentă, unde sunt definite.
De exemplu, o variabilă de instanţă privată poate fi utilizată de metodele din aceeaşi clasă,
însă nu şi de obiectele din alte clase. în acelaşi fel, metodele private pot fi accesate din clasa unde
sunt definite, însă nu şi din altele. Această restricţie afectează şi moştenirea: nici variabilele private
şi nici metodele private nu pot fi moştenite de subclase.
Variabilele private sunt foarte folositoare în două cazuri:
• Atunci când alte clase nu au nici un motiv să folosească variabila respectivă
• Atunci când altă clasa poate produce dezastre dacă variabila respectivă este folosită
necorespunzător.
De exemplu, să considerăm o clasă Java, denumită Bingo, care generează numere de bingo
pentru un site Internet de jocuri. Această clasă conţine o variabilă denumită rataCastiguri, prin care
se poare controla numărul de câştiguri şi de pierderi generate. Această variabilă are un impact
deosebit asupra jocului. Dacă ea ar putea fi modificată de alte clase, performanţa jocului s-ar putea
modifica substanţial. Pentru a vă proteja împotriva unui astfel de scenariu, puteţi declara variabila
rataCastiguri drept private.

Folosirea modificatorului private reprezintă principala metodă pentru încapsularea unui


obiect. Fără utilizarea acestuia pentru a ascunde variabile şi metode, nu puteţi limita modul în care
este folosită o clasă; accesul este liber la toate variabilele şi metodele din cadrul clasei.
Accesul public

În unele cazuri veţi avea nevoie ca o metodă sau o variabilă a clasei să fie disponibilă complet
oricărei alte clase care doreşte să o folosească. O astfel de variabilă este variabila de clasă black a
clasei Color. Această variabilă este folosită atunci când o clasă doreşte să folosească culoarea
neagră, deci nu trebuie să existe nici o restricţie de acces.
De obicei, variabilele de clasă sunt declarate publice.
Modificatorul public face ca o metodă sau o variabilă să fie complet disponibilă unei alte clase. Aţi
folosit acest modificator în toate aplicaţiile pe care le-aţi scris până acum, ca în instrucţiunea
următoare:
public static void main(String[ ] argumente) {
// ...
}
Metoda main ( ) a unei aplicaţii trebuie să fie publică. Altfel, ea nu poate fi apelată de
interpretorul java pentru a lansa în execuţie clasa.
Datorită moştenirii, toate variabilele şi metodele publice ale unei clase sunt preluate şi de
subclasele sale.

Accesul protejat

Al treilea nivel de control al accesului este limitarea vizibilităţii unei metode sau variabile doar
pentru următoarele două grupuri:
• Subclasele unei clase
• Alte clase din acelaşi pachet
Puteţi realiza acest lucru folosind modificatorul protected, ca în următorul exemplu:
protected boolean avemNevoieDeMaiMultaOdihna = true;
Acest nivel de control al accesului este folositor dacă doriţi implementarea unei subclase. Clasa
dumneavoastră poate conţine o metodă sau o variabilă care să ajute şi la realizarea sarcinilor
subclasei.
Deoarece o subclasă moşteneşte cea mai mare parte a comportamentului şi atributelor, ea s-
ar putea să aibă cam aceleaşi sarcini de realizat.
Accesul protejat oferă subclasei şansa de a apela metoda sau variabila ajutătoare şi, în acelaşi timp,
nu permite folosirea acestora de alte clase cu care nu are relaţii de „rudenie".

Compararea nivelurilor de control al accesului

Diferenţa dintre diferitele tipuri de protecţii poate deveni puţin neclară, mai ales în cazul
metodelor şi variabilelor protejate. Tabelul următor, care prezintă în rezumat ceea ce este permis şi
unde, vă ajută să vă daţi seama de diferenţele existente între cea mai puţin restrictivă formă de
protecţie (public) şi cea mai restrictivă (private).

Vizibilitate public protected default private


Din aceeaşi clasă da da da da
Din orice clasă care aparţine aceluiaşi
da da da nu
pachet
Din orice clasa din afara pachetului da nu nu nu
Dintr-o subclasă a aceluiaşi pachet da da da nu
Dintr-o subclasă din afara pachetului da da nu nu
Controlul accesului şi moştenirea

O ultimă consideraţie în ceea ce priveşte controlul accesului se referă la subclase. Atunci când
creaţi o subclasă şi suprascrieţi o metodă, trebuie să luaţi în considerare controlul accesului definit
pentru metoda originală.
Astfel, metodele clasei Applet, cum ar fi init ( ) sau paint ( ), trebuie declarate public în applet-
urile dumneavoastră.
Ca regulă generală, nu puteţi să suprascrieţi o metodă în Java şi să faceţi noua metoda mai
controlabilă decât originalul. Totuşi, puteţi să o faceţi mai puţin controlabilă (mai publică).
În cazul metodelor moştenite se impun următoarele reguli:
• Metodele public dintr-o superclasă trebuie să fie, de asemenea, public în toate subclasele
(din acest motiv, majoritatea metodelor applet-urilor sunt public).
• Metodele protected dintr-o superclasă pot fi protected sau public în subclase; ele nu pot fi
declarate private.
• Metodele declarate fară control al accesului (deci fără modificator) pot fi declarate în
subclase cu un grad de protecţie mai ridicat.
Metodele declarate private nu sunt moştenite deloc, deci nu li se aplică regulile de mai sus.

Metode de accesare

În multe cazuri, într-o clasă puteţi avea o variabilă de instanţă cu reguli stricte pentru
valorile pe care le conţine. Un exemplu ar fi o variabilă codPostal. Un cod poştal din România
trebuie să fie un număr din patru cifre; valorile valide sunt în domeniul 1000... 9999; orice alte
valori în afara acestui domeniu nu pot reprezenta coduri poştale.
Pentru a evita ca o clasa externa să modifice incorect variabila codPostal, o puteţi declara privat,
printr-o instrucţiune de genul:
private int codPostal;
Dar dacă şi alte clase trebuie să fie în măsură să modifice această variabilă? În acest caz,
puteţi să permiteţi accesul acestora la variabila respectivă prin folosirea unei metode de accesare
(accessor) definită în aceeaşi clasă ca şi variabila codPostal.
Metodele de accesare au primit acest nume deoarece permit accesul la ceva care în mod
normal nu este accesibil. Prin folosirea unei metode de acces la o variabilă private puteţi controla
modul cum este folosită această variabilă, în exemplul cu codul poştal, clasa poate interzice
atribuirea unor valori incorecte pentru variabila codPostal.
De multe ori, pentru citirea şi scrierea variabilelor se folosesc metode de accesare diferite.
Se foloseşte convenţia ca denumirea metodelor care citesc valoarea unei variabile să înceapă cu get,
iar cele care setează valoarea variabilei să înceapă cu set. Astfel, pentru exemplul dat, metodele s-ar
numi setCodPostal (int) şi getCodPostal ( ).
Folosirea metodelor pentru accesul la variabilele de instanţa este o tehnică foarte răspândită
în programarea orientată obiect. Acest mod de abordare măreşte gradul de reutilizare a codului
deoarece evită folosirea lui necorespunzătoare.

Variabile şi metode statice

Un modificator pe care l-aţi folosit deja în programe este static. Modificatorul static este
folosit pentru crearea metodelor şi variabilelor de clasă, ca în următorul exemplu:
public class Cerc {
public static float pi = 3.14159265F;
public float arie(float r) {
return pi * r * r;
}
}
Variabilele şi metodele de clasă pot fi accesate folosind numele clasei urmat de un punct şi de
numele variabilei sau metodei, ca de exemplu Color.black sau Cerc.pi. Puteţi, de asemenea, folosi
numele unui obiect al clasei, însă pentru variabilele şi metodele de clasă este mai bine să folosiţi
numele clasei. Această convenţie stabileşte mai clar tipul variabilei sau al metodei cu care lucraţi;
variabilele şi metodele de instanţă nu pot fi referite niciodată prin numele clasei.
Următoarele instrucţiuni folosesc variabile şi metode de clasă:
float circumferinta = 2 * Cerc.pi * getRaza ( ) ;
float numarAleator = Math.random ( ) ;

Clase, metode şi variabile finale


Modificatorul final este folosit cu clase, metode şi variabile pentru a indica faptul că acestea
nu vor mai fi modificate. El are înţelesuri diferite pentru fiecare dintre aceste categorii:
• Dintr-o clasă final nu se mai pot deriva subclase.
• O metodă final nu poate fi suprascrisă de nici o subclasă.
• O variabilă final nu îşi poate schimba valoarea.

Variabile finale
Acestea mai sunt denumite şi variabile constante (sau, mai simplu, constante), deoarece nu
îşi pot modifica valoarea.
In cazul variabilelor, modificatorul final este folosit, de obicei, împreună cu modificatorul
static, pentru a crea din constantă o variabilă de clasă. Dacă valoarea nu poate fi modificată, nu
există motive ca fiecare instanţă a clasei să posede o copie a valorii; toate pot folosi o variabilă de
clasă cu acelaşi rol.
Următoarele instrucţiuni sunt exemple de declaraţii de constante:
public static final float pi = 3.1415927;

Începând cu Java 2, orice tip de variabilă poate deveni variabilă finală: clasă, instanţă sau
variabilă locală.

Metode finale
Metodele finale sunt acele metode care nu pot fi suprascrise niciodată de o subclasă. Acestea
sunt declarate folosind modificatorul final .
Singurul motiv pentru care aţi declara o metodă drept final este pentru o execuţie mai eficientă. în
mod normal, atunci când un mediu de execuţie Java (cum este interpretorul java) rulează o metodă,
el caută metoda mai întâi în clasa curentă, după aceea în superclasă şi „urcă" mai departe în ierarhie
până ce găseşte definiţia acesteia. Prin acest proces se pierde din viteză în favoarea flexibilităţii şi a
uşurinţei în dezvoltare.
Dacă o metodă este declarată final, compilatorul Java poate introduce codul executabil
(bytecode) al acesteia în oricare dintre programele care o apelează. Oricum, metoda nu s-ar putea
modifica niciodată din cauza suprascrierii de către una dintre subclase.
Atunci când proiectaţi o clasă, nu prea aveţi motive să folosiţi modificatorul final. Totuşi,
dacă doriţi ca aceasta să se execute mai rapid, puteţi declara unele metode final, pentru a mări puţin
viteza de execuţie. Această abordare elimină posibilitatea de a deriva mai târziu subclase, aşa că
trebuie să vă gândiţi bine înainte de a face această schimbare.
Biblioteca de clase Java declară multe dintre metodele mai des folosite drept final, aşa încât
acestea pot fi executate rapid atunci când sunt apelate din programe.

Clase finale
Clasele sunt finalizate prin introducerea modificatorului final în declaraţia acestora, ca în
exemplul:
public final class OAltaProblema {
// ...
}
Dintr-o clasă finală nu se pot deriva subclase. Ca şi în cazul metodelor, acest proces aduce
unele avantaje legate de viteza de execuţie.
Majoritatea claselor mai des folosite sunt finale, cum ar fi java. lang.String, java.lang.Math,
java.net. InetAddress. Dacă doriţi să creaţi o clasă care să se comporte ca un şir, dar să aibă unele
modificări, nu veţi putea să o derivaţi din clasa String şi să definiţi doar comportamentul diferit; va
trebui să o scrieţi de la zero.
Toate metodele dintr-o clasă final sunt automat finale, deci nu va mai trebui să folosiţi acest
modificator în declaraţiile lor.
Nu aveţi prea multe motive să vă declaraţi propriile clase drept finale, deoarece clasele care
pot să-şi pună la dispoziţie metodele şi atributele unor subclase sunt mult mai folositoare.

Clase şi metode abstracte


Într-o ierarhie de clase, cu cât clasa se afla pe un nivel mai înalt, cu atât definirea sa este mai
abstractă. O clasă aflată ierarhic deasupra altor clase poate defini doar comportamentul şi atributele
comune celorlalte. Definirea unor atribute şi metode specifice urmează să se facă undeva mai jos în
ierarhie.
În procesul de definire a unei ierarhii de clase, atunci când căutaţi comportamentele şi
atributele comune unor grupuri de clase, veţi descoperi uneori câte o clasă care nu se instanţiază
direct. De fapt, aceasta serveşte doar ca loc de păstrare a metodelor şi atributelor pe care le folosesc
în comun subclasele sale.
Aceste clase se numesc clase abstracte şi sunt create folosind modificatorul abstract.
Iată un exemplu:
public abstract class Animal {
// ...
}
Un alt exemplu de clasă abstractă este java.awt.Component, care este superclasa tuturor
componentelor din Abstract Windowing Toolkit. Toate componentele moştenesc această clasă, deci
ea conţine metode şi variabile folositoare tuturor. Totuşi, ea nu este o componenta generică ce poate
fi adăugată într-o interfaţă, deci nu veţi avea motive să creaţi obiecte Component într-un program.
Clasele abstracte pot conţine aceleaşi elemente ca o clasă normală, inclusiv metode
constructor, deoarece subclasele lor pot avea nevoie sa moştenească aceste metode. Clasele
abstracte pot conţine, de asemenea, metode abstracte, care sunt, de fapt, doar semnături de metode,
fără nici o implementare. Aceste metode sunt implementate în subclasele clasei abstracte. Metodele
abstracte sunt declarate folosind modificatorul abstract. Nu se poate declara o metodă abstractă într-
o clasă non-abstractă. Dacă o clasă abstractă nu conţine altceva decât metode abstracte, este mai
bine să se folosească o interfaţă.

Pachete
Folosirea pachetelor, reprezintă o modalitate de a organiza grupuri de clase. Un pachet
(package) conţine un număr de clase înrudite ca scop, ca domeniu sau din punct de vedere al
moştenirii.
Dacă programele dumneavoastră sunt mici şi folosesc un număr limitat de clase, s-ar putea
să descoperiţi că nu aveţi deloc nevoie de pachete. Însă, cu cât veţi crea mai multe programe Java,
cu atât veţi avea mai multe clase. Şi, chiar dacă aceste clase sunt bine proiectate, reutilizabile,
încapsulate şi cu interfeţe specifice către alte clase, veţi simţi nevoia să folosiţi o entitate
organizaţionala mai mare, care să vă permită să grupaţi clasele.
Pachetele sunt folositoare din mai multe motive:
• Permit organizarea claselor în unităţi (grupuri). Aşa cum pe hard disc aveţi directoare şi
subdirectoare pentru a vă organiza fişierele şi aplicaţiile, pachetele vă permit să vă organizaţi
clasele în grupuri din care puteţi folosi doar ceea ce aveţi nevoie pentru fiecare program.
• Reduc problemele datorate conflictelor de nume. Cu cât creşte numărul de clase Java, cu
atât creşte posibilitatea de a folosi un nume de clasă deja existent, ceea ce va duce la apariţia unor
conflicte şi erori la integrarea acestora în programe. Pachetele vă permit să „ascundeţi” clasele şi
deci să evitaţi aceste conflicte.
• Vă permit să protejaţi clasele, variabilele şi metodele în mai multe moduri decât la nivel de
clasă.
• Pot fi folosite la identificarea claselor. De exemplu, dacă implementaţi un set de clase
pentru a realiza o anumită sarcină, puteţi folosi pentru pachetul de clase respectiv un identificator
unic, care să vă desemneze pe dumneavoastră sau organizaţia dumneavoastră.
Chiar dacă un pachet este în esenţă, o colecţie de clase, acesta poate conţine şi alte pachete,
formând un alt nivel de organizare cumva asemănător ierarhiei de clase. Fiecare „nivel" reprezintă,
de obicei, o grupare de clase mai mică şi cu sarcini mai precise.
Biblioteca de clase Java este organizată şi ea după aceste principii. Nivelul superior este
denumit java; următorul nivel conţine denumiri cum ar fi io, net, util sau awt. Ultimul pachet (awt)
conţine încă un subnivel, unde se poate găsi pachetul image.

Folosirea pachetelor
Aţi folosit pachetele pe tot cuprinsul acestui curs: ori de câte ori aţi folosit instrucţiunea
import sau ori de câte ori aţi referit o clasă prin denumirea sa completă (de exemplu,
java.awt.Color).
Pentru a folosi o clasă conţinută într-un pachet, puteţi folosi unul dintre următoarele trei
mecanisme:
• Când clasa pe care doriţi să o folosiţi se află în pachetul java. lang (de exemplu, System
sau Date), o puteţi referi pur şi simplu prin numele ei. Clasele din java. lang sunt automat
disponibile în toate programele.
• Când clasa pe care doriţi să o folosiţi se afla într-un alt pachet, puteţi sa o referiţi
folosindu-i numele complet, adică inclusiv pe cel al pachetului (de exemplu, java. awt. Font).
• Pentru clasele pe care le folosiţi frecvent din alte pachete, puteţi importa clasele
individuale sau întregul pachet de clase. După ce clasa sau pachetul au fost importate, puteţi să
referiţi clasa doar prin numele său.
Clasele care nu sunt declarate ca făcând parte dintr-un anumit pachet sunt automat incluse
într-un pachet prestabilit. Aceste clase pot fi referite prin numele lor, de oriunde din cod.

Pachete complete şi nume de clase


Pentru a referi o clasă dintr-un alt pachet, puteţi folosi numele ei complet: numele clasei,
precedat de toate numele pachetului. Pentru aceasta nu este nevoie să importaţi clasa sau pachetul:
java.awt.Font f = new java.awt.Font( );
Are sens sa folosiţi denumirea completă doar pentru clasele pe care le folosiţi o dată sau de
două ori în cadrul programului. Dacă folosiţi aceste clase de mai multe ori sau dacă numele
pachetului este prea lung şi conţine mai multe subpachete, este mai bine să importaţi clasa
respectivă, pentru a economisi timpul pe care l-aţi pierde cu tastarea.

Comanda import
Pentru a importa clasele dintr-un pachet se foloseşte comanda import, aşa cum aţi văzut în
toate exemplele prezentate în această carte. Puteţi importa o anumită clasă, ca în exemplul următor:
import java.util.Vector;
Sau puteţi importa un întreg pachet de clase, folosind simbolul asterisc (*) în locul numelor de
clasă, astfel:
import java.awt .*;

Instrucţiunea import trebuie folosită în prima parte a definiţiei clasei, înainte de orice alte
definiţii (însă după definiţia pachetului).
Importarea unui grup de clase nu încetineşte programul şi nici nu îi măreşte dimensiunile;
vor fi încărcate doar clasele de care aveţi nevoie în codul dumneavoastră. Insă importarea unui
pachet întreg face ca lucrurile să fie mai neclare pentru cei care citesc codul dumneavoastră, pentru
că nu se va şti de unde provin clasele folosite. Opţiunea pentru importuri individuale sau în grup
depinde de stilul dumneavoastră de programare.

Conflicte de nume
După ce aţi importat o clasă sau un pachet de clase, puteţi să vă referiţi la clasă doar prin
numele sau, fără identificatorul de pachet. Într-un singur caz va trebui să fiţi mai explicit: atunci
când aveţi mai multe clase cu acelaşi nume, care provin din pachete diferite.
Iată un exemplu: Să presupunem că importaţi clase provenite din două pachete diferite, aparţinând
unor programatori diferiţi :
import claseMihai.*;
import claseMarcel.*;
În cadrul pachetului lui Mihai se află o clasă denumită Vector. Şi în pachetul lui Marcel există o
clasă cu acelaşi nume, dar cu implementare şi definiţie total diferite. Vă veţi întreba acum care
dintre versiunile clasei este folosită atunci când faceţi în programul dumneavoastră o referire la
clasa Vector:
Vector vect = new Vector(10) ;
Răspunsul este „nici una"; compilatorul Java va semnala un conflict de nume şi va refuza să
compileze programul. In acest caz, chiar dacă aţi importat ambele clase, va trebui totuşi să vă
referiţi la clasa dorită prin numele său complet, ca în exemplul:
claseMihai.Vector vect = new claseMihai.Vector(10);

Variabila CLASSPATH şi locul de dispunere a claselor


Pentru ca Java să poată folosi o clasă, trebuie să afle locul unde este dispusă în sistemul de
fişiere; altfel, va fi generată o eroare referitoare la inexistenţa clasei. Pentru găsirea claselor Java
foloseşte două elemente: numele pachetului şi directoarele referite de variabila de mediu CLASSPATH
(dacă folosiţi un sistem Windows).
Numele pachetelor corespund unor nume de directoare ale sistemului de fişiere, deci clasa
java.applet.Applet se va găsi în directorul applet, care face parte şi el din directorul java (este vorba
de java\applet\Applet. class).
Java caută aceste directoare, pe rând, în cadrul directoarelor referite de variabila CLASSPATH,
dacă aceasta este definită. Dacă nu este configurată nici o variabilă CLASSPATH, Java caută în
directorul prestabilit, java/lib, aflat în directorul cu versiunea JDK pe care o folosiţi, precum şi în
directorul curent. Java caută în aceste directoare clasa referită în sursa dumneavoastră folosindu-se
de numele pachetului şi al clasei, iar dacă nu o găseşte returnează un mesaj de eroare. Majoritatea
erorilorde tip 'class not found' (clasă inexistentă) sunt cauzate de o configurare greşită a variabilei
de mediu CLASSPATH .

Crearea propriilor pachete

Crearea unui pachet pentru clasele dumneavoastră Java nu este mai complicară decât crearea
unei clase. Trebuie numai să urmaţi cele trei etape prezentate în continuare.

Alegerea unui nume pentru pachet

Primul pas consta în alegerea unui nume. Numele ales pentru pachet depinde de modul cum
doriţi să folosiţi aceste clase. Puteţi denumi pachetul cu numele dumneavoastră saucu un nume
sugestiv. Dacă intenţionaţi să distribuiţi pachetul în Internet sau ca parte a unui produs comercial,
trebuie să folosiţi un nume de pachet care identifică în mod unic autorul.
Convenţia de denumire a pachetelor, recomandată de Sun este de a folosi numele de
domeniu Internet, cu elementele inversate.
Ideea este de a vă asigura că numele pachetului dumneavoastră este unic. Chiar dacă
pachetele pot ascunde conflictele între nume de clase, protecţia se opreşte aici. Nu puteţi fi sigur că
pachetul dumneavoastră nu va intra în conflict cu pachetul altcuiva, dacă folosiţi aceleaşi nume de
pachete.
Prin convenţie, numele pachetelor încep cu literă mică, pentru a le deosebi de numele claselor.
Astfel, în cazul denumirii complete a clasei String (java. lang. String), puteţi separa vizual foarte
uşor numele pachetului de numele clasei. Această convenţie ajută şi ea la reducerea conflictelor de
nume.
Crearea structurii de directoare

Etapa a doua în crearea unui pachet constă în crearea pe hard disc a unei structuri de
directoare conform numelui pachetului. Daca pachetul dumneavoastră are un singur nume
(pachetulmeu), este suficient să creaţi un director cu acest nume. Dacă numele pachetului este
compus din mai multe părţi, trebuie să creaţi şi subdirectoarele respective.

Adăugarea unei clase într-un pachet

Pasul final este de a introduce clasa în pachet şi de a adăuga o instrucţiune în definiţia clasei,
înainte de orice instrucţiuni import care vor fi folosite. Instrucţiunea package se foloseşte împreună
cu denumirea pachetului, în felul următor:
package figurigeometrice;
Instrucţiunea package, dacă există, trebuie să fie pe prima linie a codului fişierului sursa,
eventual după comentarii sau linii goale, însă înainte de orice comenzi import.
După ce veţi începe să folosiţi pachetele, trebuie să vă asiguraţi că toate clasele
dumneavoastră aparţin aceluiaşi pachet, pentru a reduce eventualele confuzii asupra locului unde se
găsesc.

Pachetele şi controlul accesului la clase

Am văzut care sunt modificatorii de control ai accesului pentru metode şi variabile. Puteţi,
de asemenea, controla accesul la clase, aşa cum aţi remarcat din folosirea modificatorului public în
unele declaraţii de clase din exemplele anterioare.
Dacă nu este specificat nici un modificator, claselor li se atribuie un control prestabilit al
accesului; aceasta înseamnă că respectiva clasă este disponibilă tuturor claselor din acelaşi pachet,
însă nu şi în afara pachetului - nici măcar subpachetelor. Ea nu poate fi importată sau referită prin
nume; clasele cu protecţie de pachet sunt vizibile doar în cadrul pachetului care le conţine.
Protecţia de pachet este stabilită atunci când definiţi o clasă în felul următor:
class ClasaAscunsa extends OAltaClasaAscunsa {
// . . .
}
Pentru a permite unei clase să fie vizibilă şi importabilă în afara pachetului, îi veţi atribui protecţia
publică prin folosirea modificatorului public în definiţia sa:
public class ClasaVizibila {
// . . .
}
Clasele declarate public pot fi importate de orice alte clase din afara pachetului.

Reţineţi că, atunci când folosiţi în instrucţiunea import simbolul asterisc (*), veţi importa
doar clasele publice din cadrul pachetului respectiv. Clasele ascunse (hidden) rămân invizibile şi
pot fi folosite doar de celelalte clase din pachetul respectiv.
De ce să doriţi să ascundeţi o clasă în cadrul unui pachet? Din acelaşi motiv pentru care
doriţi să ascundeţi variabilele şi metodele în cadrul unei clase: pentru că astfel aveţi clase utilitare şi
metode care sunt folosite doar de implementarea dumneavoastră sau pentru că puteţi limita interfaţa
programului dumneavoastră, minimizând efectul unor modificări de amploare. Atunci când vă
proiectaţi clasele, ar trebui să luaţi în considerare întregul pachet şi să hotărâţi pe care clase doriţi să
le declaraţi public şi pe care doriţi să le ascundeţi.
Programul următor prezintă două clase care demonstrează acest lucru. Prima este o clasă publică ce
implementează o listă înlănţuită; a doua reprezintă un nod privat al listei.
Textul complet al programului ListaInlantuita. java
1: package colectie;
2:
3: public class Liatalnlantuita {
4: private Nod radacina;
5:
6: public void add(0bject o) {
7: radacina = new Nod(o, radacina);
8: }
9: // ...
10 }
11:
12: class Nod { // Nu este publica
13: private Object continut;
14: private Nod urmatorul;
15:
16: Nod(0bject o, Nod n) {
17: continut = o;
18: urmatorul = n;
19: }
20: // ...
21: }

Clasa publică ListaInlantuita oferă un set de metode publice (cum ar fi add ( ) ) altor clase
care ar avea nevoie să le folosească. Celelalte clase nu trebuie să ştie de alte clase pe care
ListaInlantuita le foloseşte pentru a-şi îndeplini sarcinile. Nod, una dintre aceste clase ajutătoare,
este declarata din acest motiv fără modificatorul public şi deci nu va face parte din interfaţa publică
a pachetului colecţie.
Faptul că Nod nu este o clasă publică nu înseamnă că Listalnlantuita nu va mai avea acces la
ea după ce este importată într-o altă clasă. Atunci când importaţi şi folosiţi clasa Listalnlantuita,
clasa Nod va fi, de asemenea, încărcată în sistem, însă doar instanţele clasei Listalnlantuita vor avea
permisiunea să o folosească.
Crearea unui pachet bine proiectat presupune definirea unui set mic de clase publice şi de
metode care pot fi folosite de alte clase şi implementarea lor folosind un număr de clase ajutătoare,
invizibile în exterior.
Interfeţe
Interfeţele, ca şi clasele sau metodele abstracte, oferă un model de comportament care se
presupune că va fi implementat de alte clase. Totuşi, interfeţele oferă posibilităţi mult mai mari
decât clasele şi metodele abstracte, atât pentru Java, cât şi pentru proiectarea obiectelor şi a claselor,
în general.

Problema unicei moşteniri

După ce veţi câştiga ceva mai multă experienţă în proiectare, va veţi da seama că simplitatea
ierarhiei de clase Java este oarecum restrictivă, mai ales atunci când doriţi să folosiţi unele metode
din clase aflate pe diferite „ramuri" ale aceleiaşi ierarhii.
Să luăm un exemplu care va clarifica această problemă. Să presupunem că aveţi o ierarhie
de clase referitoare la biologie, pe primul nivel aflându-se clasa Animal, iar mai jos, clasele
Mamifer şi Pasare. Printre atributele clasei Mamifer se numără naşterea de pui vii şi existenţa
blănii. Clasa Pasare conţine atribute cum ar fi depunerea de ouă şi existenţa ciocului. Dar cum veţi
defini o clasă pentru ornitorinc, care, se ştie, are blană, cioc şi depune ouă? Pentru crearea clasei
Ornitorinc va trebui să combinaţi atributele din cele două clase. Dar, deoarece clasele pot avea în
Java o singură superclasă, acest tip de problemă nu poate fi rezolvat prea elegant.
Alte limbaje OOP (orientate obiect) conţin conceptul de moştenire multiplă, care rezolvă
această problemă. Dacă se foloseşte moştenirea multiplă, o clasă poate avea mai multe superclase,
împrumutând atribute de la toate. Una dintre problemele moştenirii multiple este aceea că limbajul
de programare devine mai dificil de învăţat, de folosit şi de implementat. Problemele legate de
apelarea metodelor şi de modul de organizare a ierarhiei de clase devin mult mai complicate şi este
mult mai probabil să producă ambiguităţi şi confuzii. Deoarece unul din scopurile limbajului Java
este simplitatea, s-a renunţat la moştenirea multiplă în favoarea mai simplei moşteniri unice.
Pentru rezolvarea acestor probleme Java conţine o altă ierarhie, total separată de ierarhia principală,
care conţine clase cu comportament combinat. Astfel, atunci când creaţi o nouă clasă, aceasta are o
singură superclasă primară, însă poate alege şi alte comportamente din cealaltă ierarhie. Această
nouă ierarhie se numeşte ierarhie de interfeţe. O interfaţă Java este o colecţie de comportamente
abstracte, care pot fi combinate în orice clasă pentru a introduce în acea clasă comportamente care
nu pot fi moştenite de la superclasă. Tehnic, o interfaţă Java nu conţine nimic altceva decât definiţii
de metode abstracte şi constante - fără variabile de instanţă sau implementări de metode.
Interfeţele sunt implementate şi folosite în biblioteca de clase Java ori de câte ori este nevoie
ca un anumit comportament să fie implementat în alte clase. Ierarhia de clase Java, de exemplu,
defineşte şi foloseşte interfeţele java.lang.Runnable, java.awt.image.ImageConsumer şi java. awt.
image.ImageProducer.

Interfeţele şi clasele

Clasele şi interfeţele, chiar dacă au definiţii diferite, au foarte multe în comun. Interfeţele, ca
şi clasele, sunt declarate în fişiere sursă, câte un fişier pentru fiecare interfaţă. Ca şi clasele, acestea
sunt compilate în fişiere .class folosind compilatorul Java. Şi, în majoritatea cazurilor, oriunde se
poate folosi o clasă (ca tip de dată pentru variabile, ca rezultat al unei conversii prin cast şi aşa mai
departe) se poate folosi şi o interfaţă. Programatorii Java folosesc termenul „clasă" pentru a se referi
atât la clase, cât şi la interfeţe. Interfeţele completează şi extind puterea claselor şi de aceea pot fi
tratate aproape în acelaşi fel. Una dintre puţinele diferenţe existente între clase şi interfeţe este aceea
că nu se poate obţine o instanţă a unei interfeţe: operatorul new se foloseşte doar pentru crearea
de instanţe ale unei clase.

Implementarea şi folosirea interfeţelor


Cu interfeţele puteţi face două lucruri: să le folosiţi în clasele dumneavoastră sau să definiţi
unele proprii. Să începem cu primul caz.
Pentru a folosi o interfaţă, trebuie folosit cuvântul cheie implements ca parte a definiţiei clasei. Un
astfel de exemplu este cel care a fost prezentat într-unul dintre capitolele anterioare:
public class Neko extends java.applet.Applet implements Runnable
{
// . . .
}

În acest exemplu, java.applet.Applet este superclasa, însă interfaţa Runnable extinde


comportamentul implementat de aceasta.
Deoarece interfeţele nu oferă nimic altceva decât definiţii abstracte de metode, va trebui să
implementaţi aceste metode în clasele dumneavoastră folosind semnăturile de metode provenite din
interfaţă. Reţineţi că o dată inclusă o interfaţă, va trebui să implementaţi toate metodele acesteia; nu
puteţi să alegeţi doar metodele de care aveţi nevoie. Prin implementarea unei interfeţe, înseamnă că
declaraţi utilizatorilor clasei că veţi suporta toate metodele acesteia. (Aceasta este o altă diferenţă
între interfeţe şi clasele abstracte; subclasele claselor abstracte pot alege care dintre metode să fie
implementate sau suprascrise şi să le ignore pe celelalte.)
După ce clasa pe care aţi scris-o implementează o interfaţă, subclasele acesteia vor moşteni
toate noile metode (care pot fi suprascrise sau supraîncărcate) ca şi când acestea ar fi fost definite în
superclasă. Când o clasă moşteneşte o superclasă care implementează o anumită interfaţă, nu mai
trebuie să folosiţi cuvântul cheie implements în definiţia clasei respective.
Să luăm un exemplu simplu - crearea unei noi clase, denumită Portocala. Să presupunem că
posedaţi deja o implementare bună a clasei Fruct şi o interfaţă, ModelFruct, care reprezintă ceea ce
un Fruct este capabil să facă. Doriţi ca portocala să fie un fruct, însă doriţi să fie şi un obiect sferic
care să poate fi aruncat, rotit şi aşa mai departe. Iată cum se pot defini toate acestea:

interface ModelFruct {
void stricare ( );
void stoarcere ( );
// ...
}
class Fruct implements ModelFruct {
private Color culoareaMea;
private int zilePanaLaStricare;
//...
}

interface ModelSfera {
void aruncare ( );
void rotire ( );
// ...
}
class Portocala extends Fruct implements ModelSfera {
// aruncare ( ) poate sa provoace stoarcere ( )
// (caracteristici unice pentru clasa Portocala)
}
Remarcaţi că în clasa Portocala nu este nevoie să se implementeze interfaţa ModelFruct, deoarece,
prin moştenirea clasei Fruct, deja o implementează! Unul dintre avantajele acestei structuri este
acela că vă puteţi răzgândi în privinţa clasei de unde se derivă clasa Portocala (de exemplu, dacă
este implementată o clasă mai bună, denumită Sfera) şi totuşi clasa Portocala va înţelege aceleaşi
două interfeţe:

class Sfera implements ModelSfera {


// derivata direct din clasa Object
private float raza;
// .. .
}
class Portocala extends Sfera implements ModelFruct {
// ...utilizatorii clasei Portocala nu vor remarca aceasta
// modificare!
}

Implementarea unor interfeţe multiple

Spre deosebire de ierarhia de clase cu moştenire unică în clasele dumneavoastră puteţi folosi oricâte
interfeţe doriţi; clasa rezultată va implementa comportamentul combinat: al tuturor interfeţelor
incluse. Pentru a include mai multe interfeţe într-o clasă, separaţi numele acestora prin virgule:
public class Neko extends java. applet. Applet
implements Runnable, Comestibil, Sortabil, Observable {
// ...
}
Reţineţi totuşi că folosirea unor interfeţe multiple poate da naştere la complicaţii. Ce se întâmplă
dacă două interfeţe definesc aceeaşi metodă? Puteţi rezolva această problemă în trei moduri:
• Dacă metoda are aceeaşi semnătură în ambele interfeţe, implementaţi în clasa dumneavoastră o
metodă a cărei definiţie satisface ambele interfeţe.
• Dacă metodele au liste de parametri diferite, veţi avea o situaţie de supraîncărcare a metodelor;
implementaţi ambele metode, fiecare dintre definiţii satisfăcând interfaţa respectivă.
• Dacă ambele metode au aceeaşi listă de parametri, însă returnează tipuri diferite, nu veţi putea
crea o metodă care să le satisfacă pe amândouă (deoarece supraîncărcarea metodelor ţine cont doar
de lista de parametri, nu de tipul de retur). În acest caz, încercarea de compilare a unei clase care
implementează ambele interfeţe va produce o eroare. Dacă ajungeţi în această situaţie, înseamnă că
interfeţele dumneavoastră au erori de proiectare şi trebuie reanalizate.

Alte utilizări ale interfeţelor

Aproape oriunde puteţi folosi o clasă, puteţi folosi şi o interfaţă. De exemplu, să declarăm o variabilă de tip
interfaţă:
Runnable unObiectExecutabil = new ClasaMeaDeAnimatie( ) ;
Atunci când se declară o variabilă de tipul interfaţă, acest lucru presupune ca obiectul pe care îl
referă variabila să aibă implementată acea interfaţă; deci se presupune că înţelege toate metodele
specificate de interfaţă. În acest caz, deoarece variabila unObiectExecutabil conţine un obiect de tip
Runnable, se presupune că se poate apela metoda unObiectExecutabil.run( ).
De asemenea, puteţi converti prin cast un obiect într-o interfaţă aşa cum îl convertiţi într-o altă
clasă. Să ne întoarcem la definiţia clasei Portocala, care implementa atât interfaţa ModelFruct (prin
superclasa sa, Fruct), cât şi interfaţa ModelSfera. Puteţi converti prin cast instanţe ale clasei
Portocala atât în clase, cât şi în interfeţe:
Portocala oPortocala = new Portocala ( ) ;
Fruct unFruct = (Fruct)oPortocala;
ModelFruct unModelFruct = (ModelFruct)oPortocala;
ModelSfera unModelSfera = (ModelSfera)oPortocala;

unFruct. stricare ( ); // fructele se strica


unModelFruct .stoarcere ( ); // si se storc
unModelFruct.aruncare ( ); // lucrurile care sunt ca fructele
// nu se arunca
unModelSfera.aruncare ( ); // dar lucrurile care sunt ca
// sferele da
oPortocala. stricare ( ); // portocalele pot face de toate
oPortocala.stoarcere ( );
oPortocala.aruncare ( ) ;
oPortocala.rotire ( );
Declaraţiile şi conversiile prin cast sunt folosite în acest exemplu pentru a limita comportamentul
portocalei mai aproape de fruct sau de sferă.
În final, reţineţi că interfeţele, chiar dacă se folosesc pentru combinarea metodelor diferitelor clase,
pot fi folosite şi pentru combinarea unor constante utile. De exemplu, dacă o interfaţă defineşte un
set de constante care sunt folosite apoi în mai multe clase, valoarea acestor constante poate fi
modificată global fără a trebui să modificaţi toate clasele. Acesta este un alt caz în care folosirea
interfeţelor pentru separarea proceselor de proiectare şi de implementare conduce la obţinerea unui
cod mai generic şi mai uşor de întreţinut.

Crearea şi extinderea interfeţelor


După o perioadă de folosire a interfeţelor puteţi trece la definirea unor interfeţe proprii. Acestea
arată destul de asemănător claselor; ele sunt declarate cam în acelaşi fel şi pot fi aranjate într-o
ierarhie. Totuşi, pentru declararea unei interfeţe trebuie să respectate anumite reguli.
Interfeţe noi
Declararea unei interfeţe noi se face în felul următor:
public interface Crescator {
// ....
}
Acest enunţ este practic identic cu definiţia unei clase, cu singura diferenţă că este folosit cuvântul
cheie interface în locul cuvântului class. În definiţia interfeţei puteţi avea metode şi constante.
Metodele din cadrul unei interfeţe sunt public şi abstract; puteţi să le declaraţi explicit în acest fel
sau, dacă nu, oricum vor fi transformate în metode public şi abstract. Nu puteţi declara o metodă
private sau protected în cadrul unei interfeţe. De exemplu, în următoarea definiţie a interfeţei
Crescator, metoda crescut ( ) este declarată explicit public şi abstract. în timp ce metoda
maicrescut ( ) este declarată implicit:
public interface Crescator {
public abstract void crescut ( ); // declarata explicit public
// si abstract
void maiCrescut ( ); // declarata implicit public si
// abstract
}
Observaţi că, la fel ca şi în cazul metodelor abstracte din clase, metodele din interfeţe nu au
conţinut. Reţineţi: o interfaţă necesită doar proiectare, nu presupune şi implementare.
In afară de metode, interfeţele pot conţine şi variabile, însă acestea trebuie declarate public, static şi
final (deci constante). Ca şi în cazul metodelor, acest lucru nu trebuie făcut explicit; variabilele sunt
considerate implicit public, static şi final chiar şi fără a folosi aceşti modificatori. Iată definiţia
interfeţei Crescator, care conţine două noi variabile:
public interface Crescator {
public static final int incrementare = 10;
long nrmax = 1000000; // devine public, static si final
public abstract void crescut ( ); // declarata explicit public
// si abstract
void maiCrescut ( ); // declarata implicit public si abstract
}

Interfeţele trebuie să posede, ca şi clasele, o protecţie de pachet sau publică. O interfaţă nepublică
are metode şi constante de acelaşi tip, acestea neputând fi folosite decât în clasele sau interfeţele din
acelaşi pachet.
Interfeţele, ca şi clasele, pot aparţine unui pachet dacă folosiţi instrucţiunea package în prima linie
din fişierul sursă. La fel ca şi clasele, interfeţele pot importa interfeţe sau clase din alte pachete.
Metodele din cadrul interfeţelor

Iată o problemă privind metodele din cadrul interfeţelor: ele trebuie să fie abstracte şi să se aplice oricărui tip de
clasă, dar cum se poate defini lista de parametri pentru aceste metode? Nu ştiţi dinainte ce clasă urmează să le
folosească! Răspunsul constă în faptul că, aşa cum aţi învăţat mai devreme, puteţi folosi un nume de interfaţă
peste tot unde poate fi folosit un nume de clasă. Prin definirea parametrilor metodei de tip interfaţă puteţi crea
parametri generici care funcţionează pentru orice clasă care ar folosi această interfaţă.
Să luăm, de exemplu, interfaţa ModelFruct, care defineşte metodele (fără parametri) stricare ( ) şi
stoarcere ( ). Mai puteţi avea o metodă, germinareSeminte ( ), care are un singur argument: fructul
însuşi. De ce tip urmează să fie acest argument? Nu poate fi de tip Fruct, deoarece puteţi avea o
clasă derivată din ModelFruct (deci care implementează interfaţa ModelFruct) şi care să nu fie, de
fapt, un fruct. Soluţia este de a declara în interfaţă argumentul de tip ModelFruct:

public interface ModelFruct {


public germinareSeminte (ModelFruct fr) {
// …
}
}
Apoi, în implementarea acestei metode într-o clasă, puteţi converti prin cast argumentul
genericModelFruct în obiectul corespunzător:
public class Portocala extends Fruct {
public germinareSeminte(ModelFruct fr) {
Portocala oPortocala = (Portocala)fr;
// . . .
}
}

Derivarea interfeţelor
Ca şi în cazul claselor, interfeţele pot fi organizate într-o ierarhie. Atunci când o interfaţă
moşteneşte o altă interfaţă, „subinterfaţa" primeşte toate metodele şi constantele definite în
″superinterfaţă". Pentru a deriva (extinde) o interfaţă veţi folosi cuvântul cheie extends, la fel ca în
cazul definiţiilor claselor:
public interface ModelFruct extends ModelMancare {
// ...
}
Totuşi, spre deosebire de clase, ierarhia de interfeţe nu posedă echivalentul unei clase Object;
această ierarhie nu are un „vârf". Interfeţele pot exista independent sau pot moşteni o altă interfaţă.
De asemenea, spre deosebire de ierarhia de clase, ierarhia de interfeţe este una cu moştenire
multiplă. De exemplu, o singură interfaţă poate moşteni oricâte clase are nevoie (separate prin
virgulă, în acea parte a definiţiei care foloseşte cuvântul cheie extends); noua interfaţă va conţine o
combinaţie de metode şi constante moştenite de la „părinţi". Iată definiţia unei interfeţe care
moşteneşte mai multe interfeţe:
public interface InterfataAglomerata extends Runnable, Crescator, ModelFruct, Observable {
// ...
}

În interfeţele cu moştenire multiplă, regulile de gestionare a conflictelor între numele metodelor


sunt aceleaşi ca pentru clasele care folosesc mai multe interfeţe; metodele care diferă numai prin
tipul de retur vor genera o eroare de compilare.

Clase interioare
Clasele cu care aţi lucrat până acum sunt toate membre ale unui pachet, fie că aţi folosit
instrucţiunea package, urmată de numele unui pachet, fie că a fost folosit pachetul prestabilit.
Clasele care aparţin unui pachet sunt cunoscute drept clase de nivel înalt (top-level classes). La
introducerea Java, acestea erau singurele clase suportate de limbaj.
Începând cu Java 1.1, puteţi defini o clasa în interiorul altei clase, ca şi când ar fi o metodă sau o
variabilă. Astfel de clase se numesc clase interioare (inner classes). Listingul următor prezintă
applet-ul Interior, care foloseşte o clasă interioară, denumită ButonAlbastru pentru a reprezenta
butoanele care au culoarea de fundal albastră.
Textul complet al programului Interior.java.
1: import java.awt.Button;
2: import java.awt.Color;
3:
4: public class Interior extends java.applet.Applet {
5: Button b1 = new Button ("0ne");
6: ButonAlbastru b2 = new ButonAlbastru("Two");
7:
8: public void init ( ) {
9: add(b1);
10: add(b2);
11: }
12: class ButonAlbastru extends Button {
13: ButonAlbastru (String eticheta) {
14: super(eticheta) {
15: this .setBackground(Color.blue) ;
16: }
17: }
18: }

În acest exemplu, clasa ButonAlbastru nu este decât o clasă ajutătoare inclusă în acelaşi fişier sursă
ca şi clasa principală a programului. Singura diferenţă este că această clasă ajutătoare este definită
în cadrul fişierului clasă, ceea ce aduce câteva avantaje:
• Clasele interioare sunt invizibile pentru alte clase, ceea ce înseamnă că nu trebuie să vă faceţi
probleme privind conflictele de nume cu alte clase.
• Clasele interioare pot avea acces la variabilele şi metodele din domeniul de vizibilitate al
clasei de nivel superior, lucru care nu ar fi fost valabil dacă ar fi fost separate.
În majoritatea cazurilor, o clasă interioară este o clasă de dimensiuni reduse şi cu un scop limitat. În
applet-ul Interior, deoarece clasa ButonAlbastru nu conţine atribute sau metode complexe, este
indicată pentru a fi implementată drept clasă interioară.
Numele clasei interioare este asociat cu numele clasei care o conţine şi este atribuit automat la
compilarea programului. În exemplul clasei ButonAlbastru, JDK îi va atribui numele
Interior$ButonAlbastru.class.
Clasele interioare, chiar dacă par că aduc îmbunătăţiri minore limbajului Java, reprezintă, de fapt, o
modificare semnificativă a limbajului.
Regulile care guvernează domeniul de vizibilitate al unei clase interioare sunt aceleaşi care se aplică
şi variabilelor. Numele unei clase interioare nu este vizibil în afara domeniului său, exceptând cazul
când se foloseşte numele complet, lucru care ajută la structurarea claselor în cadrul pachetului.
Codul unei clase interioare poate folosi nume simple, din domeniile de vizibilitate pe care le
cuprinde, cum ar fi variabilele de clasă şi de instanţă ale claselor pe care le conţine, precum şi
variabilele locale din blocurile incluse.
În plus, puteţi defini o clasă de nivel înalt ca membru static al unei alte clase de nivel înalt. Spre
deosebire de o clasă interioară, o clasă de nivel înalt nu poate folosi direct variabilele de instanţă ale
unei alte clase. Posibilitatea de a imbrica în acest fel clasele permite oricărei clase de nivel înalt să
ofere o organizare de tipul pachetelor pentru un grup de clase de nivel mai scăzut, înrudite din punct
de vedere logic.
Exceptii Java

Până în acest moment, este mai mult ca sigur că aţi întâlnit cel puţin o situaţie de excepţie Java - probabil
atunci când aţi introdus greşit numele unei metode sau aţi făcut o greşeală în cod care a dus la apariţia unei probleme.
Se poate, de asemenea, ca programul să se termine anormal, după ce a afişat pe ecran ceva
erori. Aceste erori sunt excepţii. Atunci când programul se termină brusc, este din cauză că a fost
semnalată o excepţie (thrown - „aruncată"). Excepţiile fi semnalate de sistem sau, explicit, de
programul pe care l-aţi scris.
Am folosit termenul "semnalate" deoarece excepţiile pot fi şi interceptate (caught).
Interceptarea unei excepţii presupune tratarea situaţiilor speciale pentru ca programul
dumneavoastră să nu se mai blocheze. Faptul că o excepţie a fost semnalată înseamnă, în Java, că "a
apărut o eroare".
Excepţiile Java sunt, de fapt, obiecte, instanţe ale unor clase care moştenesc clasa
Throwable. O instanţă a clasei Throwable este creată atunci când se semnalează o excepţie. Figura
următoare prezintă o porţiune ierarhiei de clase pentru excepţii.

Throwable

Error Exception

IOException
RuntimeException

FileNotFound
ClassNotFoundE Exception
xception
EOFException
AWTException
MalFormedURL
Exception

SocketException

Clasa Throwable are două subclase: Error şi Exception. Instanţele clasei Error reprezintă
erori interne ale mediului de lucru Java (maşina virtuală). Aceste erori sunt rare şi, de obicei, fatale;
nu puteţi face mare lucru cu ele (nici să le interceptaţi şi nici să le semnalaţi), există pentru ca Java
să le poată folosi dacă are nevoie de ele.
Subclasele Exception se împart în două grupuri:
• Excepţii de execuţie (runtime), care sunt subclase ale clasei RuntimeException, cum ar fi
ArrayIndexOutofBounds, SecurityException sau NullPointerException
• Alte excepţii, cum ar fi EOFException sau MalformedURLException
Majoritatea claselor de excepţie fac parte din pachetul java.lang (cum ar fi Throwable,
Exception sau RuntimeException). Celelalte pachete definesc şi ele excepţii, care pot fi folosite în
toată biblioteca de clase. De exemplu, pachetul java.io defineşte o clasă de excepţie generică,
denumită IOException, care nu este folosită doar în pachetul java.io, pentru excepţiile de
intrare/ieşire (EOFException, FileNotFoundException), ci şi în clasele pachetului java.net, pentru
excepţii de reţea cum ar fi MalformedURLException.

Gestionarea excepţiilor

În majoritatea cazurilor, compilatorul Java impune gestionarea excepţiilor atunci când


încercaţi să folosiţi metode ce presupun apariţia unor excepţii; dacă nu trataţi aceste excepţii, codul
nu va fi compilat. În această secţiune veţi învăţa despre verificarea consistenţei şi despre folosirea
cuvintelor cheie try, catch şi finally pentru tratarea excepţiilor ce pot apărea.

Verificarea consistenţei excepţiilor

Cu cât lucraţi mai mult cu biblioteci Java, cu atât mai mult creşte posibilitatea de a întâlni o
eroare (o excepţie!) de compilare, asemănătoare acesteia:
Program.java:32: Exception java.lang.InterruptedException must be
caught or it must be declared in the throws clause of this method.
(Program.java:32: Excepţia java.lang.InterruptedException trebuie interceptată sau trebuie declarată
în clauza throws a acestei metode.)
În Java, o metodă poate indica tipurile de erori pe care le poate semnala. De exemplu,
metodele care citesc din fişiere pot semnala excepţii IOException, deci aceste metode sunt
declarate cu un modificator special care indică potenţialele erori. Atunci când folosiţi aceste metode
în programele dumneavoastră Java, trebuie să vă protejaţi codul împotriva acestor excepţii. Această
regulă este verificată chiar de compilator, în acelaşi fel în care verifică şi dacă aţi apelat metodele
cu numărul corespunzător de argumente sau dacă aţi atribuit variabilelor tipurile de date declarate.
De ce se face această verificare? Deoarece astfel programele dumneavoastră sunt mai puţin
expuse erorilor fatale şi terminării anormale, pentru că ştiţi dinainte ce tipuri de excepţii pot fi
semnalate de metodele folosite în program. Nu trebuie să mai citiţi cu atenţie documentaţia sau
codul unui obiect pentru a vă asigura că aţi tratat toate potenţialele probleme - Java face aceste
verificări în locul dumneavoastră. Pe de altă parte, dacă definiţi metodele astfel încât să indice
excepţiile pe care le pot semnala, Java poate avertiza utilizatorii acestor obiecte că trebuie să trateze
erorile respective.
Protejarea codului şi interceptarea erorilor

Să presupunem că aţi scris un cod şi că la un test de compilare aţi obţinut un mesaj de excepţie. În funcţie de
mesaj, fie interceptaţi eroarea, fie declaraţi că metoda o poate semnala. Să luăm primul caz: interceptarea potenţialelor
excepţii.
Pentru a intercepta o excepţie trebuie realizate două lucruri:
• Protejaţi codul care conţine metoda ce poate semnala excepţia în cadrul unui bloc try.
• Testaţi şi trataţi excepţia în cadrul unui bloc catch.
Operaţiunile try (încearcă) şi catch (interceptează) înseamnă, de fapt, „încearcă această
porţiune de cod, care poate cauza o excepţie. Dacă se execută cu succes, continuă programul. dacă
nu, interceptează excepţia şi trateaz-o."
Un astfel de caz este atunci când creaţi animaţie pe care o opriţi o dată pe secundă:
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
Chiar dacă aici s-au folosit instrucţiunile try şi catch, acesta nu este un exemplu prea bun.
Iată ce se întâmplă în aceste instrucţiuni: metoda de clasă Thread.sleep() poate să semnaleze o
excepţie de tip InterruptedException, ceea ce înseamnă că firul de execuţie a fost oprit dintr-un
motiv oarecare.
Pentru a trata această excepţie, apelul metodei sleep() este încadrat într-un bloc try, după
care se defineşte un bloc catch asociat. Acest bloc catch primeşte toate obiectele
InterruptedException care sunt semnalate din blocul try.
Motivul pentru care acest cod nu constituie un bun exemplu de tratare a excepţiilor este că
nu există nimic în interiorul blocului catch - cu alte cuvinte, se interceptează excepţia atunci când
apare, însă ca răspuns la aceasta nu se face nimic. Cu excepţia cazurilor simple (cum e acesta, unde
excepţia într-adevăr nu contează), trebuie să scrieţi în cadrul blocului catch un cod care să realizeze
o anumită acţiune după apariţia excepţiei.
Partea din interiorul instrucţiunii catch este similară listei de argumente a unei metode. Ea
conţine clasa a cărei excepţie a fost interceptată şi un nume de variabilă (de obicei se foloseşte e).
Astfel, în cadrul blocului puteţi să vă referiţi la obiectul excepţie interceptat.
O folosire uzuală a acestui obiect este apelarea metodei getMessage(). Această metodă este
prezentă în toate excepţiile şi afişează un mesaj detaliat referitor la ceea ce s-a întâmplat.
Următorul exemplu reprezintă o versiune revizuită a instrucţiunii try.. . catch,din exemplul
anterior:
try {
Thread.sleep(1000) ;
} catch (InterruptedException e) {
System.out.println("Eroare: " + e.getMessage());
}

Clauza finally

Să presupunem că în codul dumneavoastră există o anumită acţiune care trebuie neapărat


executată, indiferent ce se întâmplă, indiferent dacă excepţia este semnalată sau nu. Acest lucru se
face, de obicei, pentru a elibera anumite resurse după folosire, pentru a închide un fişier după
deschidere sau ceva de acest gen. Chiar dacă puteţi introduce aceste acţiuni atât în cadrul unui bloc
catch, cât şi în afara sa, aceasta înseamnă o duplicare a codului în două locuri diferite. În loc să
procedaţi astfel, introduceţi o copie a codului într-o porţiune specială a blocului try. . .catch,
denumită finally.
Următorul exemplu prezintă modul cum este structurat un bloc try. . .catch . . . finally:
try {
citesteFisierText();
} catch (IOException e) {
// tratati erorile de intrare/iesire
} finally {
inchideFisierText() ;
}
Instrucţiunea finally este folositoare, de fapt, în afara excepţiilor; puteţi, de asemenea, să o
folosiţi pentru executarea unui cod de reiniţializare după o instrucţiune return, break sau continue
din cadrul unui ciclu. În ultimul caz puteţi folosi o instrucţiune try doar cu blocul finally, fără
blocul catch.

Declararea metodelor care pot semnala


exceptii
În exemplele anterioare aţi învăţat cum se tratează metodele care pot semnala excepţii (prin protejarea codului
şi interceptarea eventualelor excepţii). Compilatorul Java verifică dacă v-aţi ocupat într-un fel sau altul de excepţiile
care pot apărea - dar cum ştie el asupra căror excepţii să vă atragă atenţia?
Răspunsul este că metoda originală indică în semnătura sa excepţiile pe care le poate semnala. Puteţi folosi
acest mecanism în propriile metode, de fapt, este bine să procedaţi astfel pentru a vă asigura că utilizatorii vor fi
avertizaţi asupra excepţiilor ce pot apărea.
Pentru a indica faptul că o metodă poate semnala o excepţie, veţi folosi în definiţia sa o
clauză specială, denumită throws.

Clauza throws

Pentru a indica faptul că o porţiune a unei metode poate semnala o excepţie, este suficient să
adăugaţi cuvântul cheie throws după semnătura metodei (înainte de acolada deschisă) şi să
specificaţi numele excepţiilor pe care le poate semnala metoda dumneavoastră:
public boolean metodaMea(int x, int y) throws oExceptie {
// ...
}
Dacă metoda dumneavoastră poate semnala mai multe tipuri de excepţii, puteţi să le
specificaţi pe toate în clauza throws, separate prin virgule:
public boolean oAltaMetodaAMea(int x, int y)
throws oExceptie, oADouaExceptie, oATreiaExceptie {
// ...
}
La fel ca în cazul catch, puteţi folosi o superclasă a unui grup de excepţii pentru a indica faptul că metoda
dumneavoastră poate semnala oricare dintre subclasele acelei excepţii:
public void oAltaMetoda() throws IOException {
// .. .
}
Specificarea clauzei throws în definiţia metodei dumneavoastră nu înseamnă decât că
metoda poate semnala o excepţie dacă ceva merge prost, nu şi că va face acest lucru. Clauza throws
oferă doar o informaţie suplimentară referitoare la potenţialele excepţii şi permite compilatorului
Java să se asigure că metoda este corect folosită de utilizatori.
Manipularea datelor prin fluxuri Java

Majoritatea programelor pe care le creaţi în Java trebuie să interacţioneze cu anumite surse


de date. Există nenumărate metode de păstrare a informaţiilor pe un calculator, cum ar fi fişiere pe
hard disc sau pe CD-ROM, pagini într-un site Web sau chiar în memoria calculatorului.
Aţi putea crede că există diferite tehnici pentru manipularea datelor de pe diversele
dispozitive de stocare. Din fericire, nu este cazul. În Java, informaţiile pot fi stocate sau apelate
folosind un sistem de comunicaţie denumit flux (stream), implementat în pachetul java.io.
Fluxurile reprezintă un mecanism puternic de manipulare a datelor.

Introducere în fluxuri

Toate datele din Java sunt scrise sau citite folosind fluxuri. Fluxurile, aşa cum le arată şi denumirea, transportă
ceva dintr-un loc în altul.
Un flux (stream) reprezintă calea pe care o urmează datele într-un program. Un flux de
intrare transportă datele de la sursă la program, iar un flux de ieşire transportă datele din program
către o destinaţie.
Există două tipuri de fluxuri: fluxuri de octeţi şi fluxuri de caractere. Octeţii pot păstra
valori întregi din domeniul 0. . . 255. În acest format pot fi reprezentate o multitudine de date, cum
ar fi date numerice, programe executabile, comunicaţii Internet sau cod Java (bytecode) - adică
fişierele clasă care sunt executate pe o maşină virtuală Java. De fapt, orice tip de date poate fi
reprezentat printr-o serie de octeţi.
Fluxurile de caractere reprezintă un tip special de fluxuri de octeţi, care se folosesc numai
pentru datele de tip text (tipăribile). Ele diferă de fluxurile de octeţi prin faptul că setul de caractere
Java suportă codificarea Unicode, un standard prin care se pot reprezenta mai multe caractere decât
dacă s-ar folosi octeţi.
Toate datele de tip text, cum ar fi fişierele text, paginile Web sau alte formate de text,
trebuie să folosească fluxuri de caractere.
Folosirea unui flux
Indiferent dacă folosiţi fluxuri de octeţi sau de caractere, procedura este asemănătoare.
În cazul şirurilor de intrare, prima etapă constă în crearea unui obiect asociat cu sursa de
date. De exemplu, dacă sursa este un fişier de pe hard discul dumneavoastră, acestuia trebuie să i se
asocieze un obiect de tip FileInputStream.
Odată obţinut obiectul asociat fluxului, puteţi citi informaţii din acest flux folosind una
dintre metodele obiectului. Clasa FileInputStream posedă o metodă read(), care returnează un
octet citit din fişier.
Atunci când aţi terminat de citit informaţia din flux, trebuie să apelaţi metoda close(), pentru
a indica faptul că aţi terminat de folosit fluxul.
În cazul fluxurilor de ieşire, veţi începe prin a crea un obiect asociat cu destinaţia datelor.
Un astfel de obiect poate fi creat pornind de la clasa BufferedReader, care constituie o metodă
eficientă de creare a fişierelor text.
Metoda write() reprezintă cea mai simplă metodă de a transmite informaţii către destinaţia
unui flux. De exemplu, metoda write() aparţinând dasei BufferedReader poate transmite caractere
individuale unui flux de ieşire.
Ca şi în cazul fluxurilor de intrare, pentru un flux de ieşire trebuie apelată metoda close ()
atunci când nu mai există date de transmis.
Filtrarea unui flux
Cea mai simplă metodă de a folosi un flux este de a îl crea şi de a-i apela metodele de
transmitere sau de recepţie a datelor, în funcţie de rolul lui (flux de intrare sau flux de ieşire).
Majoritatea claselor folosite în prezent permit obţinerea unor rezultate mai sofisticate prin
asocierea fluxului cu un filtru, înainte de a citi sau scrie date.
Un filtru este un tip de flux care schimbă modul în care se lucrează cu un flux existent.
Procedura de folosire a unui filtru pentru un flux presupune următoarele:
• Crearea unui flux asociat cu sursa de date sau cu destinaţia datelor.
• Asocierea unui filtru cu acest flux.
• Citirea şi scrierea datelor de la/în filtru, şi nu direct în flux.
Metodele pe care le apelaţi în cazul filtrelor sunt aceleaşi cu cele pe care le apelaţi în cadrul
fluxurilor: există metodele read () şi write (), asemănătoare celor ale unui flux nefiltrat.
Puteţi chiar să asociaţi un filtru unui alt filtru, creând situaţii de genul: un flux de intrare este
asociat unui fişier text, este trecut printr-un filtru de traducere româno-englez şi, în final, este trimis
la destinaţie - o persoană care doreşte să îl citească.

Fluxuri de octeţi

Toate fluxurile de octeţi sunt subclase ale InputStream sau OutputStream. Aceste clase
sunt abstracte, deci nu puteţi obţine un flux prin crearea de obiecte direct din aceste clase. În
schimb, puteţi crea fluxuri prin folosirea unor subclase ale acestora, cum ar fi:
• FileInputStream şi FileOutputStream-Octeţi stocaţi în fişiere pe disc, pe CD-ROM sau pe
alte dispozitive de stocare.
• DataInputStream şi DataOutputStream - Un flux de octeţi filtrat, din care pot fi citite date
de tip întreg sau în virgulă mobilă (float). InputStream este superclasa tuturor fluxurilor de intrare.

Fluxuri de fişiere
Fluxurile de octeţi cu care veţi lucra de obicei sunt fluxuri de fişiere folosite pentru
transferul de date între program şi fişierele aflate pe hard discuri, pe CD-ROM sau pe alte
dispozitive de stocare ce pot fi referite printr-o cale de director şi un nume.
Puteţi transmite octeţi unui flux de ieşire şi puteţi citi octeţi dintr-un flux de intrare.
Fluxuri de intrare din fişiere
Un flux de intrare din fişier poate fi creat cu ajutorul constructorului FileInputStream
(String). Şirul transmis ca argument trebuie să fie numele fişierului. Puteţi include şi calea către
fişier, în cazurile în care acesta se află în alt director decât cel care conţine clasa. Următoarea
instrucţiune creează un flux de intrare pentru fişierul date.dat:
FileInputStream fis = new FileInputStream("date.dat") ;
După crearea fluxului de intrare din fişier, puteţi citi octeţi din acesta apelându-i metoda
read (). Această metodă returnează o valoare întreagă ce conţine următorul octet din flux. Dacă
metoda retumează -1, care nu este o valoare acceptată pentru un octet, înseamnă că s-a ajuns la
sfârşitul fluxului de fişier.
Pentru a citi mai mulţi octeţi din flux se foloseşte metoda read(byte[], int, int), care posedă
următoarele argumente:
• Un tablou de octeţi în care se vor memora datele
• Elementul din cadrul tabloului unde se va stoca primul octet de date
• Numărul de octeţi care se vor citi
Spre deosebire de alte metode read (), aceasta nu retumează date din flux, ci un întreg care
reprezintă numărul de octeţi citiţi sau -1 dacă nu s-au citit octeţi şi s-a ajuns la sfârşitul fluxului.
Următoarele instrucţiuni folosesc un ciclu while pentru a citi datele din obiectul df de tip
FileInputStream:
int octetNou = 0;
while (octetNou != -1) {
octetNou = df.read();
System.out.println(octetNou +" ”);
}
Această buclă citeşte întregul fişier referit de obiectul df, câte un octet o dată, şi afişează valoarea
fiecărui octet, urmată de un spaţiu. De asemenea, când se ajunge la sfârşitul fişierului va fi afişată
valoarea -1, lucru pe care îl puteţi evita folosind un test if.
Fluxuri de ieşire în fişiere
Un flux de ieşire în fişier poate fi creat folosind constructorul FileOutputStream (String).
Utilizarea sa este identică cu cea a constructorului FileInputStream (String), deci puteţi specifica
şi calea până la fişier, nu numai numele acestuia.
Trebuie să aveţi grijă atunci când specificaţi fişierul în care scrieţi. Dacă folosiţi numele unui fişier deja existent, o dată
cu începerea scrierii datelor, acesta va fi şters definitiv.
Puteţi crea un flux de ieşire în fişier care să adauge date (append) la sfârşitul unui fişier existent
folosind constructorul FileOutputStream(String, boolean). Şirul specifică numele fişierului, iar
argumentul boolean, dacă are valoarea true, va adăuga datele la sfârşitul fişierului, în loc să
suprascrie datele existente.
Pentru a scrie octeţi într-un fişier se foloseşte metoda write ( int) a fluxului de ieşire. După ce s-a
scris ultimul octet, fluxul trebuie închis folosind metoda close ().
Pentru a scrie mai mulţi octeţi se poate folosi metoda write(byte[], int, int), care funcţionează
similar metodei read (byte[], int, int) descrisă anterior. Argumentele acestei metode sunt tabloul
care conţine octeţii ce vor fi scrişi, poziţia de început din tablou şi numărul de octeţi ce trebuie
scrişi.

Filtrarea fluxurilor

Fluxurile filtrate sunt fluxuri care modifică informaţia trimisă printr-un flux existent.
Acestea sunt create folosind subclase ale FilterInputStream sau FilterOutputStream.
Aceste filtre nu realizează singure nici un fel de operaţie de filtrare. De fapt, ele posedă subclase, cum ar fi
BufferInputStream sau DataOutputStream, care sunt folosite pentru anumite tipuri de filtrări.

Filtre de octeti

Informaţia este furnizată mai rapid dacă poate fi transmisă în blocuri de date mai mari, chiar
dacă aceste blocuri sunt recepţionate mai repede decât pot fi procesate.
Un tampon (buffer) reprezintă o zonă în care se pot păstra date înainte de a fi nevoie să fie
citite sau scrise de un program. Prin folosirea unui tampon puteţi avea acces la date fără să accesaţi
din nou sursa originală de date.
Fluxuri cu tampon
Un flux de intrare cu tampon umple un tampon cu date care nu au fost încă procesate; când
programul are nevoie de aceste date, le caută în zona tampon înainte de a apela fluxul sursă original.
Această tehnică este mai eficientă. Această descongestionare a fluxului nu face altceva decât să
uşureze eforturile de folosire a acestuia. Fluxurile de octeţi folosesc clasele BufferedInputStream
şi BufferedOutputStream.
Fluxurile de intrare cu tampon sunt create folosind unul din constructorii:
• BufferedInputStream(InputStream) Creează un flux de intrare cu tampon pentru obiectul
InputStream specificat.
• BufferedInputStream(InputStream, int) Creează fluxul de intrare cu tampon InputStream
specificat, având o dimensiune a zonei tampon egală cu valoarea argumentului întreg.
Cea mai uşoară modalitate de a citi date dintr-un flux de intrare cu tampon este de a apela metoda read () a acestuia,
fără argumente; aceasta retumează o valoare întreagă între 0 şi 255, care reprezintă următorul octet din flux. Dacă s-a
ajuns la sfârşitul fluxului şi nu s-a găsit nici un octet, se retumează valoarea -1.
Puteţi, de asemenea, folosi metoda read(byte [] , int, int), care este disponibilă şi pentru alte fluxuri
de intrare, caz în care datele sunt încărcate într-un tablou de octeţi.
Un flux de ieşire cu tampon este creat prin folosirea următorilor constructori:
• BufferedOutputStream(OutputStream) Creează un flux de ieşire cu tampon pentru obiectul
OutputStream specificat.
• BufferedOutputStream(OutputStream, int) Creează fluxul de ieşire cu tampon OutputStream
specificat, alocând o zonă tampon de dimensiunea specificată prin argumentul int.
Pentru a scrie un singur octet în fluxul de ieşire poate fi folosită metoda write(int) a acestuia, iar
pentru a scrie mai mulţi octeţi dintr-o dată se poate folosi metoda write (byte [ ], int, int).
Argumentele acestei metode sunt tabloul de octeţi, poziţia iniţială a acestuia şi numărul de octeţi
care trebuie transmis.
Atunci când datele sunt direcţionate într-un flux cu tampon, conţinutul acestuia nu va fi transmis
către destinaţie decât după ce zona tampon s-a umplut sau dacă se apelează explicit metoda flush()
a fluxului.
Fluxuri de date
Dacă trebuie să lucraţi cu date care nu sunt reprezentate drept octeţi sau caractere, puteţi
folosi fluxuri de date de intrare şi de ieşire. Aceste fluxuri filtrează un flux de octeţi existent astfel
încât următoarele tipuri de date să poată fi citite direct din flux: boolean, byte, double, float, int,
long şi short.
Un flux de date de intrare este creat folosind constructorul DataInputStream (InputStream).
Argumentul trebuie să fie un flux de intrare existent, de exemplu, un flux de intrare cu tampon sau
un flux de intrare din fişier. Reciproc, un flux de date de ieşire necesită utilizarea constructorului
DataOutputStream (OutputStream), care foloseşte ca argument fluxul de ieşire asociat.
Următoarea listă prezintă metodele de citire şi de scriere ce pot fi folosite pentru fluxurile de date de
intrare, respectiv de ieşire:
• readBoolean(), writeBoolean(boolean)
• readByte(), writeByte (int)
• readDouble(), writeDouble(double) :
• readFloat(), writeFloat(float)
• readInt(), writeInt(int)
• readLong(), writeLong(long)
• readShort(), writeShort(int)
Fiecare dintre metodele de intrare returnează tipul de dată primitivă indicat de numele metodei. De
exemplu, metoda readFloat () retumează o valoare float.
Există şi metodele readUnsignedByte () şi readUnsignedShort (), care pot citi valori de tip octet
sau întregi scurţi fără semn. Aceste tipuri de date nu sunt suportate în Java, deci vor fi retumate ca
valori întregi.
Octeţii fără semn iau valori de la 0 la 255. Aceasta diferă de tipul de variabilă byte din Java, care ia valori între -128 şi
127. în acelaşi fel, o variabilă întreagă scurtă fără semn ia valori între 0 şi 65535, în loc de domeniul -32768 şi 32767,
suportat de tipul short în Java.
Nu toate metodele de citire dintr-un flux de date de intrare retumează o valoare ce poate indica
faptul că s-a ajuns la sfârşitul fluxului. Vă mai puteţi aştepta la apariţia excepţiei EOFException
care este semnalată la atingerea sfârşitului unui flux. Ciclul în care se citesc date poate fi încadrat
într-un bloc try, iar instrucţiunea catch asociată trebuie să trateze doar excepţiile EOFException.
In cadrul blocului catch puteţi să apelaţi şi metoda close () şi să efectuaţi alte acţiuni de
reiniţializare.

Fluxuri de caractere

Aceste fluxuri se folosesc pentru lucrul cu orice text reprezentat în format ASCII sau
Unicode .
Clasele folosite pentru a citi şi scrie aceste fluxuri sunt subclase ale daselor Reader şi
Writer. Este indicat să folosiţi aceste clase pentru lucrul cu text, şi nu fluxuri de octeţi.
Tehnicile pentru lucrul cu fluxuri de caractere s-au îmbunătăţit considerabil în versiunile
ulterioare Java 1.02, o dată cu introducerea claselor Reader şi Writer şi a subclaselor acestora; ele
oferă suport pentru setul de caractere Unicode şi permit o manevrare mai uşoară a textelor. Un
applet java compatibil cu versiunea 1.02 poate citi caracterele folosind clasele pentru fluxuri de
octeţi descrise anterior.
Citirea fişierelor text
Principala clasă folosită pentru citirea de fluxuri de caractere dintr-un fişier este FileReader.
Această clasă moşteneşte clasa InputStreamReader, care citeşte un flux de octeţi şi îi converteşte
în valorile întregi corespunzătoare caracterelor Unicode.
Un flux de intrare de caractere este asociat unui fişier folosind constructorul FileReader (String).
Şirul reprezintă numele fişierului şi poate conţine şi calea până la acesta.
Următoarea instrucţiune creează un obiect FileReader denumit web şi îl asociază cu fişierul text
denumit index. html:
FileReader web = new FileReader("index.html”);
După ce aţi obţinut un obiect de acest tip, puteţi să apelaţi următoarele metode de citire a
caracterelor din fişier:
• read () returnează următorul caracter din flux, ca valoare întreagă
• read (char [ ], int, int) citeşte caractere într-un tablou şi primeşte ca argumente tabloul de
caractere, poziţia de începere şi numărul de caractere de citit. A doua metodă funcţionează
asemănător echivalentului său din clasele de fluxuri de intrare de octeţi. În loc să returneze
caracterul următor din flux, aceasta returnează fie numărul de caractere citite, fie -1 dacă s-a ajuns
la sfârşitul fluxului şi nu s-a citit nimic.
Următoarea metodă încarcă un fişier text folosind obiectul FileReader şi afişează caracterele
conţinute de acesta:
FileReader text = new FileReader("readme.txt");
int octetCitit;
do {
octetCitit = text.read();
if (octetCitit != -1)
System.out.print( (char)octetCitit) ;
} while (octetCitit != -1) ;
System.out.println(" ");
text.close() ;
Deoarece metoda read () a unui flux de caractere returnează o valoare întreagă, trebuie să convertiţi
prin cast această valoare înainte de a o afişa, de a o salva într-un tablou sau de a o folosi într-un şir.
Fiecare caracter posedă un cod numeric care reprezintă poziţia sa în setul de caractere Unicode.
Valoarea întreagă citită din flux reprezintă chiar acest cod numeric. Dacă doriţi să citiţi din fişier o
întreagă linie de text, şi nu caracter cu caracter, puteţi folosi o combinaţie a daselor FileReader şi
BufferedReader.
Clasa BufferedReader citeşte un caracter din fluxul de intrare şi îl memorează într-o zonă tampon,
pentru mai multă eficienţă. Pentru a crea o versiune care foloseşte tampon, trebuie să existe un
obiect de tip Reader. Pentru crearea obiectului BufferedReader se pot folosi următorii
constructori:
• BufferedReader(Reader) Creează un flux de caractere cu zonă tampon, asociat obiectului
Reader specificat (de exemplu FileReader).
• BufferedReader (Reader, int) Creează un flux de caractere asodat obiectului Reader, cu o zonă
tampon de dimensiunea specificată de argumentul întreg.
Dintr-un flux de caractere cu tampon se poate citi folosind metodele read () şi read(char[ ] , int,
int), asemănătoare celor descrise pentru FileReader. Puteţi citi o întreagă linie de text folosind
metoda readLine ().
Metoda readLine () returnează un obiect String care conţine următoarea linie de text din flux, fără a
include şi caracterul (sau caracterele) care reprezintă sfârşitul de linie. Dacă se ajunge la sfârşitul
fluxului, valoarea şirului returnat va fi null.
Sfârşitul de linie este indicat astfel:
• Un caracter de linie nouă (newline - '\n')
• Un caracter de retur de car (carriage return – ’\r’)
• Un retur de car urmat de o linie nouă
Scrierea în fişiere text
Clasa FileWriter este folosită pentru scrierea unui flux de caractere într-un fişier. Aceasta
este o subclasă a OutputStreamWriter, care are rolul de a converti codurile caracterelor Unicode
în octeţi.
Există doi constructori FileWriter: FileWriter (String) şi FileWriter (String, boolean).
Şirul din primul argument reprezintă numele fişierului către care va fi direcţionat fluxul de caractere
şi poate conţine şi calea. Argumentul opţional de tip boolean trebuie să ia valoarea true dacă se
doreşte ca datele să fie adăugate la sfârşitul unui fişier existent. Ca şi în cazul altor clase de scriere
în fluxuri, trebuie să aveţi grijă să nu suprascrieţi accidental un fişier existent.
Clasa FileWriter conţine trei metode ce pot fi folosite pentru a scrie date într-un flux:
• write(int) Scrie un caracter.
• write(char [], int, int) Scrie caracterele din tabloul specificat, începând de la poziţia dată şi
având numărul de caractere dat.
• write(String, int, int) Scrie caractere din şirul specificat, începând de la poziţia dată şi având
numărul de caractere dat.
Următorul exemplu scrie un flux de caractere într-un fişier folosind clasa FileWriter şi metoda
write (int):
FileWriter litere = new FileWriter("alfabet.txt");
for (int i = 65; i < 91; i++) litere.write((char)i) ;
litere.close() ;
Metoda close () este folosită pentru închiderea fluxului, după ce toate caracterele au fost trimise în
fişierul destinaţie. lată conţinutul fişierului alfabet.txt produs de acest cod:
ABCDEFGHIJKLMNOPQRSTUVXYZ
Clasa BufferedWriter poate fi folosită pentru scrierea într-un flux de caractere cu zonă tampon.
Obiectele acestei clase sunt create folosind constructorul BufferedWriter (Writer) sau
BufferedWriter (Writer, int). Argumentul Writer poate fi oricare dintre clasele de fluxuri de
caractere de ieşire, cum ar fi FileWriter. Al doilea argument, opţional, este o valoare întreagă ce
indică dimensiunea zonei tampon care va fi folosită.
BufferedWriter posedă aceleaşi trei metode de ieşire ca şi FileWriter: write(int),
write(char[], int, int) şi write(String, int, int).
0 altă metodă folositoare este newLine (), care trimite caracterul (sau caracterele) ce specifică
sfârşitul liniei pe platforma folosită pentru rularea programului.
Diferitele caractere de sfârşit de linie pot crea neplăceri la transferul unui fişier de pe un sistem de
operare pe altul, cum ar fi cazul când un utilizator Windows 95 copiază un fişier pe un server Web
care foloseşte sistemul de operare Linux. Folosind metoda newLine() în loc de un literal (cum ar fi
'\n'), puteţi utiliza programul dumneavoastră pe diferite platforme.
Metoda close () este apelată pentru a închide fluxul de caractere de ieşire şi pentru a asigura că toate
datele memorate în zona tampon au fost trimise către destinaţia fluxului.

Filtre de fişiere şi de nume de fişiere

În toate exemplele de până acum, pentru referirea la numele fişierului implicat într-o
operaţie cu fluxuri s-a folosit un şir. De multe ori, acest lucru este suficient, însă dacă doriţi să
copiaţi, să redenumiţi sau să realizaţi alte activităţi cu fişiere, puteţi folosi şi obiecte de tip File.
Clasa File, care face şi ea parte din pachetul java.io, reprezintă o referinţă către un fişier sau
un director. Pot fi folosiţi următorii constructori File:
• File ( String) Creează un obiect File pentru directorul specificat - nu este indicat nici un nume
de fişier, deci acesta se referă doar la un director.
• File(String, String) Creează un obiect File pentru directorul specificat şi pentru fişierul cu
numele specificat.
• File(File, String) Creează un obiect File a cărui cale este specificată de obiectul File şi al cărui
nume este dat de şirul specificat.
Într-un obiect de tip File puteţi apela mai multe metode folositoare. Metoda exists () returnează o
valoare boolean care arată dacă fişierul există sub numele şi în directorul specificate la crearea
obiectului File. Dacă fişierul există, puteţi folosi metoda length(), care retumează o valoare de tip
întreg long ce reprezintă dimensiunea fişierului în octeţi.
Metoda renameTo (File) redenumeşte fişierul sub numele specificat de argumentul File. Se
retumează o valoare boolean, care indică dacă operaţia s-a finalizat cu succes sau nu.
Metoda delete () sau deleteOnExit () se apelează pentru ştergerea unui fişier sau a unui folder.
Metoda delete () încearcă să facă imediat ştergerea (şi returnează o valoare boolean care indică
succesul operaţiei). Metoda deleteOnExit () aşteaptă şi încearcă să şteargă fişierul după ce restul
programului şi-a terminat execuţia. Această metodă nu retumează nici o valoare iar programul
trebuie să se termine la un moment dat pentru ca metoda să funcţioneze.
Metoda mkdir () se foloseşte pentru a crea un director specificat de obiectul File pentru care s-a
apelat. Aceasta retumează o valoare boolean care indică succesul sau eşecul operaţiei. Nu există o
metodă echivalentă pentru ştergerea directoarelor, deoarece metoda delete () poate fi folosită la fel
de bine şi pentru directoare şi pentru fişiere.
Ca în cazul tuturor operaţiilor care implică lucrul cu fişiere, aceste metode trebuie folosite cu grijă pentru a evita
ştergerea unor fişiere sau directoare sau suprascrierea unor fişiere. Nu există nici o metodă pentru recuperarea fişierelor
sau a directoarelor şterse.
Fiecare dintre aceste metode va semnala o excepţie SecurityException dacă programul nu are
permisiunile necesare pentru executarea operaţiilor respective, deci trebuie folosite blocuri try. . .
catch sau clauze throws pentru a trata aceste excepţii.
Proiectarea unei interfete utilizator cu ajutorul Swing

Swing, care face parte din biblioteca JFC (Java Foundation Classes), reprezintă o extensie
a pachetului AWT (Abstract Windowing Toolkit), care a fost integrată începând cu versiunea 2 a
Java.
Swing oferă o funcţionare îmbunătăţită faţă de predecesorul sau - noi componente, funcţii
avansate ale acestora, o mai bună tratare a evenimentelor, precum şi un aspect adaptabil.
Toate elementele Swing fac parte din pachetul javax.swing. Pentru a folosi o clasă Swing,
trebuie să folosiţi fie o instrucţiune import explicită, fie una generală, cum ar fi următoarea:
import javax.swing.* ;
Procesul de folosire a unei componente Swing nu este diferit de cel al folosiri
componentelor AWT. Veţi crea componenta apelând constructorul său, apelând, dacă este nevoie,
metodele componentei şi apoi adăugând componenta într-un container.
Toate componentele Swing sunt subclase ale clasei JComponent.

Cadrul unei aplicatii

Primul pas în crearea unei aplicaţii Swing constă în crearea unei subclase a clasei JFrame.
Clasa JFrame este o extensie a clasei Frame (cadru) şi poate fi folosită într-un mod asemănător

Adăugarea de componente într-un cadru Swing

Lucrul cu un obiect JFrame este mai complicat decât lucrul cu echivalentul său AWT, în
loc să adăugaţi containerele şi componentele direct în cadru, trebuie să le adăugaţi într-un container
intermediar, denumit panou de conţinut (content pane).
Un cadru JFrame este împărţit în mai multe panouri diferite. Panoul principal cu care
lucraţi este panoul de conţinut, care reprezintă aria completă a cadrului în care pot fi plasate
componente.
Pentru a adăuga o componentă în panoul de conţinut se procedează astfel:
• Creaţi un obiect JPanel (versiunea Swing a panoului - Panel).
• Adăugaţi componentele (care pot fi şi containere) în obiectul JPanel folosind
metoda add (Component) a acestuia.
• Definiţi acest obiect JPanel drept panou de conţinut folosind metoda
setContentPane(Container). Obiectul JPanel este singurul argument al metodei.

Lucrul cu Swing

Există componente Swing echivalente pentru toate componentele AWT pe care le-aţi
învăţat până acum. În majoritatea cazurilor, constructorii componentelor Swing sunt similari
constructorilor AWT, aşa că nu va fi nevoie să învăţaţi lucruri noi pentru a putea folosi
componentele Swing.
Pentru multe componente există şi alţi constructori, care primesc ca argument un obiect
Icon. O pictogramă (icon) este o imagine de dimensiuni mici, de obicei în format GIF, care poate
fi folosită pentru butoane, etichete sau alte elemente ale interfeţei pentru identificarea componentei.
Aceste pictograme sunt întâlnite aproape peste tot în sistemele de operare grafice cum sunt
Windows sau MacOS.
Un obiect Icon se creează în acelaşi fel ca un obiect Image. Constructorul primeşte ca unic
argument numele unui fişier sau o adresă URL. Următorul exemplu încarcă o pictogramă din
fişierul desen.gif şi creează un obiect JButton având ca etichetă pictograma respectivă.
ImageIcon fig = new ImageIcon(”desen.gif”);
JButton buton = new JButton(fig);
JPanel panou = new JPanel();
panou.add(buton);
setContentPane (panou) ;

Etichete

Etichetele sunt implementate în Swing folosind clasa JLabel.


Caracteristicile sunt asemănătoare cu ale etichetelor AWT, însă acum puteţi include şi
pictograme. În plus, alinierea unei etichete poate fi specificată folosind una din cele trei variabile
ale clasei
SwingConstants: LEFT, CENTER sau RIGHT.
Iată câteva metode constructor ce pot fi folosite:
• JLabel(String,int) Creează o etichetă cu şirul şi alinierea specificate.
• JLabel(String,Icon,int) Creează o etichetă cu textul, pictograma şi alinierea
specificate.

Butoane

Butoanele Swing sunt descrise în clasa JButton. Ele pot folosi o etichetă de text (la fel ca
butoanele AWT), o etichetă pictogramă sau o combinaţie a acestora.
lată câteva dintre metodele constructor ce pot fi folosite:
• JButton (String) Creează un buton cu textul specificat.
• JButton (Icon) Creează un buton cu pictograma specificată.
• JButton (String, Icon) Creează un buton cu textul şi pictograma specificate.

Câmpuri de text

Câmpurile de text sunt implementate în Swing folosind clasa JTextField. Diferenţa dintre
aceste câmpuri text şi echivalentul lor AWT este că metoda setEchoChar(char) nu mai este
suportată de JTextField pentru mascarea textului introdus.
lată ce metode constructor puteţi folosi:
• JTextField(int) Creează un câmp de text cu lăţimea specificată.
• JTextField(String, int) Creează un câmp de text cu textul şi lăţimea specificate.
Pentru crearea unui câmp de text care foloseşte caractere de mascare se foloseşte clasa
JPasswordField. Această clasă posedă aceleaşi metode constructor ca şi JTextField:
JPasswordField(int) şi JPasswordField(String, int).
O dată creat câmpul de text pentru parole, puteţi folosi metoda setEchoChar(char) pentru a masca
datele de intrare cu caracterul specificat.

Zone de text

Zonele de text sunt implementate în Swing folosind clasa JTextArea. Aceasta foloseşte
următoarele metode constructor;
• JTextArea (int, int) Creează o zona de text cu numărul de rânduri şi de coloane specificat.
• JTextArea (String, int, int) Creează o zonă de text cu textul, numărul de rânduri şi de
coloane specificate.

Casete de validare şi butoane radio

Clasa JCheckBox implementează în Swing casetele de validare. Comportamentul său este


acelaşi cu cel din AWT, având în plus posibilitatea de a folosi pictograme pentru etichetare.
Metodele constructor sunt următoarele:
• JCheckBox(String) Creează o casetă de validare cu eticheta text specificată.
• JCheckBox(String, boolean) Creează o casetă de validare cu eticheta text specificată, care
este selectată daca al doilea argument are valoarea true.
• JCheckBox(Icon) Creează o casetă de validare cu eticheta pictogramă specificată.
• JCheckBox (Icon, boolean) Creează o casetă de validare cu eticheta pictogramă
specificată,.
• JCheckBox (String, Icon) Creează o casetă de validare cu eticheta de text şi eticheta
pictogramă specificate, care este selectată dacă al doilea argument are valoarea true
• JCheckBox (String, Icon, boolean) Creează o casetă de validare cu eticheta de text şi
eticheta pictogramă specificate
Grupurile de casete de validare sunt implementate în Swing prin clasa ButtonGroup. Aşa
cum aţi văzut, la un moment dat nu poate fi selectată decât o singură componentă a unui grup de
casete de validare. Pentru aceasta, veţi crea un obiect ButtonGroup şi veţi folosi metoda add
(Component) pentru a adăuga o componentă în grup.
Butoanele radio sunt implementate în Swing prin intermediul clasei JRadioButton.
Metodele constructor sunt aceleaşi ca pentru clasa JCheckBox.
Schimbarea numelui din CheckboxGroup în ButtonGroup reflectă extensia în
funcţionalitate - pot fi grupate atât butoanele radio, cât şi butoanele.

Liste de opţiuni

Listele de opţiuni, care în AWT erau create folosind clasa Choice, sunt disponibile acum
prin intermediul clasei JComboBox.
O listă de opţiuni este creată parcurgând următoarele etape:
1. Se foloseşte constructorul JComboBox() fără nici un argument.
2. Se foloseşte metoda addItem(Obiect) a casetei combo pentru a adăuga elemente în listă.
3. Se foloseşte metoda setEditable(boolean) a casetei combo, cu argumentul având
valoarea false.
Această ultimă metodă transformă caseta combo într-o listă de opţiuni - singurele opţiuni
disponibile pentru utilizator sunt elementele conţinute de listă.
Când caseta combo este editabilă permite introducerea de text din partea utilizatorului, în locul
alegerii unei opţiuni din listă. De la această combinaţie provine şi numele casetei (combo).
Bare de derulare

Barele de derulare sunt implementate în Swing folosind clasa JScrollBar. Funcţionarea


acestora este identică cu cea a barelor de derulare AWT; puteţi folosi următoarele metode
constructor:
• JScrollBar(int) Creează o bară de derulare cu orientarea specificată.
• JScrollBar(int, int, int, int, int) Creează o bară de derulare cu orientarea, valoarea
iniţială, dimensiunea casetei de derulare, valoarea minimă şi valoarea maximă specificate.
Orientarea este specificată prin variabilele clasei SwingConstants, HORIZONTAL sau
VERTICAL.

Noile caracteristici ale Swing

În afară de faptul că extinde modul de funcţionare a componentelor şi a containerelor faţă de


Abstract Windowing Toolkit, Swing oferă şi alte caracteristici complet noi, cum ar fi: aspect (look
and feel) configurabil, secvenţe de taste de accelerare, sfaturi dependente de context (ToolTips) şi
casete de dialog standard.

Stabilirea aspectului

Swing posedă un administrator de interfaţă care controlează aspectul componentelor - modul


în care butoanele, etichetele şi celelalte elemente sunt reprezentate pe ecran.
Administrarea aspectului este sarcina clasei UIManager, care face parte din pachetul
javax.swing.*. Opţiunile pentru aspect diferă în funcţie de mediul de dezvoltare folosit. In Java 2
sunt disponibile următoarele opţiuni:
• Un aspect caracteristic Windows 95 sau Windows NT
• Un aspect caracteristic sistemului Motif X-Window
• Metal, un nou aspect Swing, independent de platformă
Clasa UIManager posedă metoda setLookAndFeel(LookAndFeel), care este folosită
pentru alegerea aspectului unui program.
Pentru a obţine un obiect LookAndFeel care poate fi transmis ca argument metodei
setLookAndFeel() puteţi folosi una dintre metodele UIManager:
• getCrossPlatformLookAndFeelClassName() Această metodă returnează un obiect
LookAndFeel care reprezintă aspectul Metal, independent de platformă.
• getSystemLookAndFeelClassName() Această metodă returnează un obiect
LookAndFeel care reprezintă un obiect caracteristic sistemului.
Daca nu poate stabili aspectul interfeţei, metoda setLookAndFeel() semnalează o excepţie
UnsupportedLookAndFeel.
Următoarele instrucţiuni pot fi folosite pentru a stabili un aspect Metal în orice program:
try {
UIManager.setLookAndFeel(
UIManager.getCrossPlatformLookAndFeelClassName ()) ;
} catch (Exception e) {
System.err.println(”Nu pot stabili aspectul:” + e) ;
}
Pentru a stabili aspectul sistemului, în cadrul metodei setLookAndFeel se foloseste metoda
getSystemLookAndFeelClassName(). Aceasta are rezultate diferite pe sisteme de operare diferite.
Folosind metoda getSystemLookAndFeelClassName(), un utilizator de Windows 95 va obţine un
aspect Windows 95, iar un utilizator UNIX va obţine un aspect Motif.

Secvente de taste de accelerare

O secvenţă de taste de accelerare (key accelerator), denumită şi secvenţă de prescurtare


(keyboard mnemonic), reprezintă o grupare de taste care poate fi folosită pentru a controla
comportamentul unei componente a interfeţei utilizator. Aceste taste permit folosirea unui program
fără ajutorul mouse-ului; tehnica este înglobată în Swing ca parte a suportului pentru persoane cu
nevoi speciale - clase care permit folosirea programelor Java de către nevăzători sau alte persoane
cu incapacităţi fizice.
Tastele de accelerare simulează acţiunea mouse-ului, iar modul lor de folosire depinde de
platforma folosită. Pe un calculator care rulează Windows 95, tastele de accelerare sunt disponibile
apăsând tasta Alt împreună cu o altă tastă.
Tastele de accelerare sunt stabilite prin apelul metodei setMnemonic(char) a componentei
care va fi controlată prin secvenţa respectivă. Argumentul char este tasta care va fi folosită în
combinaţia de accelerare. Următorul exemplu creează un obiect JButton şi asociază caracterul ‘i’
ca parte a secvenţei de accelerare pentru acesta:
JButton butonInfo = new Jbutton ("Informatii");
butonInfo.setMnemonic(‘i’) ;
Apăsarea combinaţiei Alt+I este echivalentă cu executarea unui clic pe componenta
butonInfo.

Sfaturi dependente de context

O altă modalitate de a face ca programul să fie mai prietenos cu utilizatorul este de a asocia
sfaturi dependente de context (ToolTips) cu componentele unei interfeţe. Elementele TooITip sunt
folosite pentru a descrie scopul unei componente. Atunci când învăţaţi să folosiţi un program pentru
prima dată, aceste sfaturi reprezintă un ajutor binevenit.
Pentru a asocia un sfat ToolTip cu o componentă, apelaţi metoda setToolTipText (String)
pentru componenta respectivă. Şirul ar trebui să conţină o descriere sumară a rolului componentei.
Următorul exemplu creează o componentă JScrollBar şi îi asociază acesteia un element
ToolTip.
JScrollBar viteza = new JscrollBar();
viteza.setToolTipText("Modifica viteza de animatie”);
Textul ToolTip se poate întinde pe o singură linie, aşa că nu puteţi folosi caracterul linie
nouă (newline – ‘\n’) pentru a scrie mai multe rânduri.

Descrieri şi nume de componente


O altă metodă de a face mai accesibilă o interfaţă este de a oferi o descriere text pentru
componentele Swing. Această tehnică presupune două etape:
1. Obţineţi obiectul AccessibleContext asociat componentei prin apelarea metodei
getAccessibleContext() a acesteia.
2. Apelaţi metoda setAccessibleDescription(String) pentru obiectul AccessibleContext.
Argumentul şir trebuie să conţină textul descrierii componentei.
De exemplu, următorul exemplu stabileşte descrierea unui obiect JButton:
JButton terminare = new JButton("Terminare");
terminare.getAccessibleContext().setAccessibleDescription( "C
ând executati clic pe acest buton, programul se va termina”);
Metoda setAccessibleName (String) funcţionează în acelaşi fel ca metoda
setAccessibleDescription (String). Aceasta poate fi folosită pentru a da componentei un nume care
să descrie pe scurt rolul ei. În cazul butonului Terminare din exemplul anterior, numele de "Buton
de terminare" ar fi foarte potrivit. Următorul exemplu stabileşte pentru câmpul de text cu numele
"Camp text":
JTextField ct = new JTextField();
ct.getAccessibleContext().setAccessibleName("Camp text”);

Casete de dialog standard

Clasa JOptionPane oferă mai multe metode care pot fi folosite pentru a crea casete de
dialog standard: mici ferestre în care se pune o întrebare, se atrage atenţia utilizatorului sau se
afişează un scurt mesaj informativ.
Fără îndoială că aţi mai văzut casete de dialog de acest fel - atunci când sistemul
dumneavoastră se blochează, apare o casetă de dialog care vă anunţă veştile proaste. Sau atunci
când ştergeţi fişiere se poate folosi, de asemenea, o casetă de dialog, care să vă întrebe încă o dată
dacă chiar doriţi să faceţi acest lucru. Aceste ferestre reprezintă o modalitate eficientă de
comunicare cu utilizatorul, fară efortul suplimentar de a crea o clasă nouă care să reprezinte
fereastra, de a adăuga componente în ea şi de a scrie metode de tratare a evenimentelor pentru
preluarea datelor de intrare. Dacă se foloseşte una dintre casetele de dialog standard oferite de dasa
JOptionPane, toate aceste lucruri sunt realizate automat.
Există patru tipuri de casete de dialog standard:
• ConfirmDialog O casetă de dialog care pune o întrebare şi posedă trei
butoane, corespunzătoare răspunsurilor Yes, No şi Cancel.
• InputDialog O casetă de dialog care aşteaptă introducerea unui text.
• MessageDialog O casetă de dialog care afişează un mesaj.
• OptionDialog O casetă de dialog care cuprinde toate celelalte trei tipuri.
Fiecare dintre aceste casete de dialog posedă propria metodă în cadrul clasei
JOptionPane.

Casete de dialog de confirmare

Cea mai simplă metodă de creare a unei casete de dialog Yes/No/Cancel (Da/Nu/Anulare)
este de a folosi metoda showConfirmDialog (Component, Object). Argumentul Component
specifică containerul care va fi considerat părintele casetei de dialog; această informaţie este
folosită pentru a determina unde se va afişa pe ecran fereastra de dialog. Dacă se foloseşte valoarea
null pentru acest argument sau containerul nu este un obiect Frame, caseta de dialog va fi afişată în
centrul ecranului.
Al doilea argument poate fi un şir, o componentă sau o pictogramă. Dacă este un şir de text,
acesta va fi afişat în cadrul casetei de dialog. Dacă este o altă componentă sau o pictograma, în locul
mesajului text va fi afişat obiectul respectiv.
Această metodă returnează una dintre cele trei valori posibile, reprezentate prin întregi, care
sunt variabile de clasă ale JOptionPane: YES_OPTION, NO_OPTION sau CANCEL_OPTION.
În continuare se prezintă un exemplu care foloseşte o casetă de dialog de confirmare cu un
mesaj text şi memorează răspunsul în variabila raspuns:
int raspuns;
raspuns = JOptionPane.showConfirmDialog(null, "Pot sa sterg
toate fisierele dumneavoastra personale confidentiale?”);
Există o altă metodă ce oferă mai multe opţiuni pentru dialogul de confirmare:
showConfirmDialog(Component, Object, String, int, int). Primele două argumente sunt
aceleaşi ca pentru cealaltă metodă, iar ultimele trei sunt următoarele:
• Un şir care va fi afişat ca bară de titlu a casetei de dialog.
• Un întreg care indică ce butoane vor fi afişate. Acesta trebuie să aibă valoarea egală
cu una dintre variabilele de clasă YES_NO_CANCEL_OPTION sau YES_NO_OPTION.
• Un întreg care descrie tipul de casetă de dialog cu ajutorul variabilelor de clasă
ERROR_MESSAGE, INFORMATION_MESSAGE, PLAIN_MESSAGE,
QUESTION_MESSAGE sau WARNING_MESSAGE. Acest argument este folosit pentru a
determina care pictogramă se afişează în caseta de dialog, lângă mesaj.
int raspuns = JOptionPane.showConfirmDialog(null, "Error reading file. Want to try
again?", "File Input Error.", JoptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE);

Casete de dialog de intrare

O casetă de dialog de intrare afişează o întrebare şi foloseşte un câmp de text pentru a


memora răspunsul.
Cea mai simplă modalitate de a crea o casetă de dialog de intrare este de a apela metoda
showInputDialogBox (Component, Object). Argumentele sunt componenta părinte, respectiv şirul,
componenta sau pictograma care se vor afişa în casetă.
Exemplu:
String raspuns = JOptionPane.showInputDialogBox (null,"Enter your
name:");
Puteţi crea o casetă de dialog de intrare şi cu ajutorul metodei showInputDialog
(Component, Object, String, int). Primele două argumente sunt aceleaşi cu ale metodei anterioare,
iar ultimele două reprezintă:
• Titlul afişat în bara de titlu a casetei de dialog.
• Una din cele cinci variabile de clasă care descriu tipul casetei de dialog:
ERROR_MESSAGE, INFORMATION_MESSAGE, PLAIN_MESSAGE,
QUESTION_MESSAGE sau WARNING_MESSAGE.
Următoarea instrucţiune creează o caseta de dialog de intrare folosind această metodă:
String raspuns=JOptionPane.showInputDialog(null, "Care este codul
dunineavoastra postal?", "Introduceti codul postal,
JOptionPane.QUESTION_MESSAGE);

Casete de dialog pentru mesaje

Caseta de dialog pentru mesaje este o fereastră simplă în care se afişează o informaţie.
O casetă de dialog pentru mesaje poate fi creată printr-un apel al metodei
showMessageDialog (Component, Object). Ca şi în cazul celorlalte casete de dialog, argumentele
sunt componenta părinte, respectiv şirul, componenta sau pictograma care se vor afişa.
Spre deosebire de alte casete de dialog, casetele pentru mesaje nu întorc nici un fel de
valoare de răspuns
Exemplu:
JOptionPane.showMessageDialog(null, "The program has been
uninstalled.") ;
Mai puteţi crea o astfel de casetă de mesaje şi cu ajutorul metodei
showMessageDialogComponent(Object, String, int). Modul de folosire este identic cu al metodei
showInputDialog(), cu aceleaşi argumente, cu excepţia faptului că metoda showMessageDialog()
nu returnează nici o valoare.
Instrucţiunea următoare creează o casetă de dialog pentru mesaje folosind această ultimă
metodă:
JOptionPane.showMessageDialog(null,"Un asteroid a distrus Pamantul",
"Alerta: pericol de asteroizi", JOptionPane.WARNING_MESSAGE) ;

Casete de dialog cu opţiuni

Cea mai complexă casetă de dialog este caseta de dialog cu opţiuni, care combină
caracteristicile tuturor celorlalte casete de dialog. Această casetă poate fi creată folosind metoda
showOptionDialog (Component, Object, String, int, int, Icon, Object [ ], Object);
Argumentele acestei metode sunt prezentate în continuare:
• Componenta părinte a casetei de dialog
• Textul, pictograma sau componenta de afişat
• Şirul ce va fi afişat în bara de titlu
• Tipul casetei, folosind variabilele de clasă YES_NO_OPTION,
YES_NO_CANCEL_OPTION sau literalul 0, dacă vor fi folosite alte butoane.
• Pictograma care va fi afişată; se folosesc variabilele de clasă ERROR_MESSAGE,
INFORMATION_MESSAGE, PLAIN_MESSAGE, QUESTION_MESSAGE,
WARNING_MESSAGE sau literalul 0, dacă nu se foloseşte nici una dintre acestea
• Un obiect Icon care va fi afişat în locul uneia dintre pictogramele stabilite de argumentul
anterior
• Un tablou de obiecte care păstrează componentele sau alte obiecte ce vor reprezenta
opţiunile casetei de dialog, dacă nu se folosesc valorile YES_NO_OPTION sau
YES_NO_CANCEL_OPTION
• Obiectul care reprezinta selecţia prestabilită, în cazul în care nu se folosesc opţiunile
YES_NO_OPTION sau YES_NO_CANCEL_OPTION
Ultimele două argumente vă permit să creaţi o gamă largă de opţiuni pentru caseta de
dialog. Puteţi crea un tablou de butoane, de etichete, de câmpuri de text sau chiar o combinaţie de
diferite obiecte. Aceste componente vor fi afişate folosind administratorul de dispunere secvenţial
(flow manager); nu există nici o modalitate de a specifica un alt administrator de dispunere în
cadrul casetei de dialog.

Tratarea evenimentelor utilizator cu ajutorul Swing

Pentru a face dintr-o interfaţă Java funcţională un program Java funcţional, va trebui ca
interfaţa să răspundă la evenimentele produse de utilizator.
Swing tratează evenimentele în mod diferit, folosind un set de clase denumite interceptoare
de evenimente (event listeners).

Evenimentul principal

În sistemul de tratare a evenimentelor, evenimentele erau prelucrate prin intermediul unui


set de metode disponibile tuturor componentelor. Metode cum ar fi mouseDown(), keyDown() sau
action() puteau fi suprascrise de orice program AWT care dorea să trateze aceste evenimente.
Aceste sistem de tratare a evenimentelor este caracteristic doar Java 1.02, deoarece în următoarele
versiuni ale limbajului a fost introdusă o versiune mult îmbunătăţită a acestuia.
Acest nou sistem se foloseşte în aplicaţiile Swing.

Interceptoare de evenimente

Dacă o clasă doreşte să răspundă unui eveniment utilizator conform sistemului Java 2 de
tratare a evenimentelor, ea trebuie să implementeze o interfaţă care să prelucreze evenimentele.
Aceste interfeţe se numesc interceptoare de evenimente (event listeners).
Fiecare interceptor tratează un anumit tip de eveniment, iar o clasă poate implementa oricâte
evenimente are nevoie.
Sunt disponibile următoarele interceptoare de evenimente:
• ActionListener Tratează evenimente de acţiune, care sunt generate de acţiunea unui
utilizator asupra unei componente cum ar fi execuţia unui clic pe un buton.
• AdjustmentListener Tratează evenimente de modificare, care sunt generate de
modificarea unei componente, cum ar fi deplasarea unei bare de derulare,
• FocusListener Tratează evenimente de selecţionare, care sunt generate atunci când o
componentă, cum ar fi un câmp de text, devine selectată sau pierde acest atribut.
• ItemListener Tratează evenimente de element, care sunt generate atund când este
modificată starea unui element cum ar fi o casetă de validare.
• KeyListener Tratează evenimente de tastatură, care apar atunci când un utilizator
introduce date prin intermediul tastaturii.
• MouseListener Tratează evenimente de mouse, care sunt generate de clicuri cu
mouse-ul, de pătrunderea indicatorului mouse-ului pe suprafaţa unei componente sau de părăsirea
acesteia.
• MouseMotionListener Tratează evenimente de deplasare a mouse-ului, care sunt
folosite pentru a memora toate mişcarile mouse-ului pe o componenta.
• WindowListener Tratează evenimente de ferestre, care sunt generate de maximizarea,
minimizarea, mutarea sau închiderea ferestrelor.
Următoarea clasă este declarată astfel încât să poată trata atât evenimentele de acţiune, cât şi
pe cele de text;
public class Test extends JFrame implements ActionListener,
TextListener {
// . . .
}
Pacherul java.awt.event conţine toate interceptoarele principale de evenimente, precum şi
pe cele asociate unor evenimente specifice. Pentru a folosi aceste clase în programele
dumneavoastră puteţi să le importaţi individual sau să folosiţi o instrucţiune de tipul:
import java.awt.event.*;

Configurarea componentelor
Prin definirea unei clase ca interceptor de evenimente aţi stabilit faptul că aceasta poate „asculta"
(intercepta) un anumit tip de eveniment. Totuşi nu se va întâmpla nimic dacă nu continuaţi cu un al doilea
pas: trebuie să asociaţi componentei un interceptor de acelaşi tip; la folosirea componentei, acesta va
genera evenimentele respective.
După crearea unei componente, pentru a o asocia cu un anumit tip de interceptor puteţi apela
una din următoarele metode:
• addActionListener() Pentru componente JButton, JCheckBox, JComboBox,
JTextField şi JRadioButton
• addAdjustmentListener() Pentru componente JScrollBar
• addFocusListener() Pentru toate componentele Swing
• addItemListener() Pentru componente JButton, JCheckBox, JComboBox şi
JRadioButton
• addKeyListener() Pentru toate componentele Swing
• addMouseListener() Pentru toate componentele Swing
• addMouseMotionListener() Pentru toate componentele Swing
• addWindowListener() Pentru toate componentele JWindow şi JFrame
O greşeală des întâlnită în programele Java o constituie modificarea unei componente după
introducerea acesteia într-un container. Metodele de interceptare şi celelalte configurări trebuie
stabilite pentru o componentă înainte de a o adăuga într-un container; altfel toate acestea vor fi
ignorate la execuţia programului.
Următorul exemplu creează un obiect JButton şi îi asociază un interceptor de evenimente de
acţiune:
JButton terminare = new JButton("Terminare");
terminare.addActionListener(this) ;
Toate metodele add. . .() folosesc un singur argument: obiectul care interceptează
evenimentele de tipul respectiv. Folosirea cuvântului cheie this indică faptul că obiectul interceptor
este chiar clasa curentă. Puteţi specifica şi alte obiecte, atât timp cât clasa respectivă implementează
interfaţa de interceptare corespunzătoare.

Metode de tratare a evenimentelor

Atunci când asociaţi o interfaţă unei clase, clasa trebuie să implementeze toate metodele
conţinute de interfaţă.
În cazul interceptoarelor de evenimente, fiecare metodă este apelată automat de sistemul de
ferestre atunci când are loc evenimentul corespunzător.
Interfaţa ActionListener posedă o singură metodă: actionPerformed(). Toate clasele care
implementează ActionListener trebuie să conţină o metodă cu următoarea structură:
public void actionPerformed(ActionEvent evt) {
// aici se trateaza evenimentul
}
Dacă în cadrul interfeţei grafice utilizator există o singură componentă care are asociat un
interceptor de evenimente de acţiune, atunci metoda actionPerformed() poate fi folosită pentru a
răspunde la un eveniment generat de componentă.
Dacă mai multe componente au asociate interceptoare de evenimente de acţiune, trebuie să
folosiţi metoda pentru a afla mai întâi care dintre componente a fost folosită, după care să acţionaţi
corespunzător.
În metoda actionPerformed() se transmite ca argument un obiect ActionEvent. Acest
obiect poate fi folosit pentru a afla detalii despre componenta care a generat evenimentul.
ActionEvent şi celelalte obiecte eveniment fac parte din pachetul java.awt.event şi sunt
subclase ale clasei EventObject.
Fiecare metodă de tratare a evenimentelor primeşte ca argument un obiect eveniment de un
anumit tip. Pentru a determina componenta care a transmis evenimentul se poate folosi metoda
getSource() a obiectului, ca în exemplul următor:
public void actionPerformed(ActionEvent evt) {
Object sursa = evt.getSource();
}
Obiectul returnat de metoda getSource() poate fi comparat cu o componentă folosind
operatorul = =. În exemplul actionPerformed() anterior se pot folosi următoarele instrucţiuni:
if (sursa == butonTerminare)
terminaProgram() ;
else if (sursa == sortareInregistrari)
sorteazaInregistrari() ;
Acest exemplu apelează metoda terminareProgram() în cazul când evenimentul a fost
generat de obiectul butonTerminare sau metoda sortareInregistrari() dacă evenimentul a fost
generat de obiectul sortareInregistrari.
Multe metode de tratare a evenimentelor apelează metode diferite pentru fiecare tip de
eveniment sau de componentă. Aceasta face ca metoda de tratare a evenimentelor să fie uşor de
citit. În plus, dacă o clasă conţine mai multe metode de tratare a evenimentelor, fiecare poate apela
aceleaşi metode pentru realizarea sarcinii dorite.
O altă tehnică utilă în cadrul unei metode de tratare a evenimentelor o constituie folosirea
operatorului instanceof, care permite testarea fiecărui tip de componentă care putea genera
evenimentul. Următorul exemplu poate fi folosit într-un program care conţine un buton şi un câmp
de text, fiecare dintre acestea generând evenimente de acţiune:
void actionPerformed(ActionEvent evt) {
Object sursa = evt.getSource() ;
if (sursa instanceof JTextField)
calculeazaScor() ;
else if (sursa instanceof JButton)
terminaProgram() ;
}

Evenimente de actiune

Evenimentele de acţiune apar atunci când utilizatorul realizează o acţiune asupra unui obiect
de tip JButton, JCheckBox, JTextField sau JRadioButton.
Pentru a trata aceste evenimente, o clasă trebuie să implementeze interfaţa ActionListener.
în plus, trebuie apelată metoda addActionListener() pentru fiecare componentă care urmează să
genereze evenimente de acţiune, cu excepţia cazurilor în care doriţi să ignoraţi evenimentele de
acţiune ale unei componente.
Interfaţa ActionListener conţine o singură metodă: actionPerformed(ActionEvent) care
are următoarea formă:
public void actionPerformed(ActionEvent evt) {
// ...
}
În afară de metoda getSource(), puteţi folosi metoda getActionCommand() cu argumentul
ActionEvent, pentru a afla mai multe informaţii despre sursa evenimentului.
În mod prestabilit, comanda acţiunii reprezintă textul asociat componentei, cum ar fi
eticheta de pe un buton JButton. Puteţi însă să definiţi o comandă de acţiune diferită pentru fiecare
componentă, folosind metoda setActionCommand(String). Argumentul şir trebuie să conţină
textul dorit pentru comanda acţiunii.
De exemplu, următoarele instrucţiuni creează obiectele JButton şi JTextField şi le asociază
amândurora comanda de acţiune "Sortare fişiere":
JButton sortare = new JButton("Sortare”);
JTextField nume = new JTextField() ;
sortare.setActionCommand(”Sortare fisiere”);
nume.setActionCommand(”Sortare fisiere”);

Evenimente de modificare

Evenimentele de modificare (ajustare) apar atund când o componentă JScrollBar este


deplasată folosind săgeţile, caseta de derulare sau printr-un clic undeva pe bară. Pentru a trata aceste
evenimente, o clasă trebuie să implementeze interfaţa AdjustmentListener.
Interfaţa AdjustmentListener conţine o singură metodă, care are următoarea formă:

adjustmentValueChanged(AdjustmentEvent evt) {
// ...
}
Pentru a obţine valoarea curentă a obiectului JScrollBar puteţi folosi metoda getValue()
având ca argument obiectul AdjustmentEvent. Această metodă returnează o valoare întreagă care
reprezintă valoarea barei de derulare..
Puteţi determina şi modul cum a fost deplasată bara de derulare dacă folosiţi metoda
getAdjustmentType() a obiectului AdjustmentEvent. Aceasta returnează una din următoarele
cinci valori, care sunt variabile de clasă ale clasei Adjustment:
• UNIT_INCREMENT O creştere cu 1 a valorii, produsă de executarea unui clic pe
săgeata de deplasare a barei sau pe o tastă cu săgeată
• UNIT_DECREMENT O descreştere a valorii cu 1
• BLOCK_INCREMENT O creştere mai mare a valorii, cauzată de un clic pe bara de
derulare, în zona dintre casetă şi săgeată
• BLOCK_DECREMENT O descreştere mai mare a valorii
• TRACK O modificare produsă de deplasarea casetei de derulare

Evenimente de selecţionare

Evenimentele de selecţionare (focus events) apar atunci când o componentă primeşte sau
pierde dreptul de a primi datele de intrare în cadrul interfeţei grafice utilizator. Starea de
selecţionare determină componenta care este activată la un moment dat pentru a primi date de
intrare de la tastatură. Dacă unul din câmpurile de text este selecţionat (într-o interfaţă utilizator cu
mai multe câmpuri de text editabile), în acesta se va vedea un cursor care pâlpâie. Orice text
introdus de la tastatura este memorat în componenta selecţionată.
Starea de selecţionare (focus) se aplică tuturor componentelor care pot primi date de intrare
de la tastarură. În cazul unui obiect JButton selecţionat, pe suprafaţa acestuia va fi reprezentat un
contur cu linie întreruptă.
Pentru a trata un eveniment de selecţionare, o clasă trebuie să implementeze interfaţa
FocusListener. Această interfaţă conţine două metode: focusGained(FocusEvent) şi
focusLost(FocusEvent), care au următoarea formă:

public void focusGained(FocusEvent evt) {


// ...
}
public void focusLost(FocusEvent evt) {
// ...
}
Pentru a determina care obiect a obţinut sau a pierdut starea de selecţionare se poate folosi
metoda getSource() pentru obiectul FocusEvent, primit ca argument de metodele focusGained()
sau focusLost().

Evenimente de element

Evenimentele de element (item events) apar atunci când se selectează sau se deselectează un
element de tip JButton, JCheckBox, JComboBox sau JRadioButton. Pentru a trata aceste
evenimente, o clasă trebuie să implementeze interfaţa ItemListener.
Această interfaţă conţine o singură metodă: itemStateChanged (ItemEvent), care are
următoarea formă:
void itemStateChanged(ItemEvent evt) {
// ...
}
Pentru a determina elementul care a produs evenimentul se poate folosi metoda getItem(),
care primeşte ca argument obiectul ItemEvent.
De asemenea, puteţi determina dacă elementul a fost selectat sau deselectat folosind metoda
getStateChange(). Aceasta returnează o valoare întreagă, egală cu una dintre variabilele de clasă
ItemEvent. DESELECTED sau ItemEvent.SELECTED.

Evenimente de tastatură

Evenimentele de tastatură (key events) sunt generate atunci când se apasă o tastă. Orice
componentă poate genera aceste evenimente, iar pentru a fi suportate într-o clasă, aceasta trebuie să
implementeze interfaţa KeyListener.
Această interfaţă conţine trei metode: keyPressed(KeyEvent), keyReleased(KeyEvent) şi
keyTyped(KeyEvent), care au următoarea formă:
public void keyPressed(KeyEvent evt) {
//...
}
public void keyReleased(KeyEvent evt) {
// ...
}
public void keyTyped(KeyEvent evt) {
// ...
}
Metoda getkeyChar() a obiectului KeyEvent returnează caracterul tastei asociate
evenimentului. Dacă nu există caracterul Unicode al tastei, metoda getKeyChar() returnează o
valoare egală cu variabila de clasă KeyEvent.CHAR_UNDEFINED.

Evenimente de mouse

Evenimentele de mouse sunt generate, de obicei, de mai multe tipuri de interacţiuni:


• Clic de mouse
• Indicatorul mouse-ului pătrunde pe suprafaţa componentei
• Indicatorul mouse-ului părăseşte suprafaţa componentei
Orice componentă poate genera aceste evenimente, care sunt implementate de o clasă prin
intermediul interfeţei MouseListener. Această interfaţă posedă cinci metode:
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(Mouse.Event)
mousePressed(Mouse.Event)
mouseReleased(MouseEvent)
Fiecare dintre acestea are forma generică prezentată mai jos pentru mouseReleased
(MouseEvent):
mouseReleased(MouseEvent evt) {
// ...
}
Pentru obiectele MouseEvent pot fi folosite unnătoarele metode:
• getClickCount() Returnează o valoare întreagă ce reprezintă de câte ori s-a executat clic
• getPoint() Returnează coordonatele x,y ale punctului unde s-a executat clic pe
componentă, sub forma unui obiect Point.
• getX() Returnează poziţia x
• getY() Returnează poziţia y

Evenimente de deplasare a mouse-ului

Evenimentele de mişcare a mouse-ului apar atunci când mouse-ul este deplasat pe o


componentă. Ca şi în cazul altor evenimente de mouse, orice componentă poate genera evenimente
de deplasare a mouse-ului. Pentru a suporta aceste evenimente, o clasă trebuie să implementeze
interfaţa MouseMotionListener.
Această interfaţă conţine două metode: mouseDragged(MouseMotionEvent) şi
mouseMoved(MouseMotionEvent), care au următoarea formă:
public void mouseDragged(MouseEvent evt) {
// ...
}
public void mouseMoved(MouseEvent evt) {
// ...
}
Spre deosebire de alte interfeţe de interceptoare de evenimente pe care le-aţi întâlnit până
acum, MouseMotionListener nu posedă un tip propriu de eveniment, ci foloseşte evenimente
MouseEvent.
Din acest motiv, puteţi apela orice metode corespunzătoare evenimentelor de mouse:
getClickCount(), getPoint (), getX() sau getY().
Evenimente de fereastră

Evenimentele de fereastră apar atunci când utilizatorul deschide sau închide un obiect
fereastră, cum ar fi JFrame sau JWindow. Orice componenta poate genera aceste evenimente, iar
pentru a le trata, o clasă trebuie să implementeze interfaţa windowListener.
Interfaţa WindowListener conţine şapte metode:
windowActivated(WindowEvent)
windowClosed(WindowEvent)
windowClosing(WindowEvent)
windowDeactivated(WindowEvent)
windowDeiconified(WindowEvent)
windowIconified(WindowEvent)
windowOpened(WindowEvent)
Toate acestea au următoarea formă, prezentată aici pentru metoda
windowOpened(WindowEvent):
public void windowOpened(WindowEvent evt) {
// . . .
}
Metodele windowClosing() şi windowClosed() sunt asemănătoare, însă una este apelată o
dată cu închiderea ferestrei, iar cealaltă - după închiderea acesteia. Dacă doriţi, este posibil ca în
cadrul metodei windowClosing() să decideţi anularea închiderii ferestrei.
Comunicarea prin Internet
Biblioteca de clase Java conţine pachetul java.net, care face posibilă comunicarea cu
programele Java prin intermediul reţelelor. Pachetul oferă funcţii abstracte inter-platformă pentru
operaţiile de reţea simple, cum ar fi conectarea şi transferul de fişiere folosind protocoale Web sau
crearea de socluri (sockets) specifice UNIX.
Folosite împreună cu fluxurile de intrare sau de ieşire, citirea şi scrierea fişierelor prin
reţea devin la fel de simple ca folosirea fişierelor de pe discul local.

Comunicaţia prin reţea în Java


Comunicaţia în reţea (networking) reprezintă capacitatea unui applet sau a unei aplicaţii de
a stabili conexiuni cu un alt sistem prin intermediul reţelei. Comunicaţia prin reţea în Java
presupune folosirea claselor din pachetul java.net, care oferă funcţii de reţea abstracte, multi-
platformă, pentru principalele operaţii de lucru în reţea, cum ar fi conectarea şi transferul de fişiere
folosind protocoale Web sau crearea de socluri (sockets) tip UNIX. Folosite împreună cu fluxurile
de intrare sau de ieşire, operaţiile de citire sau scriere de fişiere prin reţea devin la fel de simple ca
şi citirea sau scrierea fişierelor pe sistemul local.
Bineînţeles, există şi restricţii. De obicei, applet-urile Java nu pot citi sau scrie pe discul maşinii pe
care rulează browserul. De asemenea, applet-urile Java nu se pot conecta la alte sisteme decât la cel
de unde au fost încărcate iniţial. Chiar şi cu aceste restricţii, puteţi realiza proiecte interesante şi
puteţi beneficia de avantajele oferite de Web pentru citirea sau procesarea informaţiilor din Internet.
Această secţiune descrie două modalităţi simple de comunicare cu sistemele din Internet:
• getInputStream() este o metodă care deschide o conexiune către o adresă URL şi vă
permite să extrageţi date din acea conexiune
• Clasele soclu, Socket şi ServerSocket, vă permit să deschideţi o conexiune standard care
foloseşte socluri, conexiune prin care puteţi citi sau scrie date.

Deschiderea de conexiuni Web

În loc să cereţi browserului să încarce conţinutul fişierului, uneori este nevoie să obţineţi
conţinutul acestuia pentru a fi prelucrat de un applet. Dacă fişierul pe care doriţi să îl obţineţi este
stocat în Web şi poate fi accesat folosind adrese URL (http, FTP şi aşa mai departe), programul
dumneavoastră Java poate folosi clasa URL pentru a-l obţine.
Din motive de securitate, applet-urile se pot conecta în mod prestabilit doar la maşina de
unde au fost încărcate iniţial. Aceasta înseamnă că dacă applet-urile sunt stocate pe un sistem
denumit www.prefect.com, singura maşină către care applet-ul poate deschide o conexiune este
sistemul respectiv - şi, mai precis, cu numele respectiv, deci aveţi grijă la folosirea denumiri-lor
echivalente (alias). Dacă fişierul pe care applet-ul doreşte să îl apeleze se află pe acelaşi sistem,
atunci folosirea de conexiuni URL reprezintă cea mai simplă metodă de a-l obţine.
Restricţiile de securitate schimbă modul de scriere şi testare a applet-urilor care încarcă fişiere pe
baza adreselor lor URL. Deoarece până acum nu aţi folosit conexiuni de reţea, aţi putut testa applet-
urile pe sistemul local prin simpla încărcare a fişierelor HTML într-un browser sau în utilitarul
appletviewer. Acest lucru nu poate fi făcut în cazul applet-urilor care deschid conexiuni de reţea.
Pentru ca aceste applet-uri să funcţioneze corect trebuie să procedaţi într-unul din următoarele
moduri:
• Rulaţi browserul pe aceeaşi maşină pe care rulează şi serverul Web. Dacă nu aveţi acces la
un server Web, puteţi instala şi rula unul pe propria dumneavoastră maşină.
• Copiaţi clasa şi fişierul HTML pe serverul Web ori de câte ori doriţi să le testaţi, apoi rulaţi
applet-ul din pagina Web, nu de pe sistemul local.
În acest mod vă veţi da seama dacă applet-ul şi conexiunea deschisă de acesta se află pe
aceeaşi maşină. Dacă încercaţi să încărcaţi un applet sau un fişier de pe alte servere, veţi obţine o
excepţie de securitate, împreună cu o mulţime de alte mesaje de eroare afişate pe ecran sau pe
consola Java.
Din aceste motive, acunci când vă conectaţi la Internet pentru a-i folosi resursele, este
bine să folosiţi aplicaţii, care nu suferă de aceste restricţii.

Deschiderea unui flux în reţea

Există mai multe modalităţi de transferare a informaţiilor prin intermediul unui flux. Clasele
şi metodele alese depind de formatul informaţiei şi de ceea ce doriţi să faceţi cu ele.
Una dintre resursele pe care le puteţi apela din programele Java o reprezintă fişierele text din
World Wide Web, indiferent daca sunt fişiere HTML sau un alt tip de fişiere de text simplu.
Pentru a încărca un document text din Web şi a-l citi linie cu linie, puteţi folosi următoarea
tehnică, formată din patru etape:
• Creaţi un obiect URL care reprezintă adresa World Wide Web a resursei.
• Creaţi un obiect URLConnection care încarcă obiectul URL şi realizează o conexiune la
maşina care stochează resursa respectivă.
• Folosind metoda getInputStream() a obiectului URLConnection, creaţi un flux de intrare
InputStreamReader care poate citi un flux de date de la adresa URL.
• Folosind fluxul de intrare, creaţi un obiect BufferedReader, care măreşte eficienţa citirii
caracterelor dintr-un flux de intrare.
Între punctul A (documentul Web) şi punctul B (programul Java) se desfăşoară o mulţime
de operaţii: adresa URL se foloseşte pentru a crea o conexiune URL, care se foloseşte pentru a
crea un flux de intrare, care se foloseşte pentru a crea un flux de intrare cu tampon. Necesitatea
interceptării eventualelor excepţii care pot apărea pe parcursul acestui proces măreşte şi mai mult
gradul lui de complexitate.
În continuare este prezentat un exemplu care foloseşte această tehnică în patru paşi pentru
a deschide o conexiune la un site web pentru a citi un document html. După citirea completă a
documentului acesta este afişat într-o zonă de text (TextArea).

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;

public class CitireFisier extends Frame implements Runnable {


Thread executabil;
URL page;
TextArea box = new TextArea("Transfer text ...");

public CitireFisier() {
super("Transfer fisier");
add(box);
try {
page = new URL("http://www.masini.ro//masini.html");
}
catch (MalformedURLException e) {
System.out.println("URL gresit: " + page);
}
}

public static void main(String[] arguments) {


CitireFisier cadru = new CitireFisier();

WindowListener l = new WindowAdapter() {


public void windowClosing(WindowEvent e) {
System.exit(0);
}
};
cadru.addWindowListener(l);

cadru.pack();
cadru.setVisible(true);
if (cadru.executabil == null) {
cadru.executabil = new Thread(cadru);
cadru.executabil.start();
}
}

public void run() {


URLConnection conn = null;
InputStreamReader in;
BufferedReader data;
String line;
StringBuffer buf = new StringBuffer();
try {
conn = this.page.openConnection();
conn.connect();
box.setText("Conexiune deschisa ...");
in = new InputStreamReader(conn.getInputStream());
data = new BufferedReader(in);
box.setText("Citeste date ...");
while ((line = data.readLine()) != null) {
buf.append(line + "\n");
}
box.setText(buf.toString());
}
catch (IOException e) {
System.out.println("Eroare ! :" + e.getMessage());
}
}
}

Socluri

Pentru aplicaţiile de reţea unde folosirea claselor URL sau URLConnection nu este
suficientă (cum ar fi cazurile când se folosesc alte protocoale sau pentru comunicaţii în reţea
generice), Java oferă clasele Socket şi ServerSocket drept o metodă proprie, echivalentă tehnicilor
de programare cu socluri TCP standard (sockets).
Clasa Socket oferă o interfaţă soclu client asemănătoare soclurilor UNIX. Crearea unei noi instanţe
a clasei Socket, prin care se deschide o conexiune, se face astfel (numeGazda este numele maşinii gazdă,
iar numarPort este numărul portului):
Socket conexiune = new Socket(numeGazda, numarPort);

Observaţie

Dacă folosiţi socluri într-un applet, nu uitaţi că vă aflaţi sub incidenţa restricţiilor de
securitate proprii unui applet, care vă interzic să vă conectaţi la un alt sistem decât la cel de unde
provine applet-ul.
O dată soclul deschis, puteţi folosi fluxuri de intrare sau de ieşire pentru a citi din acesta:
BufferedInputStream bis =
new BufferedInputStream(conexiune.getInputStream());
DataInputStream intrare = new DataInputStream(bis);
BufferedOutputStream bos =
new BufferedOutputStream(conexiune.getOutputStream() ) ;
DataOutputStream iesire = new DataOutputStream(bos);
După ce aţi terminat de lucrat cu un soclu, nu uitaţi să îl închideţi. (Acest lucru va închide, de
asemenea, fluxurile de intrare sau de ieşire pe care le-aţi atribuit soclului.)
conexiune.close() ;
Soclurile server funcţionează asemănător, cu excepţia metodei accept(). Un soclu server ascultă pe
un port TCP pentru a intercepta o cerere de conexiune din partea unui client; atund când clientul se
conectează la un port, metoda accept() acceptă conexiunea de la acesta. Prin folosirea soclurilor client şi
server puteţi crea aplicaţii care comunică prin reţea.
Pentru a crea un soclu server ataşat unui port, se instanţiază clasa ServerSocket, specificând
numărul portului:
ServerSocket sConexiune = new ServerSocket(8888);
Folosiţi metoda accept() pentru a „asculta” portul respectiv şi pentru a accepta eventualele
conexiuni ale clienţilor:
sConexiune.accept();
O dată conexiunea efectuată, puteţi folosi fluxuri de intrare şi de ieşire pentru a citi sau scrie
de la, către un client.

Pentru a prezenta comunicaţia în reţea prin Java, este prezentat aici un program “Banal”,
care foloseşte clasele Socket pentru implementarea unei aplicaţii client – server de reţea.
Exemplul funcţionează în felul următor: programul server aşteaptă conectarea unui client.
La conectarea unui client, serverul pune o întrebare şi aşteaptă un răspuns. La celălalt capăt clientul
recepţionează întrebarea, o afişează către utilizator şi aşteaptă un răspuns de la acesta. Utilizatorul
introduce răspunsul, care este trimis înapoi către server. Serverul verifică dacă răspunsul este corect
şi anunţă rezultatul. După aceasta, serverul întreabă clientul dacă doreşte să i se pună altă întrebare;
dacă răspunsul este afirmativ, procesul se repetă.
Aplicaţia are nevoie de 2 părţi: pe partea de server este necesar un program care să
monitorizeze un anumit port al calculatorului gazdă, acolo unde urmează să se conecteze clientul.
La detectarea unui client, serverul alege o întrebare şi o trimite clientului pe portul respectiv. După
aceasta, serverul intră într-o stare de aşteptare până ce recepţionează răspunsul de la client. După
primirea răspunsului, serverul verifică corectitudinea acestuia şi anunţă clientul rezultatul obţinut.
Apoi clientul este întrebat dacă doreşte o nouă întrebare, ş.a.m.d. Pe scurt serverul realizează
următoarele acţiuni:
1. Aşteaptă conectarea unui client
2. Acceptă conectarea unui client
3. Transmite clientului o întrebare aleatoare
4. Aşteaptă răspunsul de la client
5. Verifică răspunsul şi anunţă clientul rezultatul
6. Întreabă clientul dacă doreşte o altă întrebare
7. Aşteaptă un răspuns (y/n) de la client
8. Dacă este cazul revine la pasul 3
Pe partea de client este o aplicaţie care rulează în linia de comandă. Clientul se conectează la server
şi aşteaptă o întrebare. Atunci când primeşte o întrebare de la server, clientul o afişează
utilizatorului şi aşteaptă introducerea unui răspuns. Acest răspuns este trimis înapoi către server,
după care se aşteaptă răspunsul acestuia. Clientul afişează răspunsul de la server şi permite
utilizatorului să opteze pentru continuarea programului (dacă mai doreşte o nouă întrebare). Clientul
trimite apoi răspunsul utilizatorului şi îşi termină execuţia, în cazul acesta nu doreşte continuarea
conversaţiei. Principalele activităţi ale clientului sunt următoarele:
1. Se conectează la server
2. Aşteaptă trimiterea unei întrebări
3. Afişează întrebarea şi primeşte răspunsul introdus de utilizator
4. Trimite răspunsul către server
5. Aşteaptă un răspuns de la server
6. Afişează răspunsul şi îl întreabă pe utilizator dacă doreşt o nouă întrebare
7. Trimite serverului răspunsul utilizatorului
8. Dacă este cazul, revine la pasul 2.
Textul sursă al aplicaţiei este prezentat în continuare.

import java.io.*;
import java.net.*;
import java.util.Random;

public class ServerBanal extends Thread {


private static final int NRPORT = 1234;
private static final int ASTEAPTACLIENT = 0;
private static final int ASTEAPTARASPUNS = 1;
private static final int ASTEAPTACONFIRMARE = 2;
private String[] intrebari;
private String[] raspunsuri;
private ServerSocket serverSocket;
private int nrIntrebari;
private int crt = 0;
private int stare = ASTEAPTACLIENT;
private Random aleator = new Random();
public ServerBanal() {
super("ServerBanal");
try {
serverSocket = new ServerSocket(NRPORT);
System.out.println("ServerBanal este in executie ...");
}
catch (IOException e) {
System.err.println("Exception: nu se poate crea soclul");
System.exit(1);
}
}

public static void main(String[] arguments) {


ServerBanal server = new ServerBanal();
server.start();
}

public void run() {


Socket clientSocket = null;

// Initializare vectori cu intrebari si raspunsuri


if (!initIntRasp()) {
System.err.println("Error: nu se pot initializa intrebarile
si raspunsurile");
return;
}

// Asteapta clientul si pune intrebari banale


while (true) {
// Asteapta un client
if (serverSocket == null)
return;
try {
clientSocket = serverSocket.accept();
}
catch (IOException e) {
System.err.println("Exceptie: nu se poate face
conectarea la soclul client");
System.exit(1);
}

// Se poarta dialogul intrebare/raspuns


try {
InputStreamReader isr = new
InputStreamReader(clientSocket.getInputStream());
BufferedReader is = new BufferedReader(isr);
PrintWriter os = new PrintWriter(new
BufferedOutputStream(clientSocket.getOutputStream()), false);
String linieIesire;

//Transmite cererea serverului


linieIesire = citireIntrari(null);
os.println(linieIesire);
os.flush();

// Citeste si afiseaza intrarile utilizator


while (true) {
String linieIntrare = is.readLine();
if (linieIntrare.length() > 0) {
linieIesire = citireIntrari(linieIntrare);
os.println(linieIesire);
os.flush();
if (linieIesire.equals("La revedere!."))
break;
}
}

// Inchidere fluxuri
os.close();
is.close();
clientSocket.close();
}
catch (Exception e) {
System.err.println("Exception: " + e);
e.printStackTrace();
}
}
}

private boolean initIntRasp() {


try {
File fisierIntrare = new File("intrebari.txt");
FileInputStream inStream = new FileInputStream(fisierIntrare);
byte[] data = new byte[(int)fisierIntrare.length()];

// Se citesc intrebarile si raspunsurile


if (inStream.read(data) <= 0) {
System.err.println("Error: nu se pot citi intrebarile si rasp");
return false;
}
// Numarare intrebari si raspunsuri
for (int i = 0; i < data.length; i++)
if (data[i] == (byte)'\n') nrIntrebari++;
nrIntrebari /= 2;
intrebari = new String[nrIntrebari];
raspunsuri = new String[nrIntrebari];

// Se introduc intrebarile si raspunsurile in tablouri separate


int start = 0, nr = 0;
boolean esteIntr = true;
for (int i = 0; i < data.length; i++)
if (data[i] == (byte)'\n') {
if (esteIntr) {
intrebari[nr] = new String(data, start, i - start - 1);
esteIntr = false;
}
else {
raspunsuri[nr] = new String(data, start, i - start - 1);
esteIntr = true;
nr++;
}
start = i + 1;
}
}
catch (FileNotFoundException e) {
System.err.println("Exceptie: nu se gaseste fisierul cu
intrebari si raspunsuri");
return false;
}
catch (IOException e) {
System.err.println("Exceptie: eroare la citire intrebari");
return false;
}

return true;
}

String citireIntrari(String inStr) {


String outStr = null;

switch (stare) {
case ASTEAPTACLIENT:
// Pune o intrebare
outStr = intrebari[crt];
stare = ASTEAPTARASPUNS;
break;

case ASTEAPTARASPUNS:
// Verifica raspunsul
if (inStr.equalsIgnoreCase(raspunsuri[crt]))
outStr = "Raspuns corect! Doriti o noua intrebare? (y/n)";
else
outStr = "Raspuns gresit! Raspunsul corect este " +
raspunsuri[crt] + ". Doriti alta intrebare? (y/n)";
stare = ASTEAPTACONFIRMARE;
break;

case ASTEAPTACONFIRMARE:
// Se asteapta confirmarea continuarii dialogului
if (inStr.equalsIgnoreCase("Y")) {
crt = Math.abs(aleator.nextInt()) % intrebari.length;
outStr = intrebari[crt];
stare = ASTEAPTARASPUNS;
}
else {
outStr = "La revedere!.";
stare = ASTEAPTACLIENT;
}
break;
}
return outStr;
}
}

import java.io.*;
import java.net.*;

public class Banal {


private static final int NRPORT = 1234;

public static void main(String[] arguments) {


Socket socket = null;
InputStreamReader isr = null;
BufferedReader in = null;
PrintWriter out = null;
String adresa;
// Cauta adresa in argumentele liniei de comanda
if (arguments.length != 1) {
System.out.println("Mod de utilizare: java Banal <adresa>");
return; }
else
adresa = arguments[0];
// Initializare soclu si fluxuri
try {
socket = new Socket(adresa, NRPORT);
isr = new InputStreamReader(socket.getInputStream());
in = new BufferedReader(isr);
out = new PrintWriter(socket.getOutputStream(),true);
}
catch (IOException e) {
System.err.println("Err: nu se poate crea soclul fluxului"
+ e.getMessage());
System.exit(1);
}
// Citeste datele de intrare si raspunsurile serverului si le prelucreaza
try {
StringBuffer str = new StringBuffer(128);
String inStr;
int c;
while ((inStr = in.readLine()) != null) {
System.out.println("Server: " + inStr);
if (inStr.equals("La revedere")) break;
while ((c = System.in.read()) != '\n') str.append((char)c);
System.out.println("Client: " + str);
out.println(str.toString());
out.flush();
str.setLength(0);
}
// Inchide fluxuri
out.close();
in.close();
socket.close();
}
catch (IOException e) {
System.err.println("I/O error: "+ e.toString());
}
}}
Artificii cu applet-uri

Metoda showStatus()

Metoda showStatus() a clasei Applet vă permite să afişaţi un şir în bara de stare a browserului care
rulează applet-ul. Puteţi folosi această metodă pentru afişarea mesajelor de eroare, a legăturilor, pentru
indicaţii sau pentru alte mesaje de stare.
Această metodă poate fi apelată printr-o instrucţiune de genul:
getAppletContext().showStatus(”Pentru a începe, executati clic pe
applet”);
Metoda getAppletContext() permite applet-ului dumneavoastră să acceseze caracteristicile
browserului care l-a încărcat. Metoda showStatus() foloseşte acest mecanism pentru a afişa mesaje
de stare.

Informatii despre applet

Biblioteca AWT (Abstract Windowing Toolkit) oferă un mecanism de declarare în cadrul


applet-ului a unor informaţii legate de autor, de drepturile de copiere sau alte informaţii importante.
Un browser Web poate oferi un mecanism de afişare a acestor informaţii, dacă acestea au fost
definite de programatorul applet-ului.
Pentru a oferi informaţii legate de applet-ul dumneavoastră trebuie să suprascrieţi metoda
getAppletInfo(), în modul următor:
public String getAppletInfo() {
return ” Copyright 2002 Popescu Ion”
}
Crearea de legături în cadrul applet-urilor

Deoarece applet-urile rulează în cadrul browserelor Web, ar fi bine ca acestea să poată


încărca noi pagini Web. Java oferă un mecanism prin care se poate indica browserului să încarce o
nouă pagină. De exemplu, puteţi folosi acest mecanism pentru a crea imagini animate care să
încarce o nouă pagină la execuţia unui clic pe suprafaţa lor.
Veţi crea o instanţă a clasei URL pentru legătura către pagina dorită.
Pentru a crea un nou obiect URL puteţi folosi unul dintre cei patru constructori:
• URL (String) creează un obiect URL cu o adresă Web completă, cum ar fi
http://www.prefect.com/java21 sau ftp://ftp.netscape.com.
• URL (URL, String) creează un obiect URL având o adresă de bază provenită din
obiectul URL specificat şi o cale relativă provenită din şirul transmis ca argument secund. Puteţi
folosi getDocumentBase() pentru adresa URL a paginii care conţine applet-ul sau getCodebase()
pentru adresa URL a clasei applet-ului. Calea relativă va fi concatenată cu adresa de bază.
• URL (String, String, int, String) creează un obiect URL pornind de la protocol
(cum ar fi http sau ftp), numele maşinii (www.prefect.com, ftp.netcom.com etc.), numărul de port
(80 pentru http)şi un nume de fişier sau de director.
• URL (String, String, String) este identic cu constructorul anterior, minus
argumentul întreg cu numărul portului.
Atunci când folosiţi constructorul URL (String), trebuie să trataţi excepţiile
MalformedURLException. Pentru aceasta puteţi folosi un bloc try. . . catch, ca în exemplul
următor:
try {
unURL = new URL ("http://www.mcp.com" );
} catch (MalformedURLException e) {
System.out.println("Adresa URL incorecta: " + unURL) ;
}
O dată obţinut obiectul URL, tot ceea ce mai trebuie să faceţi este să îl transmiteţi
browserului; drept urmare, acesta va încarca adresa respectivă:
getAppletContext().showDocument(unURL);
Browserul ce conţine un applet Java cu codul prezentat mai sus va încărca şi va afişa
documentul de la adresa URL respectivă.
Aplicaţia următoare prezintă două clase: ButonLink şi o clasă ajutătoare Marcaj. Appletul
ButonLink afişează trei butoane care indică trei locaţii Web diferite; dacă se execută clic pe aceste
butoane, va fi încărcat documentul de la locaţia respectivă.
Textul sursă al aplicaţiei este prezentat în continuare:

import java.awt.*;
import java.net.*;

public class ButonLink extends java.applet.Applet {


Marcaj ListaMarcaje[] = new Marcaj[3];

public void init() {


ListaMarcaje[0] = new Marcaj("Catalog auto",
"http://www.masini.ro");
ListaMarcaje[1] = new Marcaj("Macmillan Computer Publishing",
"http://www.mcp.com");
ListaMarcaje[2]= new Marcaj("JavaSoft",
"http://java.sun.com");

GridLayout gl = new GridLayout(ListaMarcaje.length, 1, 10, 10);


setLayout(gl);
for (int i = 0; i < ListaMarcaje.length; i++) {
add(new Button(ListaMarcaje[i].name));
}
}

public boolean action(Event evt, Object arg) {


if (evt.target instanceof Button) {
saltLa( (String)arg );
return true;
}
else return false;
}

void saltLa(String name) {


URL theURL = null;
for (int i = 0; i < ListaMarcaje.length; i++) {
if (name.equals(ListaMarcaje[i].name))
theURL = ListaMarcaje[i].url;
}
if (theURL != null)
getAppletContext().showDocument(theURL);
}
}

class Marcaj {
String name;
URL url;

Marcaj(String name, String theURL) {


this.name = name;
try {
this.url = new URL(theURL);
} catch (MalformedURLException e) {
System.out.println("URL inexistent: " + theURL);
}
}
}

Comunicarea între applet-uri

Uneori doriţi să folosiţi o pagină HTML care să conţină mai multe applet-uri diferite. Pentru
aceasta, tot ceea ce trebuie să faceţi este să folosiţi de mai multe ori eticheta <APPLET>. Browserul
va crea diferite instanţe pentru fiecare applet care apare în pagină.
Dar dacă doriţi să comunicaţi între aceste applet-uri? Dacă doriţi ca o modificare făcută într-un
applet să le afecteze cumva şi pe celelalte? Cea mai bună modalitate de accesare a diferitelor applet-uri din
pagină este de a folosi contextele.
Un context reprezintă un mijloc prin care se poate descrie mediul din care face parte ceva.
În acest caz, contextul applet-ului este definit în clasa AppletContext şi este folosit pentru
comunicaţia între applet-uri.
Pentru a obţine o instanţă a acestei clase pentru applet-ul dumneavoastră, veţi folosi metoda
getAppletContext(), şi nu un anumit constructor.
De exemplu, apelarea metodei trimiteMesaj() pentru toate applet-urile dintr-o pagină,
inclusiv cel curent, foloseşte metoda getApplets() şi un ciclu for de genul:
for (Enumeration e = getAppletContext().getApplets();
e.hasMoreElements();) {
Applet curent = (SubclasaMeaApplet) (e.nextElement());
curent.trimiteMesaj ();
}
Metoda getApplets() returnează un obiect de tip Enumeration care conţine o listă a applet-
urilor din pagină. Parcurgerea acestei liste vă permite să accesaţi pe rând fiecare element. Reţineţi
că fiecare element al obiectului Enumeration este o instanţă a clasei Object; pentru ca applet-ul să se
comporte în modul dorit (şi să accepte mesaje de la alte applet-uri) trebuie să îl convertiţi prin cast
la o instanţă a subclasei applet-ului dumneavoastră (în acest caz, clasa SubclasaMeaApplet).
Apelarea unei metode într-un anumit applet este ceva mai complicată. Pentru aceasta,
trebuie să asociaţi fiecărui applet un nume şi să faceţi referirea prin numele respectiv.
Pentru a asocia unui applet un nume se foloseşte atributul NAME al etichetei <APPLET>:
<P>Acest applet trimite informatii:
<APPLET CODE=”AppletulMeu.class" WIDTH=100 HEIGHT=150 NAME=”Expeditor”>
</APPLET>
<P>Acest applet primeste informatii de la expeditor:
<APPLET CODE="AppletulMeu.class" WIDTH=100 HEIGHT=150 NAME=”Destinatar”>
</APPLET>
Pentru a obţine o referinţă la un alt applet din aceeaşi pagină se foloseşte metoda
getApplet() pentru contextul applet-ului cu acel nume. Aceasta va avea ca rezultat obţinerea unei
referinţe la applet-ul cu numele respectiv. După aceasta, vă puteţi referi la acest applet ca la oricare
alt obiect: apelaţi metode, modificaţi variabilele de instanţă şi aşa mai departe. Iată codul care
realizează acest lucru:
// accesati applet-ul destinatar
Applet destinatar = (SubclasaMeaApplet)getAppletContext().
getApplet("Destinatar”);
// comandati-i sa se actualizeze
destinatar.actualizare(text, valoare);
În acest exemplu s-a folosit metoda getApplet() pentru a se obţine o referinţă către applet-ul
cu numele "Destinatar". Observaţi că obiectul returnat de getApplet() este o instanţă a clasei
generice Applet; veţi dori, probabil, să îl convertiţi prin cast către o instanţă a subclasei
dumneavoastră. O dată obţinută referinţa către applet, puteţi apela apoi metodele sale ca şi când ar fi
orice alt obiect din mediul dumneavoastră de execuţie. Aici, de exemplu, dacă ambele applet-uri
conţin o metodă denumită actualizare(), puteţi comanda applet-ului destinatar să se actualizeze
singur folosind informaţiile din applet-ul curent.
Denumirea applet-urilor şi referirea lor prin metodele prezentate în această secţiune permit
comunicarea şi sincronizarea applet-urilor, obţinându-se astfel un comportament uniform pentru
toate applet-urile din pagină.

Decuparea, copierea şi lipirea

Începând cu versiunea 1.1 a Java s-a introdus suport pentru operaţiunile de decupare,
copiere şi lipire (cut, copy, paste) între componentele folosite într-o interfaţă utilizator AWT şi alte
programe neimplementate în Java, care rulează pe aceeaşi platformă.
Anterior, AWT permitea doar copierea şi lipirea datelor între componentele care posedau
această facilitare pe platformele native (de exemplu, textul putea fi copiat şi lipit numai între
câmpuri sau zone de text). Această facilitate a fost extinsă astfel încât şi alte date sau obiecte să
poată fi transferate de la o componentă la alta.
Pentru a transfera date de la o componentă la alta trebuie să definiţi un obiect transferabil,
apoi să modificaţi sau să creaţi componente care să aibă capacitatea de a transfera obiectul
respectiv.
Clasele şi interfeţele folosite în acest scop sunt conţinute în pachetul java.awt.datatransfer.

Crearea de obiecte transferabile

Un obiect transferabil este un obiect care poate fi mutat dintr-o componentă în alta folosind
mecanismul de transfer oferit de AWT şi care încapsulează un set de date ce urmează a fi
transferate (de exemplu, text formatat). Mai concis, un obiect transferabil este un obiect care
implementează interfaţa Transferable.
Atunci când creaţi un obiect transferabil, trebuie să decideţi mai întâi ce aspecte va suporta
obiectul respectiv. Un aspect (flavor) este, în acest caz, formatul de date care urmează a fi
transferat. De exemplu, dacă vreţi să copiaţi text formatat HTML dintr-un browser şi să îl lipiţi într-
un alt loc, datele respective pot fi reprezentate în diferite formate: ca text formatat, ca text simplu
sau drept cod HTML. Atributele datelor determină modul în care obiectul copiat şi cel care urmează
a fi lipit negociază transferul de date propriu-zis. Dacă sursa şi destinaţia transferului de date nu
suportă acelaşi set de aspecte, transferul de date nu poate avea loc.
Aspectele datelor sunt descrise folosind tipurile MIME, adică mecanismul de negociere a
conţinutului care este folosit de programele de poştă electronică sau de World Wide Web. În afară
de numele logic al aspectului, acesta mai posedă şi un nume descriptiv, care poate fi tradus în
diferite limbaje internaţionale. Aspectele de date pot avea, de asemenea. o clasă reprezentativă - de
exemplu, dacă datele sunt un şir Unicode, acesta este reprezentat de clasa String. Dacă aspectul de
date nu este reprezentat de nici o clasă, va fi folosită implicit clasa InputStream.
Pentru a crea un nou aspect de date trebuie creată o instanţă a clasei DataFlavor folosind
unul din următorii constructori:
• DataFlavor(Class, String) creează un aspect de date care reprezintă o clasă Java.
Argumentul String reprezintă numele descriptiv al aspectului. Obiectul DataFlavor rezultat va avea
tipul MIME application/x-javaserializedobject.
• DataFlavor (String, String) creează un aspect de date care reprezintă un tip MIME,
unde primul argument este tipul MIME, iar al doilea reprezintă numele descriptiv. Clasa care
reprezintă acest aspect de date va fi InputStream.
După ce aţi obţinut acest obiect cu aspectul de date, puteţi interoga valorile sale sau puteţi
compara tipurile sale MIME cu cele ale altor obiecte aspect de date pentru a negocia modul cum vor
fi transferate datele.
Aspectele de date sunt folosite de obiectele transferabile, care sunt definite folosind interfaţa
Transferable. Un obiect transferabil va conţine datele ce urmează a fi transferate, precum şi
instanţe pentru fiecare dintre aspectele de date care reprezintă obiectul respectiv. Pentru ca obiectul
dumneavoastră transferabil să poată fi într-adevăr negociat şi transferat, trebuie să implementaţi şi
metodele getTransferDataFlavors(), isDataFlavorSupported() şi getTransferData(). (Pentru
detalii, consultaţi documentaţia interfeţei Transferable.)
Clasa StringSelection implementează un obiect transferabil simplu, pentru transferul
şirurilor de text, folosind obiecte DataFlavor şi interfaţa Transferable.
Reţineţi că obiectele transferabile sunt folosite pentru încapsularea datelor şi pentru
descrierea formatului (aspectului) acestora; ele nu au nici un rol în formatarea datelor, la nici una
dintre părţile implicate în transfer. Aceasta este responsabilitatea programului dumneavoastră atunci
când folosiţi zona Clipboard pentru a obţine date de la o sursă.

Folosirea zonei Clipboard

După ce aţi definit obiectul transferabil, puteţi folosi zona Clipboard pentru a transfera
obiectul între componente sau între Java şi platforma nativă. Java 2 oferă un mecanism foarte
simplu pentru lucrul cu zona Clipboard, prin care puteţi copia şi accesa informaţii în/din această
zonă. Puteţi folosi fie zona Clipboard standard a sistemului pentru a face schimb de date cu celelalte
programe care rulează pe platforma nativă, fie propriile instanţe de zone Clipboard sau mai multe
seturi specializate de zone Clipboard.
Zonele Clipboard sunt reprezentate în Java de clasa Clipboard, care face parte tot din
pachetul java.awt.datatransfer. Puteţi accesa zona Clipboard a sistemului folosind metodele
getToolkit() şi getSystemClipboard(); getToolkit () vă permite să accesaţi diferite funcţii ale
sistemului:
Clipboard clip = getToolkit().getSystemClipboard();
Important de remarcat pentru zona Clipboard a sistemului: applet-urile nu au voie să
acceseze această zonă din motive de securitate (aici se pot afla informaţii confidenţiale). Astfel,
applet-urile nu pot face schimburi de informaţii în nici un sens cu platforma nativă prin intermediul
zonei Clipboard. Totuşi, se pot folosi zone Clipboard interne pentru copierea şi lipirea datelor între
componentele unui applet.
Orice componentă care doreşte să folosească zona Clipboard - fie să pună date acolo
folosind copierea (copy) sau decuparea (cut), fie să preia date folosind lipirea (paste) - trebuie să
implementeze interfaţa ClipboardOwner. Această interfaţă are o singură metodă:
lostOwnership(), care este apelată atunci când o altă componentă preia controlul asupra zonei
Clipboard.
Pentru a implementa operaţia de copiere sau decupare (copy sau cut) trebuie parcurse
următoarele etape:
1. Creaţi o instanţă a obiectului Transferable, care să păstreze datele ce urmează a fi
copiate.
2. Creaţi o instanţă a obiectului care implementează interfaţa ClipboardOwner (care poate
fi clasa curentă sau chiar obiectul Transferable).
3. Dacă folosiţi zona Clipboard a sistemului, folosiţi metoda getSystemClipboard() pentru
a obţine o referinţă la aceasta.
4. Apelaţi metoda setContents() a zonei Clipboard, având ca argumente obiectul
transferabil şi obiectul care implementează interfaţa ClipboardOwner. Folosind această metodă,
obiectul dumneavoastră şi-a „adjudecat" posesia asupra zonei Clipboard.
5. Metoda lostOwnership() este apelată atunci când un alt obiect preia controlul asupra
zonei Clipboard. Această metodă trebuie implementată dacă doriţi să realizaţi o anumită acţiune la
apariţia evenimentului (sau puteţi crea o metodă vidă atund când nu vă interesează dacă cineva a
înlocuit conţinutul zonei Clipboard).
Pentru implementarea unei operaţii de lipire (paste) trebuie parcurse următoarele etape:
1. Folosiţi metoda getContents() a clasei Clipboard, care returnează un obiect transferabil.
2. Folosiţi metoda getTransferDataFlavors() a obiectului transferabil pentru a afla ce
aspecte de date suporta obiectul transferabil. Stabiliţi ce aspect veţi folosi.
3. Accesaţi datele conform aspectului dorit, folosind metoda getTransferData() a obiectului
transferabil.
Un astfel de exemplu este prezentat în continuare.

import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;

public class CopyPaste extends Frame


implements ActionListener, ClipboardOwner {

Button copy, paste;


TextField tfCopy, tfPaste;
Clipboard clip;

public static void main(String[] arguments) {


CopyPaste test = new CopyPaste();
WindowListener l = new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
};
test.addWindowListener(l);
test.setSize(200, 150);
test.show();
}

CopyPaste() {
super("Copy and Paste");
clip = getToolkit().getSystemClipboard();
FlowLayout flo = new FlowLayout();
setLayout(flo);

copy = new Button("Copy From");


tfCopy = new TextField(25);
paste = new Button("Paste To");
tfPaste = new TextField(25);

copy.addActionListener(this);
paste.addActionListener(this);
paste.setEnabled(false);

add(copy);
add(tfCopy);
add(paste);
add(tfPaste);
}

void doCopy() {
if (tfCopy.getText() != null) {
String txt = tfCopy.getText();
StringSelection trans = new StringSelection(txt);
clip.setContents(trans, this);
paste.setEnabled(true);
}
}

void doPaste() {
Transferable toPaste = clip.getContents(this);
if (toPaste != null) {
try {
String txt = (String)toPaste.getTransferData(
DataFlavor.stringFlavor);
tfPaste.setText(txt);
paste.setEnabled(false);
} catch (Exception e) {
System.out.println("Error -- " + e.toString());
}
}
}

public void actionPerformed(ActionEvent e) {


if (e.getSource() == copy)
doCopy();
else if (e.getSource() == paste)
doPaste();
}

public void lostOwnership(Clipboard clip,


Transferable contents) {
}
}

You might also like