Limbajul Java

  • Uploaded by: Radu
  • 0
  • 0
  • June 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Limbajul Java as PDF for free.

More details

  • Words: 64,631
  • Pages: 168
www.cartiaz.ro – Carti si articole online gratuite de la A la Z

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

\U0045

\u6abe

În al doilea pas al citirii fişierului sursă, sunt recunoscute ca şi caractere de sfârşit de linie caracterele ASCII CR şi ASCII LF. În acelaşi timp, secvenţa de caractere ASCII CR-ASCII LF este tratată ca un singur sfârşit de linie şi nu două. În acest mod, Java suportă în comun standardele de terminare a liniilor folosite de diferite sisteme de operare: MacOS, Unix şi DOS.

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

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

Cuvinte cheie Identificatori Literali Separatori Operatori

Cuvinte cheie Cuvintele cheie sunt secvenţe de caractere ASCII rezervate de limbaj pentru uzul propriu. Cu ajutorul lor, Java îşi defineşte unităţile sintactice de bază. Nici un program nu poate să utilizeze aceste secvenţe altfel decât în modul în care sunt definite de limbaj. Singura excepţie este aceea că nu există nici o restricţionare a apariţiei cuvintelor cheie în şiruri de caractere sau comentarii. Cuvintele cheie ale limbajului Java sunt: abstract boolean break byte case cast catch char class const continue default do double else extends final finally float

for future generic goto if implements import inner instanceof intinterface long native new null operator outer package private protected

public rest return short static super switch synchronized this throw throws transient try var void volatile while byvalue

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 \u0030-\u0039

0-9

Explicaţie cifre ISO-LATIN-1

\u0660-\u0669

cifre Arabic-Indic

\u06f0-\u06f9

cifre Eastern Arabic-Indic

\u0966-\u096f

cifre Devanagari

\u09e6-\u09ef

cifre Bengali

\u0a66-\ u0a6f

cifre Gurmukhi

\u0ae6-\u0aef

cifre Gujarati

\u0b66-\u0b6f

cifre Oriya

\u0be7-\u0bef

cifre Tamil

\u0c66-\u0c6f

cifre Telugu

\u0ce6-\u0cef

cifre Kannada

\u0d66-\u0d6f

cifre Malayalam

\u0e50-\u0e59

cifre Thai

\u0ed0-\u0ed9

cifre Lao

\u1040-\u1049

cifre Tibetan Tabelul 1 Cifrele Unicode.

Un caracter Unicode este o literă dacă este în următoarele intervale şi nu este cifră: Reprezentare Unicode Caracter ASCII

Explicaţie

\u0024

$

semnul dolar (din motive istorice)

\u0041-\u005a

A-Z

litere majuscule Latin

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

_

underscore (din motive istorice)

\u0061-\u007a

a-z

litere minuscule Latin

\u00c0-\u00d6

diferite litere Latin cu diacritice

\u00d8-\u00f6

diferite litere Latin cu diacritice

\u00f8-\u00ff

diferite litere Latin cu diacritice

\u0100-\u1fff

alte alfabete şi simboluri non-CJK

\u3040-\u318f

Hiragana, Katakana, Bopomofo, şi Hangul

\u3300-\u337f

cuvinte pătratice CJK

\u3400-\u3d2d

simboluri Hangul coreene

\u4e00-\u9fff

Han (Chinez, Japonez, Corean)

\uf900-\ufaff

compatibilitate Han Tabelul 2. Literele Unicode.

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

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

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

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

234871

2345678908

Exemplul al d oilea şi al patrulea sunt literali întregi lungi. Pentru a exprima un literal întreg în baza 16 trebuie să definim cifrele de la 10 la 15. Convenţia va fi următoarea: 10 - a, A 11 - b, B 12 - c, C

13 - d, D 14 - e, E 15 - f, F

În plus, pentru că un literal întreg în baza 16 poate începe cu o literă, vom adăuga prefixul 0x sau 0X. Dacă nu am adăuga acest sufix, compilatorul ar considera că este vorba despre un identificator. Exemple de literali întregi în baza 16: 0xa34 0X123

0x2c45L

0xde123abccdL

Ultimele două exemple sunt literali întregi lungi. Pentru a reprezenta un literal întreg în baza 8, îl vom preceda cu cifra 0. Restul cifrelor pot fi oricare între 0 şi 7. Cifrele 8 şi 9 nu sunt admise în literalii întregi în baza 8. Exemple de literali întregi în baza 8: 0234500123001234567712345677L

Valoarea maximă a unui literal întreg normal este de 2147483647 (231-1), scrisă în baza 10. În baza 16, cel mai mare literal pozitiv se scrie ca 0x7fffffff iar în baza 8 ca 017777777777. Toate trei scrierile reprezintă de fapt aceeaşi valoare, doar că aceasta este exprimată în baze diferite. Cea mai mică valoare a unui literal întreg normal este -2147483648 (-231), respectiv 0x80000000 şi 020000000000. Valorile 0xffffffff şi 037777777777 reprezintă amândouă valoarea -1. Specificarea în sursă a unui literal întreg normal care depăşeşte aceste limite reprezintă o eroare de compilare. Cu alte cuvinte, dacă folosim în sursă numărul: 21474836470 de exemplu, fără să punem sufixul de număr lung după el, compilatorul va genera o eroare la analiza sursei. Valoarea maximă a unui literal întreg lung este, în baza 10, 9223372036854775807L (2 63-1). În octal, asta înseamnă 0777777777777777777777L iar în baza 16 0x7fffffffffffffffL. În mod asemănător, valoarea minimă a unui literal întreg lung este -9223372036854775808L (-263-1), în octal această valoare este 0400000000000000000000L iar în baza 16 este 0x8000000000000000L. La fel ca şi la literalii întregi normali, depăşirea acestor limite este o eroare de compilare. Literali flotanţi Literalii flotanţi reprezintă numere reale. Ei sunt formaţi dintr-o parte întreagă, o parte fracţionară, un exponent şi un sufix de tip. Exponentul, dacă există, este introdus de litera e sau E urmată opţional de un semn al exponentului. Este obligatoriu să existe măcar o cifră fie în partea întreagă fie în partea zecimală şi punctul zecimal sau litera e pentru exponent. Sufixul care indică tipul flotantului poate fi f sau F în cazul în care avem o valoare flotantă normală şi d sau D dacă avem o valoare flotantă dublă. Dacă nu este specificat nici un sufix, valoarea este implicit dublă. Valoarea maximă a unui literal flotant normal este 3.40282347e+38f iar valoarea cea mai mică reprezentabilă este 1.40239846e-45f, ambele reprezentate pe 32 de biţi. Valoarea maximă reprezentabilă a unui literal flotant dublu este de 1.79769313486231570e+308 iar valoarea cea mai mică reprezentabilă este 4.94065645841246544e324, ambele reprezentate pe 64 de biţi.

www.cartiaz.ro – Carti si articole online gratuite de la A la Z La fel ca şi la literalii întregi, este o eroare să avem exprimat în sursă un literal mai mare decât valoarea maximă reprezentabilă sau mai mic decât cea mai mică valoare reprezentabilă. Exemple de literali flotanţi: 1.0e45f

-3.456f

0.

.01e-3

Primele două exemple reprezintă literali flotanţi normali, iar celelalte literali flotanţi dubli. Literali booleeni Literalii booleeni nu pot fi decât true sau false, primul reprezentând valoarea booleană de adevăr iar celălalt valoarea booleană de fals. True şi false nu sunt cuvinte rezervate ale limbajului Java, dar nu veţi putea folosi aceste cuvinte ca identificatori. Literali caracter Un literal de tip caracter este utilizat pentru a exprima caracterele codului Unicode. Reprezentarea se face fie folosind o literă, fie o secvenţă escape. Secvenţele escape ne permit reprezentarea caracterelor care nu au reprezentare grafică şi reprezentarea unor caractere speciale precum backslash şi însăşi caracterul apostrof. Caracterele care au reprezentare grafică pot fi reprezentate între apostrofe, ca în exemplele: 'a' 'Ş' ','

Pentru restul caracterelor Unicode trebuie să folosim secvenţe escape. Dintre acestea, câteva sunt predefinite în Java, şi anume:

Secvenţă escape

Caracterul reprezentat

'\b'

caracterul backspace BS \u0008

'\t'

caracterul tab orizontal HT \u0009

'\n'

caracterul linefeed LF \u000a

'\f'

caracterul formfeed FF \u000c

'\r'

caracterul carriage return CR \u000d

'\"'

caracterul ghilimele \u0022

'\''

caracterul apostrof \u0027

'\\'

caracterul backslash \u005c

Tabelul 3 Secvenţe escape predefinite în Java.

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

'\oo' '\too'

www.cartiaz.ro – Carti si articole online gratuite de la A la Z unde o este o cifră octală iar t este o cifră octală între 0 şi 3. Nu este corect să folosiţi ca valori pentru literale caracter secvenţa '\u000d' (caracterul ASCII CR), sau altele care reprezintă caractere speciale, pentru că acestea fiind secvenţe escape Unicode sunt transformate foarte devreme în timpul procesării sursei în caractere CR şi sunt interpretate ca terminatori de linie. Exemple de secvenţe escape: '\n' '\u23a'

'\34'

dacă după caracterul backslash urmează altceva decât: b, t, n, f, r, ", ', \, 0, 1, 2, 3, 4, 5, 6, 7 se va semnala o eroare de compilare. În acest moment secvenţele escape Unicode au fost deja înlocuite cu caractere Unicode native. Dacă u apare după \, se semnalează o eroare de compilare. Literali şir de caractere Un literal şir de caractere este format din zero sau mai multe caractere între ghilimele. Caracterele care formează şirul de caractere pot fi caractere grafice sau secvenţe escape ca cele definite la literalii caracter. Dacă un literal şir de caractere conţine în interior un caracter terminator de linie va fi semnalată o eroare de compilare. Cu alte cuvinte, nu putem avea în sursă ceva de forma: "Acesta este greşit! "

chiar dacă aparent exprimarea ar reprezenta un şir format din caracterele A, c, e, s, t, a, spaţiu, e, s, t, e, linie nouă, g, r, e, ş, i, t, !. Dacă dorim să introducem astfel de caractere terminatoare de linie într-un şir va trebui să folosim secvenţe escape ca în: Acesta este\ngreşit

Dacă şirul de caractere este prea lung, putem să-l spargem în bucăţi mai mici pe care să le concatenăm cu operatorul +. Fiecare şir de caractere este în fapt o instanţă a clasei de obiecte String declarată standard în pachetul java.lang. Exemple de şiruri de caractere: ""

"\"""Şir de caractere"

"unu" + "doi"

Primul şir de caractere din exemplu nu conţine nici un caracter şi se numeşte şirul vid. Ultimul exemplu este format din două şiruri distincte concatenate.

Separatori Un separator este un caracter care indică sfârşitul unei unităţi lexicale şi începutul alteia. Separatorii sunt necesari atunci când unităţi lexicale diferite sunt scrise fără spaţii între ele. Acestea se pot totuşi separa dacă unele dintre ele conţin caractere separatori. În Java separatorii sunt următorii: (){}[];,.

Exemple de separare: a[i]

sin(56)

În primul exemplu nu avem o singură unitate lexicală ci patru: a, [, i, ]. Separatorii [ şi ] ne dau această informaţie. În al doilea exemplu, unităţile lexicale sunt tot 4 sin, (, 56, ).

www.cartiaz.ro – Carti si articole online gratuite de la A la Z Atenţie, separatorii participă în acelaşi timp şi la construcţia sintaxei limbajului. Ei nu sunt identici cu spaţiile deşi, ca şi acestea, separă unităţi lexicale diferite.

Operatori Operatorii reprezintă simboluri grafice pentru operaţiile elementare definite de limbajul Java. Lista tuturor operatorilor limbajului Java este: = > < ! ~ ?: = = <= >= != && || ++ -+ - * / & | ^ % << >> >>> += -= *= /= &= |= ^= %= <<= >>= >>>=

Să mai precizăm deocamdată că toţi operatorii joacă şi rol de separatori. Cu alte cuvinte, din secvenţa de caractere: vasile+gheorghe

putem extrage trei unităţi lexicale, vasile, + şi gheorghe.

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



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

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

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

Tipul unei variabile Tipul unei variabile poate fi fie unul dintre tipurile primitive definite de limbajul Java fie o referinţă. Creatorii limbajului Java au avut grijă să definească foarte exact care sunt caracteristicile fiecărui tip primitiv în parte şi care este setul de valori care se poate memora în variabilele care au tipuri primitive. În plus, a fost exact definită şi modalitatea de reprezentare a acestor tipuri primitive în memorie. În acest fel, variabilele Java devin independente de platforma hardware şi software pe care lucrează. În acelaşi spirit, Java defineşte o valoare implicită pentru fiecare tip de dată, în cazul în care aceasta nu a primit nici o valoare de la utilizator. În acest fel, ştim întotdeauna care este valoarea cu care o variabilă intră în calcul. Este o practică bună însă aceea ca programele să nu depindă niciodată de aceste iniţializări implicite. Numele variabilelor Numele variabilei poate fi orice identificator Java. Convenţia nescrisă de formare a numelor variabilelor este aceea că orice variabilă care nu este finală are un nume care începe cu literă minusculă în timp ce variabilele finale au nume care conţin numai majuscule. Dacă numele unei variabile care nu este finală conţine mai multe cuvinte, cuvintele începând cu cel de-al doilea se scriu cu litere minuscule dar cu prima literă majusculă. Exemple de nume de variabile care nu sunt finale ar putea fi: culoarea numărulDePaşi următorulElement

Variabilele finale ar putea avea nume precum: PORTOCALIUVERDEALBASTRUDESCHIS

Iniţializarea variabilelor Limbajul Java permite iniţializarea valorilor variabilelor chiar în momentul declarării acestora. Sintaxa este următoarea: Tip NumeVariabilă = ValoareIniţială;

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

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

Rândurile de mai sus reprezintă declaraţia a două variabile de tip boolean numite terminat respectiv areDreptate. Cele două variabile au, după declaraţie, valoarea false. Tipul caracter Orice limbaj de programare ne oferă într-un fel sau altul posibilitatea de a lucra cu caractere grafice care să reprezinte litere, cifre, semne de punctuaţie, etc. În cazul limbajului Java acest lucru se poate face folosind tipul primitiv numit tip caracter. O variabilă de tip caracter poate avea ca valoare coduri Unicode reprezentate pe 16 biţi, adică doi octeţi. Codurile reprezentabile astfel sunt foarte multe, putând acoperi caracterele de bază din toate limbile scrise existente. În Java putem combina mai multe caractere pentru a forma cuvinte sau şiruri de caractere mai lungi. Totuşi, trebuie să precizăm că aceste şiruri de caractere nu trebuiesc confundate cu tablourile de caractere pentru că ele conţin în plus informaţii legate de lungimea şirului.

Codul nu este altceva decât o corespondenţă între numere şi caractere fapt care permite conversii între variabile întregi şi caractere în ambele sensuri. O parte din aceste transformări pot să altereze valoarea originală din cauza dimensiunilor diferite ale zonelor în care sunt memorate cele două tipuri de valori. Convertirea caracterelor în numere şi invers poate să fie utilă la prelucrarea în bloc a caracterelor, cum ar fi trecerea tuturor literelor minuscule în majuscule şi invers. Atunci când declarăm un caracter fără să specificăm o valoare iniţială, el va primi automat ca valoare implicită caracterul null al codului Unicode, \u0000. Pentru a declara o variabilă de tip caracter folosim cuvântul rezervat char ca în exemplele următoare: char primaLiteră; char prima, ultima;

În cele două linii de cod am declarat trei variabile de tip caracter care au fost automat iniţializate cu caracterul null. Tipuri întregi Tipul octet

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

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

Tipul întreg

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

În fine, pentru cei care vor să reprezinte numerele întregi cu semn pe 8 octeţi, 64 de biţi, 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. există

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

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

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

Dacă valorile reprezentabile în variabile flotante nu sunt destul de precise sau destul de mari, puteţi folosi tipul flotant dublu care foloseşte opt octeţi pentru reprezentare, urmând acelaşi standard IEEE Valorile finite reprezentabile cu flotanţi dubli sunt de forma: sm2e

unde s este semnul +1 sau -1, m este mantisa, un întreg pozitiv mai mic decât 253 iar e este un exponent întreg între -1045 şi 1000. Valoarea implicită în acest caz este 0.0d. Pentru a declara flotanţi dubli, Java defineşte cuvântul rezervat double ca în: double distanţaPânăLaLună;

În afară de valorile definite până acum, standardul IEEE defineşte câteva valori speciale reprezentabile pe un flotant sau un flotant dublu. Reali speciali definiţi de IEEE

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

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

Din acest moment, variabila referinţă de clasă numită mingeaMea va putea păstra doar referinţe către obiecte de tip Minge sau către obiecte aparţinând unor clase derivate din clasa Minge. De exemplu, dacă avem o altă clasă, derivată din Minge, numită MingeDeBaschet, putem memora în referinţa mingeaMea şi o trimitere către o instanţă a clasei MingeDeBaschet. În mod general însă, nu se pot păstra în variabila mingeaMea referinţe către alte clase de obiecte. Dacă se încercă acest lucru, eroarea va fi semnalată chiar în momentul compilării, atunci când sursa programului este examinată pentru a fi transformată în instrucţiuni ale maşinii virtuale Java. Tipul referinţă către o interfaţă

Tipul referinţă către o interfaţă permite păstrarea unor referinţe către obiecte care respectă o anumită interfaţă. Clasa obiectelor referite poate fi oricare, atâta timp cât clasa respectivă implementează interfaţa cerută. Declaraţia se face cu următoarea sintaxă: ObiectSpaţioTemporal mingeaLuiVasile;

în care tipul este chiar numele interfeţei cerute. Dacă clasa de obiecte Minge declară că implementează această interfaţă, atunci variabila referinţă mingeaLuiVasile poate lua ca valoare referinţa către o instanţă a clasei Minge sau a clasei MingeDeBaschet. Prin intermediul unei variabile referinţă către o interfaţă nu se poate apela decât la funcţionalitatea cerută în interfaţa respectivă, chiar dacă obiectele reale oferă şi alte facilităţi, ele aparţinând unor clase mai bogate în metode. Tipul referinţă către un tablou Tipul referinţă către un tablou este un tip referinţă care poate păstra o trimitere către locaţia din memorie a unui tablou de elemente. Prin intermediul acestei referinţe putem accesa elementele tabloului furnizând indexul elementului dorit. Tablourile de elemente nu există în general ci ele sunt tablouri formate din elemente de un tip bine precizat. Din această cauză, atunci când declarăm o referinţă către un tablou, trebuie să precizăm şi de ce tip sunt elementele din tabloul respectiv. La declaraţia referinţei către tablou nu trebuie să precizăm şi numărul de elemente din tablou. Iată cum se declară o referinţă către un tablou de întregi lungi: long numere[];

Numele variabilei este numere. Un alt exemplu de declaraţie de referinţă către un tablou: Minge echipament[];

Declaraţia de mai sus construieşte o referinţă către un tablou care păstrează elemente de tip referinţă către o instanţă a clasei Minge. Numele variabilei referinţă este echipament. Parantezele drepte de după numele variabilei specifică faptul că este vorba despre un tablou.

Clasa de memorare Fiecare variabilă trebuie să aibă o anumită clasă de memorare. Această clasă ne permite să aflăm care este intervalul de existenţă şi vizibilitatea unei variabile în contextul execuţiei unui program. Este important să înţelegem exact această noţiune pentru că altfel vom încerca să referim variabile înainte ca acestea să fi fost create sau după ce au fost distruse sau să referim variabile care nu sunt vizibile din zona de program în care le apelăm. Variabile locale Aceste variabile nu au importanţă prea mare în contextul întregii aplicaţii, ele servind la rezolvarea unor probleme locale. Variabilele locale sunt declarate, rezervate în memorie şi utilizate doar în interiorul unor blocuri de instrucţiuni, fiind distruse automat la ieşirea din aceste blocuri. Aceste variabile sunt vizibile doar în interiorul blocului în care au fost create şi în subblocurile acestuia.

Variabile statice Variabilele statice sunt în general legate de funcţionalitatea anumitor clase de obiecte ale căror instanţe folosesc în comun aceste variabile. Variabilele statice sunt create atunci când codul specific clasei în care au fost declarate este încărcat în memorie şi nu sunt distruse decât atunci când acest cod este eliminat din memorie. Valorile memorate în variabile statice au importanţă mult mai mare în aplicaţie decât cele locale, ele păstrând informaţii care nu trebuie să se piardă la dispariţia unei instanţe a clasei. De exemplu, variabila în care este memorat numărul de picioare al obiectelor din clasa Om nu trebuie să fie distrusă la dispariţia unei instanţe din această clasă. Aceasta din cauză că şi celelalte instanţe ale clasei folosesc aceeaşi valoare. Şi chiar dacă la un moment dat nu mai există nici o instanţă a acestei clase, numărul de picioare ale unui Om trebuie să fie accesibil în continuare pentru interogare de către celelalte clase. Variabilele statice nu se pot declara decât ca variabile ale unor clase şi conţin în declaraţie cuvântul rezervat static. Din cauza faptului că ele aparţin clasei şi nu unei anumite instanţe a clasei, variabilele statice se mai numesc uneori şi variabile de clasă. Variabile dinamice Un alt tip de variabile sunt variabilele a căror perioadă de existenţă este stabilită de către programator. Aceste variabile pot fi alocate la cerere, dinamic, în orice moment al execuţiei programului. Ele vor fi distruse doar atunci când nu mai sunt referite de nicăieri. La alocarea unei variabile dinamice, este obligatoriu să păstrăm o referinţă către ea într-o variabilă de tip referinţă. Altfel, nu vom putea accesa în viitor variabila dinamică. În momentul în care nici o referinţă nu mai trimite către variabila dinamică, de exemplu pentru că referinţa a fost o variabilă locală şi blocul în care a fost declarată şi-a terminat execuţia, variabila dinamică este distrusă automat de către sistem printr-un mecanism numit colector de gunoaie. Colectorul de gunoaie poate porni din iniţiativa sistemului sau din iniţiativa programatorului la momente bine precizate ale execuţiei. Pentru a rezerva spaţiu pentru o variabilă dinamică este nevoie să apelăm la o expresie de alocare care foloseşte cuvântul rezervat new. Această expresie alocă spaţiul necesar pentru un anumit tip de valoare. De exemplu, pentru a rezerva spaţiul necesar unui obiect de tip Minge, putem apela la sintaxa: Minge mingeaMea = new Minge( );

iar pentru a rezerva spaţiul necesar unui tablou de referinţe către obiecte de tip Minge putem folosi declaraţia: Minge echipament[] = new Minge[5];

Am alocat astfel spaţiu pentru un tablou care conţine 5 referinţe către obiecte de tip Minge. Pentru alocarea tablourilor conţinând tipuri primitive se foloseşte aceeaşi sintaxă. De exemplu, următoarea linie de program alocă spaţiul necesar unui tablou cu 10 întregi, creând în acelaşi timp şi o variabilă referinţă spre acest tablou, numită numere: int numere[] = new int[10];

Tablouri de variabile Tablourile servesc, după cum s-a văzut, la memorarea secvenţelor de elemente de acelaşi tip. Tablourile unidimensionale au semnificaţia vectorilor de elemente. Se poate întâmpla să lucrăm şi cu tablouri de referinţe către tablouri, în acest caz modelul fiind acela al unei matrici bidimensionale, sau putem extinde definiţia şi pentru mai mult de două dimensiuni. Declaraţia variabilelor de tip tablou Pentru a declara variabile de tip tablou, trebuie să specificăm tipul elementelor care vor umple tabloul şi un nume pentru variabila referinţă care va păstra trimiterea către zona de memorie în care sunt memorate elementele tabloului. Deşi putem declara variabile referinţă către tablou şi separat, de obicei declaraţia este făcută în acelaşi timp cu alocarea spaţiului ca în exemplele din paragraful anterior. Sintaxa Java permite plasarea parantezelor drepte care specifică tipul tablou înainte sau după numele variabilei. Astfel, următoarele două declaraţii sunt echivalente: int[ ] numere; int numere[ ];

Dacă doriţi să folosiţi tablouri cu două dimensiuni ca matricile, puteţi să declaraţi un tablou de referinţe către tablouri cu una dintre următoarele trei sintaxe echivalente: float [ ][ ] matrice; float[ ] matrice[]; float matrice[][];

De precizat că şi în cazul dimensiunilor multiple, declaraţiile de mai sus nu fac nimic altceva decât să rezerve loc pentru o referinţă şi să precizeze numărul de dimensiuni. Alocarea spaţiului pentru elementele tabloului trebuie făcută explicit. Pentru tablourile cu mai multe dimensiuni, rezervarea spaţiului se poate face cu următoarea sintaxă: byte [][]octeti = new byte[23][5];

În expresia de alocare sunt specificate în clar numărul elementelor pentru fiecare dimensiune a tabloului. Iniţializarea tablourilor. Limbajul Java permite şi o sintaxă pentru iniţializarea elementelor unui tablou. Într-un astfel de caz este rezervat automat şi spaţiul de memorie necesar memorării valorilor iniţiale. Sintaxa folosită în astfel de cazuri este următoarea: char []caractere = { ’a’, ’b ’, ’c ’, ’d ’ };

Acest prim exemplu alocă spaţiu pentru patru elemente de tip caracter şi iniţializează aceste elemente cu valorile dintre acolade. După aceea, creează variabila de tip referinţă numită caractere şi o iniţializează cu referinţa la zona de memorie care păstrează cele patru valori. Iniţializarea funcţionează şi la tablouri cu mai multe dimensiuni ca în exemplele următoare: int [][]numere = { { 1, 3, 4, 5 }, { 2, 4, 5 }, { 1, 2, 3, 4, 5 } }; double [][][]reali = { { { 0.0, -1.0 }, { 4.5 } }, { { 2.5, 3.0 } } };

După cum observaţi numărul iniţializatorilor nu trebuie să fie acelaşi pentru fiecare element.

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

// dimensiune primeşte valoarea 25

sau float [][]multiTablou = new float[3][4]; int dimensiune1 = multiTablou[2].length; int dimensiune2 = multiTablou.length;

// dimensiune1 primeşte valoarea 4 // dimensiune2 primeşte valoarea 3

Referirea elementelor din tablou Elementele unui tablou se pot referi prin numele referinţei tabloului şi indexul elementului pe care dorim să-l referim. În Java, primul element din tablou este elementul cu numărul 0, al doilea este elementul numărul 1 şi aşa mai departe. Sintaxa de referire foloseşte parantezele pătrate [ şi ]. Între ele trebuie specificat indexul elementului pe care dorim să-l referim. Indexul nu trebuie să fie constant, el putând fi o expresie de complexitate oarecare. Iată câteva exemple: int []tablou = new int[10]; tablou[3] = 1; // al patrulea element primeşte valoarea 1 float [][]reali = new float[3][4]; reali[2][3] = 1.0f; // al patrulea element din al treilea tablou // primeşte valoarea 1

În cazul tablourilor cu mai multe dimensiuni, avem în realitate tablouri de referinţe la tablouri. Asta înseamnă că dacă considerăm următoarea declaraţie: char [][]caractere = new char [5][];

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

Variabila referinţă numită caractere conţine deocamdată un tablou de 5 referinţe la tablouri de caractere. Cele cinci referinţe sunt iniţializate cu null. Putem iniţializa aceste tablouri prin atribuiri de expresii de alocare: caractere[0] = new char [3]; caractere[4] = new char [5];

Noile tablouri sunt referite din interiorul tabloului original. Elementele noilor tablouri sunt caractere.

La fel, putem scrie: char []tablouDeCaractere = caractere[0];

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

Variabila tablouDeCaractere trimite către acelaşi tablou de caractere ca şi cel referit de primul element al tabloului referit de variabila caractere. Să mai precizăm că referirea unui element de tablou printr-un index mai mare sau egal cu lungimea tabloului duce la oprirea execuţiei programului cu un mesaj de eroare de execuţie corespunzător. Alocarea şi eliberarea tablourilor În cazul în care nu avem iniţializatori, variabilele sunt iniţializate cu valorile implicite definite de limbaj pentru tipul corespunzător. Aceasta înseamnă că, pentru tablourile cu mai multe dimensiuni, referinţele sunt iniţializate cu null. Pentru eliberarea memoriei ocupate de un tablou, este suficient să tăiem toate referinţele către tablou. Sistemul va sesiza automat că tabloul nu mai este referit şi mecanismul colector de gunoaie va elibera zona. Pentru a tăia o referinţă către un tablou dăm o altă valoare variabilei care referă tabloul. Valoarea poate fi null sau o referinţă către un alt tablou. De exemplu: float []reali = new float[10]; reali = null; // eliberarea tabloului sau reali = new float[15]; // eliberarea în alt fel sau { float []reali = new float[10];

Conversii Operaţiile definite în limbajul Java au un tip bine precizat de argumente. Din păcate, există situaţii în care nu putem transmite la apelul acestora exact tipul pe care compilatorul Java îl aşteaptă. În asemenea situaţii, compilatorul are două alternative: fie respinge orice operaţie cu argumente greşite, fie încearcă să convertească argumentele către tipurile necesare. Desigur, în cazul în care conversia nu este posibilă, singura alternativă rămâne prima. În multe situaţii însă, conversia este posibilă. Să luăm de exemplu tipurile întregi. Putem să convertim întotdeauna un întreg scurt la un întreg. Valoarea rezultată va fi exact aceeaşi. Conversia inversă însă, poate pune probleme dacă valoarea memorată în întreg depăşeşte capacitatea de memorare a unui întreg scurt. În afară de conversiile implicite, pe care compilatorul le hotărăşte, există şi conversii explicite, pe care programatorul le poate forţa la nevoie. Aceste conversii efectuează de obicei

operaţii în care există pericolul să se piardă o parte din informaţii. Compilatorul nu poate hotărî de unul singur în aceste situaţii. Conversiile implicite pot fi un pericol pentru stabilitatea aplicaţiei dacă pot să ducă la pierderi de informaţii fără avertizarea programatorului. Aceste erori sunt de obicei extrem de greu de depistat. În fiecare limbaj care lucrează cu tipuri fixe pentru datele sale există conversii imposibile, conversii periculoase şi conversii sigure. Conversiile imposibile sunt conversiile pe care limbajul nu le permite pentru că nu ştie cum să le execute sau pentru că operaţia este prea periculoasă. De exemplu, Java refuză să convertească un tip primitiv către un tip referinţă. Deşi s-ar putea imagina o astfel de conversie bazată pe faptul că o adresă este în cele din urmă un număr natural, acest tip de conversii sunt extrem de periculoase, chiar şi atunci când programatorul cere explicit această conversie. Conversii de extindere a valorii În aceste conversii valoarea se reprezintă într-o zonă mai mare fără să se piardă nici un fel de informaţii. Iată conversiile de extindere pe tipuri primitive: • • • • • •

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

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

byte la char short la byte sau char char la byte sau short int la byte, short sau char long la byte, short char, sau int float la byte, short, char, int sau long double la byte, short, char, int, long sau float.

În cazul conversiilor de trunchiere la numerele cu semn, este posibil să se schimbe semnul pentru că, în timpul conversiei, se îndepărtează pur şi simplu octeţii care nu mai încap şi poate rămâne primul bit diferit de vechiul prim bit. Copierea se face începând cu octeţii mai puţin semnificativi iar trunchierea se face la octeţii cei mai semnificativi. Conversii pe tipuri referinţă Conversiile tipurilor referinţă nu pun probleme pentru modul în care trebuie executată operaţia din cauză că, referinţa fiind o adresă, în timpul conversiei nu trebuie afectată în nici un fel această adresă. În schimb, se pun probleme legate de corectitudinea logică a conversiei. De exemplu, dacă avem o referinţă la un obiect care nu este tablou, este absurd să încercăm să convertim această referinţă la o referinţă de tablou. Limbajul Java defineşte extrem de strict conversiile posibile în cazul tipurilor referinţă pentru a salva programatorul de eventualele necazuri care pot apare în timpul execuţiei. Iată conversiile posibile: •

• • •

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

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

Da

Nu

Nu

Nu

Nu Nu

Nu

Nu

char

Nu

Da

Da

Da

Nu Nu

Nu

Nu

byte

Nu

Da

Da

Nu

Nu Nu

Nu

Nu

short

Nu

Da

Da

Da

Nu Nu

Nu

Nu

int

Nu

Da

Da

Da

Da Nu

Nu

Nu

long

Nu

Da

Da

Da

Da Da

Nu

Nu

float

Nu

Da

Da

Da

Da Da

Da

Nu

double

Nu

Da

Da

Da

Da Da

Da

Da

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

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

S este o clasă care nu este finală

T este o clasă care nu este finală

T este o clasă care este finală

T este o interfaţă

T = B[] este un tablou cu elemente de tipul B

T trebuie să fie subclasă a lui S

T trebuie să fie o subclasă a lui S

eroare la compilare

S trebuie să fie Object

eroare la compilare

eroare la compilare

S este o clasă care T trebuie să fie T trebuie să fie este finală aceeaşi clasă ca şi S aceeaşi clasă ca şi S S este o interfaţă

T trebuie să implementeze interfaţa S

T trebuie să implementeze interfaţa S

T trebuie să fie o subinterfaţă a lui S

eroare la compilare

S = A[] este un tablou cu elemente de tipul A

eroare la compilare

eroare la compilare

eroare la compilare

A sau B sunt acelaşi tip primitiv sau A este un tip referinţă şi B poate fi atribuit lui A

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

Conversii explicite Conversiile de tip cast, sau casturile, sunt apelate de către programator în mod explicit. Sintaxa pentru construcţia unui cast este scrierea tipului către care dorim să convertim în paranteze în faţa valorii pe care dorim să o convertim. Forma generală este: ( Tip ) Valoare Conversiile posibile în acest caz sunt mai multe decât conversiile implicite la atribuire pentru că în acest caz programatorul este prevenit de eventuale pierderi de date el trebuind să apeleze conversia explicit. Dar, continuă să existe conversii care nu se pot apela nici măcar în mod explicit. În cazul conversiilor de tip cast, orice valoare numerică poate fi convertită la orice valoare numerică. În continuare, valorile de tip boolean nu pot fi convertite la nici un alt tip. Nu există conversii între valorile de tip referinţă şi valorile de tip primitiv. În cazul conversiilor dintr-un tip referinţă într-altul putem separa două cazuri. Dacă compilatorul poate decide în timpul compilării dacă conversia este corectă sau nu, o va decide. În cazul în care compilatorul nu poate decide pe loc, se va efectua o verificare a conversiei în timpul execuţiei. Dacă conversia se dovedeşte greşită, va apare o eroare de execuţie şi programul va fi întrerupt. Iată un exemplu de situaţie în care compilatorul nu poate decide dacă conversia este posibilă sau nu: Minge mingeaMea; .... MingeDeBaschet mingeaMeaDeBaschet; // MingeDeBaschet este o clasă derivată din clasa Minge mingeaMeaDeBaschet=(MingeDeBaschet)mingeaMea;

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

În următorul exemplu însă, se poate decide în timpul compilării imposibilitatea conversiei: MingeDeBaschet mingeaMeaDeBaschet; MingeDePolo mingeaMeaDePolo; .... mingeaMeaDePolo = ( MingeDePolo ) mingeaMeaDeBaschet;

În fine, tabelul următor arată conversiile de tip cast a căror corectitudine poate fi stabilită în timpul compilării. Conversia încearcă să transforme printr-un cast o referinţă de tip T într-o referinţă de tip S.

T este o clasă care nu este finală

T este o clasă care este finală

T este o interfaţă

T = B[] este un tablou cu elemente de tipul B

S este o clasă care nu este finală

T trebuie să fie subclasă a lui S

T trebuie să fie o subclasă a lui S

Totdeauna corectă la compilare

S trebuie să fie Object

S este o clasă care este finală

S trebuie să fie subclasă a lui T

T trebuie să fie aceeaşi clasă ca şi S

S trebuie să implementeze interfaţa T

eroare la compilare

S este o interfaţă

Totdeauna corectă la compilare

T trebuie să implementeze interfaţa S

Totdeauna corectă la compilare

eroare la compilare

S = A[] este un tablou cu elemente de tipul A

T trebuie să fie Object

eroare la compilare

eroare la compilare

A sau B sunt acelaşi tip primitiv sau A este un tip referinţă şi B poate fi convertit cu un cast la A

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

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

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

De exemplu, în următoarea operaţie amândoi operanzii vor fi convertiţi la double prin promovare aritmetică binară: float f; double i = f + 3;

După efectuarea operaţiei, valoarea obţinută va fi convertită implicit la double. În următorul exemplu, se produce o promovare unară la int de la short. short s, r; ... int min = ( r < -s ) ? r : s;

În expresia condiţională, operandul -s se traduce de fapt prin aplicarea operatorului unar - la variabila s care este de tip short. În acest caz, se va produce automat promovarea aritmetică unară de la short la int, apoi se va continua evaluarea expresiei.

Obiecte Java În primul rând să observăm că, atunci când scriem programe în Java nu facem altceva decât să definim noi şi noi clase de obiecte. Dintre acestea, unele vor reprezenta însăşi aplicaţia noastră în timp ce altele vor fi necesare pentru rezolvarea problemei la care lucrăm. Ulterior, atunci când dorim să lansăm aplicaţia în execuţie nu va trebui decât să instanţiem un obiect reprezentând aplicaţia în sine şi să apelăm o metodă de pornire definită de către aplicaţie, metodă care de obicei are un nume şi un set de parametri bine fixate. Totuşi, numele acestei metode depinde de contextul în care este lansată aplicaţia noastră. Această abordare a construcţiei unei aplicaţii ne spune printre altele că vom putea lansa oricâte instanţe ale aplicaţiei noastre dorim, pentru că fiecare dintre acestea va reprezenta un obiect în memorie având propriile lui valori pentru variabile. Execuţia instanţelor diferite ale aplicaţiei va urma desigur căi diferite în funcţie de interacţiunea fiecăreia cu un utilizator, eventual acelaşi, şi în funcţie de unii parametri pe care îi putem defini în momentul creării fiecărei instanţe.

Declaraţia unei noi clase de obiecte Pasul 1: Stabilirea conceptului reprezentat de clasa de obiecte Să vedem ce trebuie să definim atunci când dorim să creăm o nouă clasă de obiecte. În primul rând trebuie să stabilim care este conceptul care este reprezentat de către noua clasă de obiecte şi să definim informaţiile memorate în obiect şi modul de utilizare a acestuia. Acest pas este cel mai important din tot procesul de definire al unei noi clase de obiecte. Este necesar să încercaţi să respectaţi două reguli oarecum antagonice. Una dintre ele spune că nu trebuiesc create mai multe clase de obiecte decât este nevoie, pentru a nu face dificilă înţelegerea modului de lucru al aplicaţiei la care lucraţi. Cea de-a doua regulă spune că nu este bine să mixaţi într-un singur obiect funcţionalităţi care nu au nimic în comun, creând astfel clase care corespund la două concepte diferite. Medierea celor două reguli nu este întotdeauna foarte uşoară. Oricum, vă va fi mai uşor dacă păstraţi în minte faptul că fiecare clasă pe care o definiţi trebuie să corespundă unui concept real bine definit, necesar la rezolvarea problemei la care lucraţi. Şi mai păstraţi în minte şi faptul că este inutil să lucraţi cu concepte foarte generale atunci când aplicaţia dumneavoastră nu are nevoie decât de o particularizare a acestora. Riscaţi să pierdeţi controlul dezvoltării acestor clase de obiecte prea generale şi să îngreunaţi dezvoltarea aplicaţiei.

Pasul 2: Stabilirea numelui clasei de obiecte După ce aţi stabilit exact ce doriţi de la noua clasă de obiecte, sunteţi în măsură să găsiţi un nume pentru noua clasă, nume care trebuie să urmeze regulile de construcţie ale identificatorilor limbajului Java definite în capitolul anterior. Stabilirea unui nume potrivit pentru o nouă clasă nu este întotdeauna un lucru foarte uşor. Problema este că acest nume nu trebuie să fie exagerat de lung dar trebuie să exprime suficient de bine destinaţia clasei. Regulile de denumire ale claselor sunt rezultatul experienţei fiecăruia sau al unor convenţii de numire stabilite anterior. De obicei, numele de clase este bine să înceapă cu o literă majusculă. Dacă numele clasei conţine în interior mai multe cuvinte, aceste cuvinte trebuie de asemenea începute cu literă majusculă. Restul caracterelor vor fi litere minuscule. De exemplu, dacă dorim să definim o clasă de obiecte care implementează conceptul de motor Otto vom folosi un nume ca MotorOtto pentru noua clasă ce trebuie creată. La fel, vom defini clasa

MotorDiesel sau MotorCuReacţie. Dacă însă avem nevoie să definim o clasă separată pentru un motor Otto cu cilindri în V şi carburator, denumirea clasei ca MotorOttoCuCilindriÎnVSiCarburator nu este poate cea mai bună soluţie. Poate că în acest caz este preferabilă o prescurtare de forma MotorOttoVC. Desigur, acestea sunt doar câteva remarci la adresa acestei probleme şi este în continuare necesar ca în timp să vă creaţi propria convenţie de denumire a claselor pe care le creaţi.

Pasul 3: Stabilirea superclasei În cazul în care aţi definit deja o parte din funcţionalitatea de care aveţi nevoie într-o altă superclasă, puteţi să derivaţi noua clasă de obiecte din clasa deja existentă. Dacă nu există o astfel de clasă, noua clasă va fi automat derivată din clasa de obiecte predefinită numită Object. În Java, clasa Object este superclasă direct sau indirect pentru orice altă clasă de obiecte definită de utilizator. Alegerea superclasei din care derivaţi noua clasă de obiecte este foarte importantă pentru că vă ajută să refolosiţi codul deja existent. Totuşi, nu alegeţi cu uşurinţă superclasa unui obiect pentru că astfel puteţi încărca obiectele cu o funcţionalitate inutilă, existentă în superclasă. Dacă nu există o clasă care să vă ofere doar funcţionalitatea de care aveţi nevoie, este preferabil să derivaţi noua clasă direct din clasa Object şi să apelaţi indirect funcţionalitatea pe care o doriţi.

Pasul 4: Stabilirea interfeţelor pe care le respectă clasa Stabilirea acestor interfeţe are dublu scop. În primul rând ele instruiesc compilatorul să verifice dacă noua clasă respectă cu adevărat toate interfeţele pe care le-a declarat, cu alte cuvinte defineşte toate metodele declarate în aceste interfeţe. A doua finalitate este aceea de a permite compilatorului să folosească instanţele noii clase oriunde aplicaţia declară că este nevoie de un obiect care implementează interfeţele declarate. O clasă poate să implementeze mai multe interfeţe sau niciuna.

Pasul 5: Stabilirea modificatorilor clasei În unele cazuri trebuie să oferim compilatorului informaţii suplimentare relative la modul în care vom folosi clasa nou creată pentru ca acesta să poată executa verificări suplimentare asupra descrierii clasei. În acest scop, putem defini o clasă ca fiind abstractă, finală sau publică folosindune de o serie de cuvinte rezervate numite modificatori. Modificatorii pentru tipurile de clase de mai sus sunt respectiv: abstract, final şi public. În cazul în care declarăm o clasă de obiecte ca fiind abstractă, compilatorul va interzice instanţierea acestei clase. Dacă o clasă este declarată finală, compilatorul va avea grijă să nu putem deriva noi subclase din această clasă. În cazul în care declarăm în acelaşi timp o clasă de obiecte ca fiind abstractă şi finală, eroarea va fi semnalată încă din timpul compilării pentru că cei doi modificatori se exclud. Pentru ca o clasă să poată fi folosită şi în exteriorul contextului în care a fost declarată ea trebuie să fie declarată publică. Orice clasă de obiecte care va fi instanţiată ca o aplicaţie trebuie declarată publică.

Pasul 6: Scrierea corpului declaraţiei În sfârşit, după ce toţi ceilalţi paşi au fost efectuaţi, putem trece la scrierea corpului declaraţiei de clasă. În principal, aici vom descrie variabilele clasei împreună cu metodele care lucrează cu acestea. Tot aici putem preciza şi gradele de protejare pentru fiecare dintre elementele declaraţiei. Uneori numim variabilele şi metodele unei clase la un loc ca fiind câmpurile clasei.

Forma generală a unei declaraţii de clasă Sintaxa exactă de declarare a unei clase arată în felul următor:

{ abstract | final | public } class NumeClasă [ extends NumeSuperclasă ] [ implements NumeInterfaţă [ , NumeInterfaţă ] ] { [ CâmpClasă ] }

Variabilele unei clase În interiorul claselor se pot declara variabile. Aceste variabile sunt specifice clasei respective. Fiecare dintre ele trebuie să aibă un tip, un nume şi poate avea iniţializatori. În afară de aceste elemente, pe care le-am prezentat deja în secţiunea în care am prezentat variabilele, variabilele definite în interiorul unei clase pot avea definiţi o serie de modificatori care alterează comportarea variabilei în interiorul clasei, şi o specificaţie de protecţie care defineşte cine are dreptul să acceseze variabila respectivă.

Modificatori Modificatorii sunt cuvinte rezervate Java care precizează sensul unei declaraţii. Iată lista acestora: static final transient volatile

Dintre aceştia, transient nu este utilizat în versiunea curentă a limbajului Java. Pe viitor va fi folosit pentru a specifica variabile care nu conţin informaţii care trebuie să rămână persistente la terminarea programului. Modificatorul volatile specifică faptul că variabila respectivă poate fi modificată asincron cu rularea aplicaţiei. În aceste cazuri, compilatorul trebuie să-şi ia măsuri suplimentare în cazul generării şi optimizării codului care se adresează acestei variabile. Modificatorul final este folosit pentru a specifica o variabilă a cărei valoare nu poate fi modificată. Variabila respectivă trebuie să primească o valoare de iniţializare chiar în momentul declaraţiei. Altfel, ea nu va mai putea fi iniţializată în viitor. Orice încercare ulterioară de a seta valori la această variabilă va fi semnalată ca eroare de compilare. Modificatorul static este folosit pentru a specifica faptul că variabila are o singură valoare comună tuturor instanţelor clasei în care este declarată. Modificarea valorii acestei variabile din interiorul unui obiect face ca modificarea să fie vizibilă din celelalte obiecte. Variabilele statice sunt iniţializate la încărcarea codului specific unei clase şi există chiar şi dacă nu există nici o instanţă a clasei respective. Din această cauză, ele pot fi folosite de metodele statice.

Protecţie În Java există patru grade de protecţie pentru o variabilă aparţinând unei clase: • privată • protejată • publică • prietenoasă O variabilă publică este accesibilă oriunde este accesibil numele clasei. Cuvântul rezervat este public. O variabilă protejată este accesibilă în orice clasă din pachetul căreia îi aparţine clasa în care este declarată. În acelaşi timp, variabila este accesibilă în toate subclasele clasei date, chiar dacă ele aparţin altor pachete. Cuvântul rezervat este protected. O variabilă privată este accesibilă doar în interiorul clasei în care a fost declarată. Cuvântul rezervat este private.

O variabilă care nu are nici o declaraţie relativă la gradul de protecţie este automat o variabilă prietenoasă. O variabilă prietenoasă este accesibilă în pachetul din care face parte clasa în interiorul căreia a fost declarată, la fel ca şi o variabilă protejată. Dar, spre deosebire de variabilele protejate, o variabilă prietenoasă nu este accesibilă în subclasele clasei date dacă aceste sunt declarate ca aparţinând unui alt pachet. Nu există un cuvânt rezervat pentru specificarea explicită a variabilelor prietenoase. O variabilă nu poate avea declarate mai multe grade de protecţie în acelaşi timp. O astfel de declaraţie este semnalată ca eroare de compilare.

Accesarea unei variabile Accesarea unei variabile declarate în interiorul unei clasei se face folosindu-ne de o expresie de forma: ReferinţăInstanţă.NumeVariabilă Referinţa către o instanţă trebuie să fie referinţă către clasa care conţine variabila. Referinţa poate fi valoarea unei expresii mai complicate, ca de exemplu un element dintr-un tablou de referinţe. În cazul în care avem o variabilă statică, aceasta poate fi accesată şi fără să deţinem o referinţă către o instanţă a clasei. Sintaxa este, în acest caz: NumeClasă.NumeVariabilă

Vizibilitate O variabilă poate fi ascunsă de declaraţia unei alte variabile cu acelaşi nume. De exemplu, dacă într-o clasă avem declarată o variabilă cu numele unu şi într-o subclasă a acesteia avem declarată o variabilă cu acelaşi nume, atunci variabila din superclasă este ascunsă de cea din clasă. Totuşi, variabila din superclasă există încă şi poate fi accesată în mod explicit. Expresia de referire este, în acest caz: NumeSuperClasă.NumeVariabilă sau super.NumeVariabilă

în cazul în care superclasa este imediată. La fel, o variabilă a unei clase poate fi ascunsă de o declaraţie de variabilă dintr-un bloc de instrucţiuni. Orice referinţă la ea va trebui făcută în mod explicit. Expresia de referire este, în acest caz: this.NumeVariabilă

Variabile predefinite: this şi super În interiorul fiecărei metode non-statice dintr-o clasă există predefinite două variabile cu semnificaţie specială. Cele două variabile sunt de tip referinţă şi au aceeaşi valoare şi anume o referinţă către obiectul curent. Diferenţa dintre ele este tipul. Prima dintre acestea este variabila this care are tipul referinţă către clasa în interiorul căreia apare metoda. A doua este variabila super al cărei tip este o referinţă către superclasa imediată a clasei în care apare metoda. În interiorul obiectelor din clasa Object nu se poate folosi referinţa super pentru că nu există nici o superclasă a clasei de obiecte Object. În cazul în care super este folosită la apelul unui constructor sau al unei metode, ea acţionează ca un cast către superclasa imediată.

Metodele unei clase Fiecare clasă îşi poate defini propriile sale metode pe lângă metodele pe care le moşteneşte de la superclasa sa. Aceste metode definesc operaţiile care pot fi executate cu obiectul respectiv. În cazul în care una dintre metodele moştenite nu are o implementare corespunzătoare în superclasă, clasa îşi poate redefini metoda după cum doreşte. În plus, o clasă îşi poate defini metode de construcţie a obiectelor şi metode de eliberare a acestora. Metodele de construcţie sunt apelate ori de câte ori este alocat un nou obiect din clasa respectivă. Putem declara mai multe metode de construcţie, ele diferind prin parametrii din care trebuie construit obiectul. Metodele de eliberare a obiectului sunt cele care eliberează resursele ocupate de obiect în momentul în care acesta este distrus de către mecanismul automat de colectare de gunoaie. Fiecare clasă are o singură metodă de eliberare, numită şi finalizator. Apelarea acestei metode se face de către sistem şi nu există nici o cale de control al momentului în care se produce acest apel.

Declararea metodelor Pentru a declara o metodă, este necesar să declarăm numele acesteia, tipul de valoare pe care o întoarce, parametrii metodei precum şi un bloc în care să descriem instrucţiunile care trebuiesc executate atunci când metoda este apelată. În plus, orice metodă are un număr de modificatori care descriu proprietăţile metodei şi modul de lucru al acesteia. Declararea precum şi implementarea metodelor se face întotdeauna în interiorul declaraţiei de clasă. Nu există nici o cale prin care să putem scrie o parte dintre metodele unei clase într-un fişier separat care să facă referinţă apoi la declaraţia clasei. În formă generală, declaraţia unei metode arată în felul următor: [Modificator] TipRezultat Declaraţie [ClauzeThrows] CorpulMetodei Modificatorii precum şi clauzele throws pot să lipsească. Numele şi parametrii metodelor

Recunoaşterea unei anumite metode se face după numele şi tipul parametrilor săi. Pot exista metode cu acelaşi nume dar având parametri diferiţi. Acest fenomen poartă numele de supraîncărcarea numelui unei metode. Numele metodei este un identificator Java. Avem toată libertatea în a alege numele pe care îl dorim pentru metodele noastre, dar în general este preferabil să alegem nume care sugerează utilizarea metodei. Numele unei metode începe de obicei cu literă mică. Dacă acesta este format din mai multe cuvinte, litera de început a fiecărui cuvânt va fi majusculă. În acest mod numele unei metode este foarte uşor de citit şi de depistat în sursă. Parametrii metodei sunt în realitate nişte variabile care sunt iniţializate în momentul apelului cu valori care controlează modul ulterior de execuţie. Aceste variabile există pe toată perioada execuţiei metodei. Se pot scrie metode care să nu aibă nici un parametru. Fiind o variabilă, fiecare parametru are un tip şi un nume. Numele trebuie să fie un identificator Java. Deşi avem libertatea să alegem orice nume dorim, din nou este preferabil să alegem nume care să sugereze scopul la care va fi utilizat parametrul respectiv. Tipul unui parametru este oricare dintre tipurile valide în Java. Acestea poate fi fie un tip primitiv, fie un tip referinţă către obiect, interfaţă sau tablou. În momentul apelului unei metode, compilatorul încearcă să găsească o metodă în interiorul clasei care să aibă acelaşi nume cu cel apelat şi acelaşi număr de parametri ca şi apelul. Mai mult, tipurile parametrilor de apel trebuie să corespundă cu tipurile parametrilor declaraţi ai metodei găsite sau să poată fi convertiţi la aceştia.

Dacă o astfel de metodă este găsită în declaraţia clasei sau în superclasele acesteia, parametrii de apel sunt convertiţi către tipurile declarate şi se generează apelul către metoda respectivă. Este o eroare de compilare să declarăm două metode cu acelaşi nume, acelaşi număr de parametri şi acelaşi tip pentru parametrii corespunzători. Într-o asemenea situaţie, compilatorul n-ar mai şti care metodă trebuie apelată la un moment dat. De asemenea, este o eroare de compilare să existe două metode care se potrivesc la acelaşi apel. Acest lucru se întâmplă când nici una dintre metodele existente nu se potriveşte exact şi când există două metode cu acelaşi nume şi acelaşi număr de parametri şi, în plus, parametrii de apel se pot converti către parametrii declaraţi ai ambelor metode. Rezolvarea unei astfel de probleme se face prin conversia explicită (cast) de către programator a valorilor de apel spre tipurile exacte ale parametrilor metodei pe care dorim să o apelăm în realitate. În fine, forma generală de declaraţie a numelui şi parametrilor unei metode este: NumeMetodă( [TipParametru NumeParametru] [,TipParametru NumeParametru] ) Modificatori de metode

Modificatorii sunt cuvinte cheie ale limbajului Java care specifică proprietăţi suplimentare pentru o metodă. Iată lista completă a acestora în cazul metodelor: • static - pentru metodele statice • abstract - pentru metodele abstracte • final - pentru metodele finale • native - pentru metodele native • synchronized - pentru metodele sincronizate Metode statice În mod normal, o metodă a unei clase se poate apela numai printr-o instanţă a clasei respective sau printr-o instanţă a unei subclase. Acest lucru se datorează faptului că metoda face apel la o serie de variabile ale clasei care sunt memorate în interiorul instanţei şi care au valori diferite în instanţe diferite. Astfel de metode se numesc metode ale instanţelor clasei. După cum ştim deja, există şi un alt tip de variabile, şi anume variabilele de clasă sau variabilele statice care sunt comune tuturor instanţelor clasei respective şi există pe toată perioada de timp în care clasa este încărcată în memorie. Aceste variabile pot fi accesate fără să avem nevoie de o instanţă a clasei respective. În mod similar există şi metode statice. Aceste metode nu au nevoie de o instanţă a clasei sau a unei subclase pentru a putea fi apelate pentru că ele nu au voie să folosească variabile care sunt memorate în interiorul instanţelor. În schimb, aceste metode pot să folosească variabilele statice declarate în interiorul clasei. Orice metodă statică este implicit şi finală. Metode abstracte Metodele abstracte sunt metode care nu au corp de implementare. Ele sunt declarate numai pentru a forţa subclasele care vor să aibă instanţe să implementeze metodele respective. Metodele abstracte trebuie declarate numai în interiorul claselor care au fost declarate abstracte. Altfel compilatorul va semnala o eroare de compilare. Orice subclasă a claselor abstracte care nu este declarată abstractă trebuie să ofere o implementare a acestor metode, altfel va fi generată o eroare de compilare. Prin acest mecanism ne asigurăm că toate instanţele care pot fi convertite către clasa care conţine definiţia unei metode abstracte au implementată metoda respectivă dar, în acelaşi timp, nu

este nevoie să implementăm în nici un fel metoda chiar în clasa care o declară pentru că nu ştim pe moment cum va fi implementată. O metodă statică nu poate fi declarată şi abstractă pentru că o metodă statică este implicit finală şi nu poate fi rescrisă. Metode finale O metodă finală este o metodă care nu poate fi rescrisă în subclasele clasei în care a fost declarată. O metodă este rescrisă într-o subclasă dacă aceasta implementează o metodă cu acelaşi nume şi acelaşi număr şi tip de parametri ca şi metoda din superclasă. Declararea metodelor finale este utilă în primul rând compilatorului care poate genera metodele respective direct în codul rezultat fiind sigur că metoda nu va avea nici o altă implementare în subclase. Metode native Metodele native sunt metode care sunt implementate pe o cale specifică unei anumite platforme. De obicei aceste metode sunt implementate în C sau în limbaj de asamblare. Metoda propriu-zisă nu poate avea corp de implementare pentru că implementarea nu este făcută în Java. În rest, metodele native sunt exact ca orice altă metodă Java. Ele pot fi moştenite, pot fi statice sau nu, pot fi finale sau nu, pot să rescrie o metodă din superclasă şi pot fi la rândul lor rescrise în subclase. Metode sincronizate O metodă sincronizată este o metodă care conţine cod critic pentru un anumit obiect sau clasă şi nu poate fi rulată în paralel cu nici o altă metodă critică sau cu o instrucţiune synchronized referitoare la acelaşi obiect sau clasă. Înainte de execuţia metodei, obiectul sau clasa respectivă sunt blocate. La terminarea metodei, acestea sunt deblocate. Dacă metoda este statică atunci este blocată o întreagă clasă, clasa din care face parte metoda. Altfel, este blocată doar instanţa în contextul căreia este apelată metoda. Protejarea metodelor

Accesul la metodele unei clase este protejat în acelaşi fel ca şi accesul la variabilele clasei. În Java există patru grade de protecţie pentru o metodă aparţinând unei clase: • privată • protejată • publică • prietenoasă O metodă declarată publică este accesibilă oriunde este accesibil numele clasei. Cuvântul rezervat este public. O metodă declarată protejată este accesibilă în orice clasă din pachetul căreia îi aparţine clasa în care este declarată. În acelaşi timp, metoda este accesibilă în toate subclasele clasei date, chiar dacă ele aparţin altor pachete. Cuvântul rezervat este protected. O metodă declarată privată este accesibilă doar în interiorul clasei în care a fost declarată. Cuvântul rezervat este private. O metodă care nu are nici o declaraţie relativă la gradul de protecţie este automat o metodă prietenoasă. O metodă prietenoasă este accesibilă în pachetul din care face parte clasa în interiorul căreia a fost declarată la fel ca şi o metodă protejată. Dar, spre deosebire de metodele protejate, o metodă prietenoasă nu este accesibilă în subclasele clasei date dacă aceste sunt declarate ca aparţinând unui alt pachet. Nu există un cuvânt rezervat pentru specificarea explicită a metodelor prietenoase.

O metodă nu poate avea declarate mai multe grade de protecţie în acelaşi timp. O astfel de declaraţie este semnalată ca eroare de compilare.

Apelul metodelor Pentru a apela o metodă a unei clase este necesar să dispunem de o cale de acces la metoda respectivă. În plus, trebuie să dispunem de drepturile necesare apelului metodei. Sintaxa efectivă de acces este următoarea: CaleDeAcces.Metodă( Parametri ) În cazul în care metoda este statică, pentru a specifica o cale de acces este suficient să furnizăm numele clasei în care a fost declarată metoda. Accesul la numele clasei se poate obţine fie importând clasa sau întreg pachetul din care face parte clasa fie specificând în clar numele clasei şi drumul de acces către aceasta. De exemplu, pentru a accesa metoda random definită static în clasa Math aparţinând pachetului java.lang putem scrie: double aleator = Math.random();

sau, alternativ: double aleator = java.lang.Math.random();

În cazul claselor definite în pachetul java.lang nu este necesar nici un import pentru că acestea sunt implicit importate de către compilator. Cea de-a doua cale de acces este existenţa unei instanţe a clasei respective. Prin această instanţă putem accesa metodele care nu sunt declarate statice, numite uneori şi metode ale instanţelor clasei. Aceste metode au nevoie de o instanţă a clasei pentru a putea lucra, pentru că folosesc variabile non-statice ale clasei sau apelează alte metode non-statice. Metodele primesc acest obiect ca pe un parametru ascuns. De exemplu, având o instanţă a clasei Object sau a unei subclase a acesteia, putem obţine o reprezentare sub formă de şir de caractere prin: Object obiect = new Object(); String sir = obiect.toString();

În cazul în care apelăm o metodă a clasei din care face parte şi metoda apelantă putem să renunţăm la calea de acces în cazul metodelor statice, scriind doar numele metodei şi parametrii. Pentru metodele specifice instanţelor, putem renunţa la calea de acces, dar în acest caz metoda accesează aceeaşi instanţă ca şi metoda apelantă. În cazul în care metoda apelantă este statică, specificarea unei instanţe este obligatorie în cazul metodelor de instanţă. Parametrii de apel servesc împreună cu numele la identificarea metodei pe care dorim să o apelăm. Înainte de a fi transmişi, aceştia sunt convertiţi către tipurile declarate de parametri ai metodei, după cum este descris mai sus. Specificarea parametrilor de apel se face separându-i prin virgulă. După ultimul parametru nu se mai pune virgulă. Dacă metoda nu are nici un parametru, parantezele rotunde sunt în continuare necesare. Exemple de apel de metode cu parametri: String numar = String.valueOf( 12 ); // 12 -> String double valoare = Math.abs( 12.54 ); // valoare absolută String prima = numar.substring( 0, 1 ); // prima litera

Valoarea de retur a unei metode O metodă trebuie să-şi declare tipul valorii pe care o întoarce. În cazul în care metoda doreşte să specifice explicit că nu întoarce nici o valoare, ea trebuie să declare ca tip de retur tipul void ca în exemplul:

void a() { … }

În caz general, o metodă întoarce o valoare primitivă sau un tip referinţă. Putem declara acest tip ca în: long abs( int valoare ) { … }

Pentru a returna o valoare ca rezultat al execuţiei unei metode, trebuie să folosim instrucţiunea return, aşa cum s-a arătat în secţiunea dedicată instrucţiunilor. Instrucţiunea return trebuie să conţină o expresie a cărei valoare să poată fi convertită la tipul declarat al valorii de retur a metodei. De exemplu: long abs( int valoare ) { return Math.abs( valoare ); }

Metoda statică abs din clasa Math care primeşte un parametru întreg returnează tot un întreg. În exemplul nostru, instrucţiunea return este corectă pentru că există o cale de conversie de la întreg la întreg lung, conversie care este apelată automat de compilator înainte de ieşirea din metodă. În schimb, în exemplul următor: int abs( long valoare ) { return Math.abs( valoare ); }

compilatorul va genera o eroare de compilare pentru că metoda statică abs din clasa Math care primeşte ca parametru un întreg lung întoarce tot un întreg lung, iar un întreg lung nu poate fi convertit sigur la un întreg normal pentru că există riscul deteriorării valorii, la fel ca la atribuire. Rezolvarea trebuie să conţină un cast explicit: int abs( long valoare ) { return ( int )Math.abs( valoare ); }

În cazul în care o metodă este declarată void, putem să ne întoarcem din ea folosind instrucţiunea return fără nici o expresie. De exemplu: void metoda() { … if( … ) return; … }

Specificarea unei expresii în acest caz duce la o eroare de compilare. La fel şi în cazul în care folosim instrucţiunea return fără nici o expresie în interiorul unei metode care nu este declarată void.

Vizibilitate O metodă este vizibilă dacă este declarată în clasa prin care este apelată sau într-una din superclasele acesteia. De exemplu, dacă avem următoarea declaraţie: class A { … void a() { … } } class B extends A { void b() { a(); c(); … } void c() { .. } … }

Apelul metodei a în interiorul metodei b din clasa B este permis pentru că metoda a este declarată în interiorul clasei A care este superclasă pentru clasa B. Apelul metodei c în aceeaşi metodă b este permis pentru că metoda c este declarată în aceeaşi clasă ca şi metoda b. Uneori, o subclasă rescrie o metodă dintr-o superclasă a sa. În acest caz, apelul metodei respective în interiorul subclasei duce automat la apelul metodei din subclasă. Dacă totuşi dorim să apelăm metoda aşa cum a fost ea definită în superclasă, putem prefixa apelul cu numele superclasei. De exemplu: class A { … void a() { … } } class B extends A { void a() { .. } void c() { a();// metoda a din clasa B A.a();// metoda a din clasa A … } …

}

Iniţializatori statici La încărcarea unei clase sunt automat iniţializate toate variabilele statice declarate în interiorul clasei. În plus, sunt apelaţi toţi iniţializatorii statici ai clasei. Un iniţializator static are următoarea sintaxă: static

BlocDeInstrucţiuni

Blocul de instrucţiuni este executat automat la încărcarea clasei. De exemplu, putem defini un iniţializator static în felul următor: class A { static double a; static int b; static { a = Math.random(); // număr dublu între 0.0 şi 1.0 b = ( int )( a * 500 ); // număr întreg între 0 şi 500 } … }

Declaraţiile de variabile statice şi iniţializatorii statici sunt executate în ordinea în care apar în clasă. De exemplu, dacă avem următoarea declaraţie de clasă: class A { static int i = 11; static { i += 100; i %= 55; } static int j = i + 1; }

valoarea finală a lui i va fi 1 ( ( 11 + 100 ) % 55 ) iar valoarea lui j va fi 2.

Constructori şi finalizatori constructori La crearea unei noi instanţe a unei clase sistemul alocă automat memoria necesară instanţei şi o iniţializează cu valorile iniţiale specificate sau implicite. Dacă dorim să facem iniţializări suplimentare în interiorul acestei memorii sau în altă parte putem descrie metode speciale numite constructori ai clasei. Putem avea mai mulţi constructori pentru aceeaşi clasă, aceştia diferind doar prin parametrii pe care îi primesc. Numele tuturor constructorilor este acelaşi şi este identic cu numele clasei. Declaraţia unui constructor este asemănătoare cu declaraţia unei metode oarecare, cu diferenţa că nu putem specifica o valoare de retur şi nu putem specifica nici un fel de modificatori. Dacă dorim să returnăm dintr-un constructor, trebuie să folosim instrucţiunea return fără nici o expresie. Putem însă să specificăm gradul de protecţie al unui constructor ca fiind public, privat, protejat sau prietenos. Constructorii pot avea clauze throws. Dacă o clasă nu are constructori, compilatorul va crea automat un constructor implicit care nu ia nici un parametru şi care iniţializează toate variabilele clasei şi apelează constructorul superclasei fără argumente prin super( ). Dacă superclasa nu are un constructor care ia zero argumente, se va genera o eroare de compilare. Dacă o clasă are cel puţin un constructor, constructorul implicit nu mai este creat de către compilator. Când construim corpul unui constructor avem posibilitatea de a apela, pe prima linie a blocului de instrucţiuni care reprezintă corpul constructorului, un constructor explicit. Constructorul explicit poate avea două forme: this(

[Parametri] );

super(

[Parametri] );

Cu această sintaxă apelăm unul dintre constructorii superclasei sau unul dintre ceilalţi constructori din aceeaşi clasă. Aceste linii nu pot apărea decât pe prima poziţie în corpul constructorului. Dacă nu apar acolo, compilatorul consideră implicit că prima instrucţiune din corpul constructorului este: super();

Şi în acest caz se va genera o eroare de compilare dacă nu există un constructor în superclasă care să lucreze fără nici un parametru. După apelul explicit al unui constructor din superclasă cu sintaxa super( … ) este executată în mod implicit iniţializarea tuturor variabilelor de instanţă (non-statice) care au iniţializatori expliciţi. După apelul unui constructor din aceeaşi clasă cu sintaxa this( … ) nu există nici o altă acţiune implicită, deci nu vor fi iniţializate nici un fel de variabile. Aceasta datorită faptului că iniţializarea s-a produs deja în constructorul apelat. Exemplu: class A extends B { String valoare; A( String val ) { // aici există apel implicit // al lui super(), adică B() valoare = val; } A( int val ) { this( String.valueOf( val ) );// alt constructor } }

Finalizatori În Java nu este nevoie să apelăm în mod explicit distrugerea unei instanţe atunci când nu mai este nevoie de ea. Sistemul oferă un mecanism de colectare a gunoaielor care recunoaşte situaţia în care o instanţă de obiect sau un tablou nu mai sunt referite de nimeni şi le distruge în mod automat. Acest mecanism de colectare a gunoaielor rulează pe un fir de execuţie separat, de prioritate mică. Nu avem nici o posibilitate să aflăm exact care este momentul în care va fi distrusă o instanţă. Totuşi, putem specifica o funcţie care să fie apelată automat în momentul în care colectorul de gunoaie încearcă să distrugă obiectul. Această funcţie are nume, număr de parametri şi tip de valoare de retur fixe: void

finalize()

După apelul metodei de finalizare (numită şi finalizator), instanţa nu este încă distrusă până la o nouă verificare din partea colectorului de gunoaie. Această comportare este necesară pentru că instanţa poate fi revitalizată prin crearea unei referinţe către ea în interiorul finalizatorului. Totuşi, finalizatorul nu este apelat decât o singură dată. Dacă obiectul revitalizat redevine candidat la colectorul de gunoaie, acesta este distrus fără a i se mai apela finalizatorul. Cu alte cuvinte, un obiect nu poate fi revitalizat decât o singură dată. Dacă în timpul finalizării apare o excepţie, ea este ignorată şi finalizatorul nu va mai fi apelat din nou.

Crearea instanţelor O instanţă este creată folosind o expresie de alocare care foloseşte cuvântul rezervat new. Iată care sunt paşii care sunt executaţi la apelul acestei expresii: • Se creează o nouă instanţă de tipul specificat. Toate variabilele instanţei sunt iniţializate pe valorile lor implicite. • Se apelează constructorul corespunzător în funcţie de parametrii care sunt transmişi în expresia de alocare. Dacă instanţa este creată prin apelul metodei newInstance, se apelează constructorul care nu ia nici un argument. • După creare, expresia de alocare returnează o referinţă către instanţa nou creată. Exemple de creare: A o1 = new A(); B o2 = new B(); class C extends B { String valoare; C( String val ) { // aici există apel implicit // al lui super(), adică B() valoare = val; } C( int val ) { this( String.valueOf( val ) ); } } C o3 = new C( "Vasile" ); C o4 = new C( 13 );

O altă cale de creare a unui obiect este apelul metodei newInstance declarate în clasa Class. Iată paşii de creare în acest caz: • Se creează o nouă instanţă de acelaşi tip cu tipul clasei pentru care a fost apelată metoda newInstance. Toate variabilele instanţei sunt iniţializate pe valorile lor implicite. • Este apelat constructorul obiectului care nu ia nici un argument.



După creare referinţa către obiectul nou creat este returnată ca valoare a metodei newInstance. Tipul acestei referinţe va fi Object în timpul compilării şi tipul clasei reale în timpul execuţiei.

Derivarea claselor O clasă poate fi derivată dintr-alta prin folosirea în declaraţia clasei derivate a clauzei extends. Clasa din care se derivă noua clasă se numeşte superclasă imediată a clasei derivate. Toate clasele care sunt superclase ale superclasei imediate ale unei clase sunt superclase şi pentru clasa dată. Clasa nou derivată se numeşte subclasă a clasei din care este derivată. Sintaxa generală este: class

SubClasă extends SuperClasă …

O clasă poate fi derivată dintr-o singură altă clasă, cu alte cuvinte o clasă poate avea o singură superclasă imediată. Clasa derivată moşteneşte toate variabilele şi metodele superclasei sale. Totuşi, ea nu poate accesa decât acele variabile şi metode care nu sunt declarate private. Putem rescrie o metodă a superclasei declarând o metodă în noua clasă având acelaşi nume şi aceiaşi parametri. La fel, putem declara o variabilă care are acelaşi nume cu o variabilă din superclasă. În acest caz, noul nume ascunde vechea variabilă, substituindu-i-se. Putem în continuare să ne referim la variabila ascunsă din superclasă specificând numele superclasei sau folosindu-ne de variabila super. Exemplu: class A { int a = 1; void unu() { System.out.println( a ); } } class B extends A { double a = Math.PI; void unu() { System.out.println( a ); } void doi() { System.out.println( A.a ); } void trei() { unu(); super.unu(); } } Dacă apelăm metoda unu din clasa A, aceasta va afişa la consolă numărul 1.

Dacă apelăm metoda unu din clasa B, aceasta va afişa la consolă numărul PI. Apelul îl putem face de exemplu cu instrucţiunea: B obiect = new B(); obiect.unu();

Observaţi că în metoda unu din clasa B, variabila referită este variabila a din clasa B. Variabila a din clasa A este ascunsă. Putem însă să o referim prin sintaxa A.a ca în metoda doi din clasa B. În interiorul clasei B, apelul metodei unu fără nici o altă specificaţie duce automat la apelul metodei unu definite în interiorul clasei B. Metoda unu din clasa B rescrie metoda unu din clasa A. Vechea metodă este accesibilă pentru a o referi în mod explicit ca în metoda trei din clasa B. Apelul acestei metode va afişa mai întâi numărul PI şi apoi numărul 1.

Dacă avem declarată o variabilă de tip referinţă către o instanţă a clasei A, această variabilă poate să conţină în timpul execuţiei şi o referinţă către o instanţă a clasei B. Invers, afirmaţia nu este valabilă. În clipa în care apelăm metoda unu pentru o variabilă referinţă către clasa A, sistemul va apela metoda unu a clasei A sau B în funcţie de adevăratul tip al referinţei din timpul execuţiei. Cu alte cuvinte, următoarea secvenţă de instrucţiuni: A tablou[] = new A[2]; tablou[0] = new A(); tablou[1] = new B(); for( int i = 0; i < 2; i++ ) { tablou[i].unu(); }

va afişa două numere diferite, mai întâi 1 şi apoi PI. Aceasta din cauză că cel de-al doilea element din tablou este, în timpul execuţiei, de tip referinţă la o instanţă a clasei B chiar dacă la compilare este de tipul referinţă la o instanţă a clasei A. Acest mecanism se numeşte legare târzie, şi înseamnă că metoda care va fi efectiv apelată este stabilită doar în timpul execuţiei şi nu la compilare. Dacă nu declarăm nici o superclasă în definiţia unei clase, atunci se consideră automat că noua clasă derivă direct din clasa Object, moştenind toate metodele şi variabilele acesteia.

Interfeţe O interfaţă este în esenţă o declaraţie de tip ce constă dintr-un set de metode şi constante pentru care nu s-a specificat nici o implementare. Programele Java folosesc interfeţele pentru a suplini lipsa moştenirii multiple, adică a claselor de obiecte care derivă din două sau mai multe alte clase. Sintaxa de declaraţie a unei interfeţe este următoarea: Modificatori interface NumeInterf [ extends [Interfaţă][, Interfaţă]] Corp Modificatorii unei interfeţe pot fi doar cuvintele rezervate public şi abstract. O interfaţă care este publică poate fi accesată şi de către alte pachete decât cel care o defineşte. În plus, fiecare interfaţă este în mod implicit abstractă. Modificatorul abstract este permis dar nu obligatoriu. Numele interfeţelor trebuie să fie identificatori Java. Convenţiile de numire a interfeţelor le urmează în general pe cele de numire a claselor de obiecte. Interfeţele, la fel ca şi clasele de obiecte, pot avea subinterfeţe. Subinterfeţele moştenesc toate constantele şi declaraţiile de metode ale interfeţei din care derivă şi pot defini în plus noi elemente. Pentru a defini o subinterfaţă, folosim o clauză extends. Aceste clauze specifică superinterfaţa unei interfeţe. O interfaţă poate avea mai multe superinterfeţe care se declară separate prin virgulă după cuvântul rezervat extends. Circularitatea definiţiei subinterfeţelor nu este permisă. În cazul interfeţelor nu există o rădăcină comună a arborelui de derivare aşa cum există pentru arborele de clase, clasa Object. În corpul unei declaraţii de interfaţă pot să apară declaraţii de variabile şi declaraţii de metode. Variabilele sunt implicit statice şi finale. Din cauza faptului că variabilele sunt finale, este obligatoriu să fie specificată o valoare iniţială pentru aceste variabile. În plus, această valoare iniţială trebuie să fie constantă (să nu depindă de alte variabile). Dacă interfaţa este declarată publică, toate variabilele din corpul său sunt implicit declarate publice.

În ceea ce priveşte metodele declarate în interiorul corpului unei interfeţe, acestea sunt implicit declarate abstracte. În plus, dacă interfaţa este declarată publică, metodele din interior sunt implicit declarate publice. Iată un exemplu de declaraţii de interfeţe: public interface ObiectSpatial { final int CUB = 0; final int SFERA = 1; double greutate(); double volum(); double raza(); int tip(); } public interface ObiectSpatioTemporal extends ObiectSpatial { void centrulDeGreutate( long moment, double coordonate[] ); long momentInitial(); long momentFinal(); }

Cele două interfeţe definesc comportamentul unui obiect spaţial respectiv al unui obiect spaţio-temporal. Un obiect spaţial are o greutate, un volum şi o rază a sferei minime în care se poate înscrie. În plus, putem defini tipul unui obiect folosindu-ne de o serie de valori constante predefinite precum ar fi SFERA sau CUB. Un obiect spaţio-temporal este un obiect spaţial care are în plus o poziţie pe axa timpului. Pentru un astfel de obiect, în afară de proprietăţile deja descrise pentru obiectele spaţiale, trebuie să avem în plus un moment iniţial, de apariţie, pe axa timpului şi un moment final. Obiectul nostru nu există în afara acestui interval de timp. În plus, pentru un astfel de obiect putem afla poziţia centrului său de greutate în fiecare moment aflat în intervalul de existenţă. Pentru a putea lucra cu obiecte spaţiale şi spaţio-temporale este nevoie să definim diverse clase care să implementeze aceste interfeţe. Acest lucru se face specificând clauza implements în declaraţia de clasă. O clasă poate implementa mai multe interfeţe. Dacă o clasă declară că implementează o anumită interfaţă, ea este obligatoriu să implementeze toate metodele declarate în interfaţa respectivă. De exemplu, putem spune că o minge este un obiect spaţial de tip sferă. În plus, mingea are o poziţie în funcţie de timp şi un interval de existenţă. Cu alte cuvinte, mingea este chiar un obiect spaţio-temporal. Desigur, în afară de proprietăţile spaţio-temporale mingea mai are şi alte proprietăţi precum culoarea, proprietarul sau preţul de cumpărare. Iată cum ar putea arăta definiţia clasei de obiecte de tip minge: import java.awt.Color; class Minge extends Jucarie implements ObiectSpatioTemporal int culoare = Color.red; double pret = 10000.0; double raza = 0.25; long nastere; long moarte; // metodele din ObiectSpatial double greutate() { return raza * 0.5; } double raza() { return raza; } double volum() { return ( 4.0 / 3.0 ) * Math.PI * raza * raza * raza; } int tip() { return SFERA; }

{

// metodele din interfaţa ObiectSpatioTemporal boolean centrulDeGreutate( long moment, double coordonate[] ) { if( moment < nastere || moment > moarte ) { return false; } … coordonate[0] = x; coordonate[1] = y; coordonate[2] = z; return true; } long momentInitial() { return nastere; } long momentFinal() { return moarte; } int ceCuloare() { return culoare; } double cePret() { return pret; } }

Observaţi că noua clasă Minge implementează toate metodele definite în interfaţa ObiectSpatioTemporal şi, pentru că aceasta extinde interfaţa ObiectSpatial, şi metodele definite în cea din urmă. În plus, clasa îşi defineşte propriile metode şi variabile. Să presupunem în continuare că avem şi o altă clasă, Rezervor, care este tot un obiect spaţiotemporal, 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: <TITLE> Hello World Applet <APPLET CODE="HelloWorldApplet.class" WIDTH=150 HEIGHT=25> ....

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:

Şi să modificăm codul apletului după cum urmează: import java.awt.Graphics; public class HelloWorldApplet extends java.applet.Applet { private String sir; public void init() { sir=getParameter( "mesaj" ); if( sir == null ) { sir = "Hello, World!"; } resize( 150,25 ); } public void paint( Graphics g ) { g.drawString( sir, 50, 25 ); } }

În acest mod putem să controlăm la lansare iniţializarea apletului. În definiţia clasei Applet există şi două funcţii care permit navigatorului regăsirea unui minim de informaţii despre aplet. Aceste informaţii reprezintă descrierea apletului şi a parametrilor acestuia. Funcţiile care trebuiesc definite în noua clasă derivată din Applet sunt getAppletInfo şi getParameterInfo. De exemplu, putem introduce în clasa HelloWorldApplet două noi funcţii: public String getAppletInfo() { return "Applet scris de XXX "; } public String [ ][ ] getParameterInfo( ) { String info[ ][ ] = { { "Parametru", "String", "Textul de afisat" } }; return info; }

Execuţia unui aplet este marcată de câteva evenimente importante generate de către navigator. Atunci când navigatorul întâlneşte o etichetă APPLET, porneşte în primul rând încărcarea codului necesar rulării apletului. Până când acest cod nu a ajuns pe calculatorul client, apletul nu poate fi pornit. După încărcarea codului, apletul este apelat pentru iniţializare. Acesta este momentul în care apletul îşi pregăteşte parametrii şi obţine de la sistem resursele necesare rulării. După ce iniţializarea a fost terminată, navigatorul trimite către aplet o comandă de pornire. Aceasta este comanda care pune efectiv apletul în funcţiune deschizând interacţiunea cu utilizatorul.

Un aplet rulează atâta timp cât navigatorul este activ. La schimbarea paginii curente, apleturile din vechea pagină nu sunt distruse, dar primesc o comandă de oprire temporară (pe care de altfel pot să o ignore). La reîncărcarea paginii, o altă comandă de pornire este lansată spre aplet şi acest ciclu se poate relua. În sfârşit, la oprirea navigatorului, apletul primeşte o comandă de oprire definitivă, caz în care el trebuie să elibereze toate resursele pe care le blochează. Orice aplet Java reprezintă, din punctul de vedere al limbajului un nou tip de obiect, derivat din obiectul standard Applet. Atunci când navigatorul lansează un nou aplet el nu face altceva decât să instanţieze un nou obiect din această clasă. Subrutinele care tratează evenimentele descrise anterior trebuiesc definite ca metode în interiorul acestui nou tip de obiecte. În continuare, între două evenimente de pornire şi respectiv de oprire temporară a apletului navigatorul transmite către aplet evenimente specifice oricărei interfeţe grafice cum ar fi evenimentul care provoacă redesenarea spaţiului destinat apletului, evenimente legate de apăsarea unor taste sau a unor butoane ale mausului, etc. Ca răspuns la aceste evenimente, apletul trebuie să reacţioneze schimbând conţinutul ferestrei, lansând mesaje sonore, etc. Iată şi un exemplu de aplet care ne arată fazele prin care trece un aplet în timpul existenţei sale: import java.applet.Applet; import java.awt.*; public class Evenimente extends Applet { public void init() { // metoda de iniţializare // este apelată la construcţia noii instanţe de aplet System.out.println("init"); } public void paint(Graphics g) { // metoda de desenare // este apelată ori de câte ori este necesară // redesenarea ferestrei apletului System.out.println("paint"); } public void start() { // metoda de lansare in execuţie // este apelată la pornire //sau la reîntoarcerea în pagina apletului System.out.println("start"); } public void stop() { // metoda de oprire temporară a execuţiei System.out.println( "stop" ); } public void destroy() { // metoda de oprire definitivă System.out.println("destroy"); } public void update(Graphics g) { // metoda de actualizare a ferestrei apletului // este apelata atunci când nu este necesară redesenarea // întregii ferestre. În mod implicit, // metoda apelează metoda paint. System.out.println("update"); } public boolean mouseUp(Event e, int x, int y) {

// S-a ridicat butonul mouse-lui în fereastra apletului. System.out.println("mouseUp"); return false; } public boolean mouseDown(Event e, int x, int y) { // S-a apăsat butonul mouse-lui în fereastra apletului System.out.println("mouseDown"); return false; } public boolean mouseDrag(Event e, int x, int y) { // S-a mişcat mouse-ul în fereastra apletului // cu butonul apăsat System.out.println("mouseDrag"); return false; } public boolean mouseMove(Event e, int x, int y) { // S-a mişcat mouse-ul în fereastra apletului System.out.println("mouseMove"); return false; } public boolean mouseEnter(Event e, int x, int y) { // Mouse-ul a pătruns în fereastra apletului System.out.println("mouseEnter"); return false; } public boolean mouseExit(Event e, int x, int y) { // mouse-ul a ieşit din fereastra apletului System.out.println("mouseExit"); return false; } public void gotFocus() { // Fereastra apletului a devenit fereastra activă System.out.println("gotFocus"); } public void lostFocus() { // Fereastra apletului nu mai este fereastra activa System.out.println("lostFocus"); } public boolean keyDown(Event e, int x) { // S-a apăsat o tasta şi aceasta // este destinata apletului System.out.println("keyDown"); return true; } }

Puteţi rula apletul de mai sus pentru a vedea care este ordinea în care sunt apelate aceste metode de către navigator. Apletul de mai sus produce ieşiri la consolă (o fereastră text) şi nu în fereastra apletului. Dacă nu vedeţi consola, încercaţi să căutaţi prin meniurile navigatorului opţiunea de afişare a consolei Java. Din cele spuse până acum se poate deduce că apleturile Java nu au o viaţă proprie, ele fiind doar apelate din când în când de navigator. Ceea ce nu este întocmai adevărat pentru că biblioteca

standard de clase Java oferă suport pentru aplicaţii care folosesc mai multe fire de execuţie. Apleturile pot astfel crea fire de execuţie care să lucreze independent faţă de navigator.

Eticheta (tagul) APPLET Descrierea tagului pe care Sun l-a definit pentru introducerea unui aplet într-un document HTML este următoarea. < APPLET [CODEBASE = codebaseURL] CODE = appletFile [ALT = alternateText] [NAME = appletInstanceName] WIDTH = pixels HEIGHT = pixels [ALIGN = alignment] [VSPACE = pixels] [HSPACE = pixels] > [< PARAM NAME = appletParameter1 VALUE = value >] [< PARAM NAME = appletParameter2 VALUE = value >] ... [alternateHTML]

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

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

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

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

VSPACE = pixels HSPACE = pixels Atributele VSPACE şi HSPACE specifică, în pixeli, spaţiul care desparte apletul de textul care îl înconjoară. Sunt atribute opţionale. < PARAM NAME = appletParameter1 VALUE = value > Eticheta este singura modalitate prin care unui applet i se pot transmite parametrii. Appletul preia aceşti parametrii cu ajutorul metodei getParameter(), aşa cum s-a arătat mai sus. alternateHTML Dacă pagina html care conţine eticheta APPLET este vizualizată cu ajutorul unui browser care nu înţelege această etichetă, atunci browserul va ignora cele două etichete APPLET şi TAG . Browserele compatibile Java vor ignora acest cod. Atributele obligatorii ale acestei definiţii sunt numele fişierului în care este memorat codul şi dimensiunea spaţiului rezervat apletului. Minimal, tag APPLET arată în felul următor: <APPLET CODE="HelloWorldApplet.class" WIDTH=200 HEIGHT=150> Text care apare dacă navigatorul nu ştie Java

Atributele opţionale ale tagului descriu locaţia de bază a codului apletului, modul de aliniere în pagină, spaţierea şi eventualii parametrii care pot fi transmişi apletului din interiorul documentului HTML.

Structura programelor Pachete de clase

Clasele Java sunt organizate pe pachete. Aceste pachete pot avea nume ierarhice. Numele de pachete au forma următoare: [NumePachet.] NumeComponentăPachet Numele de pachete şi de componente ale acestora sunt identificatori Java. De obicei, aceste nume urmează structura de directoare în care sunt memorate clasele compilate. Rădăcina arborelui de directoare în care sunt memorate clasele este indicată de o variabilă sistem CLASSPATH. În DOS aceasta se setează în felul următor: set CLASSPATH=.;c:\java\lib

Din această rădăcină, fiecare pachet are propriul director. În director există codul binar pentru componentele pachetului respectiv. Dacă pachetul conţine subpachete, atunci acestea sunt memorate într-un subdirector în interiorul directorului pachetului. Creatorii Java recomandă folosirea unei reguli unice de numire a pachetelor, astfel încât să nu apară conflicte. Convenţia recomandată de ei este aceea de a folosi numele domeniului Internet aparţinând producătorului claselor. Astfel, numele de pachete ar putea arăta ca în: COM.Microsoft.OLE COM.Apple.quicktime.v2

şi aşa mai departe.

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

numeClasă ;

unde numele clasei include şi pachetul din care aceasta face parte. De exemplu: import java.awt.Graphics; import java.applet.Applet;

Se poate importa şi un pachet întreg, adică toate clasele aparţinând acelui pachet, printr-o instrucţiune de forma: import

numePachet.*;

De exemplu: import java.awt.*;

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

package

numePachet;

dacă această declaraţie lipseşte, clasa va fi plasată în pachetul implicit, care nu are nume. Structura generală a unui fişier sursă este următoarea: [ DeclaraţiePachet ][ InstrucţiuneImport ][ DeclaraţieDeTip ] unde declaraţia de tip poate fi o declaraţie de clasă sau de interfaţă.

Compilare şi execuţie Fişierele sursă Java au obligatoriu extensia .java. Numele lor este identic cu numele clasei sau interfeţei publice declarate în interior. În urma compilării rezultă fişiere cu nume identice cu numele claselor dar cu extensia .class indiferent dacă este vorba de o clasă sau o interfaţă. Fişierul .class este generat în directorul local şi nu direct la locaţia pachetului. Compilarea se face cu o comandă de forma: javac FişierSursă.java

Comanda aceasta, ca şi celelalte descrise în acest paragraf este specifică mediului de dezvoltare Java pus la dispoziţie de Sun, numit JDK (Java Development Kit). Există însă şi multe alte medii de dezvoltare care au propriile lor compilatoare şi interpretoare. La compilare, variabila sistem CLASSPATH trebuie să fie deja setată pentru că însuşi compilatorul Java actual este scris în Java. Pentru lansarea în execuţie a unei aplicaţii Java, trebuie să introduceţi comanda: java NumeClasă

unde numele clasei este numele aplicaţiei care conţine metoda main . Interpretorul va căuta un fişier cu numele NumeClasă.class şi va încerca să instanţieze clasa respectivă. Pentru lansarea unui aplet veţi avea nevoie de un document HTML care conţine tagul APPLET şi ca parametru al acesteia name=NumeClasă.class

La lansarea unui aplet, clasele care sunt apelate de clasa principală sunt mai întâi căutate pe sistemul pe care rulează navigatorul. Dacă nu sunt acolo, ele vor fi transferate în reţea. Asta înseamnă că transferul de cod este relativ mic, trebuie transferat doar codul specific aplicaţiei.

Eticheta 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 . Eticheta 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 are următoarea formă: poate folosi şi etichetele opţionale . •

Arhive Java Modalitatea standard de amplasare a unui applet Java într-o pagină Web este de a folosi etichetele <APPLET> sau 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> 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 , folosind parametri de appleturi. 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 , care conţine două atribute, pentru nume şi valoare, denumite NAME şi VALUE. Eticheta se introduce între etichetele <APPLET> de început şi de sfârşit, ca în exemplul: <APPLET CODE="Exemplu.class" WIDTH=100 HEIGHT=100> Aici este inserat un applet Java. 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 este aceeaşi pentru applet-urile care folosesc eticheta în loc de <APPLET>. Parametrii sunt transmişi applet-ului la încărcarea acestuia. În metoda init() a applet-ului puteţi obţine aceşti parametri folosind metoda getParameter(). Aceasta preia ca argument un şir ce reprezintă numele parametrului căutat şi întoarce un şir care conţine valoarea respectivului parametru. (Ca şi argumentele aplicaţiilor Java, toate valorile parametrilor sunt returnate drept şiruri.) Pentru a obţine valoarea parametrului font din fişierul HTML, metoda init() ar trebui să conţină ceva de genul: String numeleFontului = getParameter("font"); Observaţie Numele parametrilor specificaţi în eticheta şi numele parametrilor metodei getParameter () trebuie să fie identice, inclusiv majusculele sau minusculele folosite. Cu alte cuvinte, este diferit de . Dacă parametrii dumneavoastră nu sunt transferaţi corect applet-ului, asiguraţi-vă că au folosit acelaşi tip de caractere (majuscule sau minuscule) în denumirea parametrilor. Reţineţi că dacă parametrul aşteptat nu a fost specificat in fişierul HTML, metoda getParameter() întoarce valoarea null. De obicei, ar trebui sa testaţi valoarea null a parametrului si sa oferiţi o valoare implicită, ca in exemplul următor:

if (numeleFontului == null) numeleFontului = "Courier"; Ţineţi minte ca metoda getParameter () returnează şiruri; dacă doriţi ca parametrul sa fie alt tip de obiect sau de data, trebuie sa îl convertiţi singur. De exemplu, sa luam fişierul HTML pentru appletul Exemplu. Pentru a trata parametrul dim si a-l atribui unei variabile întregi, numită dimensiunea, aţi putea folosi următorul cod: int dimensiunea; String s = getParameter("dim"); if (s == null) dimensiunea = 12; else dimensiunea = Integer.parseInt(s);

Să cream un exemplu de applet care foloseşte această tehnică. Vom crea un applet Palindrom pentru a afişa alte texte, cum ar fi ,,Dennis and Edna sinned" sau ,,No, sir, prefer prison". Numele este transmis applet-ului printr-un parametru HTML. Proiectul va primi numele Palindrom. import java.awt.Graphics; import java.awt.Font; import java.awt.Color; public class Palindrom extends java.applet.Applet { Font f = new Font ("TimesRoman", Font.BOLD, 36); String palindrom; public void paint(Graphics ecran) { ecran.setFont(f); ecran.setColor(Color.red); ecran.drawString(palindrom, 5, 40); } public void init() { palindrom = getParameter("palindrom"); if (palindrom == null) palindrom = "Dennis and Edna sinned"; } }

Fişierul HTML care va conţine acest applet este următorul <TITLE>Pagina cu palindrom

<APPLET CODE=”Palindrom.class" WIDTH=600 HEIGHT=100> Browserul dumneavoastră nu suporta Java!

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

, 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 <TITLE>Noua pagina cu palindrom

<APPLET CODE="PalindromNou.class" WIDTH=600 HIGHT=100> Browserul dumneavoastră nu suporta Java! Deoarece aici nu a fost specificat nici un parametru, applet-ul va folosi valoarea implicită.

Clasa Graphics Puteţi să vă imaginaţi applet-ul drept o pânză pe care se vor desfăşura operaţii grafice. Aţi folosit deja metoda drawString () pentru a desena text într-un applet. Fontul şi culoarea textului au fost alese anterior desenării textului, în acelaşi fel în care un artist îşi alege culoarea şi pensula înainte de a picta. Textul nu este singurul lucru pe care îl puteţi desena în fereastra unui applet. Puteţi desena şi linii, ovale, cercuri, arcuri, dreptunghiuri şi alte poligoane. Majoritatea principalelor operaţii de desenare sunt metode definite în clasa Graphics. Într-un applet nu este nevoie să creaţi un obiect Graphics pentru a putea desena ceva – aşa cum poate vă mai amintiţi, unul dintre argumentele metodei paint () este un obiect Graphics. Acest obiect reprezintă fereastra applet-ului, iar metodele sale sunt folosite pentru a desena în applet. Clasa Graphics este parte a pachetului java.awt, deci toate applet-urile care desenează ceva trebuie să folosească instrucţiunea import pentru a putea folosi această clasă.

Sistemul de coordonate grafice Ca şi metoda drawString(), toate celelalte metode grafice posedă argumente care indică coordonatele x,y. Unele dintre acestea au mai multe seturi de coordonate, cum ar fi o linie, care posedă un set de coordonate x,y ce indică punctul de început al liniei şi încă un set de coordonate x,y care corespunde punctului final al liniei.

Sistemul de coordonate Java foloseşte ca unitate de măsură pixelul. Coordonatele punctului de origine 0,0 sunt situate în colţul din stânga - sus al ferestrei Applet. Valoarea coordonatei x creşte la dreapta originii 0,0, iar valoarea coordonatei y creşte în jos. Acest lucru diferă faţă de alte sisteme de coordonate, în care originea 0,0 se află în colţul din stânga -jos şi valoarea coordonatei y creşte în sus. Toate valorile de pixeli sunt întregi - nu se pot folosi valori zecimale pentru a afişa ceva aflat între două valori întregi.

Desenarea şi umplerea Pentru majoritatea formelor pe care le desenaţi într-un applet sunt disponibile două tipuri de metode: metode de desenare, care desenează conturul formei, şi metode de umplere, care umplu forma cu culoarea curentă. În fiecare tip de metodă, conturul obiectului este, de asemenea, desenat cu culoarea curentă.

Linii Metoda drawLine este folosită pentru a desena o linie între două puncte. Metoda primeşte patru argumente: coordonatele x,y ale punctului de început şi coordonatele x, y ale punctului final. drawLine(x1, y1, x2, y2) ; Această metodă desenează o linie începând cu punctul de coordonate (x1, y1) şi până la punctul de coordonate (x2, y2). Grosimea liniei este de un pixel.

Dreptunghiuri Clasa Graphics conţine metode pentru două tipuri de dreptunghiuri: dreptunghiuri normale şi dreptunghiuri cu colţuri . Ambele tipuri de dreptunghiuri pot fi desenate sub formă de contur sau umplute cu culoarea curentă.

Pentru a desena un dreptunghi normal se foloseşte metoda drawRect() pentru contururi sau metoda fillRect() pentru forme umplute. Ambele metode preiau patru argumente: • Coordonatele x şi y ale colţului din stânga - sus al dreptunghiului • Lăţimea dreptunghiului • Înălţimea dreptunghiului drawRect(x,y,l,h)

Poligoane Poligoanele pot fi desenate folosind metodele drawPolygon() şi fillPolygon(). Pentru a desena un poligon aveţi nevoie de coordonatele x,y ale fiecărui punct care defineşte colţurile poligonului. Poligoanele pot fi definite drept o serie de linii conectate una cu cealaltă - se desenează o linie de la un punct iniţial la un punct final, apoi punctul final este folosit ca punct iniţial pentru o altă linie şi aşa mai departe.

Puteţi specifica aceste coordonate în două moduri: • Ca o pereche de tablouri cu întregi, dintre care unul păstrează toate valorile coordonatei x şi celălalt păstrează toate valorile coordonatei y. • Ca un obiect Polygon, creat folosind un tablou cu valori întregi ale coordonatei x şi un tablou cu valori întregi ale coordonatei y. A doua metodă este mai flexibilă, deoarece permite adăugarea individuală a punctelor unui poligon, înainte de desenarea sa. În afară de coordonatele x şi y, trebuie să specificaţi numărul de puncte al poligonului. Nu se pot specifica mai multe coordonate x,y decât numărul de puncte şi nici invers. în oricare din aceste cazuri, compilatorul va semnala o eroare. Pentru a crea un obiect polygon, primul pas constă în crearea unui poligon gol, printr-o instrucţiune new, ca în cazul următor: Polygon polig = new Polygon(); Mai puteţi crea un poligon pornind de la un set de puncte, folosind tablouri cu valori întregi. Aceasta necesită un apel către constructorul Polygon (int [], int [], int), unde se specifică tabloul cu

valori pentru coordonata x, tabloul cu valori pentru coordonata y şi numărul de puncte (colţuri). Iată un exemplu de folosire a acestui constructor: int x[] = { 10, 20, 30, 40, 50 }; int y[] = { 15, 25, 35, 45, 55 }; int puncte = x.length; Polygon polig = new Polygon(x, y, puncte); După ce se creează obiectul Polygon, se pot adăuga puncte folosind metoda addpoint(). Aceasta preia ca argumente coordonatele x, y şi adaugă punctul în poligon. lată un exemplu: polig.addPoint(60, 65); Atunci când obiectul Polygon are toate punctele necesare, el poate fi desenat folosind una dintre metodele drawPolygon() sau fillPolygon() . Aceste metode au drept unic argument obiectul Polygon, ca în exemplul următor: ecran.drawPolygon(polig) ; Dacă folosiţi metoda drawPolygon() în Java 1.02, puteţi închide poligonul stabilind pentru ultimele coordonate x,y valori identice cu primele. Altfel, poligonul va rămâne deschis pe una dintre laturi. Metoda fillPolygon() închide automat forma poligonală, fără a mai fi nevoie de stabilirea coordonatelor finale.

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

Arce Un arc este o parte a unui oval, fiind implementat în Java ca o formă eliptică parţial desenată.

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

Copierea şi ştergerea Clasa Graphics conţine şi câteva funcţii de gen decupează-şi-lipeşte (cut-and-paste),

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

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

Crearea obiectelor Font Un obiect Font este creat prin apelarea constructorului său cu trei argumente: • Numele fontului • Stilul fontului • Dimensiunea în puncte a fontului Numele poate fi denumirea unui font specific, cum ar fi Arial sau Garamond Old Style, care va putea fi folosit dacă este prezent (instalat) în sistemul pe care se rulează programul Java. Există şi alte nume care pot fi folosite pentru selectarea fonturilor interne, proprii Java: TimesRoman, Helvetica, Courier, Dialog şi DialogInput.

Pot fi selectate trei stiluri de fonturi, folosind constantele Font. PLAIN, Font. BOLD şi Font. ITALIC. Aceste constante sunt întregi şi pot fi însumate pentru a obţine o combinaţie de efecte. Ultimul argument al constructorului Font () este dimensiunea fontului. Următoarea instrucţiune creează un font Dialog de 24 de puncte, cu aldine cursive. Font f = new Font("Dialog", Font.BOLD + Font.ITALIC, 24); Desenarea caracterelor şi a şirurilor

Pentru a seta fontul curent se foloseşte metoda setFont() a clasei Graphics, împreună cu un obiect Font. Următoarea instrucţiune foloseşte un obiect Font denumit ft: ecran.setFont(ft); Textul poate fi afişat într-o fereastră Applet folosind metoda drawString(). Această metodă foloseşte fontul curent selectat; dacă nu a fost selectat nici un font, este folosit unul implicit. Poate fi selectat oricând un nou font cu ajutorul metodei setFont ().

Următoarea metodă paint () creează un nou obiect Font, stabileşte fontul curent la acest obiect, după care afişează şirul „Acesta este un font.” la coordonatele 10,100. public void paint(Graphics ecran) { Font f = new Font("TimesRoman", Font.PLAIN, 72); ecran.setFont(f); ecran.drawString(„Acesta este un font.", 10, 100); } Ultimele două argumente ale metodei drawString () sunt coordonatele x, y. Valoarea x reprezintă locul de început al marginii din stânga a textului, iar y este valoarea la care se afişează linia de bază a şirului de text.

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

Tabelul Metode FontMetrics. Nume metodă

Acţiune

stringWidth (String) charWidth (char) getHeight ()

Întoarce lăţimea totală a şirului, în pixeli Întoarce lăţimea unui caracter dat Întoarce înălţimea totală a fontului

Culori Clasele Color şi ColorSpace din pachetul java.awt pot fi folosite pentru a aduce puţină culoare în applet-urile şi aplicaţiile dumneavoastră. Cu ajutorul acestor clase puteţi stabili culorile curente folosite în operaţiile de desenare, ca şi culoarea de fundal pentru un applet sau alte ferestre. De asemenea, puteţi translata o culoare dintr-un sistem de descriere în altul. În mod prestabilit, Java foloseşte culorile conform unui sistem de descriere denumit sRGB. În acest sistem, o culoare este descrisă prin cantitatea de roşu, verde şi albastru pe care o conţine - de aici şi iniţialele R(ed), G(reen) şi B(lue). Fiecare dintre aceste trei componente poate fi reprezentată ca un întreg din gama 0-255. Negrul este 0, 0, 0 lipsa completă a tuturor componentelor roşu, verde, albastru. Albul este 255, 255, 255 - valoarea maximă a tuturor

componentelor. Mai puteţi reprezenta valori sRGB folosind trei numere în virgulă mobilă, în domeniul dintre 0 şi 1,0. Folosind sRGB, Java poate reprezenta milioane de culori aflate între cele două extreme.

Un sistem de descriere a culorilor mai este numit şi paletă sau spaţiu de culori (color space), iar sRGB este doar unul dintre acestea. Există şi CMYK, un sistem folosit de imprimante şi care descrie culorile prin procentul de Cyan (azuriu), Magenta (purpuriu), Yellow (galben) şi Black (negru) pe care îl conţin. Java 2 suportă folosirea oricărui spaţiu de culori doriţi, atât timp cât folosiţi un obiect ColorSystem care defineşte respectivul sistem de descriere a culorilor. De asemenea, puteţi converti culorile din sRGB în alt spaţiu de culori şi invers. Reprezentarea internă a culorilor în Java folosind sRGB reprezintă numai spaţiul de culoare folosit în program. Dispozitivele de ieşire, cum ar fi monitoarele sau imprimantele, pot avea şi ele propriile spaţii de culoare.

Atunci când afişaţi sau tipăriţi ceva cu o anumită culoare, dispozitivul de ieşire s-ar putea să nu suporte culoarea respectivă. în acest caz, culoarea va fi înlocuită cu o alta sau cu o culoare impură (dithered), folosită pentru a aproxima culoarea care nu este disponibilă. Acest lucru se întâmplă frecvent pe World Wide Web, când o culoare indisponibilă este înlocuită cu una impură, care aproximează culoarea lipsă. Practic, culorile definite conform modelului sRGB nu vor putea fi reprezentate pe orice dispozitiv de ieşire. Dacă aveţi nevoie de un control mai precis al culorii, puteţi folosi ColorSpace sau alte clase din pachetul java.awt. color, introdus o dată cu Java 2. Pentru majoritatea programelor, folosirea sRGB, sistemul intern de reprezentare a culorilor, va fi suficientă.

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

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

darkGray(gri închis)(64,64,64) gray (gri) (128,128,128) green (verde) (0,255,0) lightGray(gri deschis)(192,192,192)

red (rosu) (255,0,0) white (alb) (255,255,255) yellow (galben) (255,255,0)

Următoarea instrucţiune stabileşte culoarea curentă pentru obiectul ecran folosind una dintre variabilele de clasă standard: ecran.setColor(Color.pink) ; Dacă aţi creat un obiect Color, el poate fi setat într-un mod asemănător:

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

Operaţii grafice avansate folosind Java2D Una dintre îmbunătăţirile aduse în Java2 este Java2D, un set de clase pentru crearea unor imagini şi texte bidirecţionale de înaltă calitate. Funcţiile Java2D conţin: • Modele speciale de umplere • Linii de diferite grosimi • Anti-aliasing pentru netezirea marginilor obiectelor desenate • Conversia prin cast a unui obiect Graphics2D Operaţiile de desenare învăţate până acum sunt apelate pentru obiecte Graphics. Pentru Java2D, acest obiect se utilizează pentru a crea un nou obiect Graphics2D public void paint(Graphics ecran) { Graphics2D ecran2D = (Graphics2D)ecran; }

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

O aplicaţie Java se termină atunci când se termină toate firele de execuţie din interiorul ei sau când nu mai există decât fire demon. Terminarea firului principal de execuţie nu duce la terminarea automată a aplicaţiei.

Crearea firelor de execuţie Există două căi de definire de noi fire de execuţie: derivarea din clasa Thread a noi clase şi implementarea într-o clasă a interfeţei Runnable . În primul caz, noua clasă moşteneşte toate metodele şi variabilele clasei Thread care implementează în mod standard, în Java, funcţionalitatea de lucru cu fire de execuţie. Singurul lucru pe care trebuie să-l facă noua clasă este să reimplementeze metoda run care este apelată automat de către mediul de execuţie la lansarea unui nou fir. În plus, noua clasă ar putea avea nevoie să implementeze un constructor care să permită atribuirea unei denumiri firului de execuţie. Dacă firul are un nume, acesta poate fi obţinut cu metoda getName care returnează un obiect de tip String . Iată un exemplu de definire a unui nou tip de fir de execuţie: class FirNou extends Thread { public FirNou( String nume ) { // apelează constructorul din Thread super( nume ); } public void run() { while( true ) { // fără sfârşit System.out.println( getName() +" Tastati ^C" ); } } } Dacă vom crea un nou obiect de tip FirNou

şi îl lansăm în execuţie acesta va afişa la infinit mesajul "Tastaţi ^C". Întreruperea execuţiei se poate face într-adevăr prin tastarea caracterului ^C, caz în care întreaga aplicaţie este terminată. Atâta timp însă cât noul obiect nu va fi întrerupt din exterior, aplicaţia va continua să se execute pentru că mai există încă fire de execuţie active şi indiferent de faptul că firul de execuţie principal s-a terminat sau nu. Iată şi un exemplu de aplicaţie care foloseşte această clasă: public class TestFirNou { public static void main( String[] arg) { new FirNou( "Primul" ).start(); } }

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

Această nouă cale se poate folosi în modul următor: class Oclasa { … } class FirNou extends Oclasa implements Runnable { public void run() { for( int i = 0; i < 100; i++ ) { System.out.println( "pasul " + i ); } } … } public class TestFirNou { public static void main( String argumente[ ] ) { new Thread( new FirNou() ).start(); // Obiectele sunt create şi folosite imediat // La terminarea instrucţiunii, ele sunt automat // eliberate nefiind referite de nimic } } După se observă, clasa Thread are şi un constructor care primeşte ca argument o instanţă a unei clase care implementează interfaţa Runnable. În acest caz, la lansarea în execuţie a noului fir, cu metoda start , se apelează metoda run din acest obiect şi nu din instanţa a clasei Thread .

Atunci când dorim să creăm un aplet care să ruleze pe un fir de execuţie separat faţă de pagina de navigator în care rulează pentru a putea executa operaţii în fereastra apletului şi în acelaşi timp să putem folosi în continuare navigatorul, suntem obligaţi să alegem cea de-a doua cale de implementare. Aceasta pentru că apletul nostru trebuie să fie derivat din clasa standard Applet . Singura alternativă care ne rămâne este aceea de a implementa în aplet interfaţa Runnable .

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

} …

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

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

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

Prioritatea firelor de execuţie Fiecare fir de execuţie are o prioritate cuprinsă între valorile MIN_PRIORITY şi MAX_PRIORITY. Aceste două variabile finale sunt declarate în clasa Thread. În mod normal însă, un fir de execuţie are prioritatea NORM_PRIORITY, de asemenea definită în clasa Thread . Mediul de execuţie Java planifică firele de execuţie la controlul unităţii centrale în funcţie de prioritatea lor. Dacă există mai multe fire cu prioritate maximă, acestea sunt planificate după un algoritm numit round-robin. Firele de prioritate mai mică intră în calcul doar atunci când toate firele de prioritate mare sunt în starea Nu Rulează .

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

Grupuri de fire de execuţie Uneori avem nevoie să acţionăm asupra mai multor fire de execuţie deodată, pentru a le suspenda, reporni sau modifica prioritatea în bloc. Din acest motiv, este util să putem grupa firele de execuţie pe grupuri. Această funcţionalitate este oferită în Java de către o clasă numită ThreadGroup . La pornirea unei aplicaţii Java, se creează automat un prim grup de fire de execuţie, numit grupul principal, main. Firul principal de execuţie face parte din acest grup. În continuare, ori de câte ori creăm un nou fir de execuţie, acesta va face parte din acelaşi grup de fire de execuţie ca şi firul de execuţie din interiorul căruia a fost creat, în afară de cazurile în care în constructorul firului specificăm explicit altceva. Într-un grup de fire de execuţie putem defini nu numai fire dar şi alte grupuri de execuţie. Se creează astfel o arborescenţă a cărei rădăcină este grupul principal de fire de execuţie. Pentru a specifica pentru un fir un nou grup de fire de execuţie, putem apela constructorii obişnuiţi dar introducând un prim parametru suplimentar de tip ThreadGroup . De exemplu, putem folosi următorul cod: ThreadGroup tg = new ThreadGroup( "Noul grup" ); Thread t = new Thread( tg, "Firul de executie" );

Acest nou fir de execuţie va face parte dintr-un alt grup de fire decât firul principal. Putem afla grupul de fire de execuţie din care face parte un anumit fir apelând metoda getThreadGroup , ca în secvenţa: Thread t = new Thread( "Firul de Executie" ); ThreadGroup tg = t.getThreadGroup();

Operaţiile definite pentru un grup de fire de execuţie sunt clasificabile în operaţii care acţionează la nivelul grupului, cum ar fi aflarea numelui, setarea unei priorităţi maxime, etc., şi operaţii care acţionează asupra fiecărui fir de execuţie din grup, cum ar fi stop, suspend sau resume.

Enumerarea firelor de execuţie Pentru a enumera firele de execuţie active la un moment dat, putem folosi metoda enumerate definită în clasa Thread precum şi în clasa ThreadGroup . Această metodă primeşte ca parametru o referinţă către un tablou de referinţe la obiecte de tip Thread pe care îl umple cu referinţe către fiecare fir activ în grupul specificat. Pentru a afla câte fire active sunt în grupul respectiv la un moment dat, putem apela metoda activeCount din clasa ThreadGroup . De exemplu: ThreadGroup grup = Thread.currentThread().getThreadGroup(); int numarFire = grup.activeCount(); Thread fire[] = new Thread[numarFire]; grup.enumerate( fire ); for( int i = 0; i < numar; i++ ) { System.out.println( fire[i].toString() );

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

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

Un exemplu Exemplul următor implementează soluţia următoarei probleme: Într-o ţară foarte îndepărtată trăiau trei înţelepţi filozofi. Aceşti trei înţelepţi îşi pierdeau o mare parte din energie certându-se între ei pentru a afla care este cel mai înţelept. Pentru a tranşa problema o dată pentru totdeauna, cei trei înţelepţi au pornit la drum către un al patrulea înţelept pe care cu toţii îl recunoşteau că ar fi mai bun decât ei.

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

} else if( primaCuloare == Color.white ){ try{ sleep( (int)( Math.random()*500) ); } catch( InterruptedException e ){ }; if( Concurs.culoare( this, concurenti[1]) != concurenti[1].aGhicit()) { synchronized( this ) { parere = Color.black; }; } } else if( adouaCuloare == Color.white ) { try{ sleep( (int)( Math.random()*500)); } catch( InterruptedException e ) { }; if( Concurs.culoare(this, concurenti[0] ) != concurenti[0].aGhicit()) { synchronized( this ) { parere = Color.black; }; } } else { try { sleep( (int)( Math.random()*500)+500 ); } catch( InterruptedException e ) { }; if( Concurs.culoare(this, concurenti[0]) != concurenti[0].aGhicit() && Concurs.culoare( this, concurenti[1] ) !=concurenti[1].aGhicit() ) { synchronized( this ) { parere = Color.black; }; } } } } public synchronized Color aGhicit() { return parere; } }

import java.awt.Color; public class Concurs { private static Color palarii[] = { Color.black, Color.black, Color.white }; private static Filozof filozofi[] = new Filozof[3]; public static void main( String args[] ) { for( int i = 0; i < args.length && i < 3; i++ ) { if( args[i].equalsIgnoreCase( "alb" ) ) { palarii[i] = Color.white; } else if(args[i].equalsIgnoreCase("negru")) { palarii[i] = Color.black; } } for( int i = 0; i < 3; i++ ) { filozofi[i] = new Filozof( "Filozoful " + ( i + 1 ) ); }

for( int i = 0; i < 3; i++ ) { filozofi[i].start(); } System.out.println( "Concurenti:" ); for( int i = 0; i < 3; i++ ) { System.out.println( "\t" +filozofi[i].getName() + " "+ (( palarii[i] == Color.white ) ?"alb":"negru" ) ); } boolean gata=false; while( !gata) { for( int i = 0; i < 3; i++ ) { if( filozofi[i].aGhicit()==palarii[i] ) { System.out.println( filozofi[i].getName() +" a ghicit." ); gata=true; } } } for( int i = 0; i < 3; i++ ) { filozofi[i].stop(); try { filozofi[i].join(); } catch( InterruptedException e ) {}; } } public static Color culoare( Filozof filozof, Filozof concurent ) { if( filozof != concurent ) { for( int i = 0; i < 3; i++ ) { if( filozofi[i] == concurent ) { return palarii[i]; } } } return null; } }

Abstract Windowing Toolkit

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

Componentele interfetei utilizator Componentele sunt poziţionate pe interfaţa utilizator prin adăugarea lor într-un container. Un container este el însuşi o componentă, deci poate fi adăugat în alte containere. Veţi folosi această caracteristică atunci când veţi începe să lucraţi cu administratori de dispunere (layout managers) pentru a aranja o interfaţă.

Cea mai uşoară modalitate de a demonstra cum se proiectează o interfaţă este folosirea containerului cu care aţi lucrat până acum - clasa Applet.

Adăugarea componentelor într-un container O componentă este adăugată într-un container astfel: Creaţi componenta. Apelaţi metoda add() a containerului, pentru componenta respectivă. Deoarece toate applet-urile sunt containere, puteţi folosi metoda add() într-un applet pentru a adăuga o componentă direct în fereastra Applet. Fiecare componentă AWT este o clasă, deci componenta respectivă este creată prin crearea unui obiect al clasei respective. • •

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

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

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

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

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

Câmpuri de text Câmpurile de text (text fields) sunt folosite pentru a crea componente în care textul poate fi modificat de utilizator. Aceste componente sunt create din clasa TextField. Pentru a crea un câmp de text, folosiţi unul din următorii constructori: • • •

TextField() creează un câmp gol, fără o lăţime specificată. TextField(int) creează un câmp gol, care are o lăţime suficientă pentru a afişa numărul specificat de caractere. TextField(String) creează un câmp completat cu şirul dat şi fără o lăţime specificată.

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

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

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

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

Suprafeţe de desenare

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

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

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

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

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

Dispunerea marginală Dispunerile marginale (border layouts), create pornind de la clasa BorderLayout, împart un container în cinci secţiuni: nord, sud, est, vest şi centru. Folosind dispunerea marginală, componentele din cele patru puncte cardinale vor ocupa spaţiul de care au nevoie, iar centrul obţine spaţiul rămas disponibil. De obicei, acest aranjament are ca rezultat o componentă centrală de dimensiuni mari, înconjurată de patru componente mai „subţiri". Dispunerea marginală se obţine folosind unul dintre constructorii Borderlayout() sau BorderLayout(int, int). Primul constructor creează o dispunere marginală fără nici un spaţiu între componente. Al doilea constructor specifică spaţiul pe orizontală, respectiv pe verticală.

După ce aţi creat o dispunere marginală şi aţi stabilit-o drept administrator curent de dispunere pentru un container, componentele sunt adăugate folosind o altă apelare a metodei add() decât cele folosite anterior: add(String, componenta) Al doilea argument al acestei metode este componenta care trebuie adăugată în container. Primul argument este un şir care indică zona unde va fi plasată componenta. Există cinci valori posibile: "North" (nord), "South" (sud), "East" (est), "West" (vest) şi "Center" Centru). Următoarea instrucţiune adaugă un buton, denumit butonTenninare, în porţiunea nordică: add( "North" , butonTerminare) ; Combinarea administratorilor de dispunere Pentru a găsi dispunerea potrivită, de cele mai multe ori trebuie folositi mai multi administratori pentru aceeaşi interfaţă. Acest lucru se poate face adăugând mai multe containere într-un container principal, fiecare dispunând de propriul administrator de dispunere. Aceste containere se numesc panouri (panels) şi sunt derivate din clasa Panel. Panourile sunt containere folosite pentru a grupa diferite componente. Când lucraţi cu panouri trebuie să ţine cont de următoarele: Panoul se umple cu componente înainte de a fi introdus într-un container mai mare; Panoul posedă propriul administrator de dispunere. Crearea unui panou: Panel panou=new Panel(); Panoului i se atribuie o metodă de dispunere prin apelarea metodei setLayout(), ca în exemplul: BorderLayout b1=new BorderLayout(); panou.setLayout(b1); Componentele se adaugă apoi folosind metoda add().

Dispuneri complexe Dispunerea în stivă

O dispunere în stivă (card layout) diferă de celelalte deoarece ascunde vederii unele componente. O dispunere în stivă este un grup de containere sau de componente afişate câte unul o dată. Fiecare container din grup este denumit card (cartelă). În mod normal, dispunerea în stivă foloseşte un panou pentru fiecare cartelă. Mai întâi se introduc componente în panouri, după care acestea se introduc în containerul pe care s-a stabilit o dispunere în stivă. O dispunere în stivă se creează cu ajutorul clasei CardLayout, apelând constructorul acesteia: CardLayout cc=new CardLayout(); Pentru a stabili administratorul de dispunere pentru un container se foloseşte metoda setLayout(). După ce aţi stabilit un container se foloseşte metoda add(String, container). Al doilea argument specifică containerul sau componenta care reprezintă cartela. Dacă este vorba de un container acesta trebuie să conţină deja toate componentele necesare. Primul argument este un şir care reprezintă numele cartelei. Acesta poate fi oricare, ca în exemplul: add(„Cartela de optiuni”, optiuni) prin care se adaugă în container un panou numit optiuni şi i se atribuie numele „Cartela de optiuni”.

După ce s-a adăugat cartela în containerul principal al programului, se poate folosi metoda show() a administratorului de dispunere în stivă, metodă ce primeşte două argumente: • Containerul în care au fost adăugate cartelele. Dacă containerul este chiar fereastra principală se poate folosi this. • Numele cartelei Următorul exemplu apelează metoda show() a administratorului de dispunere în stivă denumir cc: cc.show(this,”Cartela de date”)

La afişarea unei cartele, cartela afişată anterior este ascunsă. Dispunerea tabelară neproporţională

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

restrictii.gridwidth=1; restrictii.gridheight=1; restrictii.weightx=30; restrictii.weighty=30; restrictii.fill= GridBagConstraints.NONE; restrictii.anchor= GridBagConstraints.CENTER; tabel.setConstraints(b,restrictii); add(b); Etapele care trebuie parcurse la dispunerea tabelara neproporţională Etapa 1. Proiectarea tabelului

Initial se face o proiectare pe hârtie a tabelului, etichetând celulele tabelului după coordonatele lor x şi y. Astfel, celula din colţul de stânga sus are coordonatele 0,0, cea alăturată are coordonatele 0,1 ş.a.m.d. Etapa a doua. Crearea tabelului

O variantă ar fi să se creeze o metodă ajutătoare, care preia mai multe valori şi stabileşte restricţiile pentru acestea. Asftel metoda definireRestrictii() preia şapte argumente: un obiect GridBagConstraints şi şase valori întregi, care reprezintă variabilele de instanţă gridx, gridy, gridwidth, gridheight, weightx şi weighty. Metoda definireRestrictii este următoarea: void definireRestrictii(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int wx, int wy) { gbc.gridx=gx; gbc.gridy=gy; gbc.gridwidth=gw; gbc.gridheight=gh; gbc.weightx=wx; gbc.weighty=wy; } Se creează apoi metoda init() , în care se creează de fapt dispunerea. public void init() {GridBagLayout tabel=new GridBagLayout(); GridBagConstraints restrictii =new GridBagConstraints(); SetLayout(tabel); restrictii.fill=GridBagConstraints.BOTH; } Se vor defini apoi restrictii corespunzătoare fiecărei componente din interfaţă, ca în exemplul următor: definireRestrictii(restrictii, 0,0,1,1,100,100); Button eticheta1=new Button(„nume”); tabel.setConstraints(eticheta1,restrictii); add(eticheta1); Aceste linii vor stabili restricţiile pentru un obiect, vor crea un buton, vor asocia acestuia restricţiile şi îl vor adăuga în panou. Primele două argumente reprezintă valorile gridx şi gridy ale restricţiilor. Acestea reprezintă chiar coordonatele celulei ce conţine componenta. Dacă o componentă ocupă mai multe celule se vor introduce coordonatele componentei cea mai din stânga-sus. Următoarele argumente reprezintă gridwidth şi gridheight. Ele nu reprezintă laţimea şi înălţimea în pixeli, ci reprezintă numărul de celule pe care le ocupă componenta. Ultimele două argumente sunt proporţiile weightx şi weighty, folosite pentru a stabili proporţiile rândurilor şi a coloanelor, adică modul de distribuire al spaţiului suplimentar care rămâne neutilizat după amplasarea componentei. După stabilirea proporţiilor ele pot fi atribuite unui obiect folosind metoda GridBagConstraints().

Etapa a treia: Determinarea proporţiilor

La determinarea proporţiilor se ţine cont de obiectele care se extind pe mai multe celule. Acestea trebuie să primească valoarea 0 în direcţia în care se extind. Etapa a patra. Adăugarea şi aranjarea componentelor

În această etapă se stabilesc restricţiile care arajează componentele în cadrul celulei. Există două astfel de restricţii: fill (umplere) şi anchor (ancorare). Restricţia fill determină – pentru componentele care se pot extinde în orice direcţie – în ce direcţie se face această extindere şi poate avea una dintre următoarele valori: GridBagConstraints.BOTH, care extinde componenta pentru a umple celula în ambele direcţii GridBagConstraints.NONE, care determină componenta să fie afişată la cea mai mică dimensiune a sa GridBagConstraints.HORIZONTAL, care extinde componenta pe direcţie orizontală GridBagConstraints.VERTICAL, care extinde componenta pe direcţie verticală. Implicit restricţia fill este NONE. A doua restricţie care afectează modul în care apare o componentă într-o celulă este anchor (ancorare) şi ea se aplică doar componentelor care nu umplu întreaga celulă, indicând funcţiilor AWT unde să plaseze componenta în cadrul celulei. Valori posibile: GridBagConstraints.NORTH, GridBagConstraints.SOUTH, GridBagConstraints.NORTHEAST, GridBagConstraints.SOUTHWEST, GridBagConstraints.EAST, GridBagConstraints.WEST, GridBagConstraints.SOUTHEAST, GridBagConstraintsNORTHWEST, GridBagConstraints.CENTER.

Valoarea implicită este GridBagConstraints.CENTER.

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

Tipuri de evenimente Un eveniment este generat ca răspuns la aproape orice acţiune pe care o poate întreprinde un utilizator pe parcursul ciclului de viaţă al unui program Java. O mişcare a mouse-ului, un clic executat pe un buton, o apăsare de tastă, toate generează un eveniment. În programele dumneavoastră nu trebuie să trataţi toate evenimentele ce por apărea. De fapt, vă veţi ocupa de acele evenimente la care vreţi să răspundă programul dumneavoastră; restul vor fi ignorate. De exemplu, dacă utilizatorul execută un clic cu mouse-ul sau apasă o tastă, puteţi face ca programul să răspundă la aceste evenimente. Iată câteva dintre evenimentele care pot fi tratate în programele dumneavoastră: • Clicuri cu mouse-ul. Apăsarea butonului mouse-ului (mouse down), eliberarea acestuia (mouse up) sau apăsarea şi eliberarea butonului pe aceeaşi poziţie (mouse clic).



• •

Mişcări ale mouse-ului. Cursorul mouse-ului care părăseşte sau pătrunde pe suprafaţa ocupată de o componentă a interfeţei sau tragerea cu mouse-ul (drag - mişcările efectuate de cursor atunci când butonul mouse-ului este apăsat). Apăsări de taste. Apăsarea tastei, eliberarea tastei, tastarea (apăsarea şi eliberarea unei taste). Evenimente ale interfeţei utilizator. Execuţia de clicuri pe butoane, derularea listelor, afişarea meniurilor derulante şi aşa mai departe. Metoda handleEvent ()

Tratarea evenimentelor reprezintă domeniul în care au apărut cele mai multe modificări în trecerea de la Java 1.02 la Java 2. Evenimentele sunt generate şi executate aproximativ în acelaşi mod, indiferent de versiunea de limbaj pe care o folosiţi; diferenţa constă în modul cum acestea sunt recepţionate şi procesate. În Java 1.02, toate evenimentele care apar în ciclul de viaţă al unui program sunt tratate de o metodă denumită handleEvent(). Aceasta este definită în clasa Component, care este moştenită de java.applet.Applet, devenind astfel disponibilă tuturor applet-urilor. Atunci când un eveniment este transmis metodei handleEvent (), aceasta apelează apoi o metodă specifică de tratare a evenimentului, în funcţie de tipul acestuia. Exemple de astfel de metode specifice sunt mouseDown(), mouseUp() sau keyDown(). Pentru a trata un eveniment în programele dumneavoastră, va trebui să suprascrieţi una dintre aceste metode specifice. Apoi, la apariţia evenimentului respectiv, metoda respectivă va fi apelată. De exemplu, puteţi suprascrie metoda mouseDown() pentru a afişa un mesaj în fereastra Applet. Atunci când apare un eveniment de acest tip (apăsarea butonului mouse-ului), mesajul va fi afişat. Tratarea clicurilor de mouse Unul dintre cele mai des întâlnite evenimente care v-ar putea interesa este efectuarea unui clic cu mouse-ul. Evenimentele de acest tip apar atunci când utilizatorul execută un clic cu mouseul undeva, pe interfaţa programului. Puteţi intercepta clicurile mouse-ului pentru diferite lucruri simple - de exemplu, pentru a activa sau dezactiva sunetul într-un applet, pentru a trece la următoarea planşă a unei prezentări sau pentru a şterge ecranul. De asemenea, clicurile cu mouse-ul pot fi folosite împreună cu deplasările acestuia pentru a realiza o formă de interacţiune mai complexă cu utilizatorul. Evenimentele mouse down şi mouse up Atunci când utilizatorul execută un clic cu mouse-ul, se generează două evenimente: un eveniment mouse down, atunci când se apasă butonul mouse-ului, şi un eveniment mouse up, atunci când acesta este eliberat. Această divizare permite executarea unor acţiuni diferite în cele două etape ale clicului. Tratarea evenimentelor mouse-ului în applet-ul dumneavoastră este simplă; se suprascrie metoda corespunzătoare, care va fi apelată la apariţia respectivului eveniment. Iată un exemplu de semnătură de metodă pentru un eveniment mouse down: public boolean mouseDown(Event evt, int x, int y) {

//. . . } Metoda mouseDown() (ca şi metoda mouseUp()) primeşte trei parametri: evenimentul în sine şi coordonatele x şi y unde a apărut evenimentul respectiv. Argumentul evt reprezintă o instanţă a clasei Event. Toate evenimentele generează o instanţă a clasei Event, care conţine informaţii privind locul şi perioada când a avut loc evenimentul, tipul lui, precum şi alte informaţii. Uneori, existenţa unui astfel de pointer către un obiect Event este folositoare, aşa cum veţi descoperi mai târziu în această secţiune. Coordonatele x şi y ale evenimentului, aşa cum au fost ele transmise prin argumentele x şi y ale metodei mouseDown(), vă permit să determinaţi cu precizie locul unde s-a efectuat , clicul. Deci de exemplu, dacă evenimentul mouse down a apărut în zona unui buton grafic, puteţi activa acel buton. Reţineţi că puteţi obţine coordonatele x şi y şi din cadrul obiectului Event; în această metodă, ele au fost transmise ca argumente separate pentru a se lucra mai uşor cu ele.

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

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

Evenimentele mouse enter şi mouse exit Metodele mouseEnter() şi mouseExit() sunt apelate când indicatorul mouse-ului pătrunde sau părăseşte un applet sau o porţiune a acestuia. Atât mouseEnter(), cât şi mouseExit() posedă semnături similare cu metodele pentru tratarea clicurilor. Acestea primesc trei argumente: obiectul Event şi coordonatele x şi y ale punctului unde mouse-ul a pătruns în applet sau l-a părăsit. Următoarele exemple prezintă semnăturile metodelor mouseEnter() şi mouseExit(): public boolean mouseEnter(Event evt, int x, int y) { // . . . } public boolean mouseExit(Event evt, int x, int y) { // ... } Tratarea evenimentelor de tastatură Un eveniment de tastatură este generat ori de câte ori utilizatorul apasă o tastă. Prin folosirea acestor evenimente puteţi memora valorile tastelor apăsate de utilizator pentru a executa o anumită acţiune sau pentru a obţine date de intrare de la utilizatorii applet-ului. Pentru ca evenimentul de tastatură sa fie recepţionat de o componentă, aceasta trebuie să fie selectată (focused - „în centrul atenţiei"; cu alte cuvinte, să fie acea componentă a interfeţei selectată pentru a primi datele de intrare). Veţi învăţa mai târziu despre această selectare a componentelor, atunci când veţi lucra cu evenimente de selectare (focus events). Selectarea este mai uşor de înţeles dacă luaţi exemplul unei interfeţe care conţine mai multe câmpuri de text. Cursorul pâlpâie în câmpul de text selectat, iar utilizatorul poate introduce text în acel câmp folosind tastatura. Nici un alt câmp nu poate primi date de la tastatură până când nu este selectat. Toate componentele, inclusiv containerele, pot fi configurate pentru a fi selectate. Pentru a indica explicit că o componentă este selectată pentru a primi date de intrare (input focus), poate fi apelată metoda requestFocus() a componentei, fără argumente. Următoarea instrucţiune selectează un obiect Button, denumit terminare: terminare.requestFocus() ; Puteţi selecta fereastra Applet apelând metoda requestFocus() a acesteia.

Evenimentele key down şi key up Pentru a trata un eveniment de tastatură poate fi folosită metoda keyDown(): public boolean keyDown(Event evt, int tasta) { // ... } Valorile generate de evenimentele key down - tastă apăsată - (şi transmise metodei keyDown() drept argumentul tasta) sunt întregi care reprezintă valori Unicode, cum ar fi caractere

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

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

Tasta reprezentată

Event.HOME Event.END Event.PGUP Event.PGDN Event.UP Event.DOWN

Tasta Home Tasta End Tasta Page Up Tasta Page Down Tasta Up Tasta Down

Event.LEFT

TastaLeft

Event.RIGHT Event.F1 Event.F2 Event.P3 Event.F4 Event.F5 Event.F6 Event.F7 Event.F8 Event.F9 Event.F10 Event.F11 Event.F12

Tasta Right Tasta F1 Tasta F2 Tasta F3 Tasta F4 TastaF5 Tasta F6 Tasta F7 Tasta F8 Tasta F9 Tasta F10 TastaF11 TastaF12

Tratarea evenimentelor componentelor

.

Tehnicile de tratare a evenimentelor pe care le-aţi învăţat până acum sau axat pe interacţiunea cu utilizatorul - execuţia unui clic cu mouse-ul, apăsarea unor taste şi altele de acest gen. Există şi altfel de evenimente, care au loc pe componente cum ar fi butoane, zone de text sau alte elemente de interfaţă. De exemplu, butoanele folosesc evenimente declanşate la apăsarea lor. Nu trebuie să vă intereseze evenimentul mouse down sau mouse up sau locul unde a apărut interacţiunea respectivă; componenta se va ocupa de toate aceste evenimente în locul dumneavoastră. Următoarele evenimente pot fi generate prin interacţiunea cu componentele interfeţei: • Evenimente de acţiune (action events). Sunt principalele evenimente ale majorităţii componentelor interfeţei, prin care se indică faptul că acestea au fost "activate". Evenimentele de acţiune sunt generate atunci când este apăsat un buton, când se selectează sau se deselectează o casetă de validare sau un buton radio, când se alege o opţiune dintr-un meniu sau când utilizatorul apasă tasta Return sau Enter într-un câmp de text. • Evenimente de selectare sau deselectare a listelor (list select/deselect events). Aceste evenimente sunt generate atunci când se selectează o casetă de validare sau o opţiune dintrun meniu (listă). Aceste exemple declanşează, de asemenea un eveniment de acţiune. • Evenimente de primire sau cedare a selecţiei (got/lost focus events). Aceste evenimente pot fi generate de orice componentă, fie ca răspuns la un clic de mouse, fie la selecţionarea prin tasta Tab. Primirea selecţiei (got focus) înseamnă că acea componentă este selecţionată şi poate fi activată sau poate primi date de intrare. Cedarea selecţiei (lost focus) înseamnă că sa selecţionat o altă componentă. Tratarea evenimentelor de acţiune Un eveniment de acţiune reprezintă cel mai des întâlnit eveniment al unei interfeţe şi, din acest motiv, există o metodă specială pentru a-l trata, la fel cum există metode pentru acţiunile de mouse sau de tastatură. Pentru a intercepta un eveniment de acţiune generat de o componentă se defineşte în applet o metodă denumită action(),cu următoarea semnătură: public boolean action(Event evt, Object arg) { // ... } Această metodă action() ar trebui să arate asemănător cu metodele de tratare a evenimentelor de mouse sau de tastatură. Ca şi acele metode, aceasta primeşte ca argument un obiect care referă evenimentul respectiv. Ea mai primeşte, de asemenea, un obiect suplimentar (în acest caz, parametrul arg), care poate fi de orice tip. Tipul acestui al doilea argument al metodei de acţiune depinde de componenta interfeţei care a generat acţiunea. O caracteristică de bază ar fi că este vorba de "orice fel de argument", determinat de componenta însăşi, care poate transmite informaţiile suplimentare necesare pentru a procesa acţiunea respectivă. Tabelul următor prezintă argumentele suplimentare pentru fiecare tip de componentă. Componente Butoane Casete de validare Butoane radio

Tip de argument String Boolean Boolean

Ce conţine Eticheta butonului Întotdeauna true Întordeauna true

Meniuri de opţiuni Câmpuri de text

String String

Eticheta elementului selectat Textul din cadrul câmpului

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

Tratarea evenimentelor de selectare Aşa cum am arătat mai devreme, evenimentele de acţiune sunt de departe cele mai des întâlnite evenimente pe care le veţi trata într-o interfaţă. Totuşi, mai există şi alte elemente pe care le puteţi folosi în programele dumneavoastră: list select (selecţie listă), list deselect (deselecţie listă), got focus (primire selecţie) şi lost focus (cedare selecţie). Pentru evenimentele got focus şi lost focus se pot folosi metodele gotFocus() şi lostFocus(), care se folosesc la fel ca metoda action(). Iată semnăturile lor: public boolean gotFocus(Event evt, Object arg) { // ... } public boolean lostFocus(Event evt, Object arg) { // ... } Pentru evenimentele de selectare şi deselectare de liste nu sunt disponibile metode uşor de suprascris. Pentr a trata aceste evenimente trebuie folosită metoda handleEvent(), ca în exemplul următor: public boolean handleEvent(Event evt) { if (evt.id == Event.LIST_SELECT) handleSelect(Event); else if (evt.id == Event.LIST_DESELECT) handleDeselect(Event) ; else return super.handleEvent(evt); } În acest extras de cod, Event.LIST_SELECT şi Event.LIST_DESELECT reprezintă identificatorii oficiali ai evenimentelor de selectare/deselectare liste, iar controlul este transferat către două metode ajutătoare ( handleSelect() şi handleDeselect () ), care se presupune că sunt definite în altă parte. Remarcaţi, de asemenea, apelul către super.handleEvent() din final; acesta permite celorlalte evenimente să fie transferate fără probleme câtre metoda handleEvent() originală. Evenimente ale zonelor de text Zonele de text prezintă aceleaşi evenimente ca şi câmpurile de text. Puteţi folosi metodele gotFocus() şi lostFocus() pentru a intercepta evenimentele de primire/cedare a selecţei: public boolean gotFocus(Event evt, Object arg) { // . .. } public. boolean lostFocus(Event evt, Object arg) { // ... } Evenimente ale listelor derulante

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

Evenimente ale barelor de derulare Numai pentru diferitele tipuri de mişcări ale acestora este generată o întreagă gamă de evenimente. Pentru toate acestea va trebui să folosiţi metoda handleEvent(). Tabelul următor prezintă identificatorii de evenimente care vă interesează şi mişcările care declanşează aceste evenimente. Evenimente ale barelor de derulare Ce reprezintă Identificatorul evenimentului SCROLL_ABSOLUTE SCROLL_LINE_DOWN SCROLL_LINE_UP SCROLL_PAGE_DOWN SCROLL_PAGE_UP

Generat la deplasarea casetei barei de derulare Generat la selectarea butonului din capătul de jos sau din stânga al barei de derulare Generat la selectarea butonului din capătul de sus sau din dreapta al barei de derulare Generat la selectarea spaţiului aflat dedesubtul (sau la stânga) casetei barei de derulare Generat la selectarea spaţiului aflat deasupra (sau la dreapta) casetei barei de derulare

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

Clasele Window Clasele AWT care produc ferestre sau casete de dialog moştenesc o clasă comună, Window. Clasa Window moşteneşte clasa Container, ca şi componentele panou şi applet, şi oferă un comportament generic pentru toate elementele derivate din ferestre.

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

Cadre Cadrele (frames) sunt ferestre independente de fereastra applet-ului sau a browserului care conţine applet-ul; ele sunt ferestre separate, care conţin titluri proprii, butoane de redimensionare sau de închidere şi bare de meniuri. Puteţi crea cadre, în applet-uri, pentru a produce ferestre sau le puteţi folosi în aplicaţii pentru a păstra conţinutul acestora. Un cadru reprezintă o fereastră specifică unei anumite platforme, care conţine un titlu, o bară de meniuri, butoane de închidere şi de redimensionare şi alte elemente specifice unei ferestre. Pentru a crea un cadru se foloseşte unul din următorii constructori:

new Frame () creează un cadru simplu, fără titlu. new Frame (String) creează un cadru simplu, cu titlul dat de şirul primit ca argument. Deoarece cadrele moştenesc clasa Window, care moşteneşte clasa Container, care moşteneşte clasa Component, cadrele sunt create şi folosite cam în acelaşi fel cu celelalte componente AWT. Cadrele sunt containere, ca şi panourile, deci puteţi adăuga în ele diferite componente folosind metoda add (), ca şi în panouri (panels). Dispunerea prestabilită pentru cadre • •

este BorderLayout. Iată un exemplu care creează un cadru, stabileşte dispunerea componentelor în acesta şi adaugă două butoane: win = new Frame("Fereastra mea") ; win.setLayout(new BorderLayout(10, 20)); win.add("North", new Button("Start")) ; win.add("Center", new Button("Mutare")); Pentru a stabili dimensiunea noului cadru se foloseşte metoda resize (), care primeşte ca argumente lăţimea şi înălţimea acesteia. De exemplu, această linie de cod redimensionează fereastra la dimensiunile de 100 pixeli lăţime şi 200 pixeli înălţime: win.resize(100, 200); Deoarece în sisteme diferite se calculează în mod diferit reprezentările pixelilor şi există rezoluţii diferite, este dificilă crearea unui cadru de dimensiuni „bune" pentru oricare platformă. Ferestrele care sunt potrivite într-un sistem pot fi prea mici sau prea mari într-un altul. O metodă de evitare a acestei probleme este folosirea metodei pack() în locul metodei resize(). Metoda pack(), care nu primeşte argumente, creează o fereastră de cea mai mică dimensiune posibilă, date fiind dimensiunile curente ale componentelor pe care le conţine, administratorul de dispunere şi inserţiile folosite. Următorul exemplu creează două butoane şi le adaugă într-o fereastră. După aceasta, fereastra este adusă la cele mai mici dimensiuni posibile când conţine aceste două butoane. FlowLayout flo = new FlowLayout() ; Button ok = new Button("OK"); Button anulare = new Button("Anulare"); win.setLayout(flo); win.add(ok) ; win.add(anulare); win.pack() ; Atunci când creaţi o fereastră, aceasta este invizibilă. Pentru a fi afişată pe ecran trebuie să folosiţi metoda show(). Pentru a o ascunde din nou puteţi folosi metoda hide(). Exemplu: win.show() ; Reţineţi că atunci când o fereastră este afişată de un applet, browserul vă atrage atenţia că aceasta nu este o fereastră a sa, de obicei, printr-un mesaj în cadrul ferestrei.

Casete de dialog Casetele de dialog, din punct de vedere funcţional, sunt asemănătoare cadrelor prin faptul că afişează ferestre pe ecran. Totuşi, ele sunt destinate pentru a fi folosite drept ferestre temporare – adică pentru a vă prezenta mesaje de avertizare, pentru a vă cere anumite informaţii şi aşa mai departe.

Casetele de dialog nu au, de obicei, bare de titlu sau alte elemente caracteristice unei ferestre (cu toate că puteţi crea şi casete de dialog cu bare de titlu). Ele pot fi definite aşa încât să nu fie redimensionabile sau pot fi definite drept modale. (Casetele de dialog modale împiedică accesarea altor ferestre până în momentul când se renunţă la ele.) Casetele de dialog sunt ferestre temporare având rolul de a alerta utilizatorul cu privire la un anumit eveniment sau de a prelua date de la utilizator. Spre deosebire de cadre, casetele de dialog nu posedă, de obicei, bare de titlu sau butoane de închidere.

O casetă de dialog modală nu permite introducerea de date în alte ferestre până când nu este închisă. Nu veţi putea aduce în prim plan o altă fereastră şi nici minimiza caseta de dialog modală; aceasta trebuie închisă explicit pentru a se putea continua lucrul în sistem. De obicei, avertismentele sunt casete de dialog modale.

AWT oferă două tipuri de casete de dialog: clasa Dialog, care produce o casetă de dialog generică, şi FileDialog, care produce o casetă de dialog pentru căutarea de fişiere, specifică platformei respective.

Obiecte dialog Casetele de dialog se creează şi se folosesc asemănător ferestrelor. Pentru crearea unei casete de dialog generice se foloseşte unul din următorii constructori: • Dialog (Frame, boolean) creează o casetă de dialog invizibilă, ataşată cadrului curent, fie

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

Ataşarea casetelor de dialog la applet-uri Casetele de dialog pot fi ataşate numai cadrelor (frames). Pentru a crea o casetă de dialog, trebuie să transmiteţi o instanţă a clasei Frame uneia dintre metodele constructor ale casetei de dialog. Acest lucru înseamnă că nu puteţi crea casete de dialog ataşate applet-urilor. Deoarece applet-urile nu sunt cadre, nu le puteţi transmite ca argument unei clase Dialog. Totuşi, cu puţină îndemânare, puteţi face rost de un obiect cadru care conţine un applet (adesea, chiar browserul sau fereastra de vizualizare a applet-ului), după care să folosiţi acest obiect drept cadru pentru caseta de dialog.

Codul care face acest lucru foloseşte metoda getParent(), definită pentru toate componentele AWT. Metoda getParent() returnează obiectul care conţine obiectul curent. Deci părintele tuturor aplicaţiilor AWT trebuie să fie un cadru. Applet-urile se comportă în acelaşi mod. Prin apelarea în mod repetat a metodei getParent() veţi ajunge, până la urmă, să obţineţi o instanţă a clasei Frame. lată codul pe care trebuie să îl introduceţi în applet-ul dumneavoastră: Object ancora = getParent(); while (! (ancora instanceof Frame)) ancora = ((Component)ancora).getParent(); În prima linie a codului se creează o variabilă locală, denumită ancora, care va memora în final cadrul acestui applet. Obiectul atribuit variabilei poate fi de mai multe tipuri, aşa încât va fi declarat de tip Object. Următoarele două linii de cod definesc un ciclu while care apelează metoda getParent() în lanţ, pentru fiecare obiect, până când ajunge la un obiect de tip Frame. Se observă aici că, deoarece metoda getParent() este definită doar pentru obiectele care moştenesc clasa Component, trebuie să convertiţi prin cast valoarea ancora la tipul Component de fiecare dată când apelaţi metoda getParent(). După încheierea ciclului, obiectul conţinut de variabila ancora va fi o instanţă a clasei Frame (sau a uneia dintre subclasele acesteia). Puteţi crea un obiect Dialog ataşat acestui cadru convertind prin cast variabila ancora, pentru a vă asigura că obţineţi un obiect Frame: TextDialog dl = new TextDialog((Frame)ancora, "Enter Text",true);

Casete de dialog pentru sistemul de fişiere Clasa FileDialog permite crearea unei casete de dialog de tip File Open/Save (pentru deschiderea sau salvarea fişierelor), care vă permite să accesaţi sistemul local de fişiere. Clasa FileDialog este independentă de sistem, însă în funcţie de platformă este folosită caseta de dialog Open File (Deschidere fişier) sau Save File (Salvare fişier).

Folosirea de instanţe ale clasei FileDialog în applet-uri depinde de browser. Din cauza restricţiilor de securitate existente în applet-uri, majoritatea browserelor produc o excepţie de securitate atunci când încercaţi acest lucru. Obiectele FileDialog sunt mai folositoare în aplicaţiile independente. Pentru a crea o casetă de dialog pentru fişiere, folosiţi unul din următorii constructori: • FileDialog(Frame, String) creează o casetă de dialog ataşată unui cadru dat şi cu un titlu dat, pentru încărcarea unui fişier. • FileDialog(Frame, String, int) creează tot o casetă de dialog, însă argumentul întreg este folosit pentru a stabili dacă este vorba despre o casetă de dialog pentru încărcarea sau pentru salvarea unui fişier. (Singura diferenţă este eticheta butoanelor; de fapt, această casetă de dialog nu deschide sau nu salvează nimic.) Opţiunile pentru acest argument sunt FileDialog.LOAD sau FileDialog.SAVE. După crearea unei instanţe a clasei FileDialog se foloseşte metoda show() pentru a o afişa: FileDialog fd = new FileDialog(this, "FileDialog=”) ; fd.show() ; Atunci când utilizatorul alege un fişier şi închide caseta de dialog, el poate accesa numele fişierului ales folosind metodele getDirectory() şi getFile(). Ambele metode returnează un şir care conţine valorile alese de utilizator. După aceasta, puteţi deschide fişierul folosind metodele de lucru cu fişiere şi fluxuri (streams) de date, apoi puteţi citi sau scrie din/în acest fişier.

Evenimente de fereastră Aţi ajuns acum la ultimul tip de evenimente pe care le puteţi trata în AWT: evenimentele pentru ferestre şi casete de dialog. (în ceea ce priveşte evenimentele, caseta de dialog este tot un tip de fereastră.) Evenimentele de fereastră apar atunci când starea unei ferestre se modifică: de exemplu, când este mutată, redimensionată, minimizată, restabilită, mutată în prim-plan sau închisă. într-o aplicaţie îngrijit programată, aceste evenimente trebuie tratate - de exemplu, pentru a opri firele aflate în execuţie atunci când o fereastră este minimizată sau pentru a „face curăţenie" la închiderea ferestrei.

Puteţi folosi metoda handleEvent() pentru a testa evenimentele prezentate în tabelul următor folosind instrucţiunea switch pentru variabila de instanţă id (identificator). Evenimente de fereastră. Numele evenimentului WINDOW_DESTROY WINDOW_EXPOSE WINDOW_ICONIFY WINDOW_DEICONIFY WINDOW_MOVED

Când apare Generat atunci când o fereastră este închisă folosind butonul Close sau opţiunea de meniu Close Generat atunci când fereastra este adusă în prim-plan din spatele altor ferestre Generat atunci când fereastra este minimizată (redusă la dimensiunea unei pictograme) Generat atunci când fereastra este restabilită (când se revine de la starea de pictogramă) Generat atunci când fereastra este mutată

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

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

Menu meniuHelp = new Menu("Help") ; mbar.add(meniuHelp) ; mbar.setHelpMenu(meniuHelp) ; Dacă, pentru un motiv sau altul, doriţi ca un utilizator să nu aibă acces la un meniu, acesta poate fi dezactivat folosind metoda disable() (şi metoda enable() pentru a-l reactiva): meniulMeu.disable();

Elemente de meniu În meniurile individuale puteţi adăuga patru tipuri de elemente (opţiuni): • Instanţe ale clasei MenuItem, pentru elemente de meniu obişnuite • Instanţe ale clasei CheckBoxMenuItem, pentru elemente de meniu care au stările activat/dezactivat • •

Alte meniuri, care conţin propriile elemente de meniu Separatori, pentru liniile care separă grupuri de elemente de meniu

Crearea elementelor de meniu Elementele (opţiunile) de meniu obişnuite sunt create şi adăugate într-un meniu folosind clasa MenuItem. Mai întâi se creează o nouă instanţă a clasei MenuItem, după care se adaugă o componentă Menu folosind metoda add(): Menu meniulMeu = new Menu("Utilitare"); MenuItem m1= new MenuItem("Info") meniulMeu.add(m1) ; meniulMeu.add(new MenuItem("Culori")) ; Submeniurile pot fi adăugate prin simpla creare a unei noi instanţe a clasei Menu şi adăugarea în primul meniu. După aceasta, puteţi adăuga elemente în acest meniu: Menu submeniu = new Menu("Dimensiuni") ; meniulMeu.add(submeniu) ; submeniu.add(new Menultem("Mic")) ; submeniu.add(new Menultem("Mediu")) ; submeniu.add(new Menultemf"Mare")); Clasa CheckBoxMenuItem creează un element de meniu care posedă o casetă de validare, permiţând ca aceasta să fie activată sau dezactivată. (Prin selectarea elementului, acesta devine activat - în dreptul său apare un semn de validare; o nouă selectare a elementului duce la dispariţia acestui semn şi, implicit, la dezactivarea sa). Puteţi crea şi adăuga un element de meniu de acest tip în acelaşi fel ca elementele de meniu obişnuite:

CheckBoxMenuItem coord = new CheckBoxMenuItem("Afisare coordonate") ; meniulMeu.add(coord) ; Pentru a adăuga un separator într-un meniu (o linie care separă grupurile de opţiuni dintr-un meniu), creaţi şi adăugaţi un element de meniu care să conţină ca nume o liniuţă (-). Acest element special de meniu va fi reprezentat ca o linie de separare. Următoarele două linii de cod Java creează un astfel de separator şi îl adaugă în meniul meniulMeu:

MenuItem sep = new MenuItem("-"); meniulMeu.add(sep) ; Orice element de meniu poate fi dezactivat folosind metoda disable(), după care poate fi reactivat folosind metoda enable(). Elementele de meniu dezactivate nu pot fi selectate. Menultem element = new MenuItem("Umplere") ; meniulMeu.add(element) ; element.disable() ;

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

Crearea unor aplicaţii AWT independente Nu există diferenţe prea mari între un applet Java şi o aplicaţie grafică Java. Tot ce aţi învăţat până acum despre AWT, cum ar fi metodele grafice, tehnicile de animaţie, evenimente, componente ale interfeţei, ferestre şi casete de dialog, poate fi folosit în aplicaţiile Java în acelaşi fel în care a fost folosit în applet-uri.

Pentru crearea unei aplicaţii Java clasa aplicaţiei principale trebuie să moştenească clasa Frame. Dacă foloseşte fire de execuţie (pentru animaţie sau alte procesări), trebuie să implementeze, de asemenea, clasa Runnable: class AplicatieAWT extends Frame implements Runnable { //... } Pentru aplicaţia dumneavoastră veţi crea în metoda main() o nouă instanţă a clasei; deoarece clasa moşteneşte clasa Frame, veţi obţine o nouă fereastră AWT, pe care apoi o puteţi redimensiona şi afişa ca pe orice fereastră AWT. Caracteristicile AWT normale pentru o fereastră, care se declarau de obicei în cadrul metodei init() a unui applet, se realizează aici în cadrul metodei constructor a clasei: se stabileşte titlul, se declară un administrator de dispunere, se creează şi adaugă componente cum ar fi bara de meniuri sau alte elemente de interfaţă, se lansează fire de execuţie şi aşa mai departe. Un exemplu de aplicaţie simplă:

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

}

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

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

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

numeşte încapsulare (encapsulation): un obiect poate controla modul cum este văzut şi cum se poate interacţiona cu el din exterior. Încapsularea este procesul de prevenire a citirii sau a modificării unor variabile aparţinând unei clase de către alte clase. Singura metoda de folosire a acestor variabile este prin apelarea metodelor clasei, dacă acestea sunt disponibile. Limbajul Java oferă patru niveluri de control al accesului: public, private, protected şi un nivel special, care este specificat prin absenţa modificatorului. Accesul prestabilit Pentru majoritatea exemplelor de până acum nu s-a specificat nici un tip de control al accesului. Variabilele sau metodele au fost declarate prin instrucţiuni de genul: String nume = "Popescu Ion"; boolean este ( ) { return true; } O variabilă sau o metodă declarată fără nici un modificator al accesului este disponibilă tuturor celorlalte clase dintr-un acelaşi pachet. Mai înainte aţi putut vedea cum clasele şi bibliotecile Java sunt organizate în pachete (packages). Pachetul java. awt este unul dintre ele - un set de clase cu comportament asemănător, care alcătuiesc Abstract Windowing Toolkit. Orice variabilă declarată fără modificator poate fi citită sau modificată de orice clasă care face parte din acelaşi pachet. Orice metodă declarată astfel poate fi apelată de orice clasă din acelaşi pachet. Alte clase nu pot accesa aceste elemente în nici un mod. Acest nivel de control de fapt nu controlează prea mult accesul. Atunci când veţi analiza mai bine modul cum vor fi folosite clasele dumneavoastră de alte clase, veţi folosi în locul controlului prestabilit al accesului unul dintre cei trei modificatori specializaţi. Accesul privat Pentru a ascunde complet o metodă sau o variabilă, în scopul de a nu fi folosită de nici o altă clasă, se foloseşte modificatorul private. In acest fel, metodele sau variabilele nu pot fi accesate decât din clasa curentă, unde sunt definite. De exemplu, o variabilă de instanţă privată poate fi utilizată de metodele din aceeaşi clasă, însă nu şi de obiectele din alte clase. în acelaşi fel, metodele private pot fi accesate din clasa unde sunt definite, însă nu şi din altele. Această restricţie afectează şi moştenirea: nici variabilele private şi nici metodele private nu pot fi moştenite de subclase. Variabilele private sunt foarte folositoare în două cazuri: • Atunci când alte clase nu au nici un motiv să folosească variabila respectivă • Atunci când altă clasa poate produce dezastre dacă variabila respectivă este folosită necorespunzător. De exemplu, să considerăm o clasă Java, denumită Bingo, care generează numere de bingo pentru un site Internet de jocuri. Această clasă conţine o variabilă denumită rataCastiguri, prin care se poare controla numărul de câştiguri şi de pierderi generate. Această variabilă are un impact deosebit asupra jocului. Dacă ea ar putea fi modificată de alte clase, performanţa jocului s-ar putea modifica substanţial. Pentru a vă proteja împotriva unui astfel de scenariu, puteţi declara variabila rataCastiguri drept private. Folosirea modificatorului private reprezintă principala metodă pentru încapsularea unui obiect. Fără utilizarea acestuia pentru a ascunde variabile şi metode, nu puteţi limita modul în care este folosită o clasă; accesul este liber la toate variabilele şi metodele din cadrul clasei.

Accesul public În unele cazuri veţi avea nevoie ca o metodă sau o variabilă a clasei să fie disponibilă complet oricărei alte clase care doreşte să o folosească. O astfel de variabilă este variabila de clasă black a clasei Color. Această variabilă este folosită atunci când o clasă doreşte să folosească culoarea neagră, deci nu trebuie să existe nici o restricţie de acces. De obicei, variabilele de clasă sunt declarate publice. Modificatorul public face ca o metodă sau o variabilă să fie complet disponibilă unei alte clase. Aţi folosit acest modificator în toate aplicaţiile pe care le-aţi scris până acum, ca în instrucţiunea următoare: public static void main(String[ ] argumente) { // ... } Metoda main ( ) a unei aplicaţii trebuie să fie publică. Altfel, ea nu poate fi apelată de interpretorul java pentru a lansa în execuţie clasa. Datorită moştenirii, toate variabilele şi metodele publice ale unei clase sunt preluate şi de subclasele sale. Accesul protejat Al treilea nivel de control al accesului este limitarea vizibilităţii unei metode sau variabile doar pentru următoarele două grupuri: • Subclasele unei clase • Alte clase din acelaşi pachet Puteţi realiza acest lucru folosind modificatorul protected, ca în următorul exemplu: protected boolean avemNevoieDeMaiMultaOdihna = true; Acest nivel de control al accesului este folositor dacă doriţi implementarea unei subclase. Clasa dumneavoastră poate conţine o metodă sau o variabilă care să ajute şi la realizarea sarcinilor subclasei. Deoarece o subclasă moşteneşte cea mai mare parte a comportamentului şi atributelor, ea sar putea să aibă cam aceleaşi sarcini de realizat. Accesul protejat oferă subclasei şansa de a apela metoda sau variabila ajutătoare şi, în acelaşi timp, nu permite folosirea acestora de alte clase cu care nu are relaţii de „rudenie". Compararea nivelurilor de control al accesului Diferenţa dintre diferitele tipuri de protecţii poate deveni puţin neclară, mai ales în cazul metodelor şi variabilelor protejate. Tabelul următor, care prezintă în rezumat ceea ce este permis şi unde, vă ajută să vă daţi seama de diferenţele existente între cea mai puţin restrictivă formă de protecţie (public) şi cea mai restrictivă (private). Vizibilitate Din aceeaşi clasă Din orice clasă care aparţine aceluiaşi pachet Din orice clasa din afara pachetului Dintr-o subclasă a aceluiaşi pachet Dintr-o subclasă din afara pachetului

public da

protected da

default da

private da

da

da

da

nu

da da da

nu da da

nu da nu

nu nu nu

Controlul accesului şi moştenirea O ultimă consideraţie în ceea ce priveşte controlul accesului se referă la subclase. Atunci când creaţi o subclasă şi suprascrieţi o metodă, trebuie să luaţi în considerare controlul accesului definit pentru metoda originală. Astfel, metodele clasei Applet, cum ar fi init ( ) sau paint ( ), trebuie declarate public în appleturile dumneavoastră. Ca regulă generală, nu puteţi să suprascrieţi o metodă în Java şi să faceţi noua metoda mai controlabilă decât originalul. Totuşi, puteţi să o faceţi mai puţin controlabilă (mai publică). În cazul metodelor moştenite se impun următoarele reguli: • Metodele public dintr-o superclasă trebuie să fie, de asemenea, public în toate subclasele (din acest motiv, majoritatea metodelor applet-urilor sunt public). • Metodele protected dintr-o superclasă pot fi protected sau public în subclase; ele nu pot fi declarate private. • Metodele declarate fară control al accesului (deci fără modificator) pot fi declarate în subclase cu un grad de protecţie mai ridicat. Metodele declarate private nu sunt moştenite deloc, deci nu li se aplică regulile de mai sus. Metode de accesare În multe cazuri, într-o clasă puteţi avea o variabilă de instanţă cu reguli stricte pentru valorile pe care le conţine. Un exemplu ar fi o variabilă codPostal. Un cod poştal din România trebuie să fie un număr din patru cifre; valorile valide sunt în domeniul 1000... 9999; orice alte valori în afara acestui domeniu nu pot reprezenta coduri poştale. Pentru a evita ca o clasa externa să modifice incorect variabila codPostal, o puteţi declara privat, printr-o instrucţiune de genul: private int codPostal; Dar dacă şi alte clase trebuie să fie în măsură să modifice această variabilă? În acest caz, puteţi să permiteţi accesul acestora la variabila respectivă prin folosirea unei metode de accesare (accessor) definită în aceeaşi clasă ca şi variabila codPostal. Metodele de accesare au primit acest nume deoarece permit accesul la ceva care în mod normal nu este accesibil. Prin folosirea unei metode de acces la o variabilă private puteţi controla modul cum este folosită această variabilă, în exemplul cu codul poştal, clasa poate interzice atribuirea unor valori incorecte pentru variabila codPostal. De multe ori, pentru citirea şi scrierea variabilelor se folosesc metode de accesare diferite. Se foloseşte convenţia ca denumirea metodelor care citesc valoarea unei variabile să înceapă cu get, iar cele care setează valoarea variabilei să înceapă cu set. Astfel, pentru exemplul dat, metodele s-ar numi setCodPostal (int) şi getCodPostal ( ). Folosirea metodelor pentru accesul la variabilele de instanţa este o tehnică foarte răspândită în programarea orientată obiect. Acest mod de abordare măreşte gradul de reutilizare a codului deoarece evită folosirea lui necorespunzătoare.

Variabile şi metode statice Un modificator pe care l-aţi folosit deja în programe este static. Modificatorul static este folosit pentru crearea metodelor şi variabilelor de clasă, ca în următorul exemplu: public class Cerc { public static float pi = 3.14159265F;

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

Clase, metode şi variabile finale Modificatorul final este folosit cu clase, metode şi variabile pentru a indica faptul că acestea nu vor mai fi modificate. El are înţelesuri diferite pentru fiecare dintre aceste categorii: • Dintr-o clasă final nu se mai pot deriva subclase. • O metodă final nu poate fi suprascrisă de nici o subclasă. • O variabilă final nu îşi poate schimba valoarea. Variabile finale Acestea mai sunt denumite şi variabile constante (sau, mai simplu, constante), deoarece nu îşi pot modifica valoarea. In cazul variabilelor, modificatorul final este folosit, de obicei, împreună cu modificatorul static, pentru a crea din constantă o variabilă de clasă. Dacă valoarea nu poate fi modificată, nu există motive ca fiecare instanţă a clasei să posede o copie a valorii; toate pot folosi o variabilă de clasă cu acelaşi rol. Următoarele instrucţiuni sunt exemple de declaraţii de constante: public static final float pi = 3.1415927; Începând cu Java 2, orice tip de variabilă poate deveni variabilă finală: clasă, instanţă sau variabilă locală. Metode finale Metodele finale sunt acele metode care nu pot fi suprascrise niciodată de o subclasă. Acestea sunt declarate folosind modificatorul final . Singurul motiv pentru care aţi declara o metodă drept final este pentru o execuţie mai eficientă. în mod normal, atunci când un mediu de execuţie Java (cum este interpretorul java) rulează o metodă, el caută metoda mai întâi în clasa curentă, după aceea în superclasă şi „urcă" mai departe în ierarhie până ce găseşte definiţia acesteia. Prin acest proces se pierde din viteză în favoarea flexibilităţii şi a uşurinţei în dezvoltare. Dacă o metodă este declarată final, compilatorul Java poate introduce codul executabil (bytecode) al acesteia în oricare dintre programele care o apelează. Oricum, metoda nu s-ar putea modifica niciodată din cauza suprascrierii de către una dintre subclase. Atunci când proiectaţi o clasă, nu prea aveţi motive să folosiţi modificatorul final. Totuşi, dacă doriţi ca aceasta să se execute mai rapid, puteţi declara unele metode final, pentru a mări puţin

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

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

Pachete

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

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

Pachete complete şi nume de clase

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

Comanda import Pentru a importa clasele dintr-un pachet se foloseşte comanda import, aşa cum aţi văzut în toate exemplele prezentate în această carte. Puteţi importa o anumită clasă, ca în exemplul următor: import java.util.Vector; Sau puteţi importa un întreg pachet de clase, folosind simbolul asterisc (*) în locul numelor de clasă, astfel: import java.awt .*; Instrucţiunea import trebuie folosită în prima parte a definiţiei clasei, înainte de orice alte definiţii (însă după definiţia pachetului). Importarea unui grup de clase nu încetineşte programul şi nici nu îi măreşte dimensiunile; vor fi încărcate doar clasele de care aveţi nevoie în codul dumneavoastră. Insă importarea unui pachet întreg face ca lucrurile să fie mai neclare pentru cei care citesc codul dumneavoastră, pentru că nu se va şti de unde provin clasele folosite. Opţiunea pentru importuri individuale sau în grup depinde de stilul dumneavoastră de programare.

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

Variabila CLASSPATH şi locul de dispunere a claselor Pentru ca Java să poată folosi o clasă, trebuie să afle locul unde este dispusă în sistemul de fişiere; altfel, va fi generată o eroare referitoare la inexistenţa clasei. Pentru găsirea claselor Java

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

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

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

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

Adăugarea unei clase într-un pachet Pasul final este de a introduce clasa în pachet şi de a adăuga o instrucţiune în definiţia clasei, înainte de orice instrucţiuni import care vor fi folosite. Instrucţiunea package se foloseşte împreună cu denumirea pachetului, în felul următor: package figurigeometrice; Instrucţiunea package, dacă există, trebuie să fie pe prima linie a codului fişierului sursa, eventual după comentarii sau linii goale, însă înainte de orice comenzi import.

După ce veţi începe să folosiţi pachetele, trebuie să vă asiguraţi că toate clasele dumneavoastră aparţin aceluiaşi pachet, pentru a reduce eventualele confuzii asupra locului unde se găsesc.

Pachetele şi controlul accesului la clase Am văzut care sunt modificatorii de control ai accesului pentru metode şi variabile. Puteţi, de asemenea, controla accesul la clase, aşa cum aţi remarcat din folosirea modificatorului public în unele declaraţii de clase din exemplele anterioare. Dacă nu este specificat nici un modificator, claselor li se atribuie un control prestabilit al accesului; aceasta înseamnă că respectiva clasă este disponibilă tuturor claselor din acelaşi pachet, însă nu şi în afara pachetului - nici măcar subpachetelor. Ea nu poate fi importată sau referită prin nume; clasele cu protecţie de pachet sunt vizibile doar în cadrul pachetului care le conţine. Protecţia de pachet este stabilită atunci când definiţi o clasă în felul următor: class ClasaAscunsa extends OAltaClasaAscunsa { // . . . } Pentru a permite unei clase să fie vizibilă şi importabilă în afara pachetului, îi veţi atribui protecţia publică prin folosirea modificatorului public în definiţia sa: public class ClasaVizibila { // . . . } Clasele declarate public pot fi importate de orice alte clase din afara pachetului. Reţineţi că, atunci când folosiţi în instrucţiunea import simbolul asterisc (*), veţi importa doar clasele publice din cadrul pachetului respectiv. Clasele ascunse (hidden) rămân invizibile şi pot fi folosite doar de celelalte clase din pachetul respectiv. De ce să doriţi să ascundeţi o clasă în cadrul unui pachet? Din acelaşi motiv pentru care doriţi să ascundeţi variabilele şi metodele în cadrul unei clase: pentru că astfel aveţi clase utilitare şi metode care sunt folosite doar de implementarea dumneavoastră sau pentru că puteţi limita interfaţa programului dumneavoastră, minimizând efectul unor modificări de amploare. Atunci când vă proiectaţi clasele, ar trebui să luaţi în considerare întregul pachet şi să hotărâţi pe care clase doriţi să le declaraţi public şi pe care doriţi să le ascundeţi. Programul următor prezintă două clase care demonstrează acest lucru. Prima este o clasă publică ce implementează o listă înlănţuită; a doua reprezintă un nod privat al listei. Textul complet al programului ListaInlantuita. java 1: package colectie; 2: 3: public class Liatalnlantuita { 4: private Nod radacina; 5: 6: public void add(0bject o) { 7: radacina = new Nod(o, radacina); 8: } 9: // ... 10 } 11: 12: class Nod { // Nu este publica 13: private Object continut; 14: private Nod urmatorul;

15: 16: 17: 18: 19: 20: 21:

Nod(0bject o, Nod n) continut = o; urmatorul = n; } // ...

{

}

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

Interfeţe Interfeţele, ca şi clasele sau metodele abstracte, oferă un model de comportament care se presupune că va fi implementat de alte clase. Totuşi, interfeţele oferă posibilităţi mult mai mari decât clasele şi metodele abstracte, atât pentru Java, cât şi pentru proiectarea obiectelor şi a claselor, în general. Problema unicei moşteniri După ce veţi câştiga ceva mai multă experienţă în proiectare, va veţi da seama că simplitatea ierarhiei de clase Java este oarecum restrictivă, mai ales atunci când doriţi să folosiţi unele metode din clase aflate pe diferite „ramuri" ale aceleiaşi ierarhii. Să luăm un exemplu care va clarifica această problemă. Să presupunem că aveţi o ierarhie de clase referitoare la biologie, pe primul nivel aflându-se clasa Animal, iar mai jos, clasele Mamifer şi Pasare. Printre atributele clasei Mamifer se numără naşterea de pui vii şi existenţa blănii. Clasa Pasare conţine atribute cum ar fi depunerea de ouă şi existenţa ciocului. Dar cum veţi defini o clasă pentru ornitorinc, care, se ştie, are blană, cioc şi depune ouă? Pentru crearea clasei Ornitorinc va trebui să combinaţi atributele din cele două clase. Dar, deoarece clasele pot avea în Java o singură superclasă, acest tip de problemă nu poate fi rezolvat prea elegant. Alte limbaje OOP (orientate obiect) conţin conceptul de moştenire multiplă, care rezolvă această problemă. Dacă se foloseşte moştenirea multiplă, o clasă poate avea mai multe superclase, împrumutând atribute de la toate. Una dintre problemele moştenirii multiple este aceea că limbajul de programare devine mai dificil de învăţat, de folosit şi de implementat. Problemele legate de apelarea metodelor şi de modul de organizare a ierarhiei de clase devin mult mai complicate şi este mult mai probabil să producă ambiguităţi şi confuzii. Deoarece unul din scopurile limbajului Java este simplitatea, s-a renunţat la moştenirea multiplă în favoarea mai simplei moşteniri unice. Pentru rezolvarea acestor probleme Java conţine o altă ierarhie, total separată de ierarhia principală, care conţine clase cu comportament combinat. Astfel, atunci când creaţi o nouă clasă, aceasta are o singură superclasă primară, însă poate alege şi alte comportamente din cealaltă ierarhie. Această nouă ierarhie se numeşte ierarhie de interfeţe. O interfaţă Java este o colecţie de comportamente abstracte, care pot fi combinate în orice clasă pentru a introduce în acea clasă comportamente care nu pot fi moştenite de la superclasă. Tehnic, o interfaţă Java nu conţine nimic altceva decât definiţii de metode abstracte şi constante - fără variabile de instanţă sau implementări de metode. Interfeţele sunt implementate şi folosite în biblioteca de clase Java ori de câte ori este nevoie ca un anumit comportament să fie implementat în alte clase. Ierarhia de clase Java, de exemplu, defineşte şi foloseşte interfeţele java.lang.Runnable, java.awt.image.ImageConsumer şi java. awt. image.ImageProducer. Interfeţele şi clasele Clasele şi interfeţele, chiar dacă au definiţii diferite, au foarte multe în comun. Interfeţele, ca şi clasele, sunt declarate în fişiere sursă, câte un fişier pentru fiecare interfaţă. Ca şi clasele, acestea sunt compilate în fişiere .class folosind compilatorul Java. Şi, în majoritatea cazurilor, oriunde se poate folosi o clasă (ca tip de dată pentru variabile, ca rezultat al unei conversii prin cast şi aşa mai departe) se poate folosi şi o interfaţă. Programatorii Java folosesc termenul „clasă" pentru a se referi atât la clase, cât şi la interfeţe. Interfeţele completează şi extind puterea claselor şi de aceea pot fi tratate aproape în acelaşi fel. Una dintre puţinele diferenţe existente între clase şi interfeţe este aceea că nu se poate obţine o instanţă a unei interfeţe: operatorul new se foloseşte doar pentru crearea de instanţe ale unei clase. Implementarea şi folosirea interfeţelor

Cu interfeţele puteţi face două lucruri: să le folosiţi în clasele dumneavoastră sau să definiţi unele proprii. Să începem cu primul caz. Pentru a folosi o interfaţă, trebuie folosit cuvântul cheie implements ca parte a definiţiei clasei. Un astfel de exemplu este cel care a fost prezentat într-unul dintre capitolele anterioare: public class Neko extends java.applet.Applet implements Runnable { // . . . } În acest exemplu, java.applet.Applet este superclasa, însă interfaţa Runnable extinde comportamentul implementat de aceasta. Deoarece interfeţele nu oferă nimic altceva decât definiţii abstracte de metode, va trebui să implementaţi aceste metode în clasele dumneavoastră folosind semnăturile de metode provenite din interfaţă. Reţineţi că o dată inclusă o interfaţă, va trebui să implementaţi toate metodele acesteia; nu puteţi să alegeţi doar metodele de care aveţi nevoie. Prin implementarea unei interfeţe, înseamnă că declaraţi utilizatorilor clasei că veţi suporta toate metodele acesteia. (Aceasta este o altă diferenţă între interfeţe şi clasele abstracte; subclasele claselor abstracte pot alege care dintre metode să fie implementate sau suprascrise şi să le ignore pe celelalte.) După ce clasa pe care aţi scris-o implementează o interfaţă, subclasele acesteia vor moşteni toate noile metode (care pot fi suprascrise sau supraîncărcate) ca şi când acestea ar fi fost definite în superclasă. Când o clasă moşteneşte o superclasă care implementează o anumită interfaţă, nu mai trebuie să folosiţi cuvântul cheie implements în definiţia clasei respective. Să luăm un exemplu simplu - crearea unei noi clase, denumită Portocala. Să presupunem că posedaţi deja o implementare bună a clasei Fruct şi o interfaţă, ModelFruct, care reprezintă ceea ce un Fruct este capabil să facă. Doriţi ca portocala să fie un fruct, însă doriţi să fie şi un obiect sferic care să poate fi aruncat, rotit şi aşa mai departe. Iată cum se pot defini toate acestea: interface ModelFruct { void stricare ( ); void stoarcere ( ); // ... } class Fruct implements ModelFruct { private Color culoareaMea; private int zilePanaLaStricare; //... }

interface ModelSfera { void aruncare ( ); void rotire ( ); // ... } class Portocala extends Fruct implements ModelSfera { // aruncare ( ) poate sa provoace stoarcere ( ) // (caracteristici unice pentru clasa Portocala) } Remarcaţi că în clasa Portocala nu este nevoie să se implementeze interfaţa ModelFruct, deoarece, prin moştenirea clasei Fruct, deja o implementează! Unul dintre avantajele acestei structuri este

acela că vă puteţi răzgândi în privinţa clasei de unde se derivă clasa Portocala (de exemplu, dacă este implementată o clasă mai bună, denumită Sfera) şi totuşi clasa Portocala va înţelege aceleaşi două interfeţe: class Sfera implements ModelSfera { // derivata direct din clasa Object private float raza; // .. . } class Portocala extends Sfera implements ModelFruct { // ...utilizatorii clasei Portocala nu vor remarca aceasta // modificare! } Implementarea unor interfeţe multiple Spre deosebire de ierarhia de clase cu moştenire unică în clasele dumneavoastră puteţi folosi oricâte interfeţe doriţi; clasa rezultată va implementa comportamentul combinat: al tuturor interfeţelor incluse. Pentru a include mai multe interfeţe într-o clasă, separaţi numele acestora prin virgule: public class Neko extends java. applet. Applet implements Runnable, Comestibil, Sortabil, Observable { // ... } Reţineţi totuşi că folosirea unor interfeţe multiple poate da naştere la complicaţii. Ce se întâmplă dacă două interfeţe definesc aceeaşi metodă? Puteţi rezolva această problemă în trei moduri: • Dacă metoda are aceeaşi semnătură în ambele interfeţe, implementaţi în clasa dumneavoastră o metodă a cărei definiţie satisface ambele interfeţe. • Dacă metodele au liste de parametri diferite, veţi avea o situaţie de supraîncărcare a metodelor; implementaţi ambele metode, fiecare dintre definiţii satisfăcând interfaţa respectivă. • Dacă ambele metode au aceeaşi listă de parametri, însă returnează tipuri diferite, nu veţi putea crea o metodă care să le satisfacă pe amândouă (deoarece supraîncărcarea metodelor ţine cont doar de lista de parametri, nu de tipul de retur). În acest caz, încercarea de compilare a unei clase care implementează ambele interfeţe va produce o eroare. Dacă ajungeţi în această situaţie, înseamnă că interfeţele dumneavoastră au erori de proiectare şi trebuie reanalizate. Alte utilizări ale interfeţelor Aproape oriunde puteţi folosi o clasă, puteţi folosi şi o interfaţă. De exemplu, să declarăm o variabilă de tip interfaţă:

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

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

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

public static final int incrementare = 10; long nrmax = 1000000; // devine public, static si final public abstract void crescut ( ); // declarata explicit public // si abstract void maiCrescut ( ); // declarata implicit public si abstract } Interfeţele trebuie să posede, ca şi clasele, o protecţie de pachet sau publică. O interfaţă nepublică are metode şi constante de acelaşi tip, acestea neputând fi folosite decât în clasele sau interfeţele din acelaşi pachet. Interfeţele, ca şi clasele, pot aparţine unui pachet dacă folosiţi instrucţiunea package în prima linie din fişierul sursă. La fel ca şi clasele, interfeţele pot importa interfeţe sau clase din alte pachete. Metodele din cadrul interfeţelor Iată o problemă privind metodele din cadrul interfeţelor: ele trebuie să fie abstracte şi să se aplice oricărui tip de clasă, dar cum se poate defini lista de parametri pentru aceste metode? Nu ştiţi dinainte ce clasă urmează să le folosească! Răspunsul constă în faptul că, aşa cum aţi învăţat mai devreme, puteţi folosi un nume de interfaţă peste tot unde poate fi folosit un nume de clasă. Prin definirea parametrilor metodei de tip interfaţă puteţi crea parametri generici care funcţionează pentru orice clasă care ar folosi această interfaţă.

Să luăm, de exemplu, interfaţa ModelFruct, care defineşte metodele (fără parametri) stricare ( ) şi stoarcere ( ). Mai puteţi avea o metodă, germinareSeminte ( ), care are un singur argument: fructul însuşi. De ce tip urmează să fie acest argument? Nu poate fi de tip Fruct, deoarece puteţi avea o clasă derivată din ModelFruct (deci care implementează interfaţa ModelFruct) şi care să nu fie, de fapt, un fruct. Soluţia este de a declara în interfaţă argumentul de tip ModelFruct: public interface ModelFruct { public germinareSeminte (ModelFruct fr) { // … } } Apoi, în implementarea acestei metode într-o clasă, puteţi converti prin cast argumentul genericModelFruct în obiectul corespunzător: public class Portocala extends Fruct { public germinareSeminte(ModelFruct fr) { Portocala oPortocala = (Portocala)fr; // . . . } } Derivarea interfeţelor Ca şi în cazul claselor, interfeţele pot fi organizate într-o ierarhie. Atunci când o interfaţă moşteneşte o altă interfaţă, „subinterfaţa" primeşte toate metodele şi constantele definite în ″superinterfaţă". Pentru a deriva (extinde) o interfaţă veţi folosi cuvântul cheie extends, la fel ca în cazul definiţiilor claselor: public interface ModelFruct extends ModelMancare { // ... } Totuşi, spre deosebire de clase, ierarhia de interfeţe nu posedă echivalentul unei clase Object; această ierarhie nu are un „vârf". Interfeţele pot exista independent sau pot moşteni o altă interfaţă. De asemenea, spre deosebire de ierarhia de clase, ierarhia de interfeţe este una cu moştenire multiplă. De exemplu, o singură interfaţă poate moşteni oricâte clase are nevoie (separate prin

virgulă, în acea parte a definiţiei care foloseşte cuvântul cheie extends); noua interfaţă va conţine o combinaţie de metode şi constante moştenite de la „părinţi". Iată definiţia unei interfeţe care moşteneşte mai multe interfeţe: public interface InterfataAglomerata extends Runnable, Crescator, ModelFruct, Observable { // ... } În interfeţele cu moştenire multiplă, regulile de gestionare a conflictelor între numele metodelor sunt aceleaşi ca pentru clasele care folosesc mai multe interfeţe; metodele care diferă numai prin tipul de retur vor genera o eroare de compilare.

Clase interioare Clasele cu care aţi lucrat până acum sunt toate membre ale unui pachet, fie că aţi folosit instrucţiunea package, urmată de numele unui pachet, fie că a fost folosit pachetul prestabilit. Clasele care aparţin unui pachet sunt cunoscute drept clase de nivel înalt (top-level classes). La introducerea Java, acestea erau singurele clase suportate de limbaj. Începând cu Java 1.1, puteţi defini o clasa în interiorul altei clase, ca şi când ar fi o metodă sau o variabilă. Astfel de clase se numesc clase interioare (inner classes). Listingul următor prezintă applet-ul Interior, care foloseşte o clasă interioară, denumită ButonAlbastru pentru a reprezenta butoanele care au culoarea de fundal albastră. Textul complet al programului Interior.java. 1: import java.awt.Button; 2: import java.awt.Color; 3: 4: public class Interior extends java.applet.Applet { 5: Button b1 = new Button ("0ne"); 6: ButonAlbastru b2 = new ButonAlbastru("Two"); 7: 8: public void init ( ) { 9: add(b1); 10: add(b2); } 11: 12: class ButonAlbastru extends Button { 13: ButonAlbastru (String eticheta) { 14: super(eticheta) { 15: this .setBackground(Color.blue) ; 16: } 17: } 18: } În acest exemplu, clasa ButonAlbastru nu este decât o clasă ajutătoare inclusă în acelaşi fişier sursă ca şi clasa principală a programului. Singura diferenţă este că această clasă ajutătoare este definită în cadrul fişierului clasă, ceea ce aduce câteva avantaje: • Clasele interioare sunt invizibile pentru alte clase, ceea ce înseamnă că nu trebuie să vă faceţi probleme privind conflictele de nume cu alte clase. • Clasele interioare pot avea acces la variabilele şi metodele din domeniul de vizibilitate al clasei de nivel superior, lucru care nu ar fi fost valabil dacă ar fi fost separate.

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

Exceptii Java Până în acest moment, este mai mult ca sigur că aţi întâlnit cel puţin o situaţie de excepţie Java - probabil atunci când aţi introdus greşit numele unei metode sau aţi făcut o greşeală în cod care a dus la apariţia unei probleme.

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

Error

Exception

IOException RuntimeException ClassNotFoundE xception AWTException

FileNotFound Exception EOFException MalFormedURL Exception SocketException

Clasa Throwable are două subclase: Error şi Exception. Instanţele clasei Error reprezintă erori interne ale mediului de lucru Java (maşina virtuală). Aceste erori sunt rare şi, de obicei, fatale;

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

Gestionarea excepţiilor

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

Verificarea consistenţei excepţiilor

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

Protejarea codului şi interceptarea erorilor Să presupunem că aţi scris un cod şi că la un test de compilare aţi obţinut un mesaj de excepţie. În funcţie de mesaj, fie interceptaţi eroarea, fie declaraţi că metoda o poate semnala. Să luăm primul caz: interceptarea potenţialelor excepţii.

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

Clauza finally Să presupunem că în codul dumneavoastră există o anumită acţiune care trebuie neapărat executată, indiferent ce se întâmplă, indiferent dacă excepţia este semnalată sau nu. Acest lucru se face, de obicei, pentru a elibera anumite resurse după folosire, pentru a închide un fişier după deschidere sau ceva de acest gen. Chiar dacă puteţi introduce aceste acţiuni atât în cadrul unui bloc

catch, cât şi în afara sa, aceasta înseamnă o duplicare a codului în două locuri diferite. În loc să procedaţi astfel, introduceţi o copie a codului într-o porţiune specială a blocului try. . .catch, denumită finally. Următorul exemplu prezintă modul cum este structurat un bloc try. . .catch . . . finally: try { citesteFisierText(); } catch (IOException e) { // tratati erorile de intrare/iesire } finally { inchideFisierText() ; } Instrucţiunea finally este folositoare, de fapt, în afara excepţiilor; puteţi, de asemenea, să o folosiţi pentru executarea unui cod de reiniţializare după o instrucţiune return, break sau continue din cadrul unui ciclu. În ultimul caz puteţi folosi o instrucţiune try doar cu blocul finally, fără blocul catch.

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

Pentru a indica faptul că o metodă poate semnala o excepţie, veţi folosi în definiţia sa o clauză specială, denumită throws. Clauza throws

Pentru a indica faptul că o porţiune a unei metode poate semnala o excepţie, este suficient să adăugaţi cuvântul cheie throws după semnătura metodei (înainte de acolada deschisă) şi să specificaţi numele excepţiilor pe care le poate semnala metoda dumneavoastră: public boolean metodaMea(int x, int y) throws oExceptie { // ... } Dacă metoda dumneavoastră poate semnala mai multe tipuri de excepţii, puteţi să le specificaţi pe toate în clauza throws, separate prin virgule: public boolean oAltaMetodaAMea(int x, int y) throws oExceptie, oADouaExceptie, oATreiaExceptie { // ... }

La fel ca în cazul catch, puteţi folosi o superclasă a unui grup de excepţii pentru a indica faptul că metoda dumneavoastră poate semnala oricare dintre subclasele acelei excepţii:

public void oAltaMetoda() throws IOException { // .. .

} Specificarea clauzei throws în definiţia metodei dumneavoastră nu înseamnă decât că metoda poate semnala o excepţie dacă ceva merge prost, nu şi că va face acest lucru. Clauza throws oferă doar o informaţie suplimentară referitoare la potenţialele excepţii şi permite compilatorului Java să se asigure că metoda este corect folosită de utilizatori.

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

Toate datele din Java sunt scrise sau citite folosind fluxuri. Fluxurile, aşa cum le arată şi denumirea, transportă ceva dintr-un loc în altul.

Un flux (stream) reprezintă calea pe care o urmează datele într-un program. Un flux de intrare transportă datele de la sursă la program, iar un flux de ieşire transportă datele din program către o destinaţie. Există două tipuri de fluxuri: fluxuri de octeţi şi fluxuri de caractere. Octeţii pot păstra valori întregi din domeniul 0. . . 255. În acest format pot fi reprezentate o multitudine de date, cum ar fi date numerice, programe executabile, comunicaţii Internet sau cod Java (bytecode) - adică fişierele clasă care sunt executate pe o maşină virtuală Java. De fapt, orice tip de date poate fi reprezentat printr-o serie de octeţi. Fluxurile de caractere reprezintă un tip special de fluxuri de octeţi, care se folosesc numai pentru datele de tip text (tipăribile). Ele diferă de fluxurile de octeţi prin faptul că setul de caractere Java suportă codificarea Unicode, un standard prin care se pot reprezenta mai multe caractere decât dacă s-ar folosi octeţi. Toate datele de tip text, cum ar fi fişierele text, paginile Web sau alte formate de text, trebuie să folosească fluxuri de caractere. Folosirea unui flux

Indiferent dacă folosiţi fluxuri de octeţi sau de caractere, procedura este asemănătoare. În cazul şirurilor de intrare, prima etapă constă în crearea unui obiect asociat cu sursa de date. De exemplu, dacă sursa este un fişier de pe hard discul dumneavoastră, acestuia trebuie să i se asocieze un obiect de tip FileInputStream. Odată obţinut obiectul asociat fluxului, puteţi citi informaţii din acest flux folosind una dintre metodele obiectului. Clasa FileInputStream posedă o metodă read(), care returnează un octet citit din fişier. Atunci când aţi terminat de citit informaţia din flux, trebuie să apelaţi metoda close(), pentru a indica faptul că aţi terminat de folosit fluxul. În cazul fluxurilor de ieşire, veţi începe prin a crea un obiect asociat cu destinaţia datelor. Un astfel de obiect poate fi creat pornind de la clasa BufferedReader, care constituie o metodă eficientă de creare a fişierelor text. Metoda write() reprezintă cea mai simplă metodă de a transmite informaţii către destinaţia unui flux. De exemplu, metoda write() aparţinând dasei BufferedReader poate transmite caractere individuale unui flux de ieşire.

Ca şi în cazul fluxurilor de intrare, pentru un flux de ieşire trebuie apelată metoda close () atunci când nu mai există date de transmis. Filtrarea unui flux

Cea mai simplă metodă de a folosi un flux este de a îl crea şi de a-i apela metodele de transmitere sau de recepţie a datelor, în funcţie de rolul lui (flux de intrare sau flux de ieşire). Majoritatea claselor folosite în prezent permit obţinerea unor rezultate mai sofisticate prin asocierea fluxului cu un filtru, înainte de a citi sau scrie date. Un filtru este un tip de flux care schimbă modul în care se lucrează cu un flux existent. Procedura de folosire a unui filtru pentru un flux presupune următoarele: • Crearea unui flux asociat cu sursa de date sau cu destinaţia datelor. • Asocierea unui filtru cu acest flux. • Citirea şi scrierea datelor de la/în filtru, şi nu direct în flux. Metodele pe care le apelaţi în cazul filtrelor sunt aceleaşi cu cele pe care le apelaţi în cadrul fluxurilor: există metodele read () şi write (), asemănătoare celor ale unui flux nefiltrat. Puteţi chiar să asociaţi un filtru unui alt filtru, creând situaţii de genul: un flux de intrare este asociat unui fişier text, este trecut printr-un filtru de traducere româno-englez şi, în final, este trimis la destinaţie - o persoană care doreşte să îl citească. Fluxuri de octeţi

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

Fluxurile de octeţi cu care veţi lucra de obicei sunt fluxuri de fişiere folosite pentru transferul de date între program şi fişierele aflate pe hard discuri, pe CD-ROM sau pe alte dispozitive de stocare ce pot fi referite printr-o cale de director şi un nume. Puteţi transmite octeţi unui flux de ieşire şi puteţi citi octeţi dintr-un flux de intrare. Fluxuri de intrare din fişiere

Un flux de intrare din fişier poate fi creat cu ajutorul constructorului FileInputStream (String). Şirul transmis ca argument trebuie să fie numele fişierului. Puteţi include şi calea către fişier, în cazurile în care acesta se află în alt director decât cel care conţine clasa. Următoarea instrucţiune creează un flux de intrare pentru fişierul date.dat: FileInputStream fis = new FileInputStream("date.dat") ; După crearea fluxului de intrare din fişier, puteţi citi octeţi din acesta apelându-i metoda read (). Această metodă returnează o valoare întreagă ce conţine următorul octet din flux. Dacă metoda retumează -1, care nu este o valoare acceptată pentru un octet, înseamnă că s-a ajuns la sfârşitul fluxului de fişier. Pentru a citi mai mulţi octeţi din flux se foloseşte metoda read(byte[], int, int), care posedă următoarele argumente: • Un tablou de octeţi în care se vor memora datele • Elementul din cadrul tabloului unde se va stoca primul octet de date • Numărul de octeţi care se vor citi

Spre deosebire de alte metode read (), aceasta nu retumează date din flux, ci un întreg care reprezintă numărul de octeţi citiţi sau -1 dacă nu s-au citit octeţi şi s-a ajuns la sfârşitul fluxului. Următoarele instrucţiuni folosesc un ciclu while pentru a citi datele din obiectul df de tip FileInputStream: int octetNou = 0; while (octetNou != -1) { octetNou = df.read(); System.out.println(octetNou +" ”); } Această buclă citeşte întregul fişier referit de obiectul df, câte un octet o dată, şi afişează valoarea fiecărui octet, urmată de un spaţiu. De asemenea, când se ajunge la sfârşitul fişierului va fi afişată valoarea -1, lucru pe care îl puteţi evita folosind un test if. Fluxuri de ieşire în fişiere Un flux de ieşire în fişier poate fi creat folosind constructorul FileOutputStream (String). Utilizarea sa este identică cu cea a constructorului FileInputStream (String), deci puteţi specifica şi calea până la fişier, nu numai numele acestuia. Trebuie să aveţi grijă atunci când specificaţi fişierul în care scrieţi. Dacă folosiţi numele unui fişier deja existent, o dată cu începerea scrierii datelor, acesta va fi şters definitiv.

Puteţi crea un flux de ieşire în fişier care să adauge date (append) la sfârşitul unui fişier existent folosind constructorul FileOutputStream(String, boolean). Şirul specifică numele fişierului, iar argumentul boolean, dacă are valoarea true, va adăuga datele la sfârşitul fişierului, în loc să suprascrie datele existente. Pentru a scrie octeţi într-un fişier se foloseşte metoda write ( int) a fluxului de ieşire. După ce s-a scris ultimul octet, fluxul trebuie închis folosind metoda close (). Pentru a scrie mai mulţi octeţi se poate folosi metoda write(byte[], int, int), care funcţionează similar metodei read (byte[], int, int) descrisă anterior. Argumentele acestei metode sunt tabloul care conţine octeţii ce vor fi scrişi, poziţia de început din tablou şi numărul de octeţi ce trebuie scrişi. Filtrarea fluxurilor

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

Filtre de octeti Informaţia este furnizată mai rapid dacă poate fi transmisă în blocuri de date mai mari, chiar dacă aceste blocuri sunt recepţionate mai repede decât pot fi procesate. Un tampon (buffer) reprezintă o zonă în care se pot păstra date înainte de a fi nevoie să fie citite sau scrise de un program. Prin folosirea unui tampon puteţi avea acces la date fără să accesaţi din nou sursa originală de date. Fluxuri cu tampon

Un flux de intrare cu tampon umple un tampon cu date care nu au fost încă procesate; când programul are nevoie de aceste date, le caută în zona tampon înainte de a apela fluxul sursă original. Această tehnică este mai eficientă. Această descongestionare a fluxului nu face altceva decât să

uşureze eforturile de folosire a acestuia. Fluxurile de octeţi folosesc clasele BufferedInputStream şi BufferedOutputStream. Fluxurile de intrare cu tampon sunt create folosind unul din constructorii: • BufferedInputStream(InputStream) Creează un flux de intrare cu tampon pentru obiectul InputStream specificat. • BufferedInputStream(InputStream, int) Creează fluxul de intrare cu tampon InputStream specificat, având o dimensiune a zonei tampon egală cu valoarea argumentului întreg. Cea mai uşoară modalitate de a citi date dintr-un flux de intrare cu tampon este de a apela metoda read () a acestuia, fără argumente; aceasta retumează o valoare întreagă între 0 şi 255, care reprezintă următorul octet din flux. Dacă s-a ajuns la sfârşitul fluxului şi nu s-a găsit nici un octet, se retumează valoarea -1.

Puteţi, de asemenea, folosi metoda read(byte [] , int, int), care este disponibilă şi pentru alte fluxuri de intrare, caz în care datele sunt încărcate într-un tablou de octeţi. Un flux de ieşire cu tampon este creat prin folosirea următorilor constructori:

• BufferedOutputStream(OutputStream) Creează un flux de ieşire cu tampon pentru obiectul OutputStream specificat. • BufferedOutputStream(OutputStream, int) Creează fluxul de ieşire cu tampon OutputStream specificat, alocând o zonă tampon de dimensiunea specificată prin argumentul int. Pentru a scrie un singur octet în fluxul de ieşire poate fi folosită metoda write(int) a acestuia, iar pentru a scrie mai mulţi octeţi dintr-o dată se poate folosi metoda write (byte [ ], int, int). Argumentele acestei metode sunt tabloul de octeţi, poziţia iniţială a acestuia şi numărul de octeţi care trebuie transmis. Atunci când datele sunt direcţionate într-un flux cu tampon, conţinutul acestuia nu va fi transmis către destinaţie decât după ce zona tampon s-a umplut sau dacă se apelează explicit metoda flush() a fluxului. Fluxuri de date

Dacă trebuie să lucraţi cu date care nu sunt reprezentate drept octeţi sau caractere, puteţi folosi fluxuri de date de intrare şi de ieşire. Aceste fluxuri filtrează un flux de octeţi existent astfel încât următoarele tipuri de date să poată fi citite direct din flux: boolean, byte, double, float, int, long şi short. Un flux de date de intrare este creat folosind constructorul DataInputStream (InputStream). Argumentul trebuie să fie un flux de intrare existent, de exemplu, un flux de intrare cu tampon sau un flux de intrare din fişier. Reciproc, un flux de date de ieşire necesită utilizarea constructorului DataOutputStream (OutputStream), care foloseşte ca argument fluxul de ieşire asociat. Următoarea listă prezintă metodele de citire şi de scriere ce pot fi folosite pentru fluxurile de date de intrare, respectiv de ieşire: • readBoolean(), writeBoolean(boolean) • readByte(), writeByte (int) • readDouble(), writeDouble(double) : • readFloat(), writeFloat(float) • readInt(), writeInt(int) • readLong(), writeLong(long) • readShort(), writeShort(int) Fiecare dintre metodele de intrare returnează tipul de dată primitivă indicat de numele metodei. De exemplu, metoda readFloat () retumează o valoare float. Există şi metodele readUnsignedByte () şi readUnsignedShort (), care pot citi valori de tip octet sau întregi scurţi fără semn. Aceste tipuri de date nu sunt suportate în Java, deci vor fi retumate ca valori întregi.

Octeţii fără semn iau valori de la 0 la 255. Aceasta diferă de tipul de variabilă byte din Java, care ia valori între -128 şi 127. în acelaşi fel, o variabilă întreagă scurtă fără semn ia valori între 0 şi 65535, în loc de domeniul -32768 şi 32767, suportat de tipul short în Java.

Nu toate metodele de citire dintr-un flux de date de intrare retumează o valoare ce poate indica faptul că s-a ajuns la sfârşitul fluxului. Vă mai puteţi aştepta la apariţia excepţiei EOFException care este semnalată la atingerea sfârşitului unui flux. Ciclul în care se citesc date poate fi încadrat într-un bloc try, iar instrucţiunea catch asociată trebuie să trateze doar excepţiile EOFException. In cadrul blocului catch puteţi să apelaţi şi metoda close () şi să efectuaţi alte acţiuni de reiniţializare.

Fluxuri de caractere Aceste fluxuri se folosesc pentru lucrul cu orice text reprezentat în format ASCII sau Unicode . Clasele folosite pentru a citi şi scrie aceste fluxuri sunt subclase ale daselor Reader şi Writer. Este indicat să folosiţi aceste clase pentru lucrul cu text, şi nu fluxuri de octeţi. Tehnicile pentru lucrul cu fluxuri de caractere s-au îmbunătăţit considerabil în versiunile ulterioare Java 1.02, o dată cu introducerea claselor Reader şi Writer şi a subclaselor acestora; ele oferă suport pentru setul de caractere Unicode şi permit o manevrare mai uşoară a textelor. Un applet java compatibil cu versiunea 1.02 poate citi caracterele folosind clasele pentru fluxuri de octeţi descrise anterior. Citirea fişierelor text

Principala clasă folosită pentru citirea de fluxuri de caractere dintr-un fişier este FileReader. Această clasă moşteneşte clasa InputStreamReader, care citeşte un flux de octeţi şi îi converteşte în valorile întregi corespunzătoare caracterelor Unicode. Un flux de intrare de caractere este asociat unui fişier folosind constructorul FileReader (String). Şirul reprezintă numele fişierului şi poate conţine şi calea până la acesta. Următoarea instrucţiune creează un obiect FileReader denumit web şi îl asociază cu fişierul text denumit index. html: FileReader web = new FileReader("index.html”); După ce aţi obţinut un obiect de acest tip, puteţi să apelaţi următoarele metode de citire a caracterelor din fişier: • read () returnează următorul caracter din flux, ca valoare întreagă • read (char [ ], int, int) citeşte caractere într-un tablou şi primeşte ca argumente tabloul de caractere, poziţia de începere şi numărul de caractere de citit. A doua metodă funcţionează asemănător echivalentului său din clasele de fluxuri de intrare de octeţi. În loc să returneze caracterul următor din flux, aceasta returnează fie numărul de caractere citite, fie -1 dacă s-a ajuns la sfârşitul fluxului şi nu s-a citit nimic. Următoarea metodă încarcă un fişier text folosind obiectul FileReader şi afişează caracterele conţinute de acesta: FileReader text = new FileReader("readme.txt"); int octetCitit; do { octetCitit = text.read(); if (octetCitit != -1) System.out.print( (char)octetCitit) ; } while (octetCitit != -1) ; System.out.println(" ");

text.close() ; Deoarece metoda read () a unui flux de caractere returnează o valoare întreagă, trebuie să convertiţi prin cast această valoare înainte de a o afişa, de a o salva într-un tablou sau de a o folosi într-un şir. Fiecare caracter posedă un cod numeric care reprezintă poziţia sa în setul de caractere Unicode. Valoarea întreagă citită din flux reprezintă chiar acest cod numeric. Dacă doriţi să citiţi din fişier o întreagă linie de text, şi nu caracter cu caracter, puteţi folosi o combinaţie a daselor FileReader şi BufferedReader. Clasa BufferedReader citeşte un caracter din fluxul de intrare şi îl memorează într-o zonă tampon, pentru mai multă eficienţă. Pentru a crea o versiune care foloseşte tampon, trebuie să existe un obiect de tip Reader. Pentru crearea obiectului BufferedReader se pot folosi următorii constructori: • BufferedReader(Reader) Creează un flux de caractere cu zonă tampon, asociat obiectului Reader specificat (de exemplu FileReader). • BufferedReader (Reader, int) Creează un flux de caractere asodat obiectului Reader, cu o zonă tampon de dimensiunea specificată de argumentul întreg. Dintr-un flux de caractere cu tampon se poate citi folosind metodele read () şi read(char[ ] , int, int), asemănătoare celor descrise pentru FileReader. Puteţi citi o întreagă linie de text folosind metoda readLine (). Metoda readLine () returnează un obiect String care conţine următoarea linie de text din flux, fără a include şi caracterul (sau caracterele) care reprezintă sfârşitul de linie. Dacă se ajunge la sfârşitul fluxului, valoarea şirului returnat va fi null. Sfârşitul de linie este indicat astfel:

• Un caracter de linie nouă (newline - '\n') • Un caracter de retur de car (carriage return – ’\r’) • Un retur de car urmat de o linie nouă Scrierea în fişiere text

Clasa FileWriter este folosită pentru scrierea unui flux de caractere într-un fişier. Aceasta este o subclasă a OutputStreamWriter, care are rolul de a converti codurile caracterelor Unicode în octeţi. Există doi constructori FileWriter: FileWriter (String) şi FileWriter (String, boolean). Şirul din primul argument reprezintă numele fişierului către care va fi direcţionat fluxul de caractere şi poate conţine şi calea. Argumentul opţional de tip boolean trebuie să ia valoarea true dacă se doreşte ca datele să fie adăugate la sfârşitul unui fişier existent. Ca şi în cazul altor clase de scriere în fluxuri, trebuie să aveţi grijă să nu suprascrieţi accidental un fişier existent. Clasa FileWriter conţine trei metode ce pot fi folosite pentru a scrie date într-un flux: • write(int) Scrie un caracter. • write(char [], int, int) Scrie caracterele din tabloul specificat, începând de la poziţia dată şi având numărul de caractere dat. • write(String, int, int) Scrie caractere din şirul specificat, începând de la poziţia dată şi având numărul de caractere dat. Următorul exemplu scrie un flux de caractere într-un fişier folosind clasa FileWriter şi metoda write (int): FileWriter litere = new FileWriter("alfabet.txt"); for (int i = 65; i < 91; i++) litere.write((char)i) ; litere.close() ; Metoda close () este folosită pentru închiderea fluxului, după ce toate caracterele au fost trimise în fişierul destinaţie. lată conţinutul fişierului alfabet.txt produs de acest cod: ABCDEFGHIJKLMNOPQRSTUVXYZ

Clasa BufferedWriter poate fi folosită pentru scrierea într-un flux de caractere cu zonă tampon. Obiectele acestei clase sunt create folosind constructorul BufferedWriter (Writer) sau BufferedWriter (Writer, int). Argumentul Writer poate fi oricare dintre clasele de fluxuri de caractere de ieşire, cum ar fi FileWriter. Al doilea argument, opţional, este o valoare întreagă ce indică dimensiunea zonei tampon care va fi folosită. BufferedWriter posedă aceleaşi trei metode de ieşire ca şi FileWriter: write(int), write(char[], int, int) şi write(String, int, int). 0 altă metodă folositoare este newLine (), care trimite caracterul (sau caracterele) ce specifică sfârşitul liniei pe platforma folosită pentru rularea programului. Diferitele caractere de sfârşit de linie pot crea neplăceri la transferul unui fişier de pe un sistem de operare pe altul, cum ar fi cazul când un utilizator Windows 95 copiază un fişier pe un server Web care foloseşte sistemul de operare Linux. Folosind metoda newLine() în loc de un literal (cum ar fi '\n'), puteţi utiliza programul dumneavoastră pe diferite platforme. Metoda close () este apelată pentru a închide fluxul de caractere de ieşire şi pentru a asigura că toate datele memorate în zona tampon au fost trimise către destinaţia fluxului.

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

Fiecare dintre aceste metode va semnala o excepţie SecurityException dacă programul nu are permisiunile necesare pentru executarea operaţiilor respective, deci trebuie folosite blocuri try. . . catch sau clauze throws pentru a trata aceste excepţii.

Proiectarea unei interfete utilizator cu ajutorul Swing

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

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

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

Lucrul cu Swing Există componente Swing echivalente pentru toate componentele AWT pe care le-aţi învăţat până acum. În majoritatea cazurilor, constructorii componentelor Swing sunt similari

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

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

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

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

O dată creat câmpul de text pentru parole, puteţi folosi metoda setEchoChar(char) pentru a masca datele de intrare cu caracterul specificat.

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

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

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

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

Noile caracteristici ale Swing În afară de faptul că extinde modul de funcţionare a componentelor şi a containerelor faţă de Abstract Windowing Toolkit, Swing oferă şi alte caracteristici complet noi, cum ar fi: aspect (look and feel) configurabil, secvenţe de taste de accelerare, sfaturi dependente de context (ToolTips) şi casete de dialog standard.

Stabilirea aspectului Swing posedă un administrator de interfaţă care controlează aspectul componentelor - modul în care butoanele, etichetele şi celelalte elemente sunt reprezentate pe ecran. Administrarea aspectului este sarcina clasei UIManager, care face parte din pachetul javax.swing.*. Opţiunile pentru aspect diferă în funcţie de mediul de dezvoltare folosit. In Java 2 sunt disponibile următoarele opţiuni: • Un aspect caracteristic Windows 95 sau Windows NT • Un aspect caracteristic sistemului Motif X-Window • Metal, un nou aspect Swing, independent de platformă Clasa UIManager posedă metoda setLookAndFeel(LookAndFeel), care este folosită pentru alegerea aspectului unui program. Pentru a obţine un obiect LookAndFeel care poate fi transmis ca argument metodei setLookAndFeel() puteţi folosi una dintre metodele UIManager: • getCrossPlatformLookAndFeelClassName() Această metodă returnează un obiect LookAndFeel care reprezintă aspectul Metal, independent de platformă. • getSystemLookAndFeelClassName() Această metodă returnează un obiect LookAndFeel care reprezintă un obiect caracteristic sistemului. Daca nu poate stabili aspectul interfeţei, metoda setLookAndFeel() semnalează o excepţie UnsupportedLookAndFeel. Următoarele instrucţiuni pot fi folosite pentru a stabili un aspect Metal în orice program: try { UIManager.setLookAndFeel(

UIManager.getCrossPlatformLookAndFeelClassName ()) ; } catch (Exception e) {

System.err.println(”Nu pot stabili aspectul:” + e) ; } Pentru a stabili aspectul sistemului, în cadrul metodei setLookAndFeel se foloseste metoda getSystemLookAndFeelClassName(). Aceasta are rezultate diferite pe sisteme de operare diferite. Folosind metoda getSystemLookAndFeelClassName(), un utilizator de Windows 95 va obţine un aspect Windows 95, iar un utilizator UNIX va obţine un aspect Motif.

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

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

Descrieri şi nume de componente

O altă metodă de a face mai accesibilă o interfaţă este de a oferi o descriere text pentru componentele Swing. Această tehnică presupune două etape:

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

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

Casete de dialog de confirmare Cea mai simplă metodă de creare a unei casete de dialog Yes/No/Cancel (Da/Nu/Anulare) este de a folosi metoda showConfirmDialog (Component, Object). Argumentul Component specifică containerul care va fi considerat părintele casetei de dialog; această informaţie este folosită pentru a determina unde se va afişa pe ecran fereastra de dialog. Dacă se foloseşte valoarea null pentru acest argument sau containerul nu este un obiect Frame, caseta de dialog va fi afişată în centrul ecranului.

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

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

Casete de dialog pentru mesaje Caseta de dialog pentru mesaje este o fereastră simplă în care se afişează o informaţie.

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

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

Tratarea evenimentelor utilizator cu ajutorul Swing Pentru a face dintr-o interfaţă Java funcţională un program Java funcţional, va trebui ca interfaţa să răspundă la evenimentele produse de utilizator.

Swing tratează evenimentele în mod diferit, folosind un set de clase denumite interceptoare de evenimente (event listeners).

Evenimentul principal În sistemul de tratare a evenimentelor, evenimentele erau prelucrate prin intermediul unui set de metode disponibile tuturor componentelor. Metode cum ar fi mouseDown(), keyDown() sau action() puteau fi suprascrise de orice program AWT care dorea să trateze aceste evenimente. Aceste sistem de tratare a evenimentelor este caracteristic doar Java 1.02, deoarece în următoarele versiuni ale limbajului a fost introdusă o versiune mult îmbunătăţită a acestuia.

Acest nou sistem se foloseşte în aplicaţiile Swing.

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

Configurarea componentelor

Prin definirea unei clase ca interceptor de evenimente aţi stabilit faptul că aceasta poate „asculta" (intercepta) un anumit tip de eveniment. Totuşi nu se va întâmpla nimic dacă nu continuaţi cu un al doilea pas: trebuie să asociaţi componentei un interceptor de acelaşi tip; la folosirea componentei, acesta va genera evenimentele respective.

După crearea unei componente, pentru a o asocia cu un anumit tip de interceptor puteţi apela una din următoarele metode: • addActionListener() Pentru componente JButton, JCheckBox, JComboBox, JTextField şi JRadioButton • addAdjustmentListener() Pentru componente JScrollBar • addFocusListener() Pentru toate componentele Swing • addItemListener() Pentru componente JButton, JCheckBox, JComboBox şi JRadioButton • addKeyListener() Pentru toate componentele Swing • addMouseListener() Pentru toate componentele Swing • addMouseMotionListener() Pentru toate componentele Swing • addWindowListener() Pentru toate componentele JWindow şi JFrame O greşeală des întâlnită în programele Java o constituie modificarea unei componente după introducerea acesteia într-un container. Metodele de interceptare şi celelalte configurări trebuie stabilite pentru o componentă înainte de a o adăuga într-un container; altfel toate acestea vor fi ignorate la execuţia programului. Următorul exemplu creează un obiect JButton şi îi asociază un interceptor de evenimente de acţiune:

JButton terminare = new JButton("Terminare"); terminare.addActionListener(this) ; Toate metodele add. . .() folosesc un singur argument: obiectul care interceptează evenimentele de tipul respectiv. Folosirea cuvântului cheie this indică faptul că obiectul interceptor este chiar clasa curentă. Puteţi specifica şi alte obiecte, atât timp cât clasa respectivă implementează interfaţa de interceptare corespunzătoare.

Metode de tratare a evenimentelor Atunci când asociaţi o interfaţă unei clase, clasa trebuie să implementeze toate metodele conţinute de interfaţă. În cazul interceptoarelor de evenimente, fiecare metodă este apelată automat de sistemul de ferestre atunci când are loc evenimentul corespunzător. Interfaţa ActionListener posedă o singură metodă: actionPerformed(). Toate clasele care implementează ActionListener trebuie să conţină o metodă cu următoarea structură: public void actionPerformed(ActionEvent evt) { // aici se trateaza evenimentul } Dacă în cadrul interfeţei grafice utilizator există o singură componentă care are asociat un interceptor de evenimente de acţiune, atunci metoda actionPerformed() poate fi folosită pentru a răspunde la un eveniment generat de componentă. Dacă mai multe componente au asociate interceptoare de evenimente de acţiune, trebuie să folosiţi metoda pentru a afla mai întâi care dintre componente a fost folosită, după care să acţionaţi corespunzător.

În metoda actionPerformed() se transmite ca argument un obiect ActionEvent. Acest obiect poate fi folosit pentru a afla detalii despre componenta care a generat evenimentul. ActionEvent şi celelalte obiecte eveniment fac parte din pachetul java.awt.event şi sunt subclase ale clasei EventObject. Fiecare metodă de tratare a evenimentelor primeşte ca argument un obiect eveniment de un anumit tip. Pentru a determina componenta care a transmis evenimentul se poate folosi metoda getSource() a obiectului, ca în exemplul următor: public void actionPerformed(ActionEvent evt) { Object sursa = evt.getSource(); } Obiectul returnat de metoda getSource() poate fi comparat cu o componentă folosind operatorul = =. În exemplul actionPerformed() anterior se pot folosi următoarele instrucţiuni: if (sursa == butonTerminare) terminaProgram() ; else if (sursa == sortareInregistrari)

sorteazaInregistrari() ; Acest exemplu apelează metoda terminareProgram() în cazul când evenimentul a fost generat de obiectul butonTerminare sau metoda sortareInregistrari() dacă evenimentul a fost generat de obiectul sortareInregistrari. Multe metode de tratare a evenimentelor apelează metode diferite pentru fiecare tip de eveniment sau de componentă. Aceasta face ca metoda de tratare a evenimentelor să fie uşor de citit. În plus, dacă o clasă conţine mai multe metode de tratare a evenimentelor, fiecare poate apela aceleaşi metode pentru realizarea sarcinii dorite. O altă tehnică utilă în cadrul unei metode de tratare a evenimentelor o constituie folosirea operatorului instanceof, care permite testarea fiecărui tip de componentă care putea genera evenimentul. Următorul exemplu poate fi folosit într-un program care conţine un buton şi un câmp de text, fiecare dintre acestea generând evenimente de acţiune: void actionPerformed(ActionEvent evt) { Object sursa = evt.getSource() ; if (sursa instanceof JTextField) calculeazaScor() ; else if (sursa instanceof JButton) terminaProgram() ; }

Evenimente de actiune Evenimentele de acţiune apar atunci când utilizatorul realizează o acţiune asupra unui obiect de tip JButton, JCheckBox, JTextField sau JRadioButton. Pentru a trata aceste evenimente, o clasă trebuie să implementeze interfaţa ActionListener. în plus, trebuie apelată metoda addActionListener() pentru fiecare componentă care urmează să genereze evenimente de acţiune, cu excepţia cazurilor în care doriţi să ignoraţi evenimentele de acţiune ale unei componente. Interfaţa ActionListener conţine o singură metodă: actionPerformed(ActionEvent) care are următoarea formă: public void actionPerformed(ActionEvent evt) { // ... } În afară de metoda getSource(), puteţi folosi metoda getActionCommand() cu argumentul ActionEvent, pentru a afla mai multe informaţii despre sursa evenimentului.

În mod prestabilit, comanda acţiunii reprezintă textul asociat componentei, cum ar fi eticheta de pe un buton JButton. Puteţi însă să definiţi o comandă de acţiune diferită pentru fiecare componentă, folosind metoda setActionCommand(String). Argumentul şir trebuie să conţină textul dorit pentru comanda acţiunii. De exemplu, următoarele instrucţiuni creează obiectele JButton şi JTextField şi le asociază amândurora comanda de acţiune "Sortare fişiere": JButton sortare = new JButton("Sortare”); JTextField nume = new JTextField() ; sortare.setActionCommand(”Sortare fisiere”); nume.setActionCommand(”Sortare fisiere”);

Evenimente de modificare Evenimentele de modificare (ajustare) apar atund când o componentă JScrollBar este deplasată folosind săgeţile, caseta de derulare sau printr-un clic undeva pe bară. Pentru a trata aceste evenimente, o clasă trebuie să implementeze interfaţa AdjustmentListener. Interfaţa AdjustmentListener conţine o singură metodă, care are următoarea formă: adjustmentValueChanged(AdjustmentEvent evt) { // ... } Pentru a obţine valoarea curentă a obiectului JScrollBar puteţi folosi metoda getValue() având ca argument obiectul AdjustmentEvent. Această metodă returnează o valoare întreagă care reprezintă valoarea barei de derulare.. Puteţi determina şi modul cum a fost deplasată bara de derulare dacă folosiţi metoda getAdjustmentType() a obiectului AdjustmentEvent. Aceasta returnează una din următoarele cinci valori, care sunt variabile de clasă ale clasei Adjustment: • UNIT_INCREMENT O creştere cu 1 a valorii, produsă de executarea unui clic pe săgeata de deplasare a barei sau pe o tastă cu săgeată • UNIT_DECREMENT O descreştere a valorii cu 1 • BLOCK_INCREMENT O creştere mai mare a valorii, cauzată de un clic pe bara de derulare, în zona dintre casetă şi săgeată • BLOCK_DECREMENT O descreştere mai mare a valorii • TRACK O modificare produsă de deplasarea casetei de derulare

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

Pentru a trata un eveniment de selecţionare, o clasă trebuie să implementeze interfaţa FocusListener. Această interfaţă conţine două metode: focusGained(FocusEvent) şi focusLost(FocusEvent), care au următoarea formă: public void focusGained(FocusEvent evt) { // ... } public void focusLost(FocusEvent evt) { // ... } Pentru a determina care obiect a obţinut sau a pierdut starea de selecţionare se poate folosi metoda getSource() pentru obiectul FocusEvent, primit ca argument de metodele focusGained() sau focusLost().

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

Evenimente de tastatură Evenimentele de tastatură (key events) sunt generate atunci când se apasă o tastă. Orice componentă poate genera aceste evenimente, iar pentru a fi suportate într-o clasă, aceasta trebuie să implementeze interfaţa KeyListener. Această interfaţă conţine trei metode: keyPressed(KeyEvent), keyReleased(KeyEvent) şi keyTyped(KeyEvent), care au următoarea formă: public void keyPressed(KeyEvent evt) { //... } public void keyReleased(KeyEvent evt) { // ... } public void keyTyped(KeyEvent evt) { // ... }

Metoda getkeyChar() a obiectului KeyEvent returnează caracterul tastei asociate evenimentului. Dacă nu există caracterul Unicode al tastei, metoda getKeyChar() returnează o valoare egală cu variabila de clasă KeyEvent.CHAR_UNDEFINED.

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

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

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

Evenimente de fereastră Evenimentele de fereastră apar atunci când utilizatorul deschide sau închide un obiect fereastră, cum ar fi JFrame sau JWindow. Orice componenta poate genera aceste evenimente, iar pentru a le trata, o clasă trebuie să implementeze interfaţa windowListener. Interfaţa WindowListener conţine şapte metode: windowActivated(WindowEvent) windowClosed(WindowEvent) windowClosing(WindowEvent) windowDeactivated(WindowEvent) windowDeiconified(WindowEvent) windowIconified(WindowEvent) windowOpened(WindowEvent) Toate acestea au următoarea formă, prezentată aici pentru metoda windowOpened(WindowEvent): public void windowOpened(WindowEvent evt) { // . . . } Metodele windowClosing() şi windowClosed() sunt asemănătoare, însă una este apelată o dată cu închiderea ferestrei, iar cealaltă - după închiderea acesteia. Dacă doriţi, este posibil ca în cadrul metodei windowClosing() să decideţi anularea închiderii ferestrei.

Comunicarea prin Internet Biblioteca de clase Java conţine pachetul java.net, care face posibilă comunicarea cu programele Java prin intermediul reţelelor. Pachetul oferă funcţii abstracte inter-platformă pentru operaţiile de reţea simple, cum ar fi conectarea şi transferul de fişiere folosind protocoale Web sau crearea de socluri (sockets) specifice UNIX. Folosite împreună cu fluxurile de intrare sau de ieşire, citirea şi scrierea fişierelor prin reţea devin la fel de simple ca folosirea fişierelor de pe discul local.

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

Deschiderea de conexiuni Web În loc să cereţi browserului să încarce conţinutul fişierului, uneori este nevoie să obţineţi conţinutul acestuia pentru a fi prelucrat de un applet. Dacă fişierul pe care doriţi să îl obţineţi este stocat în Web şi poate fi accesat folosind adrese URL (http, FTP şi aşa mai departe), programul dumneavoastră Java poate folosi clasa URL pentru a-l obţine. Din motive de securitate, applet-urile se pot conecta în mod prestabilit doar la maşina de unde au fost încărcate iniţial. Aceasta înseamnă că dacă applet-urile sunt stocate pe un sistem denumit www.prefect.com, singura maşină către care applet-ul poate deschide o conexiune este sistemul respectiv - şi, mai precis, cu numele respectiv, deci aveţi grijă la folosirea denumiri-lor echivalente (alias). Dacă fişierul pe care applet-ul doreşte să îl apeleze se află pe acelaşi sistem, atunci folosirea de conexiuni URL reprezintă cea mai simplă metodă de a-l obţine.

Restricţiile de securitate schimbă modul de scriere şi testare a applet-urilor care încarcă fişiere pe baza adreselor lor URL. Deoarece până acum nu aţi folosit conexiuni de reţea, aţi putut testa appleturile pe sistemul local prin simpla încărcare a fişierelor HTML într-un browser sau în utilitarul appletviewer. Acest lucru nu poate fi făcut în cazul applet-urilor care deschid conexiuni de reţea. Pentru ca aceste applet-uri să funcţioneze corect trebuie să procedaţi într-unul din următoarele moduri: • Rulaţi browserul pe aceeaşi maşină pe care rulează şi serverul Web. Dacă nu aveţi acces la un server Web, puteţi instala şi rula unul pe propria dumneavoastră maşină. • Copiaţi clasa şi fişierul HTML pe serverul Web ori de câte ori doriţi să le testaţi, apoi rulaţi applet-ul din pagina Web, nu de pe sistemul local. În acest mod vă veţi da seama dacă applet-ul şi conexiunea deschisă de acesta se află pe aceeaşi maşină. Dacă încercaţi să încărcaţi un applet sau un fişier de pe alte servere, veţi obţine o excepţie de securitate, împreună cu o mulţime de alte mesaje de eroare afişate pe ecran sau pe consola Java. Din aceste motive, acunci când vă conectaţi la Internet pentru a-i folosi resursele, este bine să folosiţi aplicaţii, care nu suferă de aceste restricţii.

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

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

public class CitireFisier extends Frame implements Runnable {

Thread executabil; URL page; TextArea box = new TextArea("Transfer text ..."); public CitireFisier() { super("Transfer fisier"); add(box); try { page = new URL("http://www.masini.ro//masini.html"); } catch (MalformedURLException e) { System.out.println("URL gresit: " + page); } } public static void main(String[] arguments) { CitireFisier cadru = new CitireFisier(); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; cadru.addWindowListener(l); cadru.pack(); cadru.setVisible(true); if (cadru.executabil == null) { cadru.executabil = new Thread(cadru); cadru.executabil.start(); } } public void run() { URLConnection conn = null; InputStreamReader in; BufferedReader data; String line; StringBuffer buf = new StringBuffer(); try { conn = this.page.openConnection(); conn.connect(); box.setText("Conexiune deschisa ..."); in = new InputStreamReader(conn.getInputStream()); data = new BufferedReader(in); box.setText("Citeste date ..."); while ((line = data.readLine()) != null) { buf.append(line + "\n"); } box.setText(buf.toString()); }

catch (IOException e) { System.out.println("Eroare ! :" + e.getMessage()); } } }

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

Socket conexiune = new Socket(numeGazda, numarPort); Observaţie

Dacă folosiţi socluri într-un applet, nu uitaţi că vă aflaţi sub incidenţa restricţiilor de securitate proprii unui applet, care vă interzic să vă conectaţi la un alt sistem decât la cel de unde provine applet-ul. O dată soclul deschis, puteţi folosi fluxuri de intrare sau de ieşire pentru a citi din acesta: BufferedInputStream bis = new BufferedInputStream(conexiune.getInputStream()); DataInputStream intrare = new DataInputStream(bis); BufferedOutputStream bos = new BufferedOutputStream(conexiune.getOutputStream() ) ; DataOutputStream iesire = new DataOutputStream(bos); După ce aţi terminat de lucrat cu un soclu, nu uitaţi să îl închideţi. (Acest lucru va închide, de asemenea, fluxurile de intrare sau de ieşire pe care le-aţi atribuit soclului.)

conexiune.close() ; Soclurile server funcţionează asemănător, cu excepţia metodei accept(). Un soclu server ascultă pe un port TCP pentru a intercepta o cerere de conexiune din partea unui client; atund când clientul se conectează la un port, metoda accept() acceptă conexiunea de la acesta. Prin folosirea soclurilor client şi server puteţi crea aplicaţii care comunică prin reţea.

Pentru a crea un soclu server ataşat unui port, se instanţiază clasa ServerSocket, specificând numărul portului: ServerSocket sConexiune = new ServerSocket(8888); Folosiţi metoda accept() pentru a „asculta” portul respectiv şi pentru a accepta eventualele conexiuni ale clienţilor: sConexiune.accept(); O dată conexiunea efectuată, puteţi folosi fluxuri de intrare şi de ieşire pentru a citi sau scrie de la, către un client. Pentru a prezenta comunicaţia în reţea prin Java, este prezentat aici un program “Banal”, care foloseşte clasele Socket pentru implementarea unei aplicaţii client – server de reţea. Exemplul funcţionează în felul următor: programul server aşteaptă conectarea unui client. La conectarea unui client, serverul pune o întrebare şi aşteaptă un răspuns. La celălalt capăt clientul recepţionează întrebarea, o afişează către utilizator şi aşteaptă un răspuns de la acesta. Utilizatorul introduce răspunsul, care este trimis înapoi către server. Serverul verifică dacă răspunsul este corect

şi anunţă rezultatul. După aceasta, serverul întreabă clientul dacă doreşte să i se pună altă întrebare; dacă răspunsul este afirmativ, procesul se repetă. Aplicaţia are nevoie de 2 părţi: pe partea de server este necesar un program care să monitorizeze un anumit port al calculatorului gazdă, acolo unde urmează să se conecteze clientul. La detectarea unui client, serverul alege o întrebare şi o trimite clientului pe portul respectiv. După aceasta, serverul intră într-o stare de aşteptare până ce recepţionează răspunsul de la client. După primirea răspunsului, serverul verifică corectitudinea acestuia şi anunţă clientul rezultatul obţinut. Apoi clientul este întrebat dacă doreşte o nouă întrebare, ş.a.m.d. Pe scurt serverul realizează următoarele acţiuni: 1. Aşteaptă conectarea unui client 2. Acceptă conectarea unui client 3. Transmite clientului o întrebare aleatoare 4. Aşteaptă răspunsul de la client 5. Verifică răspunsul şi anunţă clientul rezultatul 6. Întreabă clientul dacă doreşte o altă întrebare 7. Aşteaptă un răspuns (y/n) de la client 8. Dacă este cazul revine la pasul 3 Pe partea de client este o aplicaţie care rulează în linia de comandă. Clientul se conectează la server şi aşteaptă o întrebare. Atunci când primeşte o întrebare de la server, clientul o afişează utilizatorului şi aşteaptă introducerea unui răspuns. Acest răspuns este trimis înapoi către server, după care se aşteaptă răspunsul acestuia. Clientul afişează răspunsul de la server şi permite utilizatorului să opteze pentru continuarea programului (dacă mai doreşte o nouă întrebare). Clientul trimite apoi răspunsul utilizatorului şi îşi termină execuţia, în cazul acesta nu doreşte continuarea conversaţiei. Principalele activităţi ale clientului sunt următoarele: 1. Se conectează la server 2. Aşteaptă trimiterea unei întrebări 3. Afişează întrebarea şi primeşte răspunsul introdus de utilizator 4. Trimite răspunsul către server 5. Aşteaptă un răspuns de la server 6. Afişează răspunsul şi îl întreabă pe utilizator dacă doreşt o nouă întrebare 7. Trimite serverului răspunsul utilizatorului 8. Dacă este cazul, revine la pasul 2. Textul sursă al aplicaţiei este prezentat în continuare. import java.io.*; import java.net.*; import java.util.Random; public class ServerBanal extends Thread { private static final int NRPORT = 1234; private static final int ASTEAPTACLIENT = 0; private static final int ASTEAPTARASPUNS = 1; private static final int ASTEAPTACONFIRMARE = 2; private String[] intrebari; private String[] raspunsuri; private ServerSocket serverSocket; private int nrIntrebari; private int crt = 0; private int stare = ASTEAPTACLIENT; private Random aleator = new Random();

public ServerBanal() { super("ServerBanal"); try { serverSocket = new ServerSocket(NRPORT); System.out.println("ServerBanal este in executie ..."); } catch (IOException e) { System.err.println("Exception: nu se poate crea soclul"); System.exit(1); } } public static void main(String[] arguments) { ServerBanal server = new ServerBanal(); server.start(); } public void run() { Socket clientSocket = null; // Initializare vectori cu intrebari si raspunsuri if (!initIntRasp()) { System.err.println("Error: nu se pot initializa intrebarile si raspunsurile"); return; } // Asteapta clientul si pune intrebari banale while (true) { // Asteapta un client if (serverSocket == null) return; try { clientSocket = serverSocket.accept(); } catch (IOException e) { System.err.println("Exceptie: nu se poate face conectarea la soclul client"); System.exit(1); } // Se poarta dialogul intrebare/raspuns try { InputStreamReader isr = new InputStreamReader(clientSocket.getInputStream()); BufferedReader is = new BufferedReader(isr); PrintWriter os = new PrintWriter(new BufferedOutputStream(clientSocket.getOutputStream()), false); String linieIesire; //Transmite cererea serverului

linieIesire = citireIntrari(null); os.println(linieIesire); os.flush(); // Citeste si afiseaza intrarile utilizator while (true) { String linieIntrare = is.readLine(); if (linieIntrare.length() > 0) { linieIesire = citireIntrari(linieIntrare); os.println(linieIesire); os.flush(); if (linieIesire.equals("La revedere!.")) break; } } // Inchidere fluxuri os.close(); is.close(); clientSocket.close(); } catch (Exception e) { System.err.println("Exception: " + e); e.printStackTrace(); } } } private boolean initIntRasp() { try { File fisierIntrare = new File("intrebari.txt"); FileInputStream inStream = new FileInputStream(fisierIntrare); byte[] data = new byte[(int)fisierIntrare.length()]; // Se citesc intrebarile si raspunsurile if (inStream.read(data) <= 0) { System.err.println("Error: nu se pot citi intrebarile si rasp"); return false; } // Numarare intrebari si raspunsuri for (int i = 0; i < data.length; i++) if (data[i] == (byte)'\n') nrIntrebari++; nrIntrebari /= 2; intrebari = new String[nrIntrebari]; raspunsuri = new String[nrIntrebari]; // Se introduc intrebarile si raspunsurile in tablouri separate int start = 0, nr = 0; boolean esteIntr = true; for (int i = 0; i < data.length; i++) if (data[i] == (byte)'\n') {

if (esteIntr) { intrebari[nr] = new String(data, start, i - start - 1); esteIntr = false; } else { raspunsuri[nr] = new String(data, start, i - start - 1); esteIntr = true; nr++; } start = i + 1; } } catch (FileNotFoundException e) { System.err.println("Exceptie: nu se gaseste fisierul cu intrebari si raspunsuri"); return false; } catch (IOException e) { System.err.println("Exceptie: eroare la citire intrebari"); return false; } return true; } String citireIntrari(String inStr) { String outStr = null; switch (stare) { case ASTEAPTACLIENT: // Pune o intrebare outStr = intrebari[crt]; stare = ASTEAPTARASPUNS; break; case ASTEAPTARASPUNS: // Verifica raspunsul if (inStr.equalsIgnoreCase(raspunsuri[crt])) outStr = "Raspuns corect! Doriti o noua intrebare? (y/n)"; else outStr = "Raspuns gresit! Raspunsul corect este " + raspunsuri[crt] + ". Doriti alta intrebare? (y/n)"; stare = ASTEAPTACONFIRMARE; break; case ASTEAPTACONFIRMARE: // Se asteapta confirmarea continuarii dialogului if (inStr.equalsIgnoreCase("Y")) { crt = Math.abs(aleator.nextInt()) % intrebari.length; outStr = intrebari[crt]; stare = ASTEAPTARASPUNS;

} else { outStr = "La revedere!."; stare = ASTEAPTACLIENT; } break; } return outStr; } }

import java.io.*; import java.net.*; public class Banal { private static final int NRPORT = 1234; public static void main(String[] arguments) { Socket socket = null; InputStreamReader isr = null; BufferedReader in = null; PrintWriter out = null; String adresa; // Cauta adresa in argumentele liniei de comanda if (arguments.length != 1) { System.out.println("Mod de utilizare: java Banal "); return; } else adresa = arguments[0]; // Initializare soclu si fluxuri try { socket = new Socket(adresa, NRPORT); isr = new InputStreamReader(socket.getInputStream()); in = new BufferedReader(isr); out = new PrintWriter(socket.getOutputStream(),true); } catch (IOException e) { System.err.println("Err: nu se poate crea soclul fluxului" + e.getMessage()); System.exit(1); } // Citeste datele de intrare si raspunsurile serverului si le prelucreaza try { StringBuffer str = new StringBuffer(128); String inStr; int c; while ((inStr = in.readLine()) != null) { System.out.println("Server: " + inStr); if (inStr.equals("La revedere")) break;

while ((c = System.in.read()) != '\n') str.append((char)c); System.out.println("Client: " + str); out.println(str.toString()); out.flush(); str.setLength(0); } // Inchide fluxuri out.close(); in.close(); socket.close(); } catch (IOException e) { System.err.println("I/O error: "+ e.toString()); } }}

Artificii cu applet-uri Metoda showStatus() Metoda showStatus() a clasei Applet vă permite să afişaţi un şir în bara de stare a browserului care rulează applet-ul. Puteţi folosi această metodă pentru afişarea mesajelor de eroare, a legăturilor, pentru indicaţii sau pentru alte mesaje de stare.

Această metodă poate fi apelată printr-o instrucţiune de genul: getAppletContext().showStatus(”Pentru a începe, executati clic pe applet”); Metoda getAppletContext() permite applet-ului dumneavoastră să acceseze caracteristicile browserului care l-a încărcat. Metoda showStatus() foloseşte acest mecanism pentru a afişa mesaje de stare.

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

Crearea de legături în cadrul applet-urilor Deoarece applet-urile rulează în cadrul browserelor Web, ar fi bine ca acestea să poată încărca noi pagini Web. Java oferă un mecanism prin care se poate indica browserului să încarce o nouă pagină. De exemplu, puteţi folosi acest mecanism pentru a crea imagini animate care să încarce o nouă pagină la execuţia unui clic pe suprafaţa lor. Veţi crea o instanţă a clasei URL pentru legătura către pagina dorită. Pentru a crea un nou obiect URL puteţi folosi unul dintre cei patru constructori: • URL (String) creează un obiect URL cu o adresă Web completă, cum ar fi http://www.prefect.com/java21 sau ftp://ftp.netscape.com. • URL (URL, String) creează un obiect URL având o adresă de bază provenită din obiectul URL specificat şi o cale relativă provenită din şirul transmis ca argument secund. Puteţi folosi getDocumentBase() pentru adresa URL a paginii care conţine applet-ul sau getCodebase() pentru adresa URL a clasei applet-ului. Calea relativă va fi concatenată cu adresa de bază. • URL (String, String, int, String) creează un obiect URL pornind de la protocol (cum ar fi http sau ftp), numele maşinii (www.prefect.com, ftp.netcom.com etc.), numărul de port (80 pentru http)şi un nume de fişier sau de director. • URL (String, String, String) este identic cu constructorul anterior, minus argumentul întreg cu numărul portului. Atunci când folosiţi constructorul URL (String), trebuie să trataţi excepţiile MalformedURLException. Pentru aceasta puteţi folosi un bloc try. . . catch, ca în exemplul următor: try { unURL = new URL ("http://www.mcp.com" );

} catch (MalformedURLException e) { System.out.println("Adresa URL incorecta: " + unURL) ; } O dată obţinut obiectul URL, tot ceea ce mai trebuie să faceţi este să îl transmiteţi browserului; drept urmare, acesta va încarca adresa respectivă: getAppletContext().showDocument(unURL); Browserul ce conţine un applet Java cu codul prezentat mai sus va încărca şi va afişa documentul de la adresa URL respectivă. Aplicaţia următoare prezintă două clase: ButonLink şi o clasă ajutătoare Marcaj. Appletul ButonLink afişează trei butoane care indică trei locaţii Web diferite; dacă se execută clic pe aceste butoane, va fi încărcat documentul de la locaţia respectivă. Textul sursă al aplicaţiei este prezentat în continuare: import java.awt.*; import java.net.*; public class ButonLink extends java.applet.Applet { Marcaj ListaMarcaje[] = new Marcaj[3]; public void init() { ListaMarcaje[0] = new Marcaj("Catalog auto", "http://www.masini.ro"); ListaMarcaje[1] = new Marcaj("Macmillan Computer Publishing", "http://www.mcp.com"); ListaMarcaje[2]= new Marcaj("JavaSoft", "http://java.sun.com"); GridLayout gl = new GridLayout(ListaMarcaje.length, 1, 10, 10); setLayout(gl); for (int i = 0; i < ListaMarcaje.length; i++) { add(new Button(ListaMarcaje[i].name)); } } public boolean action(Event evt, Object arg) { if (evt.target instanceof Button) { saltLa( (String)arg ); return true; } else return false; } void saltLa(String name) { URL theURL = null; for (int i = 0; i < ListaMarcaje.length; i++) { if (name.equals(ListaMarcaje[i].name)) theURL = ListaMarcaje[i].url; } if (theURL != null) getAppletContext().showDocument(theURL); }

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

Comunicarea între applet-uri Uneori doriţi să folosiţi o pagină HTML care să conţină mai multe applet-uri diferite. Pentru aceasta, tot ceea ce trebuie să faceţi este să folosiţi de mai multe ori eticheta <APPLET>. Browserul va crea diferite instanţe pentru fiecare applet care apare în pagină. Dar dacă doriţi să comunicaţi între aceste applet-uri? Dacă doriţi ca o modificare făcută într-un applet să le afecteze cumva şi pe celelalte? Cea mai bună modalitate de accesare a diferitelor applet-uri din pagină este de a folosi contextele.

Un context reprezintă un mijloc prin care se poate descrie mediul din care face parte ceva. În acest caz, contextul applet-ului este definit în clasa AppletContext şi este folosit pentru comunicaţia între applet-uri. Pentru a obţine o instanţă a acestei clase pentru applet-ul dumneavoastră, veţi folosi metoda getAppletContext(), şi nu un anumit constructor. De exemplu, apelarea metodei trimiteMesaj() pentru toate applet-urile dintr-o pagină, inclusiv cel curent, foloseşte metoda getApplets() şi un ciclu for de genul: for (Enumeration e = getAppletContext().getApplets(); e.hasMoreElements();) { Applet curent = (SubclasaMeaApplet) (e.nextElement()); curent.trimiteMesaj (); } Metoda getApplets() returnează un obiect de tip Enumeration care conţine o listă a appleturilor 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>:

Acest applet trimite informatii: <APPLET CODE=”AppletulMeu.class" WIDTH=100 HEIGHT=150 NAME=”Expeditor”>

Acest applet primeste informatii de la expeditor: <APPLET CODE="AppletulMeu.class" WIDTH=100 HEIGHT=150 NAME=”Destinatar”>

Pentru a obţine o referinţă la un alt applet din aceeaşi pagină se foloseşte metoda getApplet() pentru contextul applet-ului cu acel nume. Aceasta va avea ca rezultat obţinerea unei referinţe la applet-ul cu numele respectiv. După aceasta, vă puteţi referi la acest applet ca la oricare alt obiect: apelaţi metode, modificaţi variabilele de instanţă şi aşa mai departe. Iată codul care realizează acest lucru: // accesati applet-ul destinatar Applet destinatar = (SubclasaMeaApplet)getAppletContext(). getApplet("Destinatar”); // comandati-i sa se actualizeze destinatar.actualizare(text, valoare); În acest exemplu s-a folosit metoda getApplet() pentru a se obţine o referinţă către applet-ul cu numele "Destinatar". Observaţi că obiectul returnat de getApplet() este o instanţă a clasei generice Applet; veţi dori, probabil, să îl convertiţi prin cast către o instanţă a subclasei dumneavoastră. O dată obţinută referinţa către applet, puteţi apela apoi metodele sale ca şi când ar fi orice alt obiect din mediul dumneavoastră de execuţie. Aici, de exemplu, dacă ambele applet-uri conţin o metodă denumită actualizare(), puteţi comanda applet-ului destinatar să se actualizeze singur folosind informaţiile din applet-ul curent. Denumirea applet-urilor şi referirea lor prin metodele prezentate în această secţiune permit comunicarea şi sincronizarea applet-urilor, obţinându-se astfel un comportament uniform pentru toate applet-urile din pagină.

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

Crearea de obiecte transferabile Un obiect transferabil este un obiect care poate fi mutat dintr-o componentă în alta folosind mecanismul de transfer oferit de AWT şi care încapsulează un set de date ce urmează a fi transferate (de exemplu, text formatat). Mai concis, un obiect transferabil este un obiect care implementează interfaţa Transferable. Atunci când creaţi un obiect transferabil, trebuie să decideţi mai întâi ce aspecte va suporta obiectul respectiv. Un aspect (flavor) este, în acest caz, formatul de date care urmează a fi transferat. De exemplu, dacă vreţi să copiaţi text formatat HTML dintr-un browser şi să îl lipiţi întrun alt loc, datele respective pot fi reprezentate în diferite formate: ca text formatat, ca text simplu sau drept cod HTML. Atributele datelor determină modul în care obiectul copiat şi cel care urmează a fi lipit negociază transferul de date propriu-zis. Dacă sursa şi destinaţia transferului de date nu suportă acelaşi set de aspecte, transferul de date nu poate avea loc.

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

Folosirea zonei Clipboard După ce aţi definit obiectul transferabil, puteţi folosi zona Clipboard pentru a transfera obiectul între componente sau între Java şi platforma nativă. Java 2 oferă un mecanism foarte simplu pentru lucrul cu zona Clipboard, prin care puteţi copia şi accesa informaţii în/din această zonă. Puteţi folosi fie zona Clipboard standard a sistemului pentru a face schimb de date cu celelalte programe care rulează pe platforma nativă, fie propriile instanţe de zone Clipboard sau mai multe seturi specializate de zone Clipboard. Zonele Clipboard sunt reprezentate în Java de clasa Clipboard, care face parte tot din pachetul java.awt.datatransfer. Puteţi accesa zona Clipboard a sistemului folosind metodele getToolkit() şi getSystemClipboard(); getToolkit () vă permite să accesaţi diferite funcţii ale sistemului: Clipboard clip = getToolkit().getSystemClipboard(); Important de remarcat pentru zona Clipboard a sistemului: applet-urile nu au voie să acceseze această zonă din motive de securitate (aici se pot afla informaţii confidenţiale). Astfel, applet-urile nu pot face schimburi de informaţii în nici un sens cu platforma nativă prin intermediul zonei Clipboard. Totuşi, se pot folosi zone Clipboard interne pentru copierea şi lipirea datelor între componentele unui applet. Orice componentă care doreşte să folosească zona Clipboard - fie să pună date acolo folosind copierea (copy) sau decuparea (cut), fie să preia date folosind lipirea (paste) - trebuie să

implementeze interfaţa ClipboardOwner. Această interfaţă are o singură metodă: lostOwnership(), care este apelată atunci când o altă componentă preia controlul asupra zonei Clipboard. Pentru a implementa operaţia de copiere sau decupare (copy sau cut) trebuie parcurse următoarele etape: 1. Creaţi o instanţă a obiectului Transferable, care să păstreze datele ce urmează a fi copiate. 2. Creaţi o instanţă a obiectului care implementează interfaţa ClipboardOwner (care poate fi clasa curentă sau chiar obiectul Transferable). 3. Dacă folosiţi zona Clipboard a sistemului, folosiţi metoda getSystemClipboard() pentru a obţine o referinţă la aceasta. 4. Apelaţi metoda setContents() a zonei Clipboard, având ca argumente obiectul transferabil şi obiectul care implementează interfaţa ClipboardOwner. Folosind această metodă, obiectul dumneavoastră şi-a „adjudecat" posesia asupra zonei Clipboard. 5. Metoda lostOwnership() este apelată atunci când un alt obiect preia controlul asupra zonei Clipboard. Această metodă trebuie implementată dacă doriţi să realizaţi o anumită acţiune la apariţia evenimentului (sau puteţi crea o metodă vidă atund când nu vă interesează dacă cineva a înlocuit conţinutul zonei Clipboard). Pentru implementarea unei operaţii de lipire (paste) trebuie parcurse următoarele etape: 1. Folosiţi metoda getContents() a clasei Clipboard, care returnează un obiect transferabil. 2. Folosiţi metoda getTransferDataFlavors() a obiectului transferabil pentru a afla ce aspecte de date suporta obiectul transferabil. Stabiliţi ce aspect veţi folosi. 3. Accesaţi datele conform aspectului dorit, folosind metoda getTransferData() a obiectului transferabil. Un astfel de exemplu este prezentat în continuare. import java.awt.*; import java.awt.event.*; import java.awt.datatransfer.*; public class CopyPaste extends Frame implements ActionListener, ClipboardOwner { Button copy, paste; TextField tfCopy, tfPaste; Clipboard clip; public static void main(String[] arguments) { CopyPaste test = new CopyPaste(); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; test.addWindowListener(l); test.setSize(200, 150); test.show(); } CopyPaste() {

super("Copy and Paste"); clip = getToolkit().getSystemClipboard(); FlowLayout flo = new FlowLayout(); setLayout(flo); copy = new Button("Copy From"); tfCopy = new TextField(25); paste = new Button("Paste To"); tfPaste = new TextField(25); copy.addActionListener(this); paste.addActionListener(this); paste.setEnabled(false); add(copy); add(tfCopy); add(paste); add(tfPaste); } void doCopy() { if (tfCopy.getText() != null) { String txt = tfCopy.getText(); StringSelection trans = new StringSelection(txt); clip.setContents(trans, this); paste.setEnabled(true); } } void doPaste() { Transferable toPaste = clip.getContents(this); if (toPaste != null) { try { String txt = (String)toPaste.getTransferData( DataFlavor.stringFlavor); tfPaste.setText(txt); paste.setEnabled(false); } catch (Exception e) { System.out.println("Error -- " + e.toString()); } } } public void actionPerformed(ActionEvent e) { if (e.getSource() == copy) doCopy(); else if (e.getSource() == paste) doPaste(); } public void lostOwnership(Clipboard clip,

Transferable contents) { } }


Related Documents

Limbajul Java
June 2020 5
Limbajul
June 2020 12
Limbajul C
November 2019 13
Limbajul C
June 2020 16
Limbajul Florilor
May 2020 6
Limbajul Organizatiei
June 2020 2

More Documents from ""