Professional Documents
Culture Documents
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++).
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.
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.
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
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.
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
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
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)
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
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:
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 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 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;
Î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
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.
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ă 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 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.
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
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][];
Variabilele de tip referinţă caractere[0] şi tablouDeCaractere trimit spre acelaşi tablou rezervat în memorie.
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.
Î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:
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.
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.
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.
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 = 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 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;
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 = 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
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:
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.
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
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.*;
< 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.
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.
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:
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ă:
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:
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
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;
<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ă.
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.
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);
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.
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ă.
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());
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 .
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];
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.
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)).
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.
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ă
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ă.
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ă
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.
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.
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.
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.
Event.LEFT TastaLeft
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ă.
Î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);
}
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) {
// ...
}
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.
Ce reprezintă
Identificatorul evenimentului
Î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().
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.
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) ;
}
Modificatori
Î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.
Î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".
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).
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.
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 ( ) ;
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.
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.
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);
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.
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.
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.
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.
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.
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:
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.
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;
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:
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 {
// ...
}
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
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
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
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.
Î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.
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
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
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.
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
Stabilirea aspectului
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.
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.
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);
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) ;
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.
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
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.
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
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ă:
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 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.
Î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.
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 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);
}
}
cadru.pack();
cadru.setVisible(true);
if (cadru.executabil == null) {
cadru.executabil = new Thread(cadru);
cadru.executabil.start();
}
}
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;
// Inchidere fluxuri
os.close();
is.close();
clientSocket.close();
}
catch (Exception e) {
System.err.println("Exception: " + e);
e.printStackTrace();
}
}
}
return true;
}
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.*;
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.
import java.awt.*;
import java.net.*;
class Marcaj {
String name;
URL url;
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ă.
Î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.
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ă.
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.*;
CopyPaste() {
super("Copy and Paste");
clip = getToolkit().getSystemClipboard();
FlowLayout flo = new FlowLayout();
setLayout(flo);
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());
}
}
}