Severin BUMBARU

PROGRAMAREA ORIENTATĂ PE OBIECTE ÎN LIMBAJUL JAVA

Editura Fundaţiei Universitare "Dunărea de Jos" - Galaţi, 2002

1

Severin Bumbaru Severin Bumbaru Programarea orientată pe obiecte în limbajul Java Editura Fundaţiei Universitare "Dunărea de Jos" din Galaţi - 2002 ISBN 973-8352-53-3 Referent ştiinţific: prof.dr.ing. Cornelia NOVAC

2

Programarea orientata pe obiecte în limbajul Java

CUPRINSUL
0. Introducere 1. Scurtă prezentare a platformei Java şi a programării orientate pe obiecte 2. Şiruri. Elemente lexicale ale limbajului Java. Variabile şi tipuri de date primitive 3. Expresii. Instrucţiuni simple şi instrucţiuni structurate. Tratarea excepţiilor în limbajul Java 4. Tipul referinţă. Utilizarea claselor din pachetul java.lang. Tablouri. Utilizarea parametrilor din linia de comnadă 5. Declararea claselor 6. Caracteristicile obiectelor şi claselor şi reflectarea lor în declararea claselor. Interfete. Clase imbricate 7. Interfeţe utilizator grafice şi programarea orientată pe evenimente 8. Componentele grafice din JFC/Swing 9. Utilizarea ferestrelor de dialog. Desenarea 10. Introducere în HTML. Crearea şi utilizarea de appleturi 11. Fluxuri de intrare/ieşire şi fişiere 12. Fire de execuţie Index de clase şi interfeţe Bibliografie Anexă : Fişierele sursă ale claselor date ca exemple
4 7 27 66 99 137 163 198 240 267 291 306 333 363 458 460

3

Severin Bumbaru

Introducere
Obiectivele cursului Cum este structurat acest curs Recomandări privind modul de lucru 4 5 5

Obiectivele cursului
Cursul "Programare orientată pe obiecte în limbajul Java" se adresează studenţilor profilelor "Ingineria sistemelor şi calculatoarelor" şi "Electronic" de la Universitatea "Dunărea de Jos" din Galaţi şi îşi propune următoarele obiective:
      

însuşirea conceptelor de bază ale programării orientate pe obiecte; însuşirea limbajului de programare Java; formarea deprinderilor privind realizarea de aplicaţii şi miniaplicaţii în limbajul Java; iniţiere în utilizarea bibliotecilor de clase Java; însuşirea proiectării interfeţei grafice utilizator şi a programării orientate pe evenimente; iniţiere în utilizarea fluxurilor de date şi fişierelor în limbajul Java; iniţiere în programarea concurentă (realizarea de programe cu mai multe fire de execuţie).

Cursul poate fi folosit şi de toţi cei care doresc să se iniţieze în utilizarea limbajului Java şi în programarea orientată pe obiecte.

4

Programarea orientata pe obiecte în limbajul Java

Cum este structurat cursul
Cursul este structurat ca un ciclu de 12 lecţii, care cuprind atât prezentarea conceptelor care trebuie însuşite de studenţi, cât şi indicaţii asupra lucrărilor practice şi exemple de întrebări pentru verificarea cunoştinţelor. Cursul se prezintă în format electronic (hipertext), fiind alcătuit dintr-un ansamblu de pagini legate între ele. Din punct de vedere al obligativităţii citirii şi însuşirii cunoştinţelor conţinute, aceste pagini sunt situate pe patru niveluri, care difera între ele prin culoarea fundalului (background):

Nivelul 0: Informaţii cu caracter organizatoric. Trebuie citite şi respectate, dar nu constituie subiect de examinare. Nivelul 1: Concepte de bază cu caracter obligatoriu. Fără cunoaşterea lor nu poaste fi promovată această disciplină. Nivelul 2: Concepte necesare pentru o bună cunoaştere a programării orientate pe obiecte şi a limbajului Java. Constituie subiecte de verificare şi sunt luate în consideraţie la stabilirea notei. Nivelul 3: Documentaţie necesară pentru elaborarea aplicaţiilor şi miniaplicaţiilor în limbajul Java: descrierea unor clase si interfete, prezentarea pachetelor s.a. Nu se cere memorarea acestei documentaţii, dar la sustinerea programelor elaborate trebuie sa cunoaşteti semnificaţia tuturor componentelor utilizate. Nivelul 4: Informaţii suplimetare, utile pentru o bună întelegere şi însuşire a disciplinei, dar care nu constituie subiecte de examinare. Cursul conţine, de asemenea, un index de clase si interfeţe, în care se dau legături către pagini care prezintă unele clase şi interfeţe din bibliotecile platformei Java (Java API) utile în realizarea aplicaţiilor.

Recomandări privind modul de lucru
Cercetările în domeniul psihologiei educaţionale au dus la concluzia că învăţarea este eficientă atunci când studentul contribuie activ, analizând critic materialele studiate, experimentând şi susţinându-şi opiniile în faţa altora. În consecinţă, pentru o bună însuşire a acestei discipline, vă recomandăm:
 

să lucraţi în echipă; fiecare membru al echipei, după ce şi-a însuşit conceptele de bază ale unei lecţii: o propune şi realizează experimente pentru clarificarea acestor concepte; o discută cu colegii cele constatate în urma experimentelor efectuate;

5

Severin Bumbaru
  

 

membrii echipei îşi împart între ei temele de aplicaţii şi miniaplicaţii primite la laborator, concep şi realizează aceste aplicaţii (miniaplicaţii); la elaborarea aplicaţiilor şi miniaplicaţiilor, folosiţi atât acest curs, cât şi alte surse de documentare, în special documentaţia originală; dacă, în timpul realizarii aplicaţiei, întâmpinaţi dificultăţi, consultati-vă cu colegii sau cu cadrul didactic îndrumător. Faceţi aceasta numai dacă sunteţi convins că nu puteţi rezolva singur; după realizarea aplicaţiei, explicaţi colegilor de echipă cum aţi realizat-o şi ce dificultăţi aţi avut; deşi accentul se pune pe experimentare, atragem atenţia că obiectivul principal al cursului este însuşirea conceptelor care sunt puse în evidenţă în fiecare lecţie. Numai că această însuşire de concepte se realizează nu doar prim simpla citire a materialului documentar, ci prin experimentare, dezvoltare de aplicaţii şi schimb de opinii.

6

Programarea orientata pe obiecte în limbajul Java

Scurtă prezentare a platformei Java şi a programării orientate pe obiecte
O prima cunoştinţă cu limbajul şi platforma Java Scurt istoric al limbajului şi platformei Java Introducere în programarea orientată pe obiecte Prezentarea principalelor tipuri de produse software care se pot scrie în limbajul Java Şablonul unei aplicaţii simple în limbajul Java Aplicarea acestui şablon la realizarea unei aplicaţii simple, în care se afişează un text pe ecran; Editarea, compilarea şi execuţia aplicaţiilor Java Întrebări 7 17 18 19 20 21 21 26

O primă cunoştinţă cu limbajul şi platforma Java
În informatică, Java este

un limbaj de programare, ale cărui calităţi i-au permis răspândirea rapidă, fiind în prezent unul din limbajele cele mai larg folosite. Limbajul este simplu, orientat pe obiecte, robust, sigur, portabil, interpretat, neutru faţă de arhitectură, concurent, dinamic si distribuit; un mediu de execuţie pentru aplicaţiile Java, numit în prezent în engleză "Java 2 Runtime Environment", care conţine maşina virtuală Java şi un nucleu al bibliotecilor de clase Java; o platformă pentru dezvoltarea de aplicaţii în care se foloseşte limbajul Java, care se numeşte în prezent în engleză "Java 2 Platform" şi conţine: o compilatorul Java (Java Compiler); o maşina virtuală Java (Java Virtual Machine); o bibliotecile de clase Java (Java Class Libraries); o vizualizatorul de appleturi Java (Java AppletViewer); o depanatorul Java (Java Debbuger) şi alte instrumente de dezvoltare; o documentaţia; o tehnologie software puternică şi modernă, care corespunde cerinţelor lucrului în reţele de calculatoare.

Există, desigur, şi alte semnificaţii ale cuvântului Java, care însă nu au legatură directă cu informatica.

Limbajul Java
Java este un limbaj de programare de utilizare largă, care are următoarele proprietăţi:

7

Severin Bumbaru
   

 

 

este simplu: deşi sintaxa se aseamănă cu cea a limbajelor C/C++, au fost eliminate acele aspecte care constituiau surse de erori sau produceau confuzii; este orientat pe obiecte: în limbajul Java nu este posibil să se scrie aplicaţii care nu respectă normele programării orientate pe obiecte; este robust: programele scrise în Java sunt foarte fiabile, deoarece sunt prevăzute numeroase verificări atât la compilare, cât şi în timpul executării; este sigur: fiind destinat lucrului în reţele de calculatoare, la realizarea sistemului Java s-a avut în vedere necesitatea ca programele să nu poată aduce daune calculatoarelor pe care rulează, cum ar fi accesul neautorizat la informaţie sau chiar distrugerea acesteia; este portabil, adică programul poate fi mutat de pe un calculator pe altul, de alt tip hardware şi/sau cu alt sistem de operare; este compilat şi interpretat: programul sursă, scris în limbajul Java, este translatat de către compilatorul Java într-un program intermediar sub formă de cod de octeţi (engleza: bytecode). În timpul execuţiei, acest cod de octeţi este interpretat de către maşina virtuală Java, care conţine un interpretor; este neutru faţă de arhitectură: codul de octeţi generat de compilatorul Java nu depinde de arhitectura echipamentului pe care acesta va fi executat, deoarece el nu este executat direct de către procesorul hardware al acestui echipament, ci este interpretat de către maşina virtuală Java; permite programarea concurentă: în limbajul Java se pot scrie programe cu mai multe fire de execuţie (engleza: multithreading), care pot fi executate simultan şi sincronizate; este dinamic, deoarece legarea între ele a claselor şi obiectelor nu se face static (la compilare), ci dinamic (în momentul execuţiei); este distribuit, adică permite realizarea de programe utilizabile în reţele heterogene (care conţin calculatoare de tipuri diferite);

În enumerarea de mai sus, care nu este nicidecum completă apar, probabil, şi unii termeni care vă sunt, deocamdata, neclari sau necunoscuţi. Unii din aceşti termeni vor fi clarificaţi chiar în acest capitol, alţii - în capitolele următoare. Atenţie! Deşi sintaxa limbajului Java se aseamănă cu cea a limbajelor C sau C++, Java nu este C. Pe parcursul lecţiilor următoare vom atrage atenţia programatorilor de C/C++ asupra principalelor deosebiri dintre Java şi aceste limbaje.

Simplificări făcute în Java faţă de C/C++
Deşi sintaxa limbajului Java a fost inspirată de cea a limbajelor C şi C++, autorii limbajului Java au eliminat acele aspecte care produceau dificultăţi programatorilor, constituind surse de erori şi confuzii. Dintre acestea menţionăm:
    

reducerea numărului de tipuri de date primitive prin eliminarea tipurilor fără semn (unsigned); introducerea tipului de date boolean, astfel încât datele logice să nu se mai confunde cu cele întregi; şirurile de caractere nu mai sunt tablouri, ci obiecte ale clasei String; tablourile sunt ele însele obiecte; în timpul execuţiei se verifică dacă indicele se încadrează în dimensiunea tabloului şi se generează o excepţie în caz contrar; 8

Programarea orientata pe obiecte în limbajul Java
  

s-au eliminat pointerii şi operaţiile cu pointeri, menţinându-se numai tipul referinţă din C++; s-a renunţat la supraîncărcarea operatorilor; limbajul este strict tipizat, adică se verifică atât la compilare, cât şi în timpul execuţiei (în operaţiile de atribuire şi de calcul şi la transferul de parametri către subprograme), corectitudinea tipurilor de date utilizate şi se semnalează excepţiile; s-a renunţat la moştenirea multiplă (care există în C++), menţinându-se numai moştenirea simplă. Ca urmare, toate clasele formează o ierarhie unică, avand ca radacină clasa Object; distrugerea obiectelor care nu mai sunt necesare se face automat, de către un "colector de reziduuri" (în engleză: garbage collector), deci nu mai trebuie făcută prin program.

Neutralitatea arhitecturală
În mod ideal, un program sub forma de cod de octeţi (bytecode) ar trebui să poată fi executat pe orice tip de calculator, indiferent de arhitectura acestuia, tipul procesorului, capacitatea de memorie etc, deci să fie independent de platformă. În realitate, calculatorul trebuie să aibă o memorie disponibilă suficient de mare pentru a conţine cel puţin maşina virtuală Java (JVM). Apare deci o contradicţie: pentru a executa programe mari, JVM trebuie să aiba o memorie mare, deci şi calculatorul gazdă trebuie să aibă o memorie mare. Pe de altă parte, pentru a putea executa programe Java pe calculatoare de capacitate foarte mică (cum sunt cele încorporate în diverse aparate electrocasnice, aparate de automatizare etc) este necesară o JVM cu memorie mică. Un program destinat unui calculator mic poate fi executat şi pe unul cu memorie mare, dar nu şi invers. Aceasta a condus la necesitatea de a avea mai multe "medii de execuţie Java", în funcţie de dimensiunile şi destinaţia calculatorului pe care se instalează, iar între caracteristicile programelor Java nu se mai mentioneaza "independenţa de platformă" ci "neutralitatea faţă de arhitectură". Neutralitatea faţă de arhitectură înseamnă, de fapt, că arhitectura maşinii virtuale Java, care este ea însăşi un calculator abstract (organizarea memoriei, convenţiile de reprezentare a datelor în memorie, setul de instrucţiuni al procesorului etc) nu depinde de arhitectura calculatorului pe care aceasta se instaleaza. Întrucat codul de octeţi obţinut din compilarea programului Java nu se execută nemijlocit pe calculatorul gazdă, ci pe maşina virtuală Java, el poate fi acelaşi pe orice calculator pe care este instalata o astfel de maşină, deci este neutru faţă de arhitectura calculatorului gazda. Este însa necesar să precizăm că maşina virtuală Java însăşi este un produs software, care este instalat şi funcţionează direct pe calculatorul gazdă, deci respectă convenţiile arhitecturale ale acestuia. În consecinţă, pe fiecare tip de calculator trebuie instalată o maşină virtuală Java corespunzătoare. Se poate, deci, vorbi de neutralitatea faţă de arhitectura a programelor Java, dar nu şi a maşinii virtuale Java.

9

Severin Bumbaru

Calculatoare abstracte. Modelul von Neuman
Conceptul de calculator abstract
Sistemele de calcul reale sunt extrem de complicate, astfel că cel care doreşte să le cunoască în detaliu are nevoie de cunoştinţe aprofundate în domenile tehnice cărora le aparţin echipamentele care le compun (electronica, electrotehnica, mecanica etc.). Din fericire, asemenea cunostinţe nu sunt absolut necesare pentru a programa un calculator, deşi uneori pot fi utile. În realitate, programatorul nu scrie programul pentru calculatorul real, ci pentru un calculator abstract, dat sub forma unui model care elimina detaliile, păstrând numai acele elemente care sunt necesare pentru limbajul de programare folosit. Asa dar, la stabilirea schemei calculatorului abstract, trebuie să se ţină seama atât de calculatorul real pe care îl modelează, cât şi de limbajul de programare folosit. Este deci posibil ca, pentru acelaşi calculator concret, să existe mai multe modele abstracte, folosite pentru diferite limbaje. Este însă, de asemenea, posibil ca pentru mai multe calculatoare concrete să se folosească acelaşi calculator abstract, diferenţa între ele manifestandu-se prin acele detalii, care se pierd la abstractizare. În general, un calculator abstract are un set de instrucţiuni, un set de regiştri şi un model de memorie.

Modelul de memorie
Memoria calculatorului abstract este o succesiune de locaţii de memorie. Fiecare locaţie de memorie este privită ca o "casetă" care conţine un numar fix de cifre binare (biţi). Numărul de ordine al locaţiei (care arată poziţia acesteia în cadrul memoriei) se numeste adresă. În consecinţă, memoria calculatorului este adresabilă. Locaţia este cea mai mică zonă de memorie care poate fi citită sau înregistrată. La orice operaţie de citire/scriere se transferă un numar întreg de locaţii. În principiu, modelele de memorie pot să difere între ele prin numărul de biţi conţinut în locaţia de memorie (prin "lungimea" locaţiei). Totuşi, la majoritatea calculatoarelor actuale, locaţia de memorie conţine opt biţi şi se numeşte octet (engl.: byte). Iată un exemplu de model de memorie organizată pe octeţi:

10

Programarea orientata pe obiecte în limbajul Java

În acest model, în coloana din stânga apar adresele locaţiilor. Le-am scris în sistemul de numeratie zecimal, pentru a fi mai uşor de urmărit. De obicei însă, ele se exprimă în sistemul binar, sau în cel hexazecimal. Numărul de locaţii este însă în realitate, desigur, mult mai mare, exprimandu-se de regulă în megaocteţi. În coloana din dreapta au fost reprezentate locaţiile de memorie, conţinând fiecare exact opt cifre binare. Conţinutul a fost aici ales, desigur, arbitrar.

Capacitatea de memorie
Capacitatea memoriei calculatorului se poate exprima în biţi. Totuşi, întrucât majoritatea calculatoarelor moderne au memoria organizată pe octeţi, se obisnuieşte să se folosească drept unitate de masură octetul sau multiplii acestuia. Reamintim că nu trebuie sa confundam un bit cu un octet (engl.: byte). Primul este unitatea binara, exprimându-se prin cifrele binare 0 si 1. Numele este o prescurtare de la denumirea din engleză a unităţii binare: binary unit, de la care s-au luat începutul şi sfârşitul. Un octet (byte) este însă un număr format din 8 cifre binare, deci din 8 biţi. Multiplii octetului sunt kilooctetul, megaoctetul, gigaoctetul si teraoctetul. Având în vedere că sistemul de numeraţie folosit în memoria internă a calculatorului este cel binar, pentru multipli nu s-au folosit puterile lui 10, ca în sistemul zecimal, ci puterile lui 2 cele mai apropiate de acestea: 1 kilooctet (engl. kilobyte) = 1024 octeţi = 210 octeţi, prescurtat K sau Ko sau KB ; 1 megaoctet (engl. megabyte) = 1024 kiloocteţi = 1'048'576 octeţi, prescurtat M sau Mo sau MB ; 1 gigaoctet (engl.: gigabyte) = 1024 megaocteţi = 1'073'741'824 octeţi, prescurtat G sau Go sau GB ; 1 teraoctet (engl: terabyte) = 1024 gigaocteţi =1'099'511'627'776 octeţi, prescurtat T sau To sau TB ;

Procesorul abstract
Procesorul abstract este caracterizat printr-un set de regiştri şi un set de instrucţiuni. Regiştrii procesorului sunt, la fel ca locaţiile de memorie, dispozitive în care se memorează numere binare de lungime fixă, specifică procesorului respectiv. Aceasta este, în general un multiplu de 8, deci într-un registru pot fi "încărcaţi" un număr întreg de octeţi.

11

Severin Bumbaru Instrucţiunile sunt codificate intern tot prin numere binare, formate din cel puţin două părţi: un cod al operaţiei şi adresa operandului (uneori adresele operanzilor). Exemple de operaţii: - încărcarea, adică transferul unui operand din memorie într-un registru; instrucţiunea conţine codul operaţiei de încarcare, adresa din memorie a operandului şi numărul registrului în care se încarcă; - memorarea, adică inregistrarea la o anumită adresă din memorie a conţinutului unui registru; se dau: codul operaţiei, numărul registrului de origine şi adresa din memorie a destinaţiei; - operaţii de calcul (aritmetice sau logice), care se efectueaza fie între operanzii din registre, fie între un operand dintr-un registru şi unul din memorie. În instrucţiune se dau codul operaţiei şi numerele registrelor între care se face aceasta, sau numărul unui registru şi o adresă din memorie. Rezultatul operaţiei rămane, de regulă, într-un registru; - instrucţiuni de salt, prin care se modifică fluxul normal (secvenţial) al executării instrucţiunilor. În mod normal, după ce s-a executat o instrucţiune din program, se trece la cea imediat următoare, în ordinea în care acestea sunt situate în memorie. Instrucţiunea de salt indică trecerea la executarea unei instrucţiuni situată la o altă adresă din memorie. Fiecare instrucţiune este înregistrată în memorie pe o lungime unul sau mai multi octeţi.

Maşina von Neumann
Matematicianul american de origine maghiară John von Neumann a publicat în anul 1945 prima descriere a unui calculator cu program memorat, care ar fi putut fi construit. Această descriere a avut o influenţă foarte mare asupra evoluţiei ulterioare a calculatoarelor şi este cunoscută sub numele "maşina von Neumann". Maşina von Neumann este formată din: - o unitate aritmetică şi logică; - o unitate de control; - o unitate de intrare/ieşire; - o unitate de memorie. Unitatea aritmetică şi logică şi unitatea de control formează împreună procesorul (unitatea centrală) de la calculatoarele actuale.

12

Programarea orientata pe obiecte în limbajul Java Maşina von Neuman lucrează numai cu numere întregi. Unitatea aritmetică şi logică efectuează operaţiile de calcul (adunare, scădere, înmulţire, împărţire întreagă, valoare absoluta). Ea conţine doi regiştri, dintre care unul se numeste registru acumulator. Orice operaţie se face folosind regiştrii unităţii centrale: - încărcarea este transferul unui operand din memorie în unul din registri; - memorarea este transferul continutului registrului acumulator la o anumită adresă din memorie; - operaţiile de calcul (adunare, scădere, înmulţire, împărţire) se fac între numerele din cei doi regiştri, iar rezultatul rămâne în registrul acumulator; - operaţiile de intrare/ieşire se fac între registrul acumulator şi unitatea de intrare/ieşire. Dacă, de exemplu, dorim să se efectueze calculul c=a+b, aceasta înseamnă că se vor executa următoarele instructiuni: încarcă în registrul A operandul situat în memorie la adresa a; încarcă în registrul R operandul situat în memorie la adresa b; adună operanzii din cei doi regiştri (rezultatul rămâne în registrul acumulator A); memorează conţinutul registrului A la adresa c din memorie. Bineînţeles, aceste instrucţiuni vor fi reprezentate în cod binar, adica atât operaţiile, cât şi adresele din memorie vor fi exprimate prin numere în sistemul de numeraţie cu baza 2. Caracteristica principală a maşinii von Neuman este ca funcţionarea ei este un proces secvenţial: la un moment dat se execută o singură instrucţiune, iar executarea unei instrucţiuni poate să înceapă numai după ce s-a încheiat executarea celei precedente. În toate operaţiile participă unitatea centrală şi regiştrii acesteia. Primele generaţii de calculatoare construite au respectat destul de fidel modelul maşinii von Neuman. Chiar dacă, ulterior, evoluţia calculatoarelor şi a sistemelor de operare a suferit abateri de la acest model (s-a introdus accesul direct la memorie al unităţilor de intrare/ieşire, au aparut calculatoare cu mai multe procesoare, care funcţionează în paralel, etc.), limbajele de programare au continuat, în majoritatea lor, să fie făcute pentru calculatoare abstracte de tip von Neumann. Exemple bine cunoscute de astfel de limbaje sunt Fortran, Cobol, Basic, Pascal, C, C++ ş.a. Limbajul Java face parte din categoria limbajelor pentru procese concurente, în sensul că el permite să existe în paralel mai multe fire de executie, ale căror operaţii pot, în principiu, să se realizeze pe procesoare diferite. Este unul din motivele pentru care acest limbaj nu mai are la bază maşina von Neumann, ci pentru el a fost conceput un alt calculator abstract, numit maşina virtuală Java.

Mediul de execuţie Java
Mediul de execuţie Java, numit în engleză Java Runtime Environment, conţine maşina virtuală Java şi un nucleu de clase Java. Acest mediu trebuie instalat pe orice calculator, pe care se doreşte să se execute programe Java. Maşina virtuală Java (engleză: JVM - Java Virtual Machine) este, în general, implementată software sub forma unui produs program adecvat calculatorului pe care acesta se instalează.

13

Severin Bumbaru Ideea de bază a limbajului şi tehnologiei Java, este ca - pe baza acestora - să se poată creea produse software neutre faţă de arhitectura sistemului de calcul, deci care să poată fi executate pe orice echipament, de la produse de uz casnic comandate numeric (televizoare, telefoane, maşini de spălat, frigidere, etc) pană la supercalculatoare. Ea se exprimă prin sloganul "Write Once, Run Anywhere" (scrie o singură dată şi rulează oriunde), care arată că un program, după ce a fost scris şi compilat, poate fi executat (rulat) pe orice calculator. Pentru realizarea acestui obiectiv, s-a decis ca în tehnologia Java portabilitatea programelor sa se realizeze la nivel de cod de octeţi (bytecode), adică sub forma de cod binar destinat unui calculator abstract, numit maşina virtuală Java. În acest scop:

s-a întocmit o descriere riguroasă numită specificaţia maşinii virtuale Java, (The Java Virtual Machine Specification), în care se prezintă în detaliu arhitectura şi funcţionarea acestei maşini; pe fiecare calculator, pe care se execută programe Java, trebuie sa existe o implementare (o realizare concretă sub forma de produs software sau hardware) a maşinii virtuale Java, care execută efectiv programul dat sub forma de cod de octeţi.

Nucleul de clase: Limbajul Java este orientat pe obiecte. Orice program este un ansamblu de clase şi de obiecte, care sunt instanţieri ale claselor. În mediul de execuţie Java este inclusă şi o bibliotecă de clase predefinite. Este vorba, în special, de acele clase care asigură comunicarea dintre programul Java şi sistemul de operare al calculatorului gazdă. Deşi interfaţa acestor clase cu programul Java nu depinde de tipul calculatorului gazdă, implementarea lor este dependenta de platformă, la fel ca şi cea a maşinii virtuale Java.

Implementarea maşinii virtuale Java
De regulă, arhitectura şi setul de instrucţiuni al calculatorului, pe care se execută un program Java, diferă de cele ale maşinii virtuale Java. În consecinţă, codul de octeţi generat de compilatorul Java nu poate fi executat nemijlocit de procesorul (procesoarele) calculatorului pe care acesta se execută. Dacă este realizată software (aşa cum se întamplă în majoritatea cazurilor), maşina virtuală Java este ea însăşi un produs program, care este scris şi compilat special pentru tipul de calculator pe care se instalează, deci foloseşte setul de instrucţiuni nativ al acestuia. Acest produs program trebuie să respecte specificaţia maşinii virtuale Java, dar realizarea concretă depinde de tipul de calculator pe care se instalează. Mai mult, pentru acelaşi calculator pot fi realizate mai multe maşini virtuale Java, care toate respecta specificaţia, dar diferă între ele prin modul de realizare. În principiu, componenta principală a maşinii virtuale Java este un interpretor, adică un program care parcurge instrucţiunile conţinute în codul de octeţi Java din memoria maşinii virtuale Java şi le converteşte în instrucţiuni native, care pot fi executate de procesorul calculatorului gazdă. Această conversie necesită un anumit timp, din care cauză durata de execuţie a unui program interpretat este intotdeauna mai mare decât a unuia compilat direct în codul nativ. Din această cauză, principala deficienţă care se semnala la primele implementări ale maşinii virtuale Java era că durata de execuţie a programelor era sensibil mai mare decât a celor scrise in limbaje "tradiţionale" compilate, cum ar fi C, C++, Pascal, Fortran etc.

14

Programarea orientata pe obiecte în limbajul Java Implementările moderne ale maşinii virtuale Java se caracterizează prin mărirea sensibilă a vitezei de executie, care se apropie deja de cea a programelor compilate. Aceasta se realizează, în special, prin introducerea unei compilări "just in time": codul de octeţi este compilat, în momentul execuţiei, in cod nativ pentru calculatorul gazdă şi abia apoi este executat. Desigur că cea mai eficientă reducere a duratei de execuţie se produce atunci când maşina virtuală Java este implementata hardware, astfel încât codul de octeti Java (bztecode) este cod nativ al acestei maşini. În acest caz, durata de execuţie a programului este aceeaşi cu cea a unui program compilat tradiţional.

Platforma Java 2
Un mare avantaj al programatorilor in limbajul Java este că au la dispoziţie un set puternic de instrumente de dezvoltare a programelor: compilator, depanator, bibliotecă de clase, documentaţie etc. La început, acesta s-a numit "setul de dezvoltare Java" (în engleza: JDK Java Development Kit). Au existat mai multe versiuni succesive: JDK 1.0, JDK 1.1, JDK 1.2 aparute, respetiv, in anii 1996, 1997 si 1998. O componentă importantă pentru programatori a setului de dezvoltare Java este "Interfaţa pentru programarea de aplicaţii" (API - Application Programming Interface). Aceasta este acea parte din documentaţie, în care se dă specificaţia claselor, adică descrierea riguroasă a modului în care clasă respectivă este vazută de către programul de aplicaţie. Trecerea de la JDK 1.0 la JDK 1.1 a reprezentat o revizuire de concepţie, în special în ce priveşte API, astfel că numeroase clase existente în JDK 1.0 au devenit depreciate în JDK 1.1, fiind înlocuite cu clase noi. La trecerea de la JDK 1.1 la JDK 1.2 nu a mai apărut o astfel de situaţie, dar numărul de clase a fost sensibil mărit. În consecinţă, nu se mai recomanda folosirea în programele Java a JDK 1.0. În anul 1998 s-a introdus denumirea de "Platforma Java 2", care se referă la întregul complex de specificaţii, resurse software şi documentaţie puse la dispoziţie programatorilor şi utilizatorilor de Java. Ca urmare, JDK 1.2 a fost redenumit "Java 2 Platform SDK, SE v1.2" (SDK - Software Development Kit, SE - Standard Edition, v - version). In prezent, se utilizează deja "Java 2 Platform SDK, SE v1.4". Platforma Java 2 se oferă în prezent de firma Sun Microsystems în trei variante:
  

Java 2 Platform, Standard Edition (J2SE) - pentru dezvoltarea aplicaţiilor şi miniaplicaţiilor obişnuite (de pe staţiile de lucru); Java 2 Platform, Enterprise Edition (J2EE) - pentru dezvoltarea de aplicaţii pentru serverele de întreprindere; Java 2 Platform, MicroEdition (J2ME) - pentru utilizarea pe calculatoare de capacitate foarte mică, încorporate în echipamente electrice sau electronice.

În cursul nostru vom folosi J2SE, iar din documentaţia oferită de firma Sun vom folosi în special documentaţia API pentru această platformă.

15

Severin Bumbaru

Tehnologia Java
Datorită caracteristicilor sale, Java nu mai poate fi considerat doar un limbaj de programare asociat cu o platformă de dezvoltare de aplicaţii, ci a devenit o veritabilă tehnologie software. Când s-a trecut de la programarea procedurală la programarea orientată pe obiecte, s-a arătat că aceasta poate fi comparată cu trecerea de la fabricaţia artizanală la cea industrială: programul nu mai trebuie creat "de la zero", ci poate fi conceput ca un ansamblu de componente (obiecte) "prefabricate". Programatorii se pot împărţi în două categorii: cei care creeaza clasele de obiecte şi le oferă "pe piaţa" şi cei care, din aceste componente, crează aplicaţii (produse software finite) oferite utilizatorilor. Limbajele orientate pe obiecte existente anterior (C++, Object Pascal, etc) nu au reuşit, totuşi, să stea la baza unei astfel de "industrializări". Cauza principală este că, fiind limbaje compilate, nu puteau sta la baza producerii unor componente utilizabile pe orice platformă hardware şi sub orice sistem de operare şi - deci - nu puteau fi folosite fara dificultăţi într-o reţea de calculatoare eterogenă. Deşi a apărut recent, răspândirea foarte rapidă a platformei Java se datoreşte tocmai faptului că apariţia ei a corespuns cu dezvoltarea amplă a reţelelor de calculatoare, în special a Internet-ului. Un număr foarte mare de firme producătoare de software şi numeroşi programatori individuali din intreaga lume s-au angajat în dezvoltarea de clase, componente şi aplicaţii programate în Java. Numărul de clase din Java SDK creşte continuu, fiind utilizabile în domenii din ce în ce mai variate: prelucrări de date numerice, prelucrări de texte, interfaţa grafică cu utilizatorul, lucrul cu fişiere, comunicatii în reţele, legătura cu baze de date, securitatea datelor, calcul distribuit, etc. În afară de clasele din SDK (puse la dispoziţie de firma Sun Microsystems), programatorii pot folosi un numar mare de clase şi componente Java ("Java beans") oferite de alte firme, multe din ele disponibile liber pe Internet. Ca urmare, se poate spune că baza tehnologică a programării în Java creşte exponenţial. Limbajul Java în sine este simplu şi uşor de învăţat. Forţa tehnologiei Java constă nu în limbajul folosit, ci în numărul din ce în ce mai mare de clase reutilizabile, pe care programatorii le au la dispoziţie, şi în faptul că programele realizate pot fi utilizabile practic oriunde, fără a fi necesar sa fie refăcute când se trece de la un tip de calculator la altul. Aceasta presupune însă, ca în orice alta tehnologie, şi necesitatea unei standardizări, a introducerii unor norme pe care să le respecte întreaga comunitate a programatorilor. Acest rol il îndeplinesc acum specificatiile: specificatia limbajului Java, specificatia maşinii virtuale Java, Java API, etc. Întreţinerea şi dezvoltarea acestor specificaţii este facută, deocamdată, de firma Sun Microsystems. Nu este exclus ca, cu timpul, acest rol sa fie conferit unui organism de standardizare internaţional, pentru a nu se creea avantaje unui anumit producător de software. Dintre resursele cele mai importante puse la dispoziţia programatorilor de tehnologia Java, menţionăm:

Java API - specificaţia claselor interfeţei de programare de aplicaţii Java şi bibliotecile care implementează aceste clase;

16

Programarea orientata pe obiecte în limbajul Java

  

JFC (Java Foundation Classes) - biblioteca de clase pentru realizarea unor interfeţe grafice cu utilizatorul independente de sistemul de operare utilizat. Începând cu Platforma Java 2 este inclusă în Java API; Java Beans - o arhitectură de componente reutilizabile, neutre faţă de tipul calculatorului şi sistemul de operare, care pot fi utilizate în medii de programare vizuale. Se ofera şi instrumente de dezvoltare pentru astfel de componente: BDK (Beans Development Kit) pentru dezvoltarea de componente, Java Plug-in (pentru legătura cu Active-X de la Microsoft), JAF (JavaBeans Activation Framework) pentru identificarea, localizarea componentelor şi altele; JDBC (Java Data Base Connectivity) - componente pentru legătura cu baze de date; JINI - tehnologie bazată pe Java, pentru asigurarea conectivităţii echipamentelor în reţele de calculatoare; tehnologia agenţilor software - produse software care se pot deplasa de la un calculator la altul într-o reţea şi pot îndeplini în mod autonom pe calculatorul de destinaţie sarcinile pentru care au fost trimise. Platforma Java este foarte convenabilă pentru crearea şi utilizarea agenţilor în reţele eterogene. Exemple sunt tehnologia agleţilor oferita de firma IBM, cadrul de dezvoltare a agentilor Java Java Agents Framework (JAF) de la Universitatea din Massachutess, etc. şi multe altele.

Există, de asemenea, numeroase medii de programare pentru Java, oferite de diverse firme, cum sunt:
     

Forte for Java - un mediu de programare vizual, realizat si oferit gratuit pentru utilizări necomerciale de firma Sun MicroSystems. WebGain VisualCafe - un mediu de programare vizual, realizat de firma WebGain; Borland JBuilder - un mediu de programare vizual, realizat de firma Borland; VisualAge for Java - un mediu de programare vizual realizat de firma IBM; Visual J# .NET - un mediu de dezvoltare vizual al firmei Microsoft şi altele.

În acest curs, ne vom rezuma la însuşirea limbajului Java şi a unor componente din Java API, folosind numai Java 2 SDK, Standard Edition.

Scurt istoric al platformei Java
În anul 1990, un grup de programatori de la firma Sun Microsystems, între care şi James Gosling, lucrau la proiectul "Green", al cărui obiectiv era realizarea de software pentru aparate electrocasnice cu comanda numerică (maşini de spălat, frigidere, cuptoare cu microunde, telefoane, televizoare etc.). S-a încercat, pentru început, să se folosească pentru programare limbajul C++, dar s-a constatat rapid că acesta nu corespundea scopului urmărit, deoarece programele obţinute erau prea mari şi nu erau portabile la nivel de cod binar. S-a ajuns astfel la concluzia că este necesar un nou limbaj de programare, care să fie simplu, uşor de învăţat şi de utilizat, iar programul binar obţinut prin compilare să poată fi rulat pe calculatoare de capacitate mică şi cu arhitecturi diferite. Acest nou limbaj a fost numit Oak (în romaneste "nuc"; se spune că atunci când a trebuit sa-i dea un nume, Gosling s-a uitat pe fereastră şi a văzut un nuc în faţa ei), prima lui variantă fiind gata la mijlocul lui 1991.

17

Severin Bumbaru Desi s-a realizat o cantitate mare de software, s-a constatat că, în vremea respectivă, orientarea către aparatura electrocasnică ("de consum"), nu corespundea încă cerinţelor pieţei. După o serie de alte încercări, s-a ajuns la concluzia că cea mai buna utilizare a noului limbaj este realizarea de software pentru reţelele de calculatoare. Întrucat denumirea "Oak" nu a putut fi înregistrată oficial, în timpul unei discuţii "la o ceaşcă de cafea", autorii au decis sa-i dea limbajului numele Java, care a fost şi înregistrat ca marcă comerciala (TM - Trade Mark). Iată dece acest nume apare frecvent scris sub forma JavaTM. Prima utilizare comercială a acestui limbaj a fost includerea unui interpretor Java în navigatorul (browserul) de Web al firmei Netscape. Introducerea de appleturi (miniaplicaţii) Java în paginile de Web a permis ca acestea sa devina interactive, deci să capete o componentă dinamică. In continuare, folosirea limbajului şi apoi a tehnologiei Java a căpătat o dezvoltare exponenţială. Iată câteva date: - ianuarie 1996 - lansarea JDK 1.0; - decembrie 1996 - lansarea JDK 1.1, versiunea beta; - februarie 1997 - lansarea versiunii finale a JDK 1.1; - martie 1998 - se lansează JFC (Java Foundation Classes); - decembrie 1998 - se lansează "Java 2 Platform"; - iunie 1999 - se anunta trei ediţii ale platformei Java 2: J2SE, J2EE si J2ME. - mai 2000 - se lansează platforma J2SE v 1.3. - februarie 2002 - este lansată platforma J2SE v 1.4

Introducere în programarea orientată pe obiecte
Programarea orientată pe obiecte (POO) este o formă de programare, în care programatorii definesc clase de obiecte, iar programul conţine un ansamblu de clase şi obiecte, care comunică între ele prin mesaje. Clasa este o extensie a conceptului de tip de date şi conţine o structură de date, împreună cu metodele (functiile) care se aplica acestor date. Obiectul este o instantiere (o instanta) a clasei. In acelasi program se pot folosi mai multe obiecte apartinand aceleeasi clase, sau unor clase diferite. Fiecare obiect se caracterizeaza prin stare si comportament. Starea obiectului depinde de datele pe care acesta le contine, in timp ce comportamentul este dat de metodele clasei respective. In general, comunicarea prin mesaje consta in invocarea de metode. Daca obiectul a invoca o metoda a obiectului b, aceasta poate avea ca efect modificarea starii obiectului b (adica modificarea unor date continute in structura de date a lui b) si/sau poate primi o valoare intoarsa de metoda respectiva. Se considera ca, prin invocarea metodei, obiectul a a transmis un mesaj obiectului b, ceeace a provocat din partea acestuia un anumit raspuns (deci b a manifestat o anumita comportare).

18

Programarea orientata pe obiecte în limbajul Java Exemplu Sa consideram clasa poligoanelor regulate. Structura de date contine doua variabile: numarul de laturi (care este o variabila de tip intreg) si lungimea unei laturi (care este o variabila de tip real). Metodele pot fi, de exemplu, mici programe (functii) prin care se calculeaza aria, perimetrul, apotema, raza cercului circumscris etc. Pot exista si metode prin care se modifica numarul de laturi sau lungimea laturii, deci se modifica starea poligonului. Clasa are un nume (de ex. PoligonRegulat). Pot exista, evident, mai multe instante (obiecte) ale acestei clase, care toate sunt poligoane regulate, dar difera intre ele prin numarul de laturi si/sau prin lungimea laturii. Fie p un PoligonRegulat cu 4 laturi de lungime 3.5. Invocarea metodei p.arie() are ca efect calcularea functiei arie()pentru obiectul p, deci se va obtine ca raspuns valoarea 12.25. Prin invocarea acestei metode, s-a transmis obiectului p mesajul ca se cere calcularea ariei, iar comportamentul acestui obiect a constat in punerea in executie a metodei invocate si intoarcerea valorii calculate a ariei. Valoarea intoarsa depinde, evident, de starea in care se gaseste p in momentul invocarii metodei, adica de numarul de laturi si de lungimea laturii. Atat variabilele, cat si metodele pot fi statice sau nestatice. Variabilele statice (ale clasei) apartin clasei, adica au aceeasi valoare pentru toate obiectele clasei respective. Variabilele de instanta (nestatice) apartin obiectului (instantei), deci au valori diferite de la un obiect la altul.

De exemplu, daca clasa Pasare contine variabila numarAripi, aceasta variabila va avea valoarea 2 pentru toate pasarile, deci pentru toate instantele clasei respective, fiind o variabila statica sau a clasei. In schimb, variabila varsta va avea valori diferite pentru fiecare pasare, deci este o variabila a instantei (nestatica). Metodele statice (ale clasei) pot folosi numai variabilele statice ale clasei respective, in timp ce metodele nestatice pot folosi atat variabilele statice, cat si pe cele ale instantei. Din punct de vedere al modului de acces, datele si metodele unei clase pot fi publice sau private. Cele publice sunt accesibile din orice alta clasa, in timp ce cele private sunt accesibile numai din clasa careia ii apartin.

Tipuri de produse software scrise în Java
Limbajul Java este folosit cel mai frecvent pentru a scrie urmatoarele trei tipuri de programe: aplicaţie - este un produs software care se instalează pe un anumit calculator şi funcţionează direct sub controlul sistemului de operare, având acces la toate resursele calculatorului respectiv. Una din clasele aplicaţiei trebuie sa conţină metoda principală, cu care începe execuţia aplicatiei. Această metodă se numeşte main şi are forma:

19

Severin Bumbaru
public static void main(String args[]) { // corpul metodei }

applet (miniaplicaţie) - este un program care se transmite sub formă de cod de octeţi (bytecode) prin reţeaua de calculatoare şi este executat în cadrul unui navigator (browser) de Web, fără a avea acces la fişierele sau sistemul de intrare/ieşire al calculatorului pe care se execută; servlet - un program care se execută pe un server dein reţea.

Un şablon de aplicaţie simplă în limbajul Java
În prima parte a acestui curs, în aplicaţiile făcute la curs şi la laborator vom utiliza următorul şablon:
class <nume_clasa> { public static void main(String args[]) { // corpul metodei main } }

Părţile scrise cu negru (inclusiv parantezele şi acoladele) le vom considera obligatorii, iar cele scrise cursiv cu roşu sunt la latitudinea programatorului.
<nume_clasa> - este numele clasei, fiind ales de către programator cu respectarea următoarelor condiţii: - numele clasei trebuie să înceapă cu o literă şi este format numai din litere, cifre şi eventual - liniuţa de subliniere; - prin convenţie (deşi aceasta nu este o regulă de sintaxă), numele de clasă începe întotdeauna cu o majusculă; - în cazul numelor compuse din mai multe cuvinte, fiecare cuvânt începe cu majusculă; - lungimea numelui nu este limitată, dar nu este recomandabil sa fie prea mare. // corpul metodei main -

este o succesiune de instrucţiuni şi comentarii care respectă

sintaxa limbajului Java.

Părţile componente ale acestui şablon pot fi explicate pe baza cunoştintelor pe care le avem deja despre programarea orientată pe obiecte şi despre limbajele de programare. - descrierea (definirea) unei clase începe prin cuvantul class, urmat de numele clasei; - corpul clasei este cuprins între acolade { ... } - în cazul nostru, corpul clasei conţine o singura metodă; - metoda are forma unei funcţii, care se numeşte main şi are un singur argument numit args. Vom vedea ulterior că acesta este un tablou de obiecte din clasa String, adică un tablou de şiruri de caractere. Tot atunci vom vedea că acest argument serveşte pentru preluarea parametrilor din linia de comandă;

20

Programarea orientata pe obiecte în limbajul Java - metoda main (metoda principală) este publică, adică poate fi utilizată de obiecte din alta clasă (în cazul nostru, ea este apelată de maşina virtuală Java la punerea în execuţie a aplicaţiei); -metoda main este statică, deci apartine clasei şi nu instanţelor acestei clase; - privită ca o funcţie, metoda main trebuie, în principiu, să întoarcă o valoare. În acest caz, metoda nu întoarce o valoare, deci tipul valorii întoarse este void (loc gol, lipsă).

Exemplu de aplicaţie simplă
Dăm aici un exemplu de aplicaţie simplă scrisă în limbajul Java. Aplicaţia este o clasă Java, care conţine metoda main. Exemplul 1 Considerăm următorul program:

class PrimaAplicatie { public static void main(String args[]) { System.out.println("Prima noastra aplicatie a reusit!"); } }

Remarcăm urmatoarele:
  

programul respectă şablonul de aplicaţie simplă adoptat de noi; numele clasei este PrimaAplicatie; corpul clasei este constituit dintr-o singură instrucţiune, prin care este invocată metoda println a obiectului out din clasa System.

Deşi nu cunoaştem încă modul în care se definesc şi se utilizează clasele şi obiectele în limbajul Java, menţionam aici că instrucţiunea de forma
System.out.println(<sir_de_caractere>);

are ca efect afişarea pe ecran a şirului primit ca argument. În limbajul Java, instrucţiunile simple se termină cu caracterul ; (punct şi virgulă). Efectul executării acestei aplicaţii este că se afişează pe ecran textul Prima noastra
aplicatie a reusit!

Editarea, compilarea şi executarea aplicaţiilor Java
Fiecare aplicaţie Java parcurge următoarele etape: editarea programului sursă, compilarea şi eliminarea erorilor sintactice, testarea şi eliminarea erorilor de concepţie, executarea. În acest scop, pe calculatorul pe care lucraţi trebuie sa existe un set de dezvoltare Java, preferabil Java 2 Platform SDK, Standard Edition. Pentru a dezvolta aplicaţii Java:

21

Severin Bumbaru
 

dacă lucrati sub Windows, veţi folosi o fereastră MS-DOS (Command Prompt); dacă lucraţi sub Linux sau Unix, veţi folosi o fereastră de X-Terminal. Sub Linux vă recomandăm sa folosiţi KDE, sub care să selectaţi din bara de scule pictograma "Terminal Emulation", sau din meniul Applications sa alegeti optiunea "X Terminal".

Editarea fisierului sursă
Pentru început, vom considera că aplicaţia este constituită dintr-o singură clasă, care respectă şablonul indicat anterior. Programul sursă pentru această clasă va fi editat sub forma unui fişier separat, care are acelaşi nume cu clasa şi are extensia .java. De exemplu, clasa PrimaAplicatie va fi editată sub forma unui fişier de text cu numele PrimaAplicatie.java. Este preferabil să puneţi acest fişier într-un subdirector separat. De exemplu puteti crea un subdirector numit javaLab, în care să creaţi un alt subdirector numit sapt1, în care sa puneţi fişierul PrimaAplicatie.java. Pentru crearea fişierului se va folosi un editor de text simplu, care generează numai text neformatat, de exemplu "Notepad" daca lucraţi sub Windows sau sub Linux cu WinLinux99, respectiv "Text Editor" sau "Advanced Editor", daca lucraţi sub Linux cu KDE.

Compilarea şi eliminarea erorilor semnalate de compilator
Translatarea programului sursă în program sub forma de cod de octeţi (bytecode) se face cu ajutorul compilatorului Java, numit javac. În acest scop, va deplasaţi în subdirectorul în care se găseşte programul sursă pe care doriţi să-l compilaţi şi daţi comanda
javac <fisier_sursa>

De exemplu, pentru a compila fişierul care conţine clasa PrimaAplicatie mergeti în subdirectorul în care se găseste fişierul respectiv şi daţi comanda
javac PrimaAplicatie.java

După ce aţi dat această comandă, veţi obţine unul din următoarele rezultate: 1. Pe ecran apare din nou promptul sistemului de operare, fără a se afişa un mesaj de eroare. În acest caz, compilarea a decurs normal, iar dacă daţi comanda dir veţi constata ca în subdirectorul curent a apărut un nou fişier, care poartă numele clasei şi extensia class. Acesta este fişierul care conţine bytecode-ul clasei compilate. 2. Obţineţi un mesaj de eroare, care indică fie că există erori în program, care au fost sesizate de compilator, fie că există erori în linia de comandă prin care aţi cerut compilarea. În ambele cazuri, este necesar să eliminaţi erorile şi să reluaţi compilarea. Exemplu de utilizare corectă Sa considerăm ca am creat corect fişierul sursă PrimaAplicatie.java, după care dăm comanda
javac PrimaAplicatie.java

Constatăm că pe ecran nu apare nici un mesaj de eroare, ci apare din nou promptul sistemului de operare. Dacă dăm acum comanda
dir

Constatăm, de asemenea, că în lista de fişiere care se afişează apar:
PrimaAplicatie.class PrimaAplicatie.java Dintre acestea, PrimaAplicatie.java

este fişierul sursă editat de noi, în timp ce

22

Programarea orientata pe obiecte în limbajul Java
PrimaAplicatie.class

este fişierul de cod de octeti rezultat în urma compilării.

Exemplul 1 de eroare Considerăm că, după ce aţi creat fişierul sursă PrimaAplicatie.java, daţi comanda
jamac PrimaAplicatie.java

în care numele compilatorului este introdus greşit (jamac în loc de javac). Ca efect, veţi obtine un mesaj prin care se arată că cuvantul jamac nu este o comandă corectă (nu este numele unei comenzi interne a sistemului de operare sau numele unui program executabil), de exemplu:
jamac: command not found

Exemplul 2 de eroare Să considerăm acum că numele fişierului sursă este introdus greşit, de exemplu:
javac PimaAplicatie.java

În acest caz, mesajul de eroare este
can't read: PimaAplicatie.java

adică "nu pot citi: PimaAplicatie.java", ceeace înseamnă că un astfel de fişier nu există în directorul curent. Exemplul 3 de eroare Să urmărim ce se întâmplă dacă introducem comanda
javac PrimaAplicatie

în care numele fişierului este corect, dar lipşeste extensia. În acest caz, obtineţi următorul mesaj:
PrimaAplicatie is an invalid option or argument. usage: javac <options> <source files>

urmat de o lista de opţiuni. Aceasta înseamnă că nu s-a respectat sintaxa comenzii, care cere ca comanda javac să fie urmată (eventual) de una sau mai multe opţiuni din lista dată, după care trebuie să apară numele fişierului (fişierelor) sursă care trebuie compilate. Aceste nume de fişiere trebuie sa conţină obligatoriu extensia java. Exemplul 4 de eroare Să considerăm acum că fişierul sursă editat de noi conţine erori. Un exemplu este fişierul PrimaAplicatie1.java, care are următorul conţinut:
class PrimaAplicatie1 { void main(String args[]) { System.ont.println("Prima noastra aplicatie a reusit!"); } }

Compilând acest fişier obţinem următorul mesaj de eroare:
PrimaAplicatie1.java:3: No variable ont defined in class Java.lang.System System.ont.pintln("Prima noastra aplicatie a reusit!"); ^ Acest mesaj se interpretează astfel: în fişierul PrimaAplicatie1.java, linia 3, în locul marcat mai jos prin ^, apare variabila ont, care nu există în clasa System din pachetul (biblioteca) java.lang. Într-adevar, eroarea constă în faptul că apare ont în loc de out. Observăm că a fost reprodusa

linia din program în care apare eroarea şi s-a marcat prin ^ locul apariţiei erorii. Dacă dăm comanda dir, constatam că în directorul curent nu a apărut clasa PrimaAplicatie1.class. Făcând corecţia în fişier şi compilând din nou, constatăm că nu mai apare nici un mesaj de eroare, deci compilatorul nu a mai sesizat nici o eroare şi a creat fişierul care conţine codul de octeţi corespunzător. Remarcăm că, totuşi, programul nu este corect, deoarece - conform şablonului dat anterior,

23

Severin Bumbaru prima linie din metoda main trebuia să aibă forma
public static void main(String args[]){

Compilatorul nu a sesizat această eroare deoarece, în principiu, clasa poate sa conţina şi o metoda main de forma celei date în acest fisier. Vom vedea, însă, că eroarea apare la executarea programului. Exerciţiu Încercaţi să faceţi şi alte modificări în programul sursă şi să constataţi ce erori apar la compilare. De exemplu:
  

scrieti incorect cuvintele public, static, main, string, System. println; omiteţi unele dintre acolade sau paranteze; puneţi acolade sau paranteze în plus.

Puteţi introduce intenţionat şi mai multe din erorile de mai sus simultan. Compilaţi fişierul astfel modificat şi interpretati mesajele de eroare obţinute.

Executarea aplicaţiei
Dacă în directorul curent există fişierul <nume_aplicaţie>.class, executarea acestei aplicaţii se solicită prin comanda
java <nume_aplicaţie>

Remarcăm că se dă ca parametru numai numele clasei, fără extensia class. Efectul acestei comenzi este, fie executarea aplicatiei, fie aparitia unui mesaj de eroare de execuţie. Prin comanda java se pune în execuţie interpretorul maşinii virtuale Java. Acest interpretor verifică dacă în directorul curent există fişierul <nume_aplicaţie>.class. Daca acesta există, încarcă în memorie codul de octeţi pe care îl conţine şi pune în execuţie metoda public static void main(). Dacă fişierul nu există, sau dacă el nu conţine o astfel de metodă, se semnalează printr-un mesaj de eroare. Exemplul 1 Considerăm că a reuşit compilarea fişierului corect PrimaAplicatie.java şi s-a obţinut astfel fişierul PrimaAplicatie.class, după care se dă comanda
java PrimaAplicatie

Ca rezultat, pe ecran apare textul
Prima noastra aplicatie a reusit!

Exemplul 2 Să dăm acum comanda
java PimaApicatie

în care numele clasei este dat greşit. Ca rezultat, obţinem mesajul de eroare
Exception in thread "main" java.lang.NoClassDefFoundError: PimaApicatie

Acest mesaj arată că nu s-a găsit definiţia clasei PimaAplicatie (Eroarea constatată se deduce din numele acesteia: NoClassDefFoundError provine de la "no class definition found error", adica nu a fost găsită definiţia clasei PimaApicatie. Exemplul 3 Să considerăm acum că am eliminat erorile de compilare din fişierul PrimaAplicatie1.java şi am obţinut fişierul PrimaAplicatie1.class, dar că metoda main a rămas fără modificatorii public static. Am arătat că lipsa acestor modificatori nu dă eroare de compilare. Dând acum comanda
java PrimaAplicatie1

se obţine mesajul de eroare

24

Programarea orientata pe obiecte în limbajul Java
Exception in thread "main" java.lang.NoSuchMethodError: main

"No such method" se traduce prin "nu exista o astfel de metoda". Urmeaza două puncte şi numele metodei care nu există: main. Noi ştim că, în realitate, în clasa PrimaAplicatie1 exista o metodă numita main, numai că nu este cea pe care o caută interpretorul java pentru a pune aplicaţia in execuţie: acesta caută metoda
public static void main(String args[])

care, în această clasă, nu există.

Întrebări
Nivel 1
1. Ce este Java? 2. Ce calităţi are limbajul de programare Java? 3. Ce conţine mediul de execuţie Java? 4. Ce conţine platforma Java? 5. Ce este programarea orientată pe obiecte? 6. Ce este clasa? 7. Ce este obiectul şi ce legatură există între clasă şi obiect? 8. Prin ce se determină starea obiectului? 9. Prin ce se defineşte comportamentul obiectului? 10. Ce deosebire există între variabilele statice şi cele nestatice? 11. Ce deosebire există între metodele statice şi cele nestatice? 12. Ce deosebire există între datele sau metodele publice şi cele private? 13. Care sunt principalele tipuri de produse software care pot fi scrise în limbajul Java? 14. Ce deosebire există între applet şi aplicatie? 15. Ce deosebire exista între applet şi servlet? 16. Care este metoda care trebuie să existe în mod obligatoriu în orice aplicaţie? 17. Ce este un fişier sursă Java şi cum se stabileşte numele lui? 18. Ce fel de editor se foloseşte pentru crearea fişierului sursă? 19. Prin ce comandă se cere compilarea unui fişier sursă Java? 20. Ce se obţine în cazul în care compilarea a decurs corect? 21. Ce se obţine dacă compilarea nu a reuşit? 22. Prin ce comandă se cere executarea unei aplicaţii?

Nivel 2
1. 2. 3. 4. 5. 6. 7. 8. Dece limbajul Java este mai simplu decât C/C++? Limbajul Java este compilat sau interpretat? Ce fel de cod generează compilatorul Java? Ce se înţelege prin portabilitatea programelor? Dece limbajul Java este neutru faţă de arhitectură? Dece limbajul Java permite programarea concurentă? Ce este maşina virtuală Java? Ce deosebire este între specificaţia maşinii virtuale Java şi implementarea acesteia?

25

Severin Bumbaru 9. Ce sunt JDK 1.0, JDK 1.1 şi JDK 1.2? 10. Ce este SDK si ce legatura există între JDK şi SDK? 11. Ce este API? 12. Ce platforme de dezvoltare pentru Java se folosesc în prezent? 13. Ce este un navigator de Web? 14. Ce este un browser? 15. Ce este WWW? 16. Ce navigatoare de Web cunoaşteti? 17. Daţi şablonul unei aplicaţii simple şi explicati părţile componente. 18. Ce se întâmplă dacă în comanda javac numele fişierului sursă este introdus greşit? 19. Ce se întâmplă dacă în comanda javac numele fişierului sursă este introdus fără extensia java? 20. Cum sunt semnalate de compilator erorile care apar în program? 21. Ce se întâmplă dacă în faţa metodei main se omit modificatorii public static?

26

Programarea orientata pe obiecte în limbajul Java

Şiruri. Elementele lexicale ale limbajului Java. Variabile şi tipuri de date primitive
Comentarii în fişierul sursă Şiruri; concatenarea şirurilor; Metode pentru afişarea pe ecran a şirurilor; Unităţi lexicale ale limbajului Java identificatori cuvinte cheie şi cuvinte rezervate literali separatori şi operatori spaţii şi comentarii Variabile; Declararea şi iniţializarea variabilelor; variabile finale; Tipuri de date primitive şi declaraţii de tip tipul boolean tipuri numerice tipuri de date întregi tipuri de date în virgulă mobilă tipul char Întrebări 27 28 29 30 30 31 32 32 34 34 35 36 39 41 45 57 61 63

Comentarii în fişierul sursă
Comentariile sunt texte care pot fi introduse în programul sursă, dar nu sunt luate în consideraţie de compilator şi deci nu au efect în timpul executării programului. Comentariile sunt utile pentru o mai buna înţelegere a programului de către om. În programele Java, se pot folosi trei feluri de comentarii: a/ comentarii care se pot extinde pe una sau mai multe linii şi au forma
/* <comentariu> */

b/ comentarii de sfarsit de linie, care incep cu simbolul // si continua pana la sfarsitul liniei respective, deci au forma
// <comentariu>

c/ comentarii care sunt destinate reproducerii în documentaţia clasei şi au forma
/** <comentariu>*/

deci se deosebesc formal de cele de la punctul a numai prin faptul ca incep cu simbolul /**. Primele două forme de comentariu există şi în limbajul C++, în timp ce ultima este specifică numai limbajului Java.

27

Severin Bumbaru Exemplu Dăm aici ca exemplu fişierul Comentarii.java, care are la bază fişierul PrimaAplicatie.java, dar acesta fost completat cu comentarii.
/** Aplicatie in care se exemplifica folosirea celor trei tipuri de comentarii permise in limbajul Java */ class Comentarii { // aici incepe definitia clasei Comentarii /* metoda main este metoda principala a clasei, cu care incepe executarea programului */ public static void main(String args[]) { /* Urmeaza instructiunea prin care se invoca metoda println pentru afisarea unui text */ System.out.println("Prima noastra aplicatie a reusit!"); } } // sfarsitul metodei main

// sfarsitul clasei Comentarii

Dacă vom compila şi pune în executie această aplicaţie, vom constata că efectul este acelaşi ca în cazul clasei PrimaAplicaţie, deoarece comentariile introduse în fişierul sursă nu au efect în timpul execuţiei programului. OBSERVAŢIE: Comentariile sunt absolut necesare pentru înţelegerea programului. În acelaşi timp, abuzul de comentarii poate să facă urmarirea acestuia dificilă. Este, deci, recomandabil să se introducă în program toate comentariile necesare pentru o bună înţelegere a acestuia, dar nu mai mult. La aprecierea programelor scrise de Dvs. se va lua întotdeauna în consideraţie prezenţa şi calitatea comentariilor.

Şiruri
Şirul se reprezintă în program printr-o succesiune de caractere cuprinsă între ghilimele. Iată câteva exemple de şiruri:
"sir de caractere" "ABCDefgh" "1A23bc7" "*+_/?" ""

Ultimul exemplu este un şir vid (care nu conţine nici un caracter).

Concatenarea şirurilor
Asupra şirurilor se poate aplica operaţia de concatenare, reprezentata prin operatorul +. Expresia şir1+şir2, în care operatorul + este plasat între două şiruri, are ca rezultat un nou şir, care conţine cele două şiruri-operanzi puse unul după altul. De exemplu, expresia
"abcde"+"fgh"

dă ca rezultat şirul "abcdefgh".

28

Programarea orientata pe obiecte în limbajul Java Operaţia de concatenare este asociativă, dar nu este comutativă. De exemplu, expresiile următoare sunt echivalente, ca urmare a asociativităţii:
"ABC"+"DEF"+"GH" ("ABC"+"DEF")+"GH" "ABC"+("DEF"+"GH") "ABCDEFGH"

În schimb, expresia "uvw"+"ab" este echivalentă cu "uvwab", în timp ce expresia "ab"+"uvw" este echivalentă cu "abuvw", deci concatenarea nu este comutativă.

Metode pentru afişarea pe ecran a şirurilor
În capitolul precedent, am folosit deja pentru afişarea şirurilor de caractere metoda
System.out.println(<şir>)

la care vom adăuga acum şi metoda În este un şir de caractere, care se afişeaza pe ecran. Deosebirea dintre ele constă în faptul că, după afişarea şirului, metoda println transmite şi comanda de trecere la linie nouă, în timp ce metoda printnu transmite o astfel de comandă. În consecinţă, în cazul afişării cu println, următoarea afişare se va face de la început de linie nouă, iar la afişarea cu print afişarea următoare se va face în continuare, pe aceeaşi linie. Numele metodei println provine de la print line, care se traduce prin "tipăreşte o linie". Metodele println siprint aparţin obiectului out din clasa System. În limbajul Java, clasa System conţine metodele prin care se comunică cu sistemul de operare al calculatorului, iar obiectul out al acestei clase reprezintă dispozitivul de ieşire standard al sistemului, care este de obicei unitatea de afişare de la consolă (ecranul). Metoda println actionează la fel ca metoda print, cu deosebirea că adaugă la sfârşitul şirului afişat caracterul de control \n care constituie comanda de trecere la linie nouă (New Line). Exemplu Considerăm urmatoarea aplicaţie, conţinută în fişierul AfisareSiruri.java:
/* Exersarea metodelor print si println */ class AfisareSiruri { public static void main(String args[]) { System.out.println("sirul 1"); System.out.println("sirul 2"); // se afiseaza sub sirul 1 System.out.println("AB"+"CDE"); // se afiseaza ABCDE System.out.println("ab"+"cd"+"ef"); // se afiseaza abcdef System.out.println(("ab"+"cd")+"ef"); // asociativitate System.out.println("ab"+("cd"+"ef")); /* Urmatoarele trei instructiuni afiseaza in continuare, pe o singura linie. */ System.out.print("pqrst"); // nu se trece la linie noua System.out.print("UVW"); // se afiseaza in continuare System.out.print("xyz\n"); // echivalent cu println("xyz") /* Trecerea la linia urmatoare se face datorita prezentei System.out.print(<şir>) ambele metode, argumentul <şir>

29

Severin Bumbaru

caracterului \n in sirul "xyz\n" */ System.out.println("ultima linie afisata"); } }

Compilând şi executând acest program, putem constata că:
  

după executarea fiecărei metode println se trece pe ecran la o linie noua; expresiile "ab"+"cd"+"ef", ("ab"+"cd")+"ef" si"ab"+("cd"+"ef") dau rezultate identice. print("xyz\n") are acelasi efect cu println("xyz").

Unităţile lexicale ale limbajului Java
Unităţile lexicale, numite şi lexeme (engl. token, lexeme) sunt construcţiile elementare ale limbajului ("atomii" acestuia). Acestea sunt simboluri, formate din unul sau mai multe caractere, care au o anumită semnificaţie în limbaj. După rolul îndeplinit, unităţile lexicale sunt: identificatori, cuvinte cheie, cuvinte rezervate, literali, separatori,operatori, comentarii şi spaţii. De exemplu, în programul din fişierul AfisareSiruri.java distingem urmatoarele unităţi lexicale: - cuvinte cheie: class, public, static, void; - identificatori: AfisareSiruri, main, String, args, System, out, print, println; - literali: "sirul 1", "sirul 2", "AB", "CDE", "ab", "cd", "ef", ; - separatori: { } ( ) [ ] , ; - operator: +; - comentarii: /* Exersarea metodelor print si println */
// se afiseaza sub sirul 1

Vom analiza acum separat fiecare din aceste categorii de unităţi lexicale.

Identificatori
Numele date programelor sau componentelor acestora (clase, variabile, metode etc.) se numesc identificatori. Identificatorii se aleg de către programator, respectând anumite reguli. În limbajul Java, identificatorii sunt şiruri formate din litere, cifre şi caractere de subliniere ('_'), care încep cu o literă. Lungimea identificatorului nu prezintă importanţă, însă acesta nu poate conţine spaţii libere sau alte caractere, decât cele menţionate aici.

Exemple de identificatori valabili:

30

Programarea orientata pe obiecte în limbajul Java

PrimaClasa aplha viteza v15XB7 pretDeVanzare pret_de_vanzare

Este clar acum că şi exemplele date anterior ( Afisari, main, String, args, System, out, print, println) sunt, de asemenea, identificatori. Se obişnuieşte ca numele de clase să înceapă cu literă majusculă. De asemenea, se obisnuieşte ca separarea între cuvinte, în cadrul identificatorilor compuşi din mai multe cuvinte ale limbajului natural, să se facă incepând fiecare cuvant nou cu literă majusculă, dar se poate face şi prin caracterul de subliniere '_'. Acestea nu sunt însă reguli sintactice, ci doar convenţii neobligatorii. Programatorul poate adopta orice identificatori care respectă regulile şi convenţiile de mai sus şi care nu sunt cuvinte cheie sau cuvinte rezervate. Desigur însă că folosirea unor identificatori care au semnificaţie pentru om, cum ar fi viteza sau PretDeVanzare este preferabilă celor fără semnificaţie, cum ar fi v15XB7, deoarece uşurează înţelegerea şi urmărirea programului. Amintim însa că, pentru calculator, identificatorii nu au nici o alta semnificaţie, deci, din acest punct de vedere, toate exemplele de identificatori date aici sunt la fel de bune.

Cuvinte cheie
În orice limbaj de programare, există un set de cuvinte, numite cuvinte cheie, care sunt considerate simboluri sintactice şi nu pot fi folosite în program ca identificatori. În limbajul Java, există următoarele cuvinte cheie:
abstract boolean break byte case catch char class const continue default do double else extends final finally float for goto if implements import instanceof int strictfp super switch synchronized this throw throws transient try void volatile while

interface
long native new package private protected public return short static

Dintre acestea, const şi goto nu sunt folosite în prezent, dar ele au fost introduse în tabela cuvintelor cheie în vederea unei eventuale utilizări viitoare. Observăm acum că toate exemplele de cuvinte cheie date la începutul acestei secţiuni (class, public, static, void) sunt prezente în tabela de mai sus.

31

Severin Bumbaru

Cuvinte rezervate
Se consideră cuvinte rezervate acele cuvinte, care nu pot fi folosite ca identificatori, având semnificaţii speciale. Cuvintele cheie sunt şi ele considerate în majoritatea limbajelor, inclusiv Java, drept cuvinte rezervate. În afară de acestea, în limbajul Java există urmatoarele cuvinte rezervate: true, false, null. Primele două sunt valorile logice adevărat şi fals, iar al treilea are semnificaţia de referinţă nulă. De fapt, aceste cuvinte rezervate sunt forme speciale de literali.

Literali
Literalii sunt reprezentările în fişierele sursă ale valorilor constante. Exemple de literali: - caractere: 'a', 'A', '+', '$', '5'; - şiruri de caractere: "sir de caractere", "abc$79.28#^z"; - numere întregi: 14726, -25413; - numere reale: 12.7389, -0.05673, 2.3075E12, -1.4237E-5; - valori logice: true, false; - referinţa nulă: null. Am subliniat faptul că literalul este forma sub care o anumita valoare este reprezentată în fişierul sursă, deci în programul scris în limbaj de nivel superior (în cazul nostru în limbajul Java). Vom arăta ulterior că forma de reprezentare a aceloraşi valori în memoria internă a calculatorului (forma internă) este diferită de cea externă. Vom reveni asupra regulilor de scriere a literalilor când vom prezenta tipurile de date din limbajul Java.

Separatori
Separatorul este un caracter care delimitează formele sintactice sau le separă între ele. În limbajul Java se folosesc următorii separatori:
{ } ( ) [ ] ; , .

Spaţiul liber şi operatorii indeplinesc, de asemenea, rolul de separatori. Aproape toţi aceşti separatori au fost deja folosiţi în exemplele date în acest capitol.

Operatori
Operatorii sunt simboluri ale unor operaţii. Am folosit deja simbolul + ca operator de concatenare (deci simbol al operaţiei de concatenare). Operatorul poate fi format din unul sau mai multe caractere. Entitatea asupra căreia se aplică operatorul se numeşte operand. După numărul de operanzi, operatorii pot fi unari, binari sau ternari.

După numărul de operanzi deosebim: operatori unari, care se aplică unui singur operand, de ex. operatorul - în expresia -x; utilizarea operatorului unar se face, de regula, sub forma "prefix" operator operand, în care operatorul se pune în faţa operandului; uneori însă se foloseste şi forma "postfix"
operand operator

în care operatorul se pune după operand;

32

Programarea orientata pe obiecte în limbajul Java operatori binari, care se aplică asupra a doi operanzi, operatorul fiind situat între aceştia; de ex. operatorul de concatenare + în expresia "acesta este " + "un exemplu" sau operatorul de adunare a două numere + în expresia 17+28.Operatorii binari se folosesc sub forma "infix"
operand1 operator operand2

în care operatorul este situat între cei doi operanzi; operatori ternari, care se aplică asupra a trei operanzi; în limbajul Java există un singur operator ternar ( ? :) folosit în expresiile condiţionale. Operatorul ternar se scrie sub forma
operand1 ? operand2 : operand3

în care operand1 are o valoare logică (true sau false), iar ceilalti doi operanzi sunt expresii aritmetice sau logice (dar de acelaşi tip). Din exemplele de mai sus, observăm că semnificaţia unui operator poate să depindă de context, ca în cazul operatorului +, care a fost folosit atât pentru operaţia de concatenare a şirurilor, cât şi pentru cea de adunare a numerelor. Din punct de vedere matematic, operatorii sunt funcţii cu unul, două sau trei argumente (argumentele fiind operanzii). De exemplu, expresia a+b, în care + este un operator binar, iar a şi b sunt operanzi, este o funcţie de argumente a si b, care are ca valoare suma valorilor celor două argumente. După efectul operatorului asupra operanzilor, operatorii pot fi fără efect lateral, care lasa valorile operanzilor nemodificate, şi cu efect lateral, care modifică valorile operanzilor. Astfel, operatorul + din exemplul anterior, este un operator fără efect lateral. În schimb, în expresia ++a operatorul de incrementare ++ are efect lateral deoarece, în urma efectuarii operaţiei, valoarea operandului a creşte cu o unitate.

Dăm aici o listă a operatorilor folosiţi în limbajul Java. Operatori unari:
+ () ++ [] -{} new ! ~

Operatori binari
+ == & && << = . <= | || >> += * >= ^ / != %

>>> -= *= /= instanceof

&=

|=

^=

~=

<<=

>>=

>>>=

Operator ternar
?:

Remarcăm că operatorii + şi - pot fi atât unari ( ca în expresiile +a sau -a), cât şi binari (ca

33

Severin Bumbaru în expresiile a+bsaua-b). Semnificaţiile operatorilor vor fi arătate când se vor prezenta tipurile de date şi expresiile.

Comentarii
După cum s-a aratat deja, în fişierele sursă pot fi introduse comentarii, care au rolul de a da omului,care citeşte programul respectiv, anumite explicaţii necesare pentru o mai buna înţelegere a acestuia. Din punct de vedere sintactic, întregul comentariu este privit ca o singură unitate lexicală, care este ignorată de către compilator, deci nu are efect asupra codului de octeţi generat de acesta.

Spaţii
Între unităţile lexicale ale programului pot fi introduse oricât de multe spaţii libere, fără ca acestea să aibă influenţă asupra sintaxei sau semanticii programului. Mai multe spaţii libere succesive sunt tratate de compilator ca şi când ar fi un singur spaţiu.

Variabile
In matematică, variabila este un simbol dat unei valori, care aparţine unei mulţimi de valori ce constituie domeniul de definiţie al variabilei respective. În programare, variabila este un nume căruia i se asociază o valoare. Numele variabilei este un identificator, iar valoarea variabilei trebuie să aparţină unui anumit tip de date. Asupra valorilor variabilelor pot fi efectuate prin program anumite operaţii.

Exemplul 1 In expresia x=a+2, x şi a sunt variabile. Operaţiile efectuate sunt următoarele: se adună valoarea variabilei a cu valoarea 2 (dată aici sub formă de literal), iar rezultatul se atribuie ca valoare variabilei x; în consecinţă, valoarea variabilei a ramâne nemodificată, în schimb variabila x primeşte o nouă valoare. Operatorul = nu exprima aici relaţia de egalitate, ci operaţia de atribuire a unei valori unei variabile. Exemplul 2 Expresia System.out.println(a) are ca efect afişarea pe ecran a valorii variabilei a.

Valoarea variabilei trebuie să fie reprezentată în memoria calculatorului la o anumită adresă şi să ocupe acolo un anumit spaţiu (un anumit număr de biţi). În consecinţş, numim variabilă o zonă de memorie care poarta un nume şi care conţine o anumită valoare, apartinând unui tip de date. Programatorul care foloseşte un limbaj de nivel înalt, cum este şi limbajul Java, nu trebuie să cunoască adresa de memorie, la care este plasată valoarea variabilei, şi nici reprezentarea

34

Programarea orientata pe obiecte în limbajul Java internă a acesteia, fiind suficient să-i cunoasca numele şi tipul. Alocarea de spaţiu în memorie pentru fiecare variabilă se face, după caz, de către compilator sau interpretor. În schimb, numele şi tipul variabilei trebuie declarate de către programator. Remarcăm că în matematică operaţiile cu variabile se fac, de cele mai multe ori, la nivel abstract, asupra simbolurilor variabilelor şi nu asupra valorilor acestora. De exemplu, în identitatea a+a=2.a nu are importanţa ce valoare are variabila a, egalitatea fiind întotdeauna adevarată. În programare se are în vedere faptul că, atât timp cât ea exista în memorie, variabila are întotdeauna o valoare (deoarece zona de memorie aferentă nu poate fi vidă), iar operaţiile se fac asupra valorilor variabilelor şi nu asupra numelor acestora.

Declararea şi iniţializarea variabilelor
În limbajul Java, orice variabilă trebuie declarată înainte de a fi utilizată. Prin declararea variabilei se înţelege precizarea, pentru compilator, a tipului şi numelui acesteia. Iniţializarea variabilei se face atunci, când acesteia i se dă pentru prima dată o valoare şi deci i se alocă spaţiu în memorie. Dacă, la declararea variabilei, aceasta nu este şi iniţializată în mod explicit, atunci ea este iniţializata cu o valoare implicită, care va fi specificată la descrierea fiecărui tip. Iată un exemplu de declaraţie de variabilă:
int alpha, beta=3702, k;

Aceasta este o instrucţiune din program, prin care se specifică următoarele:
   alpha, beta şi k sunt numele unor variabile de tipul int; variabilei beta i se dă valoarea iniţiala 3702; variabilelor alpha si k li se dau valori iniţiale implicite corespunzătoare int (în cazul de faţă valoarea 0).

tipului

Din exemplul de mai sus, se observă că declaraţia de tip are urmatoarea formă:
tip variabila_1, ..., variabila_n;

în care:
tip - numele tipului de date căruia îi aparţin variabilele declarate; variabila_i - specificarea numelui unei variabile, urmat opţional variabilei respective precedată de simbolul =.

de valoarea

Remarcam că:
 

declaraţia de tip este o instructiune care se termină obligatoriu prin simbolul ; (punct şi virgulă); este posibil ca, într-o singură declaraţie, să apară mai multe variabile; în acest caz,

35

Severin Bumbaru specificaţiile variabilelor respective sunt separate prin virgule; indicarea valorii variabilei este opţională.

În declaraţia de tip, valoarea iniţială a variabilei poate fi dată sub forma unui literal sau a unei expresii. În ultimul caz, este necesar ca expresia să fie calculabilă, deci toate variabilele pe care le conţine să aiba deja valori date anterior. Prin convenţie, în limbajul Java numele de variabile încep întotdeauna cu literă mică. Este permis însă ca, în interiorul numelui, sa existe şi litere mari. Aceasta se întamplă atunci când numele variabilei este format din mai multe cuvinte ale limbii naturale, de exemplu vitezaMedie. Ulterior, la descrierea fiecărui tip de date, vom da şi exemple de declarare a variabilelor de tipul respectiv.

Variabile finale
În limbajul Java, se numesc variabile finale acele "variabile", ale căror valori nu pot fi modificate prin program. Acestea sunt deci, de fapt, nişte constante cu nume. Ele se aseamănă cu variabilele propriu-zise prin faptul că sunt tot perechi nume - valoare, numai că valoarea lor se dă o singură dată, sub forma de iniţializare în declaraţia de tip sau sub forma de atribuire, după care nu mai poate fi modificată. Se obişnuieşte ca numele de variabile finale să fie scrise în întregime cu majuscule. Declaraţia de tip este la fel cu cea pentru variabile obişnuite, dar are in faţă modificatorul final, care este un cuvânt cheie.

De exemplu, declaraţia
final int ALPHA=17, BETA=-1453;

serveşte pentru a specifică faptul că ALPHA şi BETA suntvariabile finale de tip int, ale caror valori sunt, respectiv, 17 si -1453 şi nu mai pot fi ulterior modificate (deci ALPHA şi BETA sunt, de fapt, nişte constante).

Tipuri de date primitive
Tipul de date este unul din conceptele fundamentale ale programării calculatoarelor. Tipul de date este o mulţime de valori, asociată cu o mulţime de operaţii care se pot face asupra valorilor respective. În limbajul Java, tipurile de date se împart în două categorii: tipuriprimitive şi tipuri referinţă.

36

Programarea orientata pe obiecte în limbajul Java Tipurile de date primitive sunt predefinite în limbaj. Aceasta înseamnă că numele, mulţimea de valori, mulţimea de operaţii şi tipul rezultatului operaţiilor pentu fiecare tip primitiv sunt impuse prin limbaj şi, deci, nu trebuie definite şi nu pot fi modificate de programator. Tipurile de date primitive în limbajul Java se clasifică astfel:
 

tipul boolean; tipurile numerice o tipuri intregi: byte, short, int, long; o tipuri reale: float si double; o tipul char

Pentru fiecare tip de date vom arăta reprezentarea externă, reprezentarea internă, operaţiile şi operatorii corespunzători. Prin reprezentare externă, înţelegem regulile după care se scriu valorile datelor respective în programe, în documente sau pe ecranul calculatorului. Reprezentarea externă a valorii într-un program se numeşte literal. Prin reprezentare internă, înţelegem forma sub care datele respective apar în memoria maşinii virtuale Java. O proprietate foarte importantă a reprezentării interne a datelor este că aceasta, fiind destinată maşinii virtuale Java, nu depinde de calculatorul concret pe care se va executa programul. Vom începe studiul cu tipul de date boolean, apoi vom studia tipurile numerice propriu-zise (intregi şi reale), după care vom studia tipul char, ca un tip numeric special.

Operaţii şi operatori
Pentru fiecare tip primitiv de date vom arăta, de asemenea, operaţiile specifice şi operatorii corespunzători. Pentru început, vom prezenta aici operaţia de atribuire şi operatorii relaţionali == şi != care se aplică tuturor tipurilor de date. Celelalte operaţii şi operatorii corespunzători se vor fi prezenta odată cu tipurile de date cărora li se aplică.

Operaţia de atribuire
Prin operaţia de atribuire se dă (se atribuie) unei variabile o nouă valoare, care o înlocuieşte pe cea deja existentă. Operatorul de atribuire este = (semnul egal, care însă aici se citeşte "se atribuie") este un operator binar cu efect lateral. Expresia a=b, în care a este o variabila, iar b este un operand care poate fi un literal, o variabilă sau o expresie, are semnificaţia "se atribuie variabilei a valoarea operandului b". Atribuirea este posibilă numai daca valoarea operandului b este de acelasi tip cu variabila a, sau dacă se poate converti implicit la acest tip. Atribuirea este o operaţie cu efect lateral, deoarece produce modificarea valorii operandului situat în partea stânga a operatorului de atribuire. Exemplu Fie x o variabilă de tip int. Expresia x=-17 se citeste "se atribuie lui x valoarea -17". Efectul operaţiei de atribuire este, în acest caz, că valoarea anterioară a variabilei x se înlocuieşte în memorie prin noua valoare. În partea dreaptă a operatorului de atribuire putea fi nu numai un literal, ca în cazul nostru, ci orice altă expresie cu valoare de tip int.

37

Severin Bumbaru

Operatorii

==

şi

!=

Pentru toate tipurile de date se pot aplica operatorii relaţionali == si !=. Aceştia sunt operatori binari fără efect lateral. La aplicarea unui astfel de operator, rezultatul operaţiei este valoarea booleană true (adevărat) sau false (fals). Operatorul == (se citeşte "este egal cu") exprimă relaţia de egalitate. Expresia a==b, unde a şi b sunt doi operanzi care pot fi literali, variabile sau expresii, are valoarea logică (booleana) true dacă valoarile celor doi operanzi sunt egale, sau are valoarea logică false, dacă egalitatea nu este satisfacută. Cei doi operanzi trebuie sa fie comparabili, deci fie ambii de tip boolean, fie ambii numerici.

Atragem atenţia asupra deosebirilor esenţiale dintre operatorul de atribuire = si operatorul de egalitate ==. a/ Operatorul de atribuire = impune ca operandul din partea stangă sa fie o variabilă, în timp ce în cazul operatorului de egalitate == ambii operanzi pot fi expresii. b/ Operatorul de atribuire = are efect lateral, care constă în modificarea valorii variabilei din partea stangă, în timp ce operatorul == nu are efect lateral, deci valorile ambilor operanzi rămân neschimbate; c/ Valoarea expresiei de atribuire a=b este identică cu valoarea atribuita variabilei a şi are deci tipul acesteia, în timp ce valoarea expresiei a==b este întotdeauna de tip boolean, indiferent de tipul operanzilor. Operatorul != (se citeşte "este diferit de") exprimă relaţia de inegalitate a celor doi operanzi. Expresia a!=b, unde a şi b sunt doi operanzi care pot fi literali, variabile sau expresii, are valoarea logică (booleană) false dacă valoarile celor doi operanzi sunt diferite, sau are valoarea logică true, dacă operanzii au valori egale.

Declaraţii de tip
Declararaţiile de tip sunt instrucţiuni prin care se specifică tipul, numele şi, dacă este necesar, valoarea iniţială a variabilelor folosite în program. În limbajul Java, declaraţiile de tip au forma:
tip variabila1, variabila2, ..., variabilaN;

în care:
tip - tipul variabilelor care se declară; variabila- numele variabilei sau, daca este necesar, numele şi valoarea iniţială date forma nume_variabilă = valoare_iniţială nume_variabila este un identificator; în limbajul Java se obişnuieşte ca numele de

sub

variabile să înceapă cu literă mică, deşi aceasta nu este o regulă de sintaxă; valoare_initiala este o valoare de acelaşi tip cu variabila, care se dă variabilei la iniţializare (în momentul când i se alocă spaţiu în memorie). Această valoare poate fi dată sub forma unui literal sau a unei expresii calculabile (în care toate variabilele au valori atribuite anterior). Remarcăm că specificaţiile variabilelor se separă între ele prin virgule, iar la sfârşitul declaraţiei se pune simbolul ; (punct şi virgulă) care, în limbajul Java, este terminatorul de

38

Programarea orientata pe obiecte în limbajul Java instructiune.

Limbajul Java este strict tipizat, deci orice variabilă trebuie să fie declarată înainte de a fi folosită. În plus, compilatorul Java verifică dacă, în momentul primei ei utilizări într-o expresie, variabila are deja o valoare. Exemplu Prin instrucţiunea
int alpha, beta=-723, gamma=beta+7; se declară că variabilele alpha, beta şi gamma sunt de tip int. Variabila alpha nu este iniţializată, beta primeşte valoarea iniţială -723, iar gamma primeşte ca valoare rezultatul calculării expresiei beta+7. Această expresie este calculabilă, întrucât beta are deja o

valoare.

Tipul boolean
Mulţimea de valori a acestui tip este {true, false}. Ea conţine cele două valori admise de logica booleană: true înseamnăadevărat, iar false înseamnă fals. Asupra datelor din acest tip pot fi aplicate operaţiile de atribuire, de comparaţie (== si !=) şi operaţiile algebrei booleene (operaţiile logice).

Operatorii booleeni
Operatorul de negaţie este un operator unar fără efect lateral şi se reprezintă prin simbolul ! (semnul exclamării). Expresia !a, în care a este un operand boolean, se citeşte non-a şi se interpretează ca negaţia lui a: daca a are valoarea true, atunci !a are valoarea false şi invers. Operatorii logici binari sunt operatori fără efect lateral, prin care se realizează operaţiile logice ŞI, SAU şi SAU-EXCLUSIV. - Operatorii & si && realizeaza operatia logica ŞI. Expresiile a&b şi a&&b ,în care a şi b sunt operanzi de tip boolean, are valoarea true(adevărat) dacă şi numai dacă atât a cât şi b au valoarea true. În celelalte cazuri expresia are valoarea false. - Operatorii | si || realizează operaţia logică SAU. Expresiile a|b şi a||b , în care a şi b sunt operanzi de tip boolean, are valoarea false dacă şi numai dacă ambii operanzi au valoarea false. În celelalte cazuri expresia are valoarea true. - Operatorul ^ realizează operatia logică SAU-EXCLUSIV. Expresia a^b , în care a şi b sunt operanzi de tip boolean, are valoarea true dacă şi numai dacă cei doi operanzi au valori diferite (unul este adevărat, iar celălalt fals). Dacă cei doi operanzi au valori identice, valoarea expresiei este false.

Deosebirile între operatorii & şi &&, respectiv între | şi || sunt următoarele: - în cazul operatorilor & şi | se evaluează în mod obligatoriu ambii operanzi; - în cazul operatorului &&, numit ŞI-condiţional, evaluarea celui de al doilea operand se face numai dacă primul operand are valoarea true; altfel, se consideră că operaţia dă valoarea false, fără a se mai evalua valoarea celui de al doilea operand;

39

Severin Bumbaru - în cazul operatorului ||, numit SAU-condiţional, evaluarea celui de al doilea operand se face numai dacă primul operand are valoarea false; altfel, se consideră ca operaţia dă valoarea true, fără a se mai evalua valoarea celui de al doilea operand. Vom reveni asupra acestor deosebiri când vom arăta cum sunt tratate în Java expresiile logice mai complicate. Acţiunea operatorilor logici este prezentată sintetic în tabela de mai jos, în care a şi b sunt doi operanzi logici.
a true true false false b true false false false a&b true false false false a&&b true false false false a|b true true false false a||b true true true false a^b false true true false

În programul din fişierul TipBoolean.java se testează unele din aceste operaţii booleene.

/* Testarea declararii variabilelor booleene si a efectului operatiilor logice */ class TipBoolean { public static void main(String args[]) { boolean alpha=true, beta=false, p, q, r,s; p=!alpha; q=alpha&&beta; r=alpha||beta; s=alpha^beta; System.out.println(" alpha="+alpha+" beta="+beta+" p="+p+ " q="+q+" r="+r+" s="+s); System.out.println("alpha&&beta="+(alpha&&beta)+ "alpha||beta="+(alpha||beta)); System.out.println("alpha==beta: "+(alpha==beta)); System.out.println("alpha!=beta: "+(alpha!=beta)); } }

Executând acest program se obţine următorul rezultat afişat pe ecran:

alpha=true beta=false p=false q=false r=true s=true alpha&&beta=false alpha||beta=true alpha==beta: false alpha!=beta: true

Se observă cu uşurinţă că rezultatele sunt corecte.

40

Programarea orientata pe obiecte în limbajul Java Pentru programatorii de C/C++ În limbajele C/C++ nu există tipul de date boolean, astfel că în locul acestuia se folosesc datele întregi. În consecinţă, în aceste limbaje, operatorii & si | nu sunt consideraţi operatori booleeni ci operatori logici pe biţi. În Java nu este permis să se utilizeze expresii aritmetice în locul unor expresii logice (booleene), aşa cum se întâmplă în C/C++.

Tipuri numerice
Sub aspect conceptual, datele care aparţin acestor tipuri sunt numere, asupra cărora pot fi aplicate operaţiile aritmetice (adunare, scădere, înmulţire, împărţire) şi operaţiile de comparaţie aritmetică (mai mic, mai mare, egal, diferit de). Din punct de vedere matematic, aceste date pot fi numere întregi sau reale. Existenţa mai multor tipuri în cadrul fiecăreia din aceste două categorii se datoreşte particularităţilor de reprezentare a datelor în memorie. Tipurile de date numerice în Java sunt următoarele:
  

tipuri întregi: byte, short, int, long; tipuri reale (în virgulă mobilă): float şi double; tipul char

Inainte de a trece la studierea fiecărui tip de date în parte, vom prezenta unele operaţii care se aplică tuturor tipurilor de date numerice: atribuirea, conversia de tip, operatiile ariţmetice şi comparaţia. Exemple pentru aplicarea acestor operaţii se vor da la studierea diferitelor tipuri concrete de date numerice.

Operaţia de atribuire
Operaţia de atribuire se poate aplica tuturor tipurilor de date, deci şi celor numerice. În expresia
variabilă = expresie

daca variabila din partea stângă aparţine unuia din tipurile numerice, atunci valoarea expresiei din partea dreaptă trebuie sa fie, de asemenea, numerică şi să aibă un tip compatibil cu cel al variabilei din partea stângă. Prin tip compatibil înţelegem fie acelaşi tip cu cel al variabilei din stânga, fie un tip numeric care poate fi convertit implicit la acesta. Dacă tipul operandului din dreapta este numeric, dar nu se converteşte implicit la cel din stânga, se poate folosi conversia de tip explicită prin operatorul cast. În acest caz, însă, există pericolul ca valoarea să se altereze prin conversie.

Conversia de tip
Dacă este necesar, datele pot fi convertite dintr-un tip în altul. După caz, conversia se poate face implicit, sau poate fi cerută explicit prin program.

41

Severin Bumbaru În limbajul Java, conversia de tip implicită se face atunci când prin conversie nu se pierde informaţie. De exemplu, dacă în expresia a=b variabila a este de tip int, iar b este de tip short sau byte, valoarea variabilei b va fi automat convertită la tipul int înainte de atribuire.

În tabela de mai jos sunt indicate cu X toate conversiile de tip care se pot realiza inplicit. În coloana din stânga este tipul datei care este supusa conversiei, iar în capul tabelei (pe prima linie) tipul către care se face conversia. byte byte short char int long float short X int X X X long X X X X float X X X X X double X X X X X X

De exemplu, tipul int se poate converti implicit în oricare din tipurile long, float sau double, dar nu şi în tipurile byte sau short. Conversia de tip explicită se face prin operatorul unar numit cast, care are forma (tip), adică este format din numele tipului către care se face conversia, cuprins între paranteze. Acesta este un operator fără efect lateral, deci care nu modifică valoarea operandului. De exemplu, expresia (byte)a se va folosi pentru a converti valoarea operandului a la tipul byte. Aceasta înseamnă că valoarea variabilei a rămâne neschimbată, însă valoarea expresiei (byte)a se obţine din cea a lui a prin convertirea ei la tipul byte. Utilizarea operatorului cast se justifică atunci când, în situaţia respectivă, conversia implicită nu este posibilă. Nu este însă greşit dacă folosim acest operator chiar şi când conversia respectivă se poate face şi implicit.

Utilizarea operatorului cast arată faptul că programatorul doreşte să se faca o anumită conversie, chiar dacă prin aceasta se poate pierde informaţie. Asupra efectelor pe care care le are conversia explicită vom reveni la prezentarea diferitelor tipuri de date numerice.

Operaţiile aritmetice
Operaţiile aritmetice sunt cele care se aplică unor operanzi numerici, având ca rezultate tot numere. După numărul de operanzi, ele pot fi unare sau binare. Unele operaţii aritmetice unare au şi efect lateral. Tipul rezultatului operaţiilor aritmetice depinde de tipul operanzilor şi va fi discutat la fiecare din tipurile numerice în parte.

42

Programarea orientata pe obiecte în limbajul Java În exemplele de expresii din această secţiune, vom considera că a şi b sunt doi operanzi numerici. În limbajul Java există următorii operatori aritmetici: Operatori unari fără efect lateral:

Operator
+ -

Exemplu de expresie
+a -a

Valoarea expresiei aceeaşi cu valoarea operandului valoarea operandului cu semn schimbat

Operatori unari cu efect lateral Operatorii de incrementare ++ şi decrementare -- au ca operanzi variabile numerice. Operatorul de incrementare ++ are ca efect lateral creşterea cu o unitate a valorii variabileioperand, iar operatorul de decrementare -- are ca efect lateral micşorarea cu o unitate a acestei valori. Acest efect are loc indiferent dacă operatorul este plasat înaintea operandului sau după acesta. În schimb, poziţia operatorului faţă de operand are importanţă la stabilirea valorii expresiei rspective. Daca operatorul este plasat în fata operandului, operaţia de incrementare sau decremantare are loc înainte de a se stabili valoarea expresiei; dacă, însă, operatorul este plasat dupa operand, valoarea expresiei se stabileşte înainte de a se face incrementarea sau decrementarea. Efectul operaţiilor este prezentat în tabelul de mai jos.

Operator Expresie Operatie
++ ++ --++a a++ --a a--

Valoarea expresiei Efect lateral
a+1 a a-1 a

preincrementare postincrementare predecrementare postdecrementare

valoarea variabilei a creste cu 1 valoarea variabilei a creste cu 1 valoarea variabilei a scade cu 1 valoarea variabilei a scade cu 1

Operatori binari Operatorii binari nu au efect lateral - deci nu modifică valorile operanzilor - şi sunt daţi în tabela de mai jos.

Operator Expresie Operatie Valoarea expresiei
+ * / % a+b a-b a*b a/b a%b

adunare scadere

suma valorilor operanzilor diferenta valorilor operanzilor

inmultire produsul valorilor operanzilor impartire catul (rezultatul impartirii) primului operand la al doilea modulo restul impartirii intregi a primului operand la al doilea

43

Severin Bumbaru Prin împărţire întreagă înţelegem împărţirea făcută astfel, încât câtul sa fie un număr întreg (fără extragerea părţii fracţionare (situate dupa virgulă).

Operaţii de atribuire compusă
Urmând tradiţia limbajului C, în limbajul Java există şi operatori de atribuire compusă, în care operaţia de atribuire este combinată cu una din operaţiile aritmetice. Operatorii de atribuire compusă sunt următorii: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>>=. Se observă că fiecare din aceşti operatori are forma op= în care op este un operator aritmetic binar. Expresia în
variabila op= operand care op este un operator aritmetic binar, este variabila = variabila op operand

echivalentă cu expresia

şi se evaluează astfel: - se calculează mai întâi valoarea expresiei (variabila op operand)în care variabila intră cu valoarea sa anterioară; - valoarea astfel calculată se atribuie ca noua valoare a variabilei din partea stânga. Aceasta nouaă valoare este, totodată, şi valoare a expresiei. Constatăm astfel că operatorii de atribuire compusă au efect lateral, la fel cu cei de atribuire.

Exemplu Fie x=3.72 si y=0.19 două variabile de tip double. Expresia x+=y se calculeaza, la fel ca expresia x=x+y, în modul următor: - se calculează valoarea expresiei x+y, care este 3.91; - se atribuie variabilei x valoarea 3.91 (efectul lateral); - valoarea astfel atribuită (3.91) este considerată şi drept valoare a expresiei x+=y.

Comparaţia
Comparaţiile sunt operaţii binare fără efect lateral, în care se compară două numere, obţinându-se ca rezultat o valoare de tip boolean. Operatorii prin care se efectuează comparaţia a două numere se numesc operatori relaţionali şi sunt daţi în tabela de mai jos. Operator
< <= > >= == !=

Semnificatie mai mic decât mai mic decât sau egal cu mai mare decât mai mare decât sau egal cu este egal cu este diferit de

Operatorii relaţionali se aplică tuturor tipurilor de date numerice, inclusiv celor de tip char. Se permite ca cei doi operanzi sa fie de tipuri diferite, de exemplu să se compare o valoare de tip byte cu una de tip double sau cu una de tip char.

44

Programarea orientata pe obiecte în limbajul Java Să consideram, de exemplu, expresia a<b, unde a şi b sunt operanzi de tipuri întregi. Dacă valoarea operandului a este mai mică decât cea a operandului b, atunci valoarea acestei expresii este true (adevărat). Dacă însă valoarea lui a nu este mai mică decât cea a lui b, ca rezultat se obţine valoarea false (fals). Menţionăm că valorile logice true şi false sunt cele două valori ale tipului de date boolean

Tipuri de date întregi
Tipurile de date întregi sunt byte, short, int, long şi char. Conceptual, datele care aparţin tipurilor byte, short, int şi long sunt numere întregi, în timp ce tipul char conţine caractere (litere, cifre, semne de punctuaţie etc). Întrucât caracterele se codifică în memoria calculatorului prin numere întregi fără semn, în limbajul Java asupra lor se pot aplica toate operaţiile pentru numere întregi. Totusi, datorită particularităţilor pe care le prezintă, noi vom trata tipul char separat.

Mulţimile de valori ale tipurilor întregi
Tipurile de date întregi propriu-zise sunt date în tabela de mai jos. Tipul Lungimea byte short int long 1 octet (8 biţi) 2 octeţi (16 biţi) 4 octeţi (32 biţi) 8 octeţi (64 biţi) Intervalul de valori [-128, 127] [-32768, 32767] [-2147483648, 2147683647] [-9223372036854775808, 9223372036854775807]

Se observă, deci, că deosebirea dintre diferitele tipuri de date întregi constă în lungimea reprezentării lor interne, care condiţionează şi mulţimea de valori a tipului respectiv. Este evident că mulţimea de valori a fiecăruia din aceste tipuri este numai o submulţime a mulţimii numerelor întregi.

Reprezentarea internă a datelor de tip byte, short, int si long se face sub forma de numere întregi cu semn, în sistemul de numeraţie binar. Primul bit al reprezentării interne este interpretat drept semn (0 pentru + si 1 pentru -). Numerele întregi pozitive se reprezintă, deci, prin numere binare care încep cu cifra 0. Numerele întregi negative se reprezintă prin complementul la doi al modulului lor. Avand în vedere că cu n cifre în baza 2 se pot reprezenta 2n valori, se obţin domeniile de valori indicate în tabelul de mai sus. De exemplu, pentru tipul byte există 28=256 valori. Limitele domeniilor de valori pentru fiecare tip întreg se pot exprima în binar în funcţie de numărul de biţi astfel: - tipul byte: -27 ... 27-1; - tipul short: -215 ... 215-1; - tipul int: -231 ... 231-1; - tipul long: -264 ... 264-1.

45

Severin Bumbaru

Sa luăm ca exemplu numerele de tip byte, care se reprezintă intern pe o lungime de 8 biţi. Cel mai mic numar pozitiv este, în acest caz, 00000000 (deci 0 extins pe toţi cei 8 biţi), iar cel mai mare numar pozitiv este01111111, care este în sistemul zecimal 27-1, adica 127. Pentru reprezentarea numerelor negative se foloşeste complementul la 2, care se obtine astfel: se ia modulul numărului respectiv (în binar) şi se inverseaza toti biţii (din 0 în 1 şi invers), după care se adună 1 la valoarea astfel obţinută. De exemplu, numarul -108 se va reprezenta astfel: se ia modulul acestuia, 108, care se reprezinta în binar pe 8 biţi sub forma 01101100. Înversând biţii se obtine 10010011. Adunând 1 (în binar) la acest număr se obţine 10010100 care este reprezentarea internă a numarului negativ -108 şi are primul bit 1. Remarcam că numarul -1 se reprezinta intern pe 8 biţi prin 11111111, iar numărul -128 prin 10000000.

Literali de tip întreg
Literalii de tip întreg servesc pentru reprezentarea în program a valorilor numerice de tip întreg şi pot fi clasificaţi astfel:
 

după tipul de date: literali de tip int şi de tip long după sistemul de numeraţie: literali zecimali, octali sau hexazecimali.

Nu există in limbajul Java literali de tip byte sau short. Literalii de tip long se deosebesc de cei de tip int prin faptul ca se termină prin litera L sau l. Se prefera litera majuscula L, deoarece litera mică l se poate confunda cu cifra 1. În sistemul zecimal, literalii întregi sunt şiruri de cifre zecimale care pot fi precedate de semn şi nu conţin în interiorul lor separatori, cum sunt punctul, virgula sau apostroful (care se folosesc de noi în scrierea uzuală a numerelor) şi nu incep cu cifra 0.

Exemple corecte de literali întregi în sistemul zecimal: - de tip int: 0 -5 276315 -176426 - de tip long: 0L -5L 3567286542987L Exemple de literali întregi scrişi greşit: 27.653.816 (conţine puncte); 5,187 (conţine virgula); 3'876'293 (conţine apostrofuri); 069354 -084931 (încep cu cifra 0). În sistemul octal, literalii întregi sunt numere cu sau fără semn, scrise în sistemul de numeraţie octal (cu baza opt) şi care încep cu cifra 0. Amintim că cifrele sistemului octal sunt 0, 1, 2, 3, 4, 5, 6, 7.

46

Programarea orientata pe obiecte în limbajul Java Exemple de literali corecţi în sistemul octal:
016724 -04507

În sistemul hexazecimal, literalii întregi sunt numere cu sau fără semn, scrise în sistemul de numeraţie hexazecimal (cu baza 16) şi care încep cu prefixul 0x. Amintim că cifrele sistemului hexazecimal sunt: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. În locul majusculelor se pot folosi şi minusculele corespunzătoare.

Exemple de literali corecţi în sistemul hexazecimal: 0x37B2E

-0x2ab7f3

Sistemele octal şi hexazecimal prezintă avantajul că au ca baze puteri ale lui 2. Din această cauză, conversia din aceste sisteme de numeraţie în binar pentru numerele pozitive se face simplu: fiecare cifra octală se reprezintă în binar prin trei biţi, iar fiecare cifră fexazecimală prin patru biţi. De exemplu, numărul octal 15132 (corespunzător literalului 015132) se va reprezenta în binar prin 001101001011010, iar numărul hexazecimal 6ac3 (corespunzator literalului 0x6ac3) se converteşte în binar prin 0110101011000011. La conversia inversă, din binar în octal, se grupează cifrele numarului binar câte trei de la dreapta la stânga, după care se înlocuieşte fiecare grup de trei biţi prin valoarea corespunzătoare în octal. În mod corespunzător, la conversia din binar în hexazecimal, se grupează biţii câte patru de la dreapta la stânga şi se înlocuieşte fiecare grup prin cifra hexazecimală corespunzătoare. De exemplu, numărul binar 0110110010110011 corespunde numerelor 066263 în octal şi 6cb3 in hexazecimal.

Operaţii şi operatori pentru date de tip întreg
Asupra datelor de tip întreg se pot aplica operaţii de atribuire, de conversie de tip, operaţii aritmetice, de comparaţie, operaţii de deplasare binară, operaţii logice pe biţi şi operaţii de atribuire compusă. Primele trei au fost discutate anterior şi vom indica aici numai unele particularităţi ale aplicării lor în cazul datelor de tipuri întregi.

Conversia de tip şi atribuirea
Diferitele tipuri de date întregi diferă între ele prin lungimea reprezentării lor interne. La convertirea unui tip mai scurt în unul mai lung (de exemplu a unui byte sau short în int) valoarea numărului rămâne neschimbată. Din această cauză, această conversie se poate face implicit. În schimb, la conversia de la o lungime mai mare la una mai mică, se reţin numai octeţii situaţi în partea dreaptă a numărului, eliminându-se octeţii din stânga care depăşesc noua lungime. Prin aceasta este posibil să se modifice valoarea numărului şi chiar semnul lui. Din această cauză, la efectuarea unor astfel de conversii, programatorul trebuie să-şi asume raăspunderea folosind operatorul cast. Exemplu Să considerăm urmatoarea aplicaţie din fişierul CastInt.java.
/* Verificarea actiunii operatorului (cast) in cazul numerelor intregi */ class CastInt {

47

Severin Bumbaru

public static void main(String args[]) { /* Declararea si initializarea variabilelor */ byte b1=73, b2=-107, b3, b4, b5; int i1=91, i2=-103, i3=22195, i4, i5; /* Atribuiri cu conversie implicita de la byte la int */ i4=b1; i5=b2; /* Atribuiri care necesita conversie explicita de la int la byte */ b3=(byte)i1; b4=(byte)i2; b5=(byte)i3; /* Afisarea rezultatelor */ System.out.println("i4="+i4+" i5="+i5+"\nb3="+b3+" b4="+b4+ " b5="+b5); } }

Compilând şi rulând pe calculator această aplicaţie obţinem următorul rezultat afişat pe ecran:
i4=73 i5=-107 b3=91 b4=-103 b5=-77

Analizând aceste rezultate, constatăm că: - valorile variabilelor i3 şi i4 au fost atribuite corect, deoarece conversia s-a făcut de la lungimea de un octet la cea de 4 octeţi, astfel că nu s-au alterat valorile; - valorile variabilelor b3 şi b4 au fost atribuite corect, deşi conversia s-a făcut prin (cast) de la lungimea de 4 octeţi la cea de 1 octet. Explicaţia este că valorile atribuite se încadrau în intervalul de valori permise pentru tipul byte; - în cazul variabilei b5 s-a atribuit valoarea -77, deşi în instrucţiunea b5=(byte)i3 variabila i3 are valoarea 22195. Prin conversie s-au modificat deci atât valoarea, cât şi semnul, deoarece 22195 nu se încadrează în mulţimea de valori a tipului byte.

Pentru cei interesaţi, putem urmări cum s-au facut efectiv aceste conversii, luând în consideraţie reprezentările interne. a/ Atribuirea i4=b1 unde b1 are valoarea 73. b1 se reprezinta intern prin octetul 01001001. Când se face conversia de la byte la int se extinde aceasta reprezentare externa prin adaugarea la stânga a trei octeţi nuli. Se obţine astfel numărul de tip int 00000000000000000000000001001001 care este valoarea 73 pe 4 octeţi. b/ Atribuirea i5=b2 unde b2 are valoarea negativă -107. b2 se reprezintă intern prin octetul 10010101 (folosind codul complementar pentru numere negative). Intrucât primul bit este 1, când se face conversia de la byte la int se adaugă la stânga trei octeţi completaţi în întregime cu cifra 1, obţinându-se reprezentarea internă 11111111111111111111111110010101. În acest fel s-a conservat semnul, obţinându-se tot numărul -107, dar reprezentat pe 4 octeţi.

48

Programarea orientata pe obiecte în limbajul Java

c/ Atribuirea b3=(byte)i1 unde i1 are valoarea 91. Reprezentarea internă a lui i1 este 00000000000000000000000001011011 şi se extinde pe 4 octeţi. Prin conversia de la int la byte se elimină cei trei octeţi din stânga, obţinându-se reprezentarea pe un octet 01011011. Întrucât s-au eliminat numai zerouri din stânga, valoarea a rămas neschimbată. d/ Atribuirea b4=(byte)i2 unde i2 are valoarea negativă -103. Reprezentarea internă a lui i2 este 11111111111111111111111110011001 fiind complementul lui 103 extins pe 4 octeţi. Prin conversie la tipul byte se elimină cei trei octeţi din stânga, obţinându-se 10011001 care este tot numărul -103, dar reprezentat pe un singur octet. e/ Atribuirea b5=(byte)i3, unde i3 are valoarea 22195. Reprezentarea internă a lui i3 pe patru octeti este 00000000000000000101011010110011. Prin conversie la tipul byte se elimina cei trei octeţi din stânga, reţinându-se octetul 10110011 care se atribuie ca valoare a lui b5. Deoarece începe cu bitul 1, aceasta se interpreteaza ca valoarea negativă -77. Constatăm astfel că prin conversie s-au modificat atât valoarea, cât şi semnul. Aceasta s-a întâmplat întrucât valoarea de la care a pornit conversia nu se încadra în mulţimea de valori a tipului byte, iar la eliminarea celor trei octeţi din partea stangă s-au pierdut cifre semnificative.

Operaţii aritmetice cu numere întregi
Asupra datelor de tip întreg se pot aplica toţi operatorii aritmetici prezentaţi anterior. Vom prezenta aici numai unele particularităţi ale operaţiilor aritmetice cu numere întregi şi vom da exemple. Tipul rezultatului operaţiilor aritmetice cu numere întregi se stabileste astfel: a/ în cazul operaţiilor de incrementare (++) şi decrementare (--) tipul rezultatului este acelaşi cu tipul operandului; b/ pentru toate celelalte operaţii, dacă cel puţin unul din operanzi este de tip long, atunci rezultatul este de tip long; altfel, rezultatul este de tip int. Remarcăm, deci, că singurele operaţii aritmetice care pot da rezultat de tip byte sau short sunt cele de incrementare sau decrementare. Împarţirea întreagă O consecinţă a modului de stabilire a rezultatului operaţiilor aritmetice este că, atunci când se face o operaţie de împărţire (/) între două numere întregi, rezultatul este un numar întreg (de tip int sau long, după caz). La împărţire se calculează, deci, numai partea întreagă a câtului. Excepţia de împărţire la zero Este posibil ca, în operatia de împarţire întreagă, al doilea operand (împărţitorul) să fie egal cu zero. În acest caz, maşina virtuală Java generează o excepţie de împărţire la zero, care face parte din clasa ArithmeticException. În programul de mai jos, din fişierul ImpartireZero.java, se testează o astfel de situaţie.

49

Severin Bumbaru

/* Testarea exceptiei de impartire la zero */ class ImpartireZero { public static void main(String args[]) { int a=5, b=0, c=0; System.out.println(a/b); } }

Se observă că în expresia a/b opeandul b are valoarea zero. La executarea acestui program se afişeaza pe ecran următorul mesaj:

Exception in thread "main" java.lang.ArithmeticException: / by zero at ImpartireZero.main(ImpartireZero.java:6)

Acest mesaj se interpretează astfel: s-a produs o excepţie din clasa ArithmeticException în firul de execuţie "main"; excepţia constă în împărţire (/) la zero şi a apărut în metoda main a clasei ImpartireZero, fiind localizată în fişierul sursă ImpartireZero.java linia 6. Se poate constata cu uşurinţă că, într-adevar, operaţia care a produs excepţia se găseşte în locul indicat. Înlocuind în acest fişier operaţia a/b prin c/b (astfel încât ambii operanzi sunt nuli) caonstatăm că, dacă repetăm compilarea şi execuţia, obţinem aceeaşi excepţie. Aşa dar, la depistarea excepţiei de împărţire la zero, maşina virtuală Java testează numai împărţitorul, nu şi deîmpărţitul. Depăşirea binară La efectuarea unor operaţii cu numere întregi este posibil ca rezultatul să fie un numar binar mai lung decât spaţiul alocat în memorie pentru tipul de date respectiv. De exemplu, dacă se înmulţesc două numere de tip int (reprezentate pe 32 biţi fiecare), rezultatul poate fi un numar mai lung de 32 biţi, deci a cărui valoare nu se mai încadrează în mulţimea de date a tipului int. O astfel de situaţie se numeşte depăşire binară. În limbajul Java, apariţia depăşirii binare nu este considerată o excepţie, ci calculul continuă, dar se reţin numai octeţii din dreapta ai rezultatului, atâţi cât corespund reprezentării interne a tipului de date respectiv (4 octeţi pentru int şi 8 octeţi pentru long). În consecinţa se pierd octeţii cei mai semnificativi şi are loc, deci, modificarea valorii şi chiar a semnului rezultatului, la fel ca în cazul conversiei de la un tip cu lungime mai mare la unul cu lungime mai mică. Exemplu În următoarea aplicaţie din fişierul Intregi.java se fac unele teste privind operaţiile cu numere întregi.

class Intregi { public static void main(String args[]) { byte b1=73, b2=-109, b3, b4, b5; short s1=9000, s2=-11000, s3; int i1=900000, i2=-1100000, i3, i4, i5; long m1=10000000000L, m2=-200000000000L, m3, m4; b3=(byte)(-b2);

50

Programarea orientata pe obiecte în limbajul Java

b4=(byte)(b1+b2); b5=++b1; s3=(short)(s2/s1); s4=(short)(s2%s1); // restul impartirii s2/s1 i3=i1+i2; i4=i1*i2; i5=(int)(m2/i1); m3=m2-m1; m4=m2*m1; System.out.println("b3="+b3+" b4="+b4+" b5="+b5); System.out.println("s3="+s3+" s4="+s4); System.out.println("i3="+i3+" i4="+i4+" i5="+i5); System.out.println("m3="+m3+" m4="+m4); System.out.println("b5*b2="+(b5*b2)+ "s1*s2="+(s1*s2)); } }

Remarcăm că la calcularea valorilor variabilelor b3, b4 şi s3 a fost necesar să se folosească conversia explicită (cast), deoarece valorile expresiilor din paranteze din partea dreaptă a operandului de atribuire sunt de tip int, în timp ce variabilele din partea stânga sunt de tip byte sau short. În mod similar s-a folosit castul la calcularea lui i5, deoarece expresia m2/m1 este de tipul long. Se poate constata cu usurinţă, suprimând din program operatorii cast din expresiile respective, că dacă nu se folosesc aceşti operatori sunt semnalate erori de compilare. În schimb, nu a fost necesar să se recurgă la cast la calcularea valorii lui b5, deoarece expresia ++b este de tip byte. Executând acest program, se obţin pe ecran următoarele rezultate:

b3=109 b4=-36 b5=74 s3=-1 s4=-2000 i3=-200000 i4=2137445376 i5=-222222 m3=-210000000000 m4=-7751640039368425472 b5*b2=-8066 s1*s2=-99000000

Din aceste rezultate constatăm că: - s-a obţinut s3=-1 si nu s3=-1.222222... deoarece în expresia s2/s1 ambii operanzi sunt de tipuri întregi, deci rezultatul este întreg (împărţire întreagă). Din acelaşi motiv s-a obţinut i5=-222222 si nu -222222.222222....; - la calcularea lui i4 nu s-a obţinut valoarea corectă -990000000000, ci valoarea 2137445376. Cauza este că valoarea depăşeşte marginea superioară a valorilor de tip int, deci s-a produs o depăşire binară şi s-au trunchiat octeţii cei mai semnificativi. Tot o depăşire binară s-a produs şi la calcularea lui m4 (de data aceasta pentru tipul long); - valorile produselor b5*b2 şi s1*s2 au fost calculate şi afisate corect, deşi ele depăşesc limitele mulţimilor de valori de tip byte, respectiv de tip short. Aceasta s-a produs deoarece expresiile respective sunt de tip int.

Comparaţia
Operatorii de comparaţie pot fi aplicaţi pentru orice fel de operanzi numerici. Ca exemplu, în fişierul ComparInt.java se da un program, în care se compară diferite numere întregi şi se

51

Severin Bumbaru afişează rezultatele.
/* Testarea operatorilor de comparatie in cazul tipurilor de date intregi */ class ComparInt { public static void main(String args[]) { byte b1=48, b2=-17; short s1=2765, s2=-12970; int i1=762983, i2=48, i3=-12970; long m1=876432906528L, m2=48; System.out.println(b1==b2); System.out.println(b1==i2); System.out.println(s1!=i2); System.out.println(i2!=m2); System.out.println(m2>i2); System.out.println(m2>=i2); System.out.println(s2<i3); System.out.println(i3<=s2); } }

Executând acest program se obţin pe ecran următoarele rezultate sub forma de valori booleene:

false true true talse true talse true

52

Programarea orientata pe obiecte în limbajul Java

Având în vedere că true înseamnă adevărat, iar false înseamnă fals, se poate constata cu uşurinta că aceste rezultate sunt corecte. Remarcăm, de asemenea, că rezultatul comparaţiei depinde numai de valorile numerelor comparate, nu şi de tipul acestora. Operaţii de deplasare binară
Urmând "tradiţia" limbajului C, limbajul Java conţine şi operatori de deplasare binară. Aceştia sunt operatori binari (cu doi operanzi) fără efect lateral. Tipul rezultatului operaţiei se stabileşte la fel ca în cazul operaţiilor aritmetice cu numere întregi. Valoarea rezultată se stabileşte astfel: se ia reprezentarea internă a primului operand şi se deplasează la stânga sau la dreapta cu un numar de poziţii binare egal cu cel de al doilea operand. Operatorii de deplasare şi efectele lor sunt prezentate în tabela de mai jos, în care a şi s sunt operanzi care aparţin unor tipuri întregi.

Operator Expresie Efect
<< >> >>> a<<s a>>s a>>>s

deplasare la stânga cu s poziţii binare deplasare la dreapta cu s poziţii binare, cu conservarea semnului deplasare la dreapta fără semn, cu s poziţii binare

Deplasarea la stânga cu s poziţii este echivalenta cu înmulţirea numărului cu 2s. Dacă s este suficient de mare, poate avea loc o depăşire binară, la fel ca în cazul înmulţirii aritmetice (cu operatorul *). Deplasarea la dreapta cu s poziţii cu operatorul >> este echivalenta cu împărţirea întreagă la 2s. Deplasarea biţilor la dreapta cu s pozitii cu operatorul >>> are asupra numerelor pozitive acelaşi efect ca cel al operatorului >>. În schimb, în cazul operanzilor negativi, în cazul operatorului >>> nu se mai conservă semnul, iar modulul numărului se modifică. Aceasta se întâmplă, întrucât pe poziţiile eliberate din partea stângă se înserează bitul 0. Exemplu În fişierul Deplasari.java se dă un exemplu de aplicaţie, în care se testează acţiunea operatorilor de deplasare binară.

/* Testarea operatiilor de deplasare binara */ class Deplasari { public static void main(String args[]) { byte b1=15, b2; int i1=1024, i2=-1024; b2=(byte)(b1<<3); System.out.println("b1="+b1+" b2="+b2); System.out.println("i1<<4="+(i1<<4)+" i1>>4="+(i1>>4)+ " i1>>>4="+(i1>>>4));

53

Severin Bumbaru

System.out.println("i2<<4="+(i2<<4)+" i2>>4="+(i2>>4)+ " i2>>>4="+(i2>>>4)); } }

Rezultatele afişate la executarea acestui program sunt următoarele:

b1=15 b2=120 i1<<4=16384 i1>>4=64 i1>>>4=64 i2<<4=-16384 i2>>4=-64 i2>>>4=268435392

Având în vedere că 23=8 şi 24=16, rezultatele obţinute pot fi verificate cu uşurinţă. Constatăm ca singura deosebire dintre efectele operatorilor >> şi >>> apare când primul operand este negativ.

Operatorul >>> nu există în limbajul C, el fiind introdus numai în Java. Pentru a inţelege mai bine efectul operatorilor de deplasare, să urmărim ce se întâmplă în exemplul de mai sus la nivelul reprezentării interne a datelor (în binar). Având în vedere că 1024=210, reprezentările interne ale operanzilor i1 şi i2 sunt următoarele:
i1=00000000000000000000010000000000 i2=11111111111111111111110000000000

După aplicarea operatorului << (deplasare binară la stânga cu inserare de zerouri pe spaţiile libere din dreapta) obţinem: - pentru operaţia i1<<4: 00000000000000000100000000000000 - pentru operaţia i2<<4: 11111111111111111100000000000000 ceeace reprezintă în zecimal numerele 214=16384 si, respectiv, -214=-16384. După aplicarea operatorului >> (deplasare la dreapta cu conservarea semnului) se obţine: - pentru operaţia i1>>4: 00000000000000000000000001000000 - pentru operaţia i2>>4: 11111111111111111111111111000000 ceeace reprezintă în sistemul zecimal numerele 26=64 şi, respectiv, -26=-64. Remarcăm că în cazul operandului negativ i2, la deplasarea la dreapta s-a înserat pe poziţiile libere bitul 1 pentru a se conserva semnul. După aplicarea operatorului >>> (deplasare la dreapta fără conservarea semnului) se obţine: - pentru operaţia i1>>>4: 00000000000000000000000001000000 - pentru operaţia i2>>>4: 00001111111111111111111111000000 Pentru operandul pozitiv i1 s-a obţinut un rezultat identic cu cel precedent. În schimb, în cazul operandului negativ i2, pe poziţiile eliberate din partea stângă s-a înserat bitul 0, ceeace a dus la schimbarea semnului şi a valorii rezultatului faţă de cazul deplăsarii cu conservarea semnului.

Operaţii logice pe biţi
Tot urmând "tradiţia" limbajelor C/C++, în limbajul Java se pot utiliza pentru tipurile de date

54

Programarea orientata pe obiecte în limbajul Java întregi şi operatorii logici pe biţi ~, &, | si ^. Aceştia sunt operatori fără efect lateral. Operaţiile logice se fac la nivel de bit, adică între fiecare bit al operandului din stânga şi bitul corespunzător al operandului din dreapta, considerându-se ca 0 este echivalent cu false, iar 1 este echivalent cu true. Operatorul unar ~ exprimă negaţia logică, deci înlocuirea lui 0 cu 1 şi invers. Operatorii binari &, | si ^ acţionează la fel ca în cazul tipului boolean, numai că se aplică la nivel de bit. Acţiunea operatorilor este dată în tabela de mai jos, în care a şi b sunt cei doi operanzi, iar ai si bisunt biţii de pe pozitia i a acestor operanzi. În tabelă se dă efectul fiecărei operaţii asupra bitului i al rezultatului.

ai 0 0 1 1

bi 0 1 0 1

~a 1 1 0 0

a&b 0 0 0 1

a|b 0 1 1 1

a^b 0 1 1 0

Exemplu În fişierul LogicaBiti.java este dat un exemplu de program, în care se testează operaţiile logice pe biţi. Pentru a se verifica manual mai uşor, am considerat că opraţiile se fac asupra datelor de tip byte. Este bine să avem însă in vedere că rezultatele operaţiilor sunt de tip int. Aceasta se poate constata cu usurinţa dacă încercăm să eliminăm castul din instrucţiunea în care se calculează b3.

/* Testarea operatiilor logice pe biti */ class LogicaBiti { public static void main(String args[]) { byte b1=17, b2=-95, b3; b3=(byte)(b1&b2); // este necesara conversie de la int la byte System.out.println("~b1="+(~b1)+" b1&b2="+b3+" b1|b2="+(b1|b2)+ " b1^b2="+(b1^b2)); } }

Rezultatele afişate la executarea acestui program sunt următoarele:

~b1=-18 b1&b2=1 b1|b2=-79 b1^b2=-80

Iată şi cum decurg operaţiile din exemplul de mai sus la nivel de bit:
b1: 00010001 b2: 10100001 ~b1: 11101110

echivalent cu 17 echivalent cu -95 echivalent cu -18

55

Severin Bumbaru

echivalent cu 1 echivalent cu -79 echibalent cu -80 Bineînţeles că toate aceste rezultate ar trebui exprimate pe 32 biţi, prin prelungire la stânga cu cate 18 cifre de 0 sau de 1 după caz, astfel încât să se conserve semnul.

b1&b2: 00000001 b1|b2: 10110001 b1^b2: 10110000

Aplicarea operatorilor de atribuire compusă
Pentru operanzii de tipuri întregi se pot aplica toţi operatorii de atribuire compusă +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>=. Exemplu În programul din fişierul AtribComp.java se testează unele operaţii de atribuire compusă. Iată acest program:
/* Testarea operatorilor de atribuire compusa */ class AtribComp { public static void main(String args[]) { int m=176, b=-15, c=16, d=-28; System.out.println(m+" "+(m+=b)+" "+m); System.out.println((m/=d)+" "+m); System.out.println(b+" "+(b<<=2)+" "+b); System.out.println(c+" "+(c|=b)+" "+c); } }

La executarea programului se obţin pe ecran următoarele rezultate:

176 161 161 -5 -5 -15 -60 -60 16 -44 -44

Prima linie afişată conţine: - valoarea variabilei m înainte de evaluarea expresiei m+=b; - valoarea expresiei m+=b; ea este egală cu m+b adică cu 176-15; - valoarea variabilei m după calcularea acestei expresii. Recomandăm sa explicaţi în mod similar şi celelalte linii afişate.

56

Programarea orientata pe obiecte în limbajul Java

Tipuri de date în virgulă mobilă
Mulţimile de valori pentru tipurile de date în virgulă mobilă
Conceptual, datele care aparţin acestor tipuri sunt numere reale. În limbajul Java există două tipuri de date reale (numite şi tipuri de date flotante sau în virgulă mobilă): Tipul
float double

Lungimea

Intervalul de valori

4 octeti (32 biti) [-3.402347e+38f, ... 3.402347e38f] 8 octeti (64 biti) [-1.7976931348623157e+308, ... 1.7976931348623157e308]

Reprezentarea internă a datelor în virgulă mobilă adoptată pentru maşina virtuală Java corespunde standardului ANSI/IEEE 754-1985. Reprezentarea externă a numerelor în virgulă mobilă se poate face în următoarele moduri: a/ ca numere reale fără exponent, în care partea întreagă este separată de cea fracţionara prin punct; b/ ca numere reale cu exponent, în care un număr întreg sau unul real fără exponent, numit mantisa sau coeficient, este urmat de un exponent zecimal, format din litera e sau E urmata de un numar intreg. Semnificaţia este că mantisa se înmulteşte cu 10 ridicat la o putere egală cu exponentul. O caracteristică importantă a numerelor în virgulă mobilă este precizia. Se numeşte precizie numărul maxim de cifre semnificative pe care îl poate avea mantisa pentru tipul de date respectiv. Aceasta depinde de numărul de biţi alocaţi mantisei în reprezentarea internă. În cazul limbajului Java, datele de tip float pot avea cel mult 7 cifre semnificative, iar cele de tip double cel mult 16 cifre semnificative. În reprezentarea externă a numărului se pot folosi şi mai multe cifre, dar cele care depăşesc lungimea admisă nu vor fi luate în consideraţie la conversia din forma externă în cea internă.

Cifrele semnificative sunt cifrele mantisei numărului, care încep de la prima cifră diferită de zero şi se termină cu ultima cifra diferita de zero. De exemplu, numărul 000102.3015000 are 7 cifre semnificative (cele subliniate), deoarece zerourile de la început şi de la sfârşit nu influenţează valoarea numărului. Acelaşi număr poate avea diferite reprezentări externe, de exemplu: 102.3015, 10.23015E1, 1.023015E2, 0.1023015E3, 0.01023015E4 etc. Pentru toate aceste numere, reprezentarea internă va fi aceeaşi. Menţionăm că numerele de mai sus, deşi au numai 7 cifre semnificative, vor fi reprezentate intern ca numere de tip double, deci pe 8 octeţi fiecare. Pentru a fi reprezentate intern ca date de tip float (pe 4 octeti) trebuie scrise cu litera f sau F la sfârşit, de exemplu 102.3015f. În afară de valorile numerice, pentru datele în virgulă mobilă sunt prevăzute şi următoarele valori speciale:

57

Severin Bumbaru corespunde valorii plus infinit; corespunde valorii minus infinit; reprezintă ceva care nu este număr (Nota Number). În consecinţă, în cazul datelor de tipuri reale, dacă a este un numar real pozitiv, operaţia a/0.0 dă ca rezultat Infinity, iar operaţia a/(-0.0) da rezultatul -Infinity. Operaţia 0.0/0.0 dă rezultatul NaN. Atenţie: valorile Infinity, -Infinity şi NaN sunt formele externe sub care acestea se afişează pe ecran, se tipăresc la imprimantă sau se scriu într-un fişier de text. Fiecăreia dintre ele îi corespunde o reprezentare internă, sub forma unui şir de 4 sau 8 octeţi, corespunzător tipului float sau double. Aceste reprezentări interne respectă regula, conform căreia, dacă sunt interpretate ca numere în virgulă mobilă, îndeplinesc următoarele condiţii: Infinity este mai mare decat orice valoare de tipul respectiv; -Infinity este mai mică decât orice valoare de tipul respectiv. În acest fel, valorile menţionate păstrează consistenţa operaţiilor de comparaţie.
Infinity -Infinity NaN -

Literalii în virgulă mobilă
Literalii în virgulă mobilă sunt reprezentările valorilor reale în programele Java. La scrierea literalilor se respectă formele externe de numere reale fără exponent sau cu exponent prezentate mai sus, cu precizarea ca literalii de tip float se termina cu litera f sau F, în timp ce literalii de tip double nu au un astfel de sufix. Exemple de literali în virgulă mobilă a/ literali de tip float fără exponent: 123.7f, -1876.53f; b/ literali de tip float cu exponent: 7.28765e12f (echivalent cu 7.28765x1012) -8 5.0754286e-8f (echivalent cu 5.075428x10 ) 3 -3.9104e3f (echivalent cu -3.9104x10 ) c/ literali de tip double fără exponent: 154236789.20973, -3401.76398653; d/ literali de tip double cu exponent:
2935.87628091e-12 -1.20937543872E23 12e23 (echivalent cu

12.0x1023)

Atât la tipul float, cât şi la tipul double, valoarea 0.0 are semn: poate exista atât +0.0 cât şi -0.0. Un 0 fără semn este echivalent cu +0.0. Pentru tipul float, aceste valori sunt scrise sub forma +0.0f şi -0.0f.

Valorile speciale Infinity, -Infinity şi NaN nu sunt literali, ci doar forme externe de afişare a valorilor interne corespunzătoare. În consecinţă, ele nu pot fi folosite în programele sursă. De exemplu, daca x este o variabilă în virgulă mobilă, nu putem face atribuirea x=Infinity.

Operaţii şi operatori pentru date în virgulă mobilă
Asupra datelor în virgulă mobilă se pot aplica operaţii de atribuire, de conversie de tip, operaţii aritmetice şi de comparaţie, atribuirea compusă. Acestea au fost deja prezentate,

58

Programarea orientata pe obiecte în limbajul Java astfel că vom indica aici numai unele particularităţi ale aplicării lor în cazul datelor în virgulă mobilă.

Conversia de tip şi atribuirea
S-a arătat deja ca orice dată numerică de tip întreg se poate converti implicit în oricare din tipurile în virgulă mobilă. De asemenea, are loc conversie implicită de la float la double. În consecinţă, folosirea conversiei explicite (cast) este necesară atunci când se trece de la double la float, sau de la un tip în virgulă mobilă la unul întreg. Conversia din double în float nu conduce niciodata la alterarea valorii, ci numai la pierderea de precizie. Aceasta se produce, întrucat se trece de la o mantisă de 52 de biţi la una de 24 biţi , deci de la cca 17 cifre semnificative la numai cca 7 cifre semnificative în sistemul zecimal. În schimb, conversia explicită de la date în virgulă mobilă la oricare din datele de tip întreg poate duce la denaturarea valorii şi a semnului numărului, la fel ca în cazul tipurilor intregi. Aceasta se întâmplă atunci când valoarea care trebuie convertită nu se încadrează în mulţimea de valori specifică tipului în care se face conversia. Unei variabile în virgulă mobilă i se pot atribui orice valori de tipuri numerice.

Operaţii aritmetice cu numere reale
Asupra datelor în virgulă mobilă se pot aplica toţi operatorii aritmetici prezentaţi anterior, inclusiv operatorii de incrementare (++), decrementare (--) şi restul împărţirii întregi (%). Vom prezenta aici numai unele particularităţi ale operaţiilor aritmetice în virgulă mobilă şi vom da exemple. O particularitate importantă a operaţiilor aritmetice în virgulă mobilă în limbajul Java este că împărţirea la zero nu mai este considerată o exceptie, ci este o operaţie permisă. Rezultatul împărţirii la zero depinde de valoarea deîmpărţitului şi de semnele celor doi operanzi. Rezultatul împărţirii 0/0 este NaN (Not a Number, deci o valoare nedefinită). Dacă deîmpărţitul este diferit de zero, rezultatul împărţirii este Infinity cu un semn care se stabileşte după semnele celor doi operanzi: + (plus) dacă au acelaşi semn, sau - (minus)dacă semnele operanzilor sunt diferite. Amintim că la numerele în virgulă mobilă în Java şi valoarea zero are semn, deci există +0.0 si -0.0. Exemplu În programul din fişierul VirgulaMobila.java, pe care îl reproducem mai jos, se testează diferite operaţii aritmetice în virgulă mobilă, inclusiv cele care au ca rezultate valori speciale.

/* Testarea operatiilor aritmetice cu numere in virgula mobila */ class VirgulaMobila { public static void main(String args[]) { double a=12.7865, b=-158.07, c=0.0, d=-0.0, inf1, inf2, n; float p=3476.15f, q=0.00237621f, r=0.0f, s=-0.0f; System.out.println("a/b="+(a/b)+" b/a="+(b/a)+" b%a="+(b%a)); System.out.println("a/c="+(a/c)+" b/c="+(b/c)); System.out.println("a/d="+(a/d)+" b/d="+(b/d));

59

Severin Bumbaru
System.out.println("c/d="+(c/d)+" d/c="+(d/c)); System.out.println("p/q="+(p/q)+" q/p="+(q/p)+" p%q="+(p%q)); System.out.println("p/r="+(p/r)+" p/s="+(p/s)); System.out.println("a/q="+(a/q)+" p/b="+(p/b)); inf1=a/c; inf2=a/d; n=c/d; System.out.println("a/inf1="+(a/inf1)+" a/inf2="+(a/inf2)); System.out.println("inf1/inf2="+(inf1/inf2)+" inf2/inf1="+ (inf2/inf1)); System.out.println("a*inf1="+(a*inf1)+" c*inf1="+(c*inf1)); } }

La executarea acestui program se afişează următorul rezultat:

a/b=-0.08089137723793256 b/a=-12.362257068001407 b%a=-4.631999999999991 a/c=Infinity b/c=-Infinity a/d=-Infinity b/d=Infinity c/d=NaN d/c=NaN p/q=1462896.8 q/p=6.835752E-7 p%q=0.001879394 p/r=Infinity p/s=-Infinity a/q=5381.048097062204 p/b=-21.991205809728285 a/inf1=0.0 a/inf2=-0.0 inf1/inf2=NaN inf2/inf1=NaN a*inf1=Infinity c*inf1=NaN

Din aceste rezultate constatăm că: - operaţiile cu infiniţi şi nedeterminări respectă regulile cunoscute din matematică (împărţirea la zero dă rezultat infinit, iar 0/0, infinit impartit la infinit şi zero înmulţit cu infinit sunt nedeterminări); - dacă cel puţin un operand este în dublă precizie (double), rezultatul este în dublă precizie; dacă ambii operanzi sunt în simplă precizie (float), rezultatul este float; - se verifică egalitatea [b/a]*a+(b%a)=b, in care [b/a] este partea intreagă a câtului b/a, iar b%a este restul împărţirii întregi a lui b la a; de exemplu, (-12)*12.7865-4.632=158.07.

Comparaţia
Pentru datele in virgula mobilă se aplică aceiaşi operatori de comparatie (relaţionali) ca şi pentru numerele întregi.

Atribuirea compusă
În cazul tipurilor de date reale se aplică următorii operatori de atribuire compusă:+=, -=, *=, /=, %=.

60

Programarea orientata pe obiecte în limbajul Java

Tipul char Tipul de date char
Datele de tip char sunt caractere, adică simboluri tipografice elementare: litere, cifre, semne de punctuaţie, simboluri matematice, etc. În limbajul Java, reprezentarea internă a caracterelor se face pe 2 octeţi (16 biţi), în sistemul Unicode. In acest sistem, caracterele sunt codificate în memoria internă prin numere întregi pozitive în intervalul [0, 65535]. În fluxurile de date de intrare/ieşire şi în fişiere, caracterele pot fi reprezentate şi în alte coduri. Codul cel mai frecvent folosit în aceste scopuri este ASCII, în care reprezentarea caracterelor se face pe un singur octet, deci prin numere întregi fără semn în intervalul [0, 255]. Remarcăm ca datele de tip char sunt singurul tip de date întregi fără semn din limbajul Java, mulţimea de valori a acestui tip de date fiind intervalul de numere naturale [0, 65535]. Reprezentarea externă a caracterelor se face în una din următoarele forme: a/ punând caracterul respectiv între apostrofuri: 'a', 'B', '+', '(', '3', etc; b/ folosind o secvenţă escape, în care apare codul numeric hexazecimal al caracterului respectiv, în Unicode, de ex: '\u006c' sau '\uffff'; într-o astfel de secvenţă, codul numeric al caracterului (format din patru cifre hexazecimale) este precedat de \u; c/ folosind o secventa escape pentru caracterele speciale din urmatorul tabel:

Caracterul
'\b' '\t' '\n' '\f' '\r' '\"' '\'' '\\'

Reprezentarea in Unicode
'\u0008'

Semnificatia deplasare la stânga cu o poziţie (backspace) tabulare orizontală (horizontal tab) trecere la linie nouă (line feed, NL) salt la pagina nouă (form feed) întoarcerea carului (carriage return, CR) ghilimele (double quote) apostrof (single quote) bară inversă (backslash)

'\u0009'
'\u000a' '\u000c' '\u000d' '\u0022' '\u0027' '\u005c'

Datele de tip char pot fi folosite în operaţii numerice, în care caz ele sunt interpretate drept numere intregi fără semn, conform cu reprezentarea lor internă prin numere binare. Din acest motiv, în limbajul Java datele de tip char sunt incluse în categoria celor de tipuri întregi.

În programele Java, secvenţele escape pot fi folosite pentru a reprezenta caracterele în orice loc din textul sursa. Peste tot unde întâlneşte în textul sursă caracterul \ (bara inversă, backslash), compilatorul consideră că urmeaza o secvenţă escape, pe care o înlocuieşte cu caracterele Unicode corespunzătoare. Exemplul 1 Şirul de caractere "abc\"def" conţine secventa escape \", care se va inlocui cu codul

61

Severin Bumbaru

caracterului ", obţinându-se şirul abc"def. Acelasi şir de caractere putea fi scris sub forma "abc\u0022def", în care în loc de secvenţa escape \" s-a folosit direct cea care conţine valoarea numerica din Unicode a caracterului ", respectiv \u0022. Exemplul 2 Ştiind că reprezentările în Unicode ale literelor e şi t sunt, respectiv, \u0074, instrucţiunea
System.out.println("Exemplu");

\u0065 şi

poate fi scrisă sub forma
Sys\u0074\u0065m.ou\u0074.prin\u0074ln("Ex\u0065mplu");

Desigur că o astfel de înlocuire a caracterelor prin secvenţe escape nu este justificată în cazul caracterelor obişnuite, dar este utilă în cazul folosirii caracterelor speciale, cum sunt caracterul de trecere la lini noua '\n' şi altele.

Operaţii şi operatori pentru date de tip char
În limbajul Java, tipul char este considerat tip de date întregi. În consecinţă, toate operaţiile care se aplică tipurilor întregi se aplică şi asupra tipului char. Atunci când un caracter se foloseşte ca numar întreg se operează, evident, cu reprezentarea internă a caracterului respectiv, tratată ca numar natural. Exemplu În programul de mai jos, dat in fişierul TestChar.java, se testează unele operaţii cu date de tip char.

/* Operatii cu date de tip char */ class TestChar { public static void main(String args[]) { char c1='A', c2='a', c3='b', c4='1', c5='*', c6='\n'; System.out.println(c1+" "+c2+" "+c6+" "+c4+" "+c5+" "+c3); System.out.println((int)c1+" "+(int)c2+" "+(int)c6+" "+ (int)c4+" "+(int)c5); System.out.println(c1+c2); System.out.println(c1*c2); System.out.println((char)107); System.out.println('2'+" "+(int)'2'+" "+'3'+" "+(int)'3'+ " "+('2'+'3')); } }

La executarea acestui program, rezultatele afişate pe ecran sunt cele de mai jos:

A a 1 * b 65 97 10 49 42 162 6305 k 2 50 3 51 101

62

Programarea orientata pe obiecte în limbajul Java După afişarea caracterelor A şi a s-a transmis către dispozitivul de afişare caracterul '\n', care a fost interpretat drept comanda de trecere la linie nouă. După aceasta au fost afşsate caracterele 1, * şi b. Pe linia următoare sunt afişate caracterele deja afişate anterior, convertite în numere întregi. Se observă că în Unicode caracterul 'A' este reprezentat prin numarul 65, caracterul 'a' prin numarul 97, caracterul '\n' prin 10 etc. Pe următoarele două linii sunt afişate rezultatele operaţiilor 'c1'+'c2' si 'c1'*'c2', obţinându-se respectiv valorile numerice 162 si 6305. Se observă că, întrucât caracterele au fost folosite ca operanzi ai unor operatori aritmetici, rezultatele obţinute sunt numerice (de tip int). Pe linia următoare se afişează numărul 107 convertit din int în char şi interpretat deci drept 'k'; Pe ultima linie putem observa deosebirea dintre forma externă a caracterului (literalul de tip char) şi codificarea lui interna. Literalul de tip char '2' este caracterul 2 si nu numarul 2. În consecinţă, acest caracter nu se reprezinta intern prin numarul întreg 2, ci prin numărul întreg 50. În mod corespunzator, caracterul '3' se reprezinta intern prin numarul 51. Suma codurilor numerice ale celor doua caractere este, evident, numărul 101.

Întrebări
Nivel 1
1. Ce este un comentariu? 2. Ce fel de comentarii pot să apară în fişierele sursă Java? 3. Cum se reprezintă un şir în program? 4. Ce este concatenarea şi care este operatorul prin care se realizează? 5. Prin ce metode se afişează un şir de caractere pe ecran? 6. Ce sunt identificatorii şi cum se alcătuiesc ei în Java? 7. Ce sunt cuvintele cheie? 8. Ce este un literal? 9. Ce separatori se folosesc în limbajul Java? 10. Ce sunt operatorii? 11. Ce este efectul lateral al operatorului? 12. Toţi operatorii au efect lateral? 13. Ce sunt variabilele? 14. Ce este numele variabilei? 15. Ce se înţelege prin declararea variabilei? 16. Ce este o variabilă finală? 17. Ce este un tip de date? 18. Ce tipuri de date primitive există în limbajul Java? 19. Care este mulţimea de valori a tipului boolean? 20. Care este operatorul de negaţie, asupra cărui tip de date se aplică şi ce efect are? 21. Ce sunt operatorii logici & si &&? 22. Ce sunt operatorii logici | si ||? 23. Care sunt tipurile de date numerice? 24. Ce este operaţia de atribuire?

63

Severin Bumbaru 25. Cum acţionează operatorii == şi !=? 26. Ce efect are operatorul logic ^? 27. Ce contine o declaraţie de tip? 28. Ce este conversia de tip? 29. Ce este castul şi care este forma lui sintactică? 30. Care sunt operatorii de incrementare şi decrementare? 31. Ce operatori de comparaţie se folosesc în cazul datelor de tipuri întregi? 32. Cum se reprezintă intern datele de tipuri întregi? 33. Cum se scriu literalii în sistemul octal? 34. Cum se scriu literalii in sistemul hexazecimal? 35. Pot avea semn literalii octali şi cei hexazecimali? 36. Ce sunt datele în virgulă mobilă şi ce corespondent au ele în matematică? 37. Ce tipuri de date în virgulă mobilă există în Java? 38. Ce sunt literalii în virgulă mobilă? 39. Ce valori speciale pot avea literalii în virgulă mobilă? 40. Ce operaţii aritmetice se poa efectua asupra numerelor în virgulă mobilă? 41. Ce se întâmplă dacă, la o împărţire în virgulă mobilă, împărţitorul are valoarea zero? 42. Ce fel de date conţine tipul char? 43. Cum se reprezintă literalii de tip char? 44. Ce este o secvenţă escape? daţi exemple. 45. Ce operaţii se pot face supra datelor de tip char? 46. Care sunt operatorii de atribuire compusă şi ce efect au?

Nivel 2
1. Ce proprietăţi are operatorul de concatenare? 2. Este concatenarea asociativă? Dar comutativă? 3. Se poate oare folosi metoda print() în loc de println()? 4. Cu ce fel de caracter încep în Java numele de clase? 5. Ce deosebire este între literali şi identificatori? 6. De câte feluri sunt operatorii după numărul de operanzi? 7. Cum se plasează operatorii unari în raport cu operanzii lor? Dar cei binari? 8. Există operatori ternari? 9. Daţi o definiţie conceptului de variabilă. 10. Prin ce simbol se termina o declaraţie de variabile? 11. În ce mod se iniţializează o variabilă? 12. Ce sunt tipurile de date primitive? 13. Ce se stabileşte la definirea unui tip de date primitive? 14. Ce sunt tipurile de date derivate? 15. Ce este o clasă şi în ce fel de limbaje se foloseşte? 16. Ce deosebire există între modurile în care acţionează operatorii logici & şi &&? 17. Ce deosebire există între modurile în care acţionează operatorii logici | şi ||? 18. Ce deosebiri există între operatorii = şi ==? 19. În ce situaţii este obligatorie folosirea castului la conversiile datelor de tipuri primitive? 20. Care sunt operatorii aritmetici unari? 21. Ce deosebire este între aşezarea operatorului de incrementare sau decrementare în faţa operandului şi după acesta?

64

Programarea orientata pe obiecte în limbajul Java 22. Care sunt domeniile de valori pentru date de tipuri intregi? 23. Ce avantaj prezintă folosirea literalilor hexazecimali sau octali faţă de cei zecimali? 24. Cum se stabileşte tipul rezultatului operaţiilor aritmetice cu numere intregi? 25. În ce caz rezultatul unei operaţii aplicate asura unui operand de tip byte este tot de tip byte? 26. Ce este împărţirea întreagă? 27. Ce este excepţia de împărţire la zero şi în ce situaţii poate să apară? 28. Ce este depăşirea binară şi în ce situaţii poate să apară? 29. Cum acţionează operatorul <<? 30. Cum acţionează operatorul >>? 31. Ce deosebire este între operatorii >> şi >>>? 32. Cum acţionează operatorii ~, &, | şi ^ când operanzii sunt de tipuri întregi? 33. Cum se reprezinta intern datele în virgulă mobilă? 34. Ce sunt cifrele semnificative ale unui număr în virgulă mobilă? 35. În ce situaţii rezultatul unei operaţii în virgulă mobilă are valoarea infinit? 36. Cum se reprezintă intern datele de tip char?

65

Severin Bumbaru

Expresii. Instrucţiuni simple şi instrucţiuni structurate. Tratarea excepţiilor
Expresii cu date primitive; precedenţa operatorilor; Expresia condiţionala Instrucţiuni simple Principiile programării structurate; Structurile de control fundamentale; Tehnica rafinărilor succesive; Instrucţiuni structurate (structuri de control) Blocul ca realizare a structurii secvenţiale; variabile locale şi domenii de vizibilitate; Structura alternativă (if .. else); Structura de comutare (switch); Structuri repetitive Ciclul cu test iniţial (while); Ciclul cu test final (do .. while); Ciclul for Instrucţiuni etichetate; Terminarea abruptă a execuţiei instrucţiunilor structurate; Tratarea excepţiilor în limbajul Java (try .. catch) Întrebari 66 70 71 74 74 78 79 79 81 85 86 86 89 90 93 93 95 97

Expresii cu date primitive
În programare, expresia este o combinaţie permisă ("legală", corectă) de simboluri, care reprezintă o valoare. Tipul expresiei este acelaşi cu tipul valorii ei. Putem avea, deci, expresii de tip boolean, int, long, float, double etc. Fiecare limbaj de programare are regulile sale, prin care se stabileşte ce expresii sunt permise sau nepermise. În limbajul Java, expresia poate conţine literali, variabile, operatori, operanzi, funcţii şi paranteze şi trebuie să poată fi evaluată (calculată), astfel încât să se obţină o valoare.

66

Programarea orientata pe obiecte în limbajul Java Construirea expresiei se face pas cu pas, având în vedere că: - un literal sau o variabilă constituie o expresie; tipul şi valoarea acestei expresii sunt cele ale literalului sau variabilei respective; Exemple de astfel de expresii sunt:
true -176 1.358746E-12 "sir de caractere" beta

- un operator împreună cu operanzii lui constituie o expresie.

De exemplu:
alpha+3.765 (s-a folosit operatorul binar +) alpha++ (s-a folosit operatorul unar postfix ++) -alpha (s-a folosit operatorul unar prefix -)

- o expresie cuprinsă între paranteze constituie o nouă expresie, a cărei valoare este identică cu cea a expresiei din interior;

De exemplu:
(-176) (alpha++) (alpha+3.765)

- valoarea unei expresii poate fi folosită ca operand într-o altă expresie.

De exemplu:
(alpha+3.765)*(alpha++)

Se observă că, procedând astfel, se pot construi expresii din ce în ce mai complicate. Utilizarea funcţiilor în expresii va fi discutată ulterior. Mernţionăm însă că şi operatorii acţionează tot ca nişte funcţii. De exemplu, evaluarea expresiei alpha+3.765 este, de fapt, calcularea unei funcţii, care are ca argumente valorile variabilei alpha şi literalului 3.765 şi are ca valoare suma acestora.

67

Severin Bumbaru

Expresia poate fi reprezentată ca un arbore sintactic, care are ca frunze literali sau variabile, iar ca rădăcină are valoarea expresiei. Fiecare nod al arborelui sintactic conţine un operator sau o funcţie, care are ca operanzi valorile subexpresiilor din nodurile-fii. De exemplu, expresia
a*b+3*c

corespunde următorului arbore:

Precedenţa operatorilor
La evaluarea unei expresii, prezinta o importanţă deosebită ordinea în care se aplică operatorii pe care îi conţine, deoarece de această ordine poate să depindă valoarea expresiei. În toate limbajele de programare, în care se folosesc expresii, se stabilesc şi anumite reguli de precedenţă şi de asociativitate, pe baza cărora se stabileşte ordinea de evaluare. Fiecărui operator i se asociază o precedenţă, adică un nivel de prioritate în aplicarea operatorului respectiv. De exemplu, în expresia a+b*c operatorul * se va aplica înaintea operatorului +, deoarece are precedenţa superioară. În consecinţă, aceasta expresie se va calcula ca şi când ar fi scrisă sub forma a+(b*c). În limbajul Java, la stabilirea ordinii operaţiilor se aplica următoarele reguli: - operatorii unari se aplică înaintea celor binari; - expresiile din interiorul parantezelor se evaluează înaintea celor din exterior; - dacă, într-o expresie, toţi operatorii au acelaşi nivel de precedenţă, ei se aplică de la stânga la dreapta; - operanzii unui operator se evalueaza înainte de a se aplica operatorul respectiv (deci operatorul se aplică întotdeauna asupra valorilor operanzilor săi); dacă operaţia este binară, operandul din partea stângă se evaluează înaintea celui din partea dreaptă; - nivelul de precedenţă al operatorilor se stabileşte conform cu tabela de mai jos, în care:
 

toţi operatorii din aceeaşi celulă a tabelei au acelaşi nivel de precedenţă; operatorii de pe un nivel superior se aplică înaintea celor de pe nivelurile inferioare. Tabela de precedenţă a operatorilor în limbajul Java

68

Programarea orientata pe obiecte în limbajul Java
. ++ -+ unar ! * + binar << < <= >> > instanceof != & ^ | && || ?: = <<= += -= >>= |= *= /= %= >>>= &= ^= [] ~ new / () - unar (<tip>) % - binar >>> >=

==

,

Exemplu În programul de mai jos, a cărui sursă se găseşte în fişierul EvExpresii.java, se calculează unele expresii cu numere întregi şi se afişează rezultatele.
/* Evaluarea expresiilor */ class EvExpresii { public static void main(String args[]) { int a=23, b=500, c=17, d; System.out.println((a+b*c)+" "+((a+b)*c)+" "+(b/a*c)+ " "+(b/(a*c))); System.out.println((-a+b*c++/3+(d=c--%4))+" "+c+" "+d); System.out.println((d<1 && c--==0)+" "+c+" "+d); System.out.println((d<1 & ++d<c)+" "+c+" "+d); } }

La executarea acestui program, se obţin pe ecran următoarele rezultate:

8523 8891 357 1 2812 17 2 false 17 2 false 17 3

Sa urmărim acum desfăşurarea calculelor care au condus la aceste rezultate. - s-a evaluat expresia (a+b*c), efectuându-se mai întâi înmulţirea b*c, deoarece operatorul

69

Severin Bumbaru * are precedenţa superioară operatorului +, şi s-a obţinut ca valoare numărul întreg 8523; - următorul operator, +, are ca operanzi numărul întreg 8523 şi şirul de caractere " ". În consecinţă, s-a convertit numarul 8523 într-un şir (trecându-se de la forma internă a acestuia la cea externă) şi s-a obţinut şirul "8523", care s-a concatenat cu şirul " ", obţinându-se şirul "8523 "; - s-a calculat expresia ((a+b)*c) obţinându-se numărul întreg 8891; de data aceasta, s-a calculat mai întâi subexpresia a+b, deoarece este între paranteze; - s-a convertit numărul 8891 din forma internă în cea externă şi s-a concatenat cu şirul obtinut anterior şi cu sirul " ", rezultând şirul "8523 8891 "; - s-a calculat expresia (b/a*c). Întrucît operatorii / şi * au acelaşi nivel de precedenţă, calculul s-a făcut de la stânga la dreapta, astfel: . s-a efectuat împărţirea întreagă b/a, obţinându-se valoarea 21; . s-a efectuat înmulţirea 21*c, obţinându-se rezultatul 357. - s-a concatenat forma externă a numărului 357 cu şirul obţinut anterior şi cu şirul " ", obţinându-se şirul "8523 8891 357 "; - s-a calculat expresia (b/(a*c)), efectuându-se acum mai întâi înmultirea a*c, apoi împărţirea întreagă a valorii lui b la rezultatul înmulţirii şi s-a obţinut valoarea 1; - s-a concatenat forma externă a acestei valori cu şirul obţinut anterior, obţinându-se şirul "8523 8891 357 1" care a fost afişat pe ecran, după care s-a trecut la linie nouă. Recomandăm să urmăriţi în acelaşi mod cum s-au calculat şi afisat celelalte expresii. Menţionăm că: - la evaluarea expresiei (-a+b*c++/3+(d=c--%4))prezinta interes cu ce valori intră în calcul variabila c şi ce valoare are operandul din dreapta al ultimului operator +; - avand în vedere că valoarea expresiei d<1 este false, la aplicarea operatorului && nu s-a mai evaluat al doilea operand, deci variabila c a rămas la valoarea ei anterioară, in timp ce la aplicarea operatorului & a fost evaluat şi al doilea operand, astfel că variabila d a fost incrementată.

Expresia condiţională
Urmând "tradiţia" limbajului C, în limbajul Java există operatorul ternar (cu trei operanzi) ?: numit operatorul condiţional, care este utilizat în cadrul următoarei expresii condiţionale:
operand1 ? operand2 : operand3

unde: operand1 - expresie booleană; operand2 şioperand3 - expresii de tipuri compatibile: fie ambii operanzi de tip boolean, fie ambii de tipuri numerice, fie ambii de tipuri referinţă. Evaluarea expresiei decurge astfel: - se evaluează operand1 obţinându-se o valoare booleană; - dacă operand1 are valoarea true, atunci valoarea expresiei condiţionale este cea care se obţine evaluând operand2; altfel, este cea care se obţine evaluând operand3.

Exemplu În programul de mai jos, dat şi în fişierul ExprCond.java, se exemplifică mai multe cazuri de utilizare a expresiei condiţionale.

70

Programarea orientata pe obiecte în limbajul Java

/* Testarea expresiei conditionale */ class ExprCond { public static void main(String args[]) { int m=17, n=-6, p=0, q=0; System.out.println(m>n ? m+1 : 2*n-2); System.out.println(m<=n ? 3*m : m-n); /* Se evalueaza numai operandul 2 */ System.out.println((m>=6 ? ++p*n : ++q-m)+" "+p+" "+q); /* Se evalueaza numai operandul 3 */ System.out.println((m<n+3 ? m-- : n++)+" "+m+" "+n); /* Un exemplu in care operanzii 2 si 3 sunt tot expresii conditionale */ System.out.println(m>p ? (n<0? m+1 : m-2) : (m>n ? m : n)); } }

Executând acest program, obţinem pe ecran urmatoarele rezultate:

18 23 -6 1 0 -6 17 -5 18

Din aceste rezultate putem constata cu uşurinţă că s-au evaluat de fiecare dată numai acei operanzi, care erau necesari în evaluarea expresiei condiţionale. În ultima instrucţiune se ilustrează şi un caz de utilizare drept operanzi a unor expresii condiţionale.

Instrucţiuni simple
În limbajul Java, procesul de calcul este controlat de instrucţiuni (enunţuri). Fiecare instrucţiune indică una sau mai multe acţiuni pe care trebuie să le execute calculatorul. Corpul oricărei metode este constituit dintr-o succesiune de instrucţiuni. Executarea metodei constă în executarea acestor instrucţiuni într-o ordine determinată. Instrucţiunile pot fi grupate în blocuri. După structura lor, instrucţiunile pot fi simple sau structurate. Instrucţiunile simple nu conţin în interiorul lor alte instrucţiuni. Există trei categorii principale de instrucţiuni (enunţuri) simple: declaraţiile de variabile locale, instrucţiunileexpresie şi instrucţiunea vidă. La scrierea lor se respectă următoarele reguli:

71

Severin Bumbaru
 

toate instrucţiunile simple se termină cu caracterul ';' (punct şi virgulă). Instrucţiunea vidă este formată numai din acest caracter; declaraţiile de variabile locale servesc pentru a specifica tipul, numele şi (opţional) valoarea iniţială a variabilelor. Forma sintactică şi utilizarea declaraţiilor au fost prezentate anterior; instrucţiunile-expresie sunt formate dintr-o expresie de atribuire, de incrementare/decrementare sau de invocare de metodă, urmată de caracterul ';'.

Notă pentru programatorii în alte limbaje În limbajul Java, declaraţiile de variabile locale sunt considerate instrucţiuni executabile, deci ele au efect la executarea programului. În majoritatea limbajelor compilate, declaraţiile de variabile locale sunt luate în consideraţie numai la compilare, deci sunt considerate instrucţiuni neexecutabile. În limbajul Java declaraţiile sunt, de asemenea, luate în consideraţie de compilator, care verifică dacă declaraţia este corectă sintactic şi, de asemenea, verifică la fiecare apariţie a unei variabile locale în programul sursă dacă: - variabila respectivă a fost declarată; - tipul variabilei corespunde contextului în care este folosită variabila respectivă; - variabila are deja o valoare. Efectul declaraţiilor de variabile locale din programele Java constă în faptul că în momentul execuţiei se alocă în memorie (pe stivă) spaţiu pentru variabilele respective şi se introduc valorile iniţiale. În consecinţă: - declaraţiile de variabile pot fi plasate oriunde în interiorul unui bloc (pot fi puse la începutul acestuia sau pot fi intercalate cu alte instrucţiuni); - "executarea" declaraţiei are loc de la stânga la dreapta şi de sus în jos, astfel că la evaluarea expresiilor prin care se calculează valoarea iniţială a unei variabile, se consideră cunoscute valorile iniţiale ale variabilelor declarate anterior. Instrucţiunile-expresie pot fi instrucţiuni de atribuire, de incrementare/decrementare sau de invocare de metodă. Instrucţiunile de atribuire sunt instrucţiuni prin care unei variabile i se atribuie o nouă valoare, modificându-se astfel valoarea avută anterior. Instrucţiunea de atribuire este o expresie de atribuire sau de atribuire compusă urmată de caracterul ';' (punct şi virgulă) deci are următoarea formă sintactică:
expresie_de_atribuire;

Valoarea acestei expresii se ignoră, astfel că acţiunea care se efectueaza la executarea instrucţiunii de atribuire constă numai în efectul lateral al acesteia, adică în modificarea valorii variabilei din partea stânga. De exemplu, dacă a şi b sunt variabile numerice, atunci a=b+7 este o expresie de atribuire, în timp ce
a=b+7;

în care la sfârşitul expresiei de atribuire s-a pus terminatorul punct şi virgulă, este o instrucţiune de atribuire. Se pot face atribuiri în lanţ, ca în exemplul următor: a=b=c=d=3;

72

Programarea orientata pe obiecte în limbajul Java

Instrucţiunile de incrementare/decrementare sunt instrucţiuni formate dintr-o expresie de incrementare sau decrementare, urmată de caracterul ';' (punct şi virgulă). De exemplu, a++ este o expresie de postincrementare, în timp ce
a++;

este o instrucţiune de postincrementare. Instrucţiunile de invocare de metodă sunt instrucţiuni prin care se cere executarea unei metode, transmiţându-i-se acesteia valorile parametrilor. Instrucţiunea constă din numele metodei, urmat de o pereche de paranteze care conţine valorile parametrilor şi se termină prin caracterul ';' (punct şi virgulă). Acţiunea cerută de instrucţiune constă în executarea metodei respective. Daca metoda este o funcţie, valoarea funcţiei se ignoră, menţinându-se numai efectele laterale ale metodei respective. De exemplu:
System.out.println("şir de caractere"); este o instrucţiune prin care se invocă metoda println a câmpului out din clasa System, transmiţându-i-se parametrul "şir de caractere". Acţiunea care se

realizează astfel constă din afişarea pe ecran a şirului primit ca parametru. Exemplu În fişierul Instructiuni.java se dă un exemplu simplu de program care conţine o secvenţa de instrucţiuni simple: o declaraţie de variabile, două instrucţiuni de atribuire, o instrucţiune de incrementare şi o invocare de metodă.
/* Exemple de instructiuni simple */ class Instructiuni { public static void main(String args[]) { double a=3.76, b=-15, c=2*a-b, d, e; // declaratie de variabile d=3*a-7; // instructiune de atribuire b++; // instructiune de incrementare e=b-17; // instructiune de atribuire /* invocare de metoda */ System.out.println("a="+a+" b="+b+" c="+c+" d="+d+" e="+e); } }

Pentru mai multa claritate, fiecare instrucţiune a fost scrisa pe o linie de program separată şi a fost însoţită de un comentariu. Se permite, însă, scrierea mai multor instrucţiuni pe o singură linie sau, invers, o instrucţiune se poate extinde pe mai multe linii. Singura restricţie este ca trecerea de la o linie la alta să se facă la nivelul unui operator sau separator (să nu se rupă in doua un simbol format din mai multe caractere, un identificator sau un literal). Remarcăm că la declararea variabilei c s-a luat în consideratie faptul că variabilele a şi b au deja valori iniţializate anterior.

73

Severin Bumbaru

Principiile programării structurate
Programarea structurată este o orientare în conceperea şi alcătuirea programelor, având ca obiectiv o mai bună gestionare a complexităţii acestora, ţinând cont de particularităţile gândirii umane. Conform metodei programării structurate, la conceperea unui program, este recomandabil să se respecte următoarele principii: 1. Teorema de structură: orice program poate fi întocmit folosind numai trei structuri de control fundamentale: structura secvenţială, structura alternativă şi structura repetitivă. 2. La conceperea programelor se recomandă să se aplice tehnica de elaborare descendentă (în engleză Top-Down), numită şi tehnica rafinărilor succesive. 3. Domeniile de valabilitate (de vizibilitate) ale variabilelor şi structurilor de date trebuie să fie limitate. În limbajul Java, pentru rezolvarea problemei realizării creşterii eficienţei muncii programatorilor s-a adoptat concepţia modernă a programării orientate pe obiecte. In consecinţă, modulele de program sunt mici şi rolul programării structurate este sensibil redus. Totuşi, fiecare metodă din limbajul Java este un mic program, la elaborarea căruia se aplică principiile programării structurate, ceeace s-a avut în vedere şi la adoptarea instrucţiunilor de bază oferite de acest limbaj. Este deci util ca şi programatorii în Java sa cunoască principiile fundamentale ale programării structurate.

Structurile de control fundamentale
Aceste structuri se referă la modalităţile în care se pot înlănţui instrucţiunile dintr-un program, deci în care se transmite "controlul" de la o instrucţiune la alta în timpul executării programului. Conform "teoremei de structură", sunt suficiente trei astfel de "structuri de control":

1. Structura secvenţială
În mod "natural", se consideră că instrucţiunile se execută în ordinea în care acestea figurează în program. Înlănţuirea instrucţiunilor (transmiterea "controlului" de la o instrucţiune la alta) se face, în acest caz, conform schemei logice din figura 1.

74

Programarea orientata pe obiecte în limbajul Java

În pseudocod, aceeaşi secvenţă de program se scrie astfel:
<instrucţiunea_1> <instrucţiunea_2> .... <instrucţiunea_n>

Există, deci, un număr oarecare (n) de instrucţiuni, care se execută una dupa alta, în ordinea în care sunt scrise în program. Toate exemplele de programe date în capitolele şi secţiunile anterioare au structura secvenţială.

2. Structura alternativă (condiţională, de decizie)
Un fragment de program are structura alternativă (numită şi structură condiţională sau de decizie), atunci când se alege una din două căi posibile, în funcţie de modul în care este satisfăcută sau nu o anumită condiţie. Schema logică a structurii de control alternative este dată în figura 2.

În această schemă logică, Condiţie este o expresie booleană. Executarea acestui fragment de program decurge astfel: se verifică mai întâi dacă este satisfăcută condiţia. Dacă ea este satisfăcută, deci expresia Condiţie are valoarea true, se execută Instrucţiunea 1, iar în caz contrar se execută Instrucţiunea 2. In pseudocod, acest fragment de program se scrie astfel:
Dacă <condiţie> atunci <instrucţiunea_1>

75

Severin Bumbaru
altfel <instrucţiunea_2> Sfarşit_dacă

În unele convenţii de pseudocod, în loc de Instrucţiunea_1 şi Instructiunea_2 pot fi puse secvenţe de instrucţiuni. Se observă că există o asemănare între instrucţiunea alternativă şi expresia condiţională. În ambele cazuri se verifică dacă este satisfacută o condiţie şi - în funcţie de rezultatul obtinut se alege una din două variante posibile. Deosebirea este următoarea: - în cazul expresiei condiţionale se alege una din cele două expresii conţinute în aceasta, iar rezultatul obţinut este valoarea expresiei astfel alese; - în cazul structurii de control alternative se alege una din cele doua instrucţiuni conţinute în această structură; instrucţiunea astfel aleasă este executată, iar rezultatul obţinut este efectul (lateral) produs de executarea acestei instrucţiuni.

3. Structura repetitivă (ciclu, buclă)
În cazul structurii de control repetitive, o instrucţiune sau o secvenţă de instrucţiuni se repetă cât timp este satisfacută o anumită condiţie. Structura repetitivă fundamentală, acceptată în teoria programării structurate, este cea de ciclu cu test iniţial. Schema logică a acestei structuri de control este dată în figura 3.

Executarea acestei structuri are loc astfel: 1. se evaluează expresia booleană care reprezintă Condiţia; 2. dacă valoarea acestei expresii este true, deci condiţia este satisfacută, se merge pe ramura DA şi se executaă instrucţiunea din corpul ciclului, după care se trece la punctul 1; 3. altfel (deci dacă condiţia nu este satisfacută) se merge pe ramura NU şi se iese din ciclu. Se observă, deci, că se va executa instrucţiunea în mod repetat, cât timp este satisfăcută condiţia. În pseudocod, această structură de control se programează astfel:
cât_timp <condiţie> execută <instrucţiune> sfârşit_ciclu

76

Programarea orientata pe obiecte în limbajul Java În unele versiuni de pseudocod, în loc de o singură <instrucţiune> se poate pune o secvenţă de instrucţiuni. Deşi, din punct de vedere al teoriei programării structurate, ciclul cu test iniţial este suficient ca structură repetitivă, în multe limbaje de programare (inclusiv Java), din motive de comoditate a programării se admite şi o a doua formă de astfel de structură, numită ciclu cu test final. Schema logică a acestui ciclu este dată în figura 4.

Se observă că, în această schemă logică, executarea instrucţiunii se repetă, ca şi în cazul ciclului precedent, cât timp este satisfacută condiţia. Deosebirea dintre cele două forme de cicluri este că, în ciclul cu test iniţial, condiţia este evaluată înainte de a se executa instrucţiunea din corpul ciclului, în timp ce în ciclul cu test final evaluarea condiţiei se face după ce instrucţiunea a fost deja executată. Aceasta este o deosebire importantă, deoarece, în cazul ciclului cu test initial, este posibil ca instrucţiunea să nu se execute niciodată (dacă chiar de la prima evaluare condiţia nu este satisfacută), în timp ce la ciclul cu test final instrucţiunea din corpul ciclului va fi executată cel puţin o dată. Aşa dar, ciclul cu test final poate fi înlocuit prin unul echivalent cu test iniţial, dar nu şi invers. Iată dece, în teoria programării structurate, ciclul cu test iniţial este considerat ca structură de control fundamentală, în timp ce cel cu test final este o structură de control admisă, dar nu fundamentală. În pseudocod, ciclul cu test final reprezentat prin schema logică din figura 4 se scrie sub forma generală:
execută <instrucţiune> cât_timp <condiţie>

În unele limbaje (de exemplu în Pascal) pentru ciclul cu test final se adoptă o schemă logică în care singura deosebire faţă de cea din figura 4 este doar prin faptul că cele două ramuri DA şi NU işi schimbă locurile între ele, ceeace în pseudocod se scrie sub forma:
execută <instrucţiune> până_când <condiţie>

77

Severin Bumbaru În acest caz, instrucţiunea din corpul ciclului se va repeta cât timp NU este satisfacuta condiţia, deci până când se va constata pentru prima oară că ea este satisfăcută. În ambele cazuri, în unele variante de pseudocod instrucţiunea din corpul ciclului poate fi înlocuită printr-o secvenţa de instrucţiuni. Dacă se are în vedere utilizarea limbajului Java, utilizarea unei secvenţe nu este necesară, deoarece ea se poate înlocui printr-o singură instrucţiune compusă, aşa cum se va arăta ulterior. Din examinarea structurilor de control prezentate în acest capitol, putem constata o proprietate importantă: schemele logice ale tuturor structurilor de control admise de metoda progrămarii structurate au fiecare un singur punct de intrare şi un singur punct de ieşire. Datorită acestei proprietăţi, ele pot fi folosite la elaborarea unor programe complexe prin tehnica rafinărilor succesive, aşa cum se va arăta în secţiunea următoare.

Tehnica rafinărilor succesive
Adoptarea metodei programării structurate a făcut posibilă elaborarea programelor prin tehnica rafinărilor succesive (engl.: stepwise refinement), cunoscută şi sub numele de tehnica descendentă (engl.:top-down), care va fi prezentată succint în cele ce urmează. La elaborarea unui program se porneşte de la specificaţia acestuia, în care se precizează CE trebuie sa facă programul respectiv (CE problemă trebuie rezolvată, CE rezultate trebuie obţinute) şi se ajunge în final la programul propriu-zis, care arată CUM trebuie acţionat pentru a se obţine aceste rezultate. De cele mai multe ori, programul elaborat conţine un număr mare de instrucţiuni, cu numeroase ramificaţii şi cicluri. Scrierea unui astfel de program poate fi o sarcină extrem de dificilă şi greu de îndeplinit, dacă nu se procedează sistematic. În plus, dacă programul nu este suficient de clar şi de bine documentat, va fi extrem de greu sau chiar imposibil să fie depanat (să se corecteze eventualele erori de programare) şi să fie modificat ulterior, dacă se modifică specificaţia. Tehnica rafinărilor succesive oferă o cale de a parcurge acest drum de la CE la CUM în mai mulţi paşi, astfel încât să se pornească de la un număr mic de enunţuri (pseudoinstrucţiuni) de nivel de abstractizare ridicat, după care - la fiecare pas de rafinare - fiecare din aceste instrucţiuni se descompune în mai multe instrucţiuni de nivel de abstractizare mai coborât (mai "concrete"), până când se ajunge la instrucţiuni scrise direct în limbajul de programare folosit (de exemplu în Java). Se pune deci un accent deosebit pe abstractizarea datelor si a instrucţiunilor. Dupa cum arăta E.W.Dijkstra, abstractizarea este calea prin care omul stapâneşte complexitatea. Dacă privim schemele logice ale structurilor de control fundamentale, permise de metoda programării structurate, constatăm cu uşurinţă că atât blocurile care conţin instrucţiuni, cât şi structurile de control admise de metoda programarii structurate au fiecare un singur punct de intrare şi un singur punct de ieşire. Aceasta înseamnă că orice bloc care conţine o instrucţiune poate fi înlocuit în schema respectivă printr-o structură de control. Ca urmare, se poate aplica următoarea cale de elaborare a programului, specifică tehnicii rafinărilor succesive: - iniţial se consideră că programul este alcatuit dintr-o singură pseudoinstrucţiune, de înalt

78

Programarea orientata pe obiecte în limbajul Java nivel de abstractizare, care constă chiar din specificaţia problemei pe care o va "rezolva" programul respectiv; - la pasul de rafinare următor, această "problemă" se descompune în două sau mai multe "subprobleme"; aceasta înseamnă că unica pseudoinstrucţiune existentă iniţial se înlocuieşte prin una din structurile admise de metoda programării structurate (structura secvenţială, alternativă sau repetitivă); această structură conţine în ea una sau mai multe instrucţiuni sau pseudoinstrucţiuni; - la paşii de rafinare următori, fiecare pseudoinstrucţiune, care nu are corespondenta directă în limbajul de programare utilizat, se inlocuieşte, de asemenea, prin una din structurile de control admise; textul pseudoinstrucţiunii înlocuite poate fi, totusi, menţinut sub formă de comentariu, astfel încât să se înţeleagă mai uşor programul; - se continuă acest proces de rafinări succesive, până când toate instrucţiunile astfel obţinute au corespondent în limbajul de programare utilizat.

Instrucţiuni structurate
Limbajul Java a fost alcătuit astfel, încât programatorul este obligat sa respecte principiile programării structurate. Aceasta se datoreşte faptului ca instrucţiunile structurate ale limbajului sunt cele prevăzute de această metodă de programare. Există următoarele instrucţiuni structurate:
   

blocul - prin care se realizează structura secvenţială; instrucţiunile if si if .. else - prin care se realizează structura alternativă; instrucţiunea switch - prin care se realizează structura de comutare (de selecţie); instrucţiunile while, do .. while şi for - prin care se realizează structurile repetitive.

În plus, în limbajul Java există o structură de control dedicată tratării excepţiilor care apar în timpul executării programului, realizată prin instrucţiunile try .. catch

Blocuri
Blocul este o secvenţă de instrucţiuni cuprinsă între acolade. Forma sintactică a blocului este, deci, următoarea:
{ secvenţă_de_instrucţiuni } unde secvenţă_de_instrucţiuniconstă dintr-un

număr oarecare de instrucţiuni succesive, putând fi şi vidă. Instrucţiunile din această secvenţă pot fi atât simple, cât şi structurate, inclusiv alte blocuri. Din punct de vedere al programării structurate, blocul este realizarea structurii de control secvenţiale. Aceasta inseamnă că instrucţiunile dintr-un bloc se execută una dupa alta, în ordinea în care ele sunt scrise în program. Tocmai de aceea se şi foloseşte termenul secvenţă

79

Severin Bumbaru de instrucţiuni pentru a indica ce conţine blocul.

Exemplu În programul de mai jos, conţinut în fişierul Blocuri.java, se exemplifică folosirea unor blocuri imbricate (cuprinse unul în altul). Prin comentarii s-a semnalat începutul şi sfârşitul fiecărui bloc, deşi aceasta nu se face în mod curent. Remarcăm că însuşi corpul metodei main (şi al oricărei alte metode) este tot un bloc. Fiecare bloc conţine atât declaraţii de variabile, cât şi instrucţiuni-expresie
/* Exemplu de program care contine blocuri imbricate */ class Blocuri { // aici incepe corpul clasei Blocuri public static void main(String args[]) { // aici incepe blocul A int m=8, n=-3; double u=m*3.5+32, v=2, w; w=m*u+v; System.out.println("m="+m+" n="+n+" u="+u+" v="+v+" w="+w); m++; { // aici incepe blocul B, continut in A int a=5; double p=m*w-3.5, q; q=n*p-a; // s-a folosit n din blocul exterior { // aici incepe blocul C continut in B System.out.println("m="+m+" a="+a+" p="+p+" q="+q); } // aici se termina blocul C } // aici se termina blocul B } // aici se termina blocul A (corpul metodei main) } // aici se incheie corpul clasei Blocuri

La executarea acestui program se afişează următoarele rezultate:

m=8 n=-3 u=60.0 v=2.0 w=482.0 m=9 a=5 p=4334.5 q=-13008.5

Remarcăm că: - în expresiile prin care se calculează valoarea iniţială a variabilei u s-a luat în consideraţie valoarea variabilei m determinată anterior; - în blocul B, la calcularea valorii iniţiale a variabilei p s-au folosit valorile variabilelor m si w calculate la executarea unor instrucţiuni-expresie anterioare; se confirmă astfel că iniţializarea variabilelor nu se face la compilare, când aceste valori nu sunt cunoscute, ci la executarea declaraţiei.

Variabile locale şi domenii de vizibilitate
Variabilele declarate într-un bloc sunt variabile locale ale blocului respectiv. Domeniul de vizibilitate al unei variabile locale începe în locul din program în care aceasta a fost declarată şi se încheie la sfârşitul blocului care conţine declaraţia respectivă. Variabila este deci "vizibilă" (poate fi utilizată) în propriul său bloc şi în toate blocurile interioare acestuia, în domeniul de vizibilitate.

80

Programarea orientata pe obiecte în limbajul Java Exemplu: În programul din fişierul Blocuri.java, dat ca exemplu mai sus, variabilele m, n, u, v şi w sunt declarate în blocul A (în însuşi corpul metodei main) şi sunt vizibile (pot fi utilizate) atât în acest bloc, cât şi în blocurile interioare B şi C. În schimb variabilele a, p şi q pot fi utilizate numai în blocul B, în care sunt declarate, şi în blocul C situat în interiorul acestuia.

În memoria maşinii virtuale Java, variabilele locale sunt plasate pe stiva procesului curent. La executarea unei instrucţiuni-declaraţie, variabilele conţinute în aceasta sunt puse pe stivă, în ordinea în care ele apar în declaraţia respectivă. Când se ajunge la sfârşitul blocului în care au fost declarate, variabilele sunt scoase de pe stivă. Cât timp se află pe stivă, variabilele sunt vizibile şi deci pot fi utilizate.

Structuri alternative
Instrucţiunea if
Instrucţiunea if serveşte pentru realizarea structurii alternative din programarea structurată. Sub forma ei cea mai simplă, această instrucţiune se scrie astfel:
if(expresie_booleană) instrucţiune

unde instrucţiunepoate fi orice instrucţiune valabilă în limbajul Java: instrucţiune simplă sau structurată, inclusiv un bloc sau un alt if. Remarcăm că în forma generală de mai sus nu am pus la sfârşit caracterul ';' (punct şi virgulă). Dacă instrucţiune este o instrucţiune simplă, ea include şi acest simbol. Semnificaţia instrucţiunii if este următoarea: dacă valoarea expresiei booleene din paranteză este true, atunci se execută instrucţiune, iar altfel nu se execută nimic, continuându-se programul.

Exemple 1. Fie instrucţiunea
if(alpha<beta) x=2*alpha+beta;

În acest caz, instrucţiune este instrucţiunea-expresie
x=2*alpha+beta;

care se termină ea însăşi prin punct şi virgulă. Înstrucţiunea if se interpretează astfel: dacă expresia booleană alpha<beta are valoarea true, atuncivariabileix i se atribuie valoarea expresiei aritmetice 2*alpha+beta. Altfel (dacă expresia booleana are valoarea false) variabila x rămâne la valoarea pe care a avut-o anterior. 2. Fie instrucţiunea
if(alpha<beta) { int gamma=alpha-3*beta; x=2*gamma; y=gamma-3; } În acest caz, instrucţiune este un bloc, care se execută numai dacă expresia booleană alpha<betaare valoarea true. Remarcăm ca după acolada prin care se închide blocul nu

s-a

mai pus punct şi virgulă.

81

Severin Bumbaru 3. Fie instrucţiunea În
if(alpha<beta) if(beta>0) System.out.println(alpha+" "+beta); acest caz, instrucţiune este un alt if.

Deşi cuvântul then nu apare explicit, în specificaţia limbajului Java această forma a lui if este cunoscută sub denumirea "instrucţiunea if-then" (if-then statement), care se traduce prin dacă .. atunci. Remarcăm că această instrucţiune realizează un caz particular al structurii alternative, unde pe ramura altfel nu se efectuează nici o acţiune.

Instrucţiunea if .. else
Instrucţiunea if .. else realizează ambele ramuri ale structurii alternative şi are forma
if(expresie_booleană) instrucţiune_1 else instrucţiune_2

în care instrucţiune_1 şi instrucţiune_2 pot fi instrucţiuni simple sau structurate. Interpretarea acestui if..else este următoarea: dacă expresie_logică are valoarea true,atunci se execută instrucţiune_1, iar altfel se execută instrucţiune_2.

Deşi cuvântul then nu apare explicit, în specificaţia limbajului Java această instrucţiune se numeşte if-then-else. Exemple 1. Fie instucţiunea În termină prin punct şi virgulă. 2. Fie instrucţiunea
if(a<b) { x=2*a-b; y=a+1; } else { x=a+b; y=b; } acest caz, instrucţiune_1 if(a<b) x=2*a-b; else x=a+b; acest caz, atât instrucţiune_1,

cât şi instrucţiune_2 sunt instrucţiuni-expresii şi se

În şi instrucţiune_2 sunt blocuri. Remarcăm că după acolada închisă a blocului nu s-a mai pus punct şi virgulă.

82

Programarea orientata pe obiecte în limbajul Java

Instrucţiuni if .. else imbricate
Atât instrucţiunea if, cât şi if..else, pot conţine alte instrucţiuni if sau if..else. În acest caz se pune problema cărui if îi aparţine un anumit else. O programare neatentă poate conduce la greşeli, în sensul că interpretarea dată de compilatorul Java poate fi diferită de cea avută în vedere de programator. Pentru a nu se produce astfel de confuzii, cel mai bine este să se grupeze instrucţiunile în blocuri. Exemplu Fie instrucţiunea
if(a<b) if(b<c) System.out.println("A"); else System.out.println("B"); else System.out.println("C");

Interpretarea corectă este, în acest caz, că primul else aparţine celui de al doilea if, iar ultimul else aparţine primului if. Această structură se poate întări scriind instrucţiunea astfel:
if(a<b) { if(b<c) System.out.println("A"); else System.out.println("B"); } else System.out.println("C");

Întrucât s-au folosit acoladele pentru a pune instrucţiunea if..else interioară sub formă de bloc, a devenit clar pentru oricine cărui if îi apartine fiecare din cei doi else.

Instrucţiunea switch
Deşi în teoria programării structurate se demonstrează că, pentru a realiza programe cu structură ramificată, este suficient să se folosească structura de control alternativă, realizată în limbajul Java prin instrucţiunea if..else, în practica programării se admit şi structuri de comutare sau de selecţie, care sunt prezente in diferite limbaje de programare. Aceste structuri permit să se aleagă una din mai multe ramuri paralele,alegerea depinzând de valoarea unei expresii numită comutator sau selector. În acelaşi timp, ele respectă principiul de bază al programării structurate, conform căruia fiecare structură de control trebuie să aibă un singur punct de intrare şi un singur punct de ieşire, astfel că se încadrează în această metodă de elaborare a programelor. În limbajul Java, structura de comutare se realizează prin instrucţiunea switch, care are următoarea formă generală:
switch (expresie) { case valoare_1: secvenţă_1 [break;] case valoare_2: secvenţă_2 [break;] .......................... case valoare_N: secvenţă_N [default: secvenţă ] }

83

Severin Bumbaru în care: - switch, case si default sunt cuvinte cheie ale limbajului Java; - expresie este o expresie de tip byte, short, int sau char; - valoare este fie un literal de tip byte, short, int sau char, fie o expresie constantă de tip int, adică o expresie care conţine numai literali sau variabile finale din tipurile menţionate; - secvenţaeste o secvenţă de instrucţiuni simple sau structurate; - parantezele drepte roşii []nu apar în program, ci indică faptul că conţinutul lor este opţional (poate sa lipsească). În limba engleză, switch înseamnă comutator. Conform acestei forme generale, după cuvantul cheie switch, există o expresie de tip int sau compatibilă cu aceasta (deci poate fi şi de tip char, byte sau short, dar nu de tip long), a cărei valoare serveşte drept "comutator". Se deschide apoi acolada corpului instrucţiunii, în care există mai multe "cazuri". Fiecare "caz" începe prin cuvantul cheie case, urmat de o valoare de tip întreg, după care apar una sau mai multe instrucţiuni (simple sau compuse) şi opţional intrucţiunea break. După ce s-au epuizat toate cazurile, opţional se poate scrie cuvântul cheie default urmat de una sau mai multe instrucţiuni. La sfârşit, se închide acolada corpului instrucţiunii switch. Executarea instrucţiunii switch decurge astfel: se evaluează mai întâi expresie şi se obţine o valoare de tip întreg, care serveşte drept comutator. Această valoare se compară, pe rând de sus în jos, cu fiecare din valorile indicate după cuvintele cheie case, până când se găseşte prima valoare care coincide cu cea a comutatorului. Dacă s-a găsit o astfel de valoare, se execută toate instrucţiunile care încep de la cazul respectiv, până la prima instrucţiune break întalnită sau, în lipsa acesteia, până la acolada de închidere a corpului instrucţiunii switch. Dacă însă nici unul din cazuri nu conţine valoarea potrivită a comutatorului, atunci se execută instrucţiunile care urmează după cuvântul cheie default sau, în lipsa acestuia, nu se execută nimic. Exemplu În fişierul TestSwitch.java se dă programul de mai jos, în care se testează instrucţiunea switch în două situaţii: în primul caz nu este utilizată instrucţiunea break, iar în al doilea caz este pusă această instrucţiune la sfârşitul secvenţei de instrucţiuni corespunzătoare fiecărui caz. Remarcăm, de asemenea, că în al doilea caz, întrucât comutatorul este de tip char, valorile numerice corespunzătoare fiecărui caz au trebuit să se încadreze în acest tip, adică să fie caractere sau numere întregi pozitive în intervalul [0, 65535].

/* Testarea instructiunii switch */ class TestSwitch { public static void main(String args[]) { int a=7; char c='@'; double u=15.3782; System.out.println("Switch fara break si cu cazuri vide"); switch(a) { case 143: u++; System.out.println("A "+u); case 2: u+=a; System.out.println("B "+u);

84

Programarea orientata pe obiecte în limbajul Java
case 7: case case case case u--; System.out.println("C "+u);

'@': 148: 15: -87: u-=a; System.out.println("D case -12: u*=a; System.out.println("E case 'F': u%=a; System.out.println("F default: u=15.3782; System.out.println("G }

"+u); "+u); "+u); "+u);

System.out.println("Switch cu break dupa fiecare caz nevid"); switch(c) { case 143: u++; System.out.println("A "+u); break; case 2: u+=a; System.out.println("B "+u); break; case 7: u--; System.out.println("C "+u); break; case '@': case 148: case 15: case 87: u-=a; System.out.println("D "+u); break; case 12: u*=a; System.out.println("E "+u); break; case 'F': u%=a; System.out.println("F "+u); break; default: u=15.3782; System.out.println("G "+u); } } }

Rezultatul rulării acestui program este următorul:

Switch fara break si cu cazuri vide C 14.3782 D 7.3782 E 51.6474 F 2.6473999999999975 G 15.3782 Switch cu break dupa fiecare caz nevid D 8.3782

85

Severin Bumbaru Se observă cu uşurinţă că, în primul caz (fără instrucţiuni break), s-a început executarea secvenţei de instrucţiuni în punctul "case 7" şi s-a continuat până la sfârşitul blocului instrucţiunii switch (inclusiv cazul default). Când s-a folosit instrucţiunea break, ieşirea s-a făcut la întalnirea acestei instrucţiuni.

Instrucţiuni pentru structuri de control repetitive
În limbajul Java, structurile de control repetitive se realizează prin trei instrucţiuni: - instrucţiunea while, prin care se programează ciclul cu test iniţial; - instrucţiunea do..while, prin care se programează ciclul cu test final; - instrucţiunea for, care este o varianta modificată a ciclului cu test iniţial, astfel încât să poată fi folosită şi la realizarea ciclului cu contor. În cele ce urmează se vor prezenta instrucţiunile menţionate mai sus.

Instrucţiunea

while

(ciclul cu test iniţial)

Această instrucţiune are forma generală
while (condiţie) instrucţiune

în care
condiţie - este o expresie de tip boolean; instrucţiune- poate fi orice instrucţiune (simplă inclusiv altă instrucţiune while.

sau structurată) a limbajului Java,

Executarea acestei instrucţiuni se face astfel: se evaluează expresia booleană condiţie şi dacă valoarea acesteia este true, deci condiţia este satisfacută - se execută instrucţiune,după care se revine la evaluarea condiţiei. acest ciclu se repetă cât timp este satisfacută condiţia.

Exemplul 1. Se va calcula în mod repetat valoarea expresiei 2e-0.25ksin(0.1k-0.08) pentru valori întregi ale variabilei k de la 0 la n, unde n este dat. În pseudocod, programul corespunzător se poate scrie astfel:
<#1. Se introduce valoarea lui n, de tip intreg> <#2. Se calculează şi se afişează valoarea expresiei 2e-0.35ksin(0.17k0.08) pentru valori ale lui k de la 0 la n >

Rafinarea pseudoinstrucţiunii #1 se poate face direct în program. La rafinarea pseudoinstrucţiunii #2 luăm în consideraţie caracterul repetitiv al acţiunii solicitate, astfel că putem folosi structura de ciclu cu test iniţial, iar programul devine:
<#1. Se introduce valoarea lui n, de tip intreg> /* #2. Se calculează şi se afisează valoarea expresiei */ k=0; // Se iniţializează k la prima sa valoare

86

Programarea orientata pe obiecte în limbajul Java
cât_timp k<=n afişează_valoarea_expresiei 2e-0.35ksin(0.17k-0.08); k=k+1; // se trece la valoarea următoare a lui k sfârşit_ciclu

Programul corespunzător în limbajul Java se găseşte în fişierul Repetare1.java. Exemplul 2. Calcularea unei sume cu număr finit de termeni. Considerăm că se cere să se calculeze suma

în care sunt date valorile variabilelor a şi b, de tip real şi n de tip întreg. Aplicând tehnica rafinărilor succesive, vom da pentru început programului următoarea structură secvenţială:
<#1. Se introduc datele de intrare n de tip întreg şi a,b de tip real> <#2. Se calculează suma S> <#3. Se afişează valoarea lui S>

Rafinarea pseudoinstrucţiunilor #1 şi #3 se poate face direct în program. Pentru rafinarea pseudoinstrucţiunii #2, remarcăm că suma S se poate calcula în modul următor: se porneşte de la valoarea iniţiala S=0 şi se adaugă la aceasta succesiv toţi termenii sumei, pentru valori ale lui k de la 0 la n. Acţiunea are un caracter repetitiv, deci se poate folosi în acest scop un ciclu cu test iniţial. Se obţine astfel programul următor în pseudocod:
<#1. Se introduc datele de intrare n de tip întreg şi a,b de tip real> /* #2. Se calculează S */ S=0; // Se atribuie lui S valoarea iniţială k=0; // Se atribuie lui k valoarea iniţială cât_timp k<=n <#2.1. Se calculează termenul curent al sumei şi se adaugă la valoarea precedentă a lui S > k=k+1; // Se trece la valoarea următoare a lui k sfârşit_ciclu afisează S;

Programul corespunzător în limbajul Java se găseste în fişierul Suma1.java. Exemplul 3. Calcularea sumei unei serii convergente. Fie S suma seriei convergente S=1+x+x2/(2!)+x3/(3!)+...+xk/(k!)+... în care k! este factorialul lui k. Demonstrarea existentei acestei sume (deci a convergenţei seriei) este de domeniul analizei matematice. Întrucât, însă, noi ştim că această serie este

87

Severin Bumbaru convergentă, putem să scriem un program pentru calcularea sumei S. Seria are un număr infinit de termeni, deci valoarea ei exactă nu poate fi calculată direct, prin adunarea lor. Totuşi, valoarea aproximativă a sumei S se poate obţine prin "trunchiere", adică luând în consideraţie numai un număr finit de termeni. Vom calcula deci, de fapt, suma parţială: Sn=1+x+x2/(2!)+x3/(3!)+...+xn/(n!) cu observaţia că numărul de termeni n nu este dinainte cunoscut. Se ştie (si se observă cu uşurinţă) că termenii seriei sunt descrescători ca modul. Ne putem deci opri din evaluarea sumei, în momentul în care constatăm că valoarea termenului curent este mult mai mică decât suma parţială deja calculata. Putem deci adopta, pentru calcularea sumei, următorul program în pseudocod:
<#1. Se introduce valoarea lui x, de tip real> S=0; // Se iniţializează suma S la valoarea 0 k=0; // Se iniţializează indicele termenului curent la valoarea 0 t=1; // Se iniţializează termenul curent la valoarea 1 (cea a primului termen) /* Se calculează suma S a seriei */ cât_timp (<#2.termenul curent t este semnificativ>) S=S+t; // Se adaugă la suma precedentă S valoarea termenului curent // şi se obţine noua valoare a sumei S k=k+1; // Se trece la indicele termenului următor; <#3.se calculează noua valoare t a termenului următor> sfârşit_ciclu afişează S.

Pentru a rafina pseudocondiţia #2 avem în vedere că, în memoria calculatorului, numerele reale au lungime finită. În consecinţă, dacă adunăm două numere foarte diferite ca ordin de mărime, "suma" lor va fi egală cu cel mai mare dintre ele. De exemplu, dacă reprezentarea numerelor reale s-ar face în sistemul zecimal cu 6 cifre semnificative, atunci suma 32.7628+0.00006924 ar fi egala cu numarul cel mai mare, adica 32.7628, deoarece cifrele de la rezultat situate după cea de a 6-a cifra semnificativă se pierd. În cazul seriei noastre, la un moment dat termenul curent t va deveni atât de mic ca modul, încât adunat la suma deja calculata S nu o va mai modifica. Vom considera deci că termenul t este semnificativ, cât timp se îndeplineşte condiţia (S+t este_diferit_de S). Pentru a rafina #3, observăm că între valorile termenului t la doi paşi succesivi k şi k-1 exista relaţia: tk=tk-1*x/k. Întrucât în program nu este necesar să folosim indicii paşilor, vom scrie această relaţie sub forma t=t*x/k, adică se atribuie ca nouă valoare a termenului curent t valoarea precedentă a acestui termen, înmulţită cu raportul x/k. Cu observaţiile de mai sus, programul de calculare a sumei seriei devine:
<#1. Se introduce valoarea lui x, de tip real> S=0; // Se iniţializează suma S la valoarea 0 k=0; // Se iniţializează indicele termenului curent la valoarea 0

88

Programarea orientata pe obiecte în limbajul Java
t=1; // Se iniţializează termenul curent la valoarea 1 (cea a primului termen) /* Se calculează suma S a seriei */ cât_timp (S+t este_diferit_de S) S=S+t; // Se adaugă la suma precedentă S valoarea termenului curent // şi se obţine noua valoare a sumei S k=k+1; // Se trece la indicele termenului următor; t=t*x/k; // se calculează noua valoare t a termenului următor sfârşit_ciclu afişează S.

Programul corespunzător în limbajul Java este dat în fişierul Serie1.java. Pentru a înţelege mai bine acest program, recomandăm să calculaţi 3-4 termeni manual pentru a urmări cum se înlănţuie aceştia.

Instrucţiunea do..while (ciclul cu test final)
Forma generală a instrucţiunii de control, prin care se realizează în limbajul Java ciclul cu test final este următoarea:
do instrucţiune while (condiţie);

unde, la fel ca în cazul instrucţiunii while,condiţie este o expresie de tip boolean, iar instrucţiune este orice instrucţiune (simplă sau structurată) din limbajul Java. Remarcăm că la sfârşitul acestei instrucţiuni (după condiţie) se pune obligatoriu ';' (punct şi virgulă). Principala deosebire faţă de instrucţiunea while pentru ciclul cu test iniţial este că testarea condiţiei se face după ce a fost executată instrucţiunea din corpul ciclului. În consecinţă, corpul ciclului va fi executat cel puţin o dată. În programul din fişierul DeosCicluri.java se face o comparaţie între funcţionarea ciclurilor cu test iniţial şi cu test final în condiţii similare.

În programele din fişierele Repetare2.java,Suma2.java şi Serie2.java se reiau exemplele din secţiunea precedentă (şi realizate în fişierele Repetare1.java, Suma1.java şi Serie1.java), dar folosind de data aceasta ciclul cu test final.

89

Severin Bumbaru

Instrucţiunea for (ciclul cu contor generalizat)
În unele limbaje de programare, pentru realizarea ciclurilor la care numărul de paşi este dinainte cunoscut, se foloseşte o instrucţiune de control specială, numită "ciclu cu contor". În pseudocod, această instrucţiune poate fi scrisă în modul următor:
pentru variabila de_la val_init la val_fin [cu_pasul pas] execută <instrucţiune sau secvenţă de instrucţiuni> sfârşit_ciclu

în care, de regulă, variabila este o variabilă (de regulă de tip întreg), numită şi contorul ciclului,val_init şi val_fin sunt respectiv valorile iniţială şi finală ale acestei variabile, iar pas este pasul de variaţie la fiecare parcurgere a ciclului. Pasul implicit este 1.

De exemplu, pentru a calcula produsul P=(a+5)*(a+6)*(a+7)*...*(a+n) se vor scrie următoarele pseudoinstrucţiuni:
P=1; pentru k de_la 5 la n execută P=P*(a+k); sfârşit_ciclu

Remarcăm că, în acest exemplu, instrucţiunea P=1 nu face parte din ciclu, dar ea a fost necesară pentru a da valoarea iniţială a variabilei P. În mod similar, pentru a calcula suma S=7+10+13+16+...+31 se pot folosi instrucţiunile
S=0; pentru j de_la 7 la 31 cu_pasul 3 execută S=S+j; sfârşit_ciclu

Remarcăm că, pentru aceleaşi calcule, se pot folosi şi ciclurile cu test iniţial sau cu test final. Astfel, primul din aceste exemple, poate fi rescris folosind ciclul cu test iniţial astfel:
P=1; k=5; cât_timp k<=n execută P=P*(a+k); k=k+1; sfârşit_ciclu

Remarcăm că, la trecerea de la ciclul cu contor la ciclul cu test iniţial, s-au introdus următoarele instrucţiuni suplimentare: - înainte de intrarea în ciclu, s-a pus instrucţiunea k=5, prin care se atribuie variabileicontor valoarea iniţială; - la sfârşitul corpului ciclului s-a introdus instrucţiunea k=k+1, prin care se trece la

90

Programarea orientata pe obiecte în limbajul Java valoarea următoare a variabilei-contor; - in instrucţiunea while s-a pus drept condiţie compararea variabilei-contor cu valoarea ei finală. Lăsăm cititorului ca exerciţiu să scrie acelaşi program, folosind ciclul cu test final. În limbajul Java, urmând tradiţia limbajului C de la care acesta a preluat o bună parte a regulilor de sintaxă, se foloseşte o instructiune generalizată, prin care se realizează atât ciclul cu contor, cât şi ciclul cu test iniţial. Această instrucţiune are forma:
for ([iniţializare]; [condiţie] ; [trecere_la_pasul_următor] ) [<instrucţiune_corp_ciclu>]

în care: formată din una sau mai multe instrucţiuni, separate prin virgule, care se execută înainte de intrarea în ciclu, astfel că sunt folosite, de regulă, pentru iniţializarea acestuia; condiţie - este o expresie de tip boolean folosită drept condiţie de continuare a ciclului cu test iniţial (condiţia este testata înainte de a se executa corpul ciclului; trecere_la_pasul_următor := instrucţiune[,instrucţiune]* - una sau mai multe instrucţiuni, separate prin virgule, care se execută după ce a fost executat corpul ciclului. astfel că sunt, în general, folosite pentru pregătirea trecerii la următoarea parcurgere a ciclului; <instrucţiune_corp_ciclu>- o instrucţiune (simpla sau structurată) care constituie corpul ciclului.
iniţializare ::= instructiune[,instructiune]*- este

Respectând aceasta formă generală, calcularea produsului dat ca exemplu mai sus în pseudocod, se programează în limbajul Java în modul urmator:
for(P=1,k=5; k<=n; k++) P*=a+k;

Se observă ca se iniţializează P la valoarea 1 şi k la valoarea 5, apoi cât timp este satisfacută condiţia k<=n se execută instrucţiunea P*=a+k, după care se execută instrucţiunea k++ (se trece la următoarea valoare a lui k). După cum se vede în forma generală, diferitele componente ale acesteia sunt opţionale; în consecinţă, instrucţiunea for poate fi scrisă sub diferite forme echivalente, de exemplu: a)
P=1; k=5; for(;k<=n;){ P*=a+k; k++; }

Observăm că, de data aceasta, instrucţiunea for(;k<=n;) are exact acelaşi efect, pe care lar fi avut instrucţiunea while(k<=n). Remarcăm, de asemenea, că cei doi separatori ';' din interiorul parantezei au fost mentinuţi, chiar dacă lipsesc zonele de iniţializare şi de

91

Severin Bumbaru incrementare.
b) for(P=1,k=5;k<=n;P*=a+k,k++);

În ultimul caz, corpul ciclului este constituit numai dintr-o instrucţiune vidă, reprezentată prin carecterul ';' pus dupa paranteza închisă. Aceste exemple sunt date şi în programul din fişierul TestFor.java. Tot acolo se arată că variabila care serveşte drept contor al ciclului poate fi, în limbajul Java, şi de tip real, iar creşterea acesteia de la o parcurgere la alta a ciclului poate fi de asemenea de tip real şi nu este obligatoriu să fie constantă. Considerăm că exemplele din acest program sunt suficiente pentru a arăta ca instrucţiunea for permite programatorului posibilităţi de utilizare foarte diverse, cu condiţia ca acesta să înţeleaga corect modul ei de funcţionare.

Declararea unor variabile în zona de iniţializare a ciclului

for

Este permis ca în zona iniţializare a ciclului for să fie incluse şi unele declaraţii de variabile. Aceste variabile sunt tratate drept variabile locale ale ciclului for respectiv şi deci nu sunt vizibile (disponibile) după ieşirea din ciclu, putând fi redefinite. Iată un astfel de exemplu:
x=0; for(int k=0; k<n; k++) S+=a*k;

În acest caz, variabila k a fost atât declarată, cât şi iniţializată, în zona de iniţializare a instrucţiunii for. În consecinţă această variabilă este disponibilă în interiorul acestei instrucţiuni, inclusiv în corpul ciclului, dar îşi pierde valabilitatea la ieşirea din acest ciclu. Daca în zona de iniţializare apare o declaraţie, ea trebuie să fie singura instrucţiune din această zonă. Astfel, în exemplul precedent nu ar fi fost permis, de exemplu, să se scrie
for(x=0, int k=0; k<n; k++) S+=a*k;

Este însă permis să se declare în zona de iniţializare mai multe variabile de acelaşi tip. Astfel, exemplul anterior poate fi modificat după cum urmează:
x=0; for(int k=0,i=9; k<n; k++, i--) S+=(a+i)*k;

Aceste exemple şi altele similare se găsesc în programul din fişierul TestFor1.java.

Exemple de programe în care se foloseşte ciclul for În fişierele Repetare3.java, Suma3.java şi Serie3.java se reiau exemplele date in fisierele Repetare1.java, Suma1.java şi Serie1.java), dar folosind instrucţiunea for în loc de while. Vă recomandăm să comparaţi în mod special programele din fişierele Serie1.java si Serie3.java si să explicaţi deosebirile constatate.

92

Programarea orientata pe obiecte în limbajul Java

Instrucţiuni etichetate
În principiu, orice instrucţiune în limbajul Java poate purta o etichetă. Instrucţiunile etichetate au forma
etichetă : instrucţiune

unde
etichetă este un identificator; instrucţiuneeste o instrucţiune simplă

sau structurată.

Exemplu
alpha: x=2*a+b;

Se permite să se folosească drept etichete chiar şi identificatori care mai sunt folosiţi în acelasi program ca nume de variabile, clase, metode etc., fară a se crea prin aceasta confuzii, deoarece compilatorul face distincţie între etichete şi nume în funcţie de contextul în care acestea sunt utilizate.

Terminarea abruptă a execuţiei instrucţiunilor structurate
În secţiunile anterioare, s-a arătat că limbajul Java respectă principiile programării structurate. În consecinţă, instrucţiunile de control au fost concepute în conformitate cu aceste principii, iar instrucţiunea goto nu există. Chiar dacă, din precauţie, cuvântul cheie gotoa fost menţinut în limbaj, el nu este încă efectiv folosit. Cu toate acestea, având în vedere experienţa practică şi pentru a veni în ajutorul programatorilor, autorii limbajului Java au admis şi anumite abateri de la structurile de control fundamentale acceptate de teoria programării structurate, fără a încălca însă principiul de bază al acesteia, conform căruia fiecare asemenea structură trebuie sa aibă un singur punct de intrare şi un singur punct de ieşire. Am arătat deja, că astfel de excepţii sunt instructiunile switch,do..while şi for. Ne vom ocupa acum de o alta abatere de la programarea structurată pură, care constă în folosirea instrucţiunilor break şi continue şi a instrucţiunilor etichetate.

Folosirea instrucţiunilor break şi continue
Instructiunile break şi continue au forma generală următoare:
break [etichetă]; continue [etichetă];

unde etichetăeste eticheta unei instrucţiuni şi este un identificator. Eticheta este opţională (de aceea a fost scrisă de noi între paranteze drepte). Instructiunea break se poate folosi în corpul unei instrucţiuni switch, while, do..while sau for şi are ca efect ieşirea forţată (abruptă) din structura de control în care se găseşte instrucţiunea respectivă. S-a arătat deja cum se foloseşte instrucţiunea break în corpul unei instrucţiuni switch. Iată acum un exemplu de folosire într-un ciclu cu test iniţial:
k=1; S=0; while(S+t!=S) {

93

Severin Bumbaru
t*=a/k; S+=t; if(t<1.0e-10*S) break; k++; }

În acest caz, în momentul în care este satisfacută condiţia (t<1.0e-10*S) din instructiunea if se iese forţat din ciclu, chiar dacă condiţia principală de continuare a ciclului (S+t!=S) este încă satisfacută. Iată şi un exemplu similar, în care se iese forţat din instrucţiunea for:
k=1; S=0; for(;;) { t*=a/k; if(S+t==S) break; S+=t; k++; }

Întrucât iniţializarea ciclului s-a făcut înainte de instructiunea for, iar condiţia de ieşire din ciclu şi pregatirea continuarii ciclului se găsesc în corpul acestuia, în paranteza lui for toate cele trei zone au ramas vide. Instrucţiunea continue se poate folosi numai în corpul ciclurilor (while, do..while, for). Dacă instrucţiunea continue nu are etichetă, efectul este că se trece peste restul instrucţiunilor din corpul ciclului, începând din punctul în care se găseşte instrucţiunea continue până la sfârşitul acestuia, dar se continuă executarea ciclului, adică se reia executarea corpului acestuia cât timp este satisfacută condiţia de continuare. Să considerăm, de exemplu, următorul fragment de program:
s=0; for(k=0; k<20; k++) { x*=a; s+=x; if(k<16) continue; System.out.println("k="+k+" x="+x+" s="+s); }

În acest caz, corpul ciclului se va repeta pentru valori ale lui k de la 0 la 19, dar instrucţiunea System.out.println(...) va fi "sărită" pentru valori ale lui k mai mici decât 16, trecându-se direct la acolada de închidere a corpului ciclului, astfel că afişările se vor face numai începand cu k=16.

Folosirea etichetei în instrucţiunea break
Necesitatea utilizării unei etichete apare atunci când există două astfel de structuri imbricate una în alta, iar noi dorim ca prin break să se iasa forţat din cea exterioară, ca în exemplul urmator:
ciclu1: while(w>0.05){ w/=a;

94

Programarea orientata pe obiecte în limbajul Java
for(int k=0; k<n; k++) { s+=w; if(s>b) break ciclu1; y+=w*s } }

Instrucţiunea break se găseşte, în acest exemplu, în corpul ciclului interior. Dacă nu ar fi conţinut eticheta ciclu1,această instrucţiune ar fi provocat numai ieşirea dim ciclul interior (for), dar s-ar fi reluat execuţia ciclului exterior (while), implicand, desigur, şi reluarea ciclului interior continut de acesta. Dacă însă este prezenta eticheta din break, se va ieşi forţat din instrucţiunea de control care poartă această etichetă, în cazul de faţă din ciclul exterior. Remarcam, deci, că eticheta conţinută în instrucţiunea break indică faptul ca se va ieşi forţat din structura de control care poartă aceeaşi etichetă. Aceasta înseamnă că instrucţiunea break cu etichetă constituie o abatere locală de la principiul de bază al programării structurate, conform căruia fiecare structură de control trebuie sa aibă un singur punct de intrare şi un singur punct de ieşire. În ultimul exemplu, ciclul interior are două puncte de ieşire: unul este ieşirea normală din ciclul for, iar celalalt este ieşirea prin break. Daca luam însă în consideraţie ciclul exterior, constatăm ca acesta respectă principiul menţionat. Desigur că pot să existe mai multe instrucţiuni de control cuprinse una în alta şi fiecare dintre ele poate avea o etichetă, care poate fi utilizată într-o instrucţiune break. Testarea a diferite situaţii de utilizare a instructiunii breakse face în programul din fişierul TestBreak.java.

Folosirea etichetei în instrucţiunea continue
Să considerăm acum următorul fragment de program:
ciclu1: for (int i=0; i<6; i++) { System.out.println("Control 1: i="+i); for(int j=3; j>0; j--) { System.out.println("Control 2: i="+i+" j="+j); if(i>1 && i<5) continue ciclu1; System.out.println("Control 3: i="+i+" j="+j); } // Sfarsitul ciclului interior System.out.println("Control 4: i="+i); } // Sfarsitul ciclului exterior

Instrucţiunea conţinue eticheta ciclu1, care aparţine ciclului exterior. În consecinţă, pentru valori ale variabilei i cuprinse între 1 şi 5, se va trece forţat la sfârşitul corpului ciclului exterior, deci nu se vor mai executa instrucţiunile de afişare "Control 3" şi "Control 4". Programul de testare pentru instrucţiunea continue se găseşte în fişierul TestCont.java.

Tratarea excepţiilor
În timpul executării programului, pot apare anumite situaţii care altereaza desfăşurarea normală a acestuia. Când apare o astfel de situaţie, de regulă, se generează o excepţie sau o eroare prin care se semnalează incidentul care a avut loc. Excepţiile şi erorile pot fi generate atât de echipamente (excepţii sau erori hardware), cât şi de programe (excepţii software).

95

Severin Bumbaru Exemple de excepţii hardware pot fi întreruperile, excepţia de împărţire la zero, unele incidente din subsistemele de intrare/ieşire etc. Excepţiile software sunt mult mai diverse, depinzând de specificul programelor care le generează. Deosebirea dintre excepţii şi erori este că excepţiile sunt considerate că pot fi tratate prin program, în timp ce erorile sunt considerate mai curând nerecuperabile. În programul din fişierul TestExcept1.java se testează apariţia unei excepţii de împărţire la zero. Întrucât variabila b are valoarea zero, la executarea împărţirii a/b procesorul calculatorului generează o excepţie aritmetică. Întrucât în programul nostru nu există instrucţiuni prin care să se trateze această excepţie, programul se întrerupe, trecându-se controlul asupra sistemului de operare al calculatorului, iar acesta afişeaza excepţia survenită şi opreşte executarea programului. În limbajul Java, există posibilitatea de a se trata prin program diferitele excepţii care apar în timpul execuţiei. În acest fel, programatorul poate să prevadă căi alternative de continuare a executării programului, fără a mai fi necesară oprirea executării lui. În mediul de lucru Java, la apariţia unei excepţii se generează un obiect special numit excepţie, care conţine informaţia despre excepţia respectivă. Acest obiect poate fi captat prin program, iar informaţia conţinută în el poate fi utilizată pentru a decide calea pe care se va merge în continuare în derularea programului. Toate obiectele de excepţie care se referă la acelaşi tip de incident formează o clasă de excepţii. Tratarea prin program a excepţiilor se face, în limbajul Java, folosind instrucţiunea try urmată de una sau mai multe clauze catch şi, opţional, de o clauză finally sub forma urmatoare:
try { secventa } catch (ClasaExceptie1variabila1) { secventa1 } catch (ClasaExceptie2 variabila2) { secventa2 } ............... catch (ClasaExceptieN variabilaN) { secventaN } [finally { secventa_finalizatoare }]

În engleza, try înseamnă încearcă, iar catch înseamnă prinde. În corpul instrucţiunii try se pune secvenţa de instrucţiuni în care este posibil ca, la executarea programului, să apară excepţii. Dacă excepţia a apărut într-un punct situat în corpul unei instrucţiuni try, aceasta instrucţiune se termină în mod abrupt, astfel că restul instrucţiunilor nu se mai execută, trecându-se direct la acolada de închidere a blocului. Se parcurg apoi, una după alta, clauzele catch, până se ajunge la cea care are între paranteze numele clasei excepţiei care s-a produs şi se execută secvenţa din corpul acestei clauze. Clauza finally este opţională şi conţine o secvenţă de instructiuni care se vor executa în final, adică după ce a fost tratată excepţia prin una din clauzele catch, sau dacă nu a fost tratată prin

96

Programarea orientata pe obiecte în limbajul Java nici una dintre acestea.

Exemplul 1: În fişierul TestExcept2.java este dată o modificare a programului din fişierul TestExcept1.java, în care excepţia este tratată prin instrucţiunea try. Întrucât se ştia că este posibilă apariţia unei excepţii aritmetice, s-a încercat să se capteze mai întai această excepţie. Ca rezervă, s-a prevăzut şi o clauza catch pentru clasa Exception, care va capta orice alta excepţie. Comparând rezultatele executării celor două programe menţionate, constatăm că, atunci când s-au tratat excepţiile prin instrucţiunea try, programul nu a mai fost oprit anormal, ci s-a executat blocul clauzei catch(ArithmeticException e1), după care s-a continuat executarea normală a programului. În schimb, instrucţiunile existente în blocul try, după cea care a generat excepţia, nu au mai fost executate. Exemplul 2: În fişierul TestExcept3.java se reia programul din exemplul anterior, dar a fost suprimată prima din clauzele catch. Executând acest program, se poate observa că şi de data aceasta excepţia aritmetică a fost captata, însa de clauza catch(Exception e). Exemplul 3: În fişierul TestExcept4.java se reia programul din Exemplul 1, dar după captarea excepţiei s-a introdus si clauza finally. Se observă ca secvenţa din aceasta clauză este executată chiar daca excepţia a fost captată prin catch. Mai mult, în fişierul TestExcept5.java s-a reluat acelaşi program, dar s-a modificat valoarea numitorului fracţiei, astfel că nu mai are loc o împărţire la zero. Se observă ca secvenţa din clauza finallyeste executată şi în acest caz.

Întrebări
Nivel 1
1. Ce este o expresie? 2. Cum se stabileşte tipul unei expresii? 3. Fie a si b variabile de tip byte, c de tip int, u de tip float şi v de tip double. Care sunt tipurile următoarelor expresii: a+b, a*c, a*c+u, u-2*v. 4. Ce este precedenţa operatorilor? 5. În ce ordine se aplică operatorii cu acelaşi nivel de precedenţă? 6. În ce ordine se evaluează operanzii unui operator binar? 7. Ce forma sintactică are expresia condiţională şi care este semnificaţia acestei expresii? 8. Ce este o instrucţiune? 9. Ce deosebire este între instrucţiunile simple şi cele structurate? 10. Ce este instrucţiunea vidă şi cum se scrie? 11. Ce este o instrucţiune-expresie? 12. Ce deosebiri există între o expresie şi o instrucţiune-expresie? 13. În ce constă teorema de structură din metoda programării structurate? 14. Care sunt categoriile de instrucţiuni de control admise de metoda programarii structurate?

97

Severin Bumbaru 15. Ce fel de structură de control este un bloc şi care este forma lui sintactică? 16. Ce sunt variabilele locale şi care este domeniul lor de vizibilitate? 17. Prin ce instrucţiuni se realizează structura de control alternativă şi care este forma lor sintactică? 18. Ce sunt excepţiile? 19. Ce deosebire este între excepţii şi erori? 20. Care este forma sintactică a instrucţiunii de tratare a exceptiilor şi cum trebuie ea interpretată?

Nivel 2
Construiţi arborele sintactic al următoarei expresii: (a+2*b/c)*(2*d-3)+3*e+5 Construiţi arborele sintactic al următoarei expresii: a=(b+=2*a)-c++*(d-=c+1) Construiţi arborii sintactici ai tuturor expresiilor din fişierul EvExpresii.java. Explicaţi rezultatele obţinute la executarea programului din fişierul ExprCond.java. Ce este o instrucţiune de atribuire şi care este efectul ei? Care este deosebirea dintre valorile expresiilor de preincrementare şi postincrementare? Dar între efectele instrucţiunilor de preincrementare şi postincrementare? 7. Ce este o instructiune de invocare de metodă şi care este efectul ei? 8. Care este schema logică a structurii de control secvenţiale şi cum trebuie interpretată? 9. Care este schema logică a structurii de control alternative şi cum trebuie interpretată? 10. Care este schema logică a ciclului cu test iniţial şi cum trebuie interpretată? 11. Care este schema logică a ciclului cu test final şi cum trebuie interpretată? 12. Unde sunt plasate variabilele locale în memoria maşinii virtuale Java? 13. Când sunt eliminate din memorie variabilele locale? 14. Cum se stabileşte cărui if îi aparţine clauza else în cazul instructiunilor if imbricate? 15. Care este forma sintactică a instrucţiunii switch şi cum este ea interpretată? 16. Ce rol are instrucţiunea break în cadrul unui bloc switch? 17. Ce este o etichetă şi ce formă sintactică are o instrucţiune etichetată? 18. Poate o etichetă să aibă acelaşi identificator cu cel al unui nume de variabilă sau de metodă? 19. Cum acţionează o instrucţiune break fără etichetă în corpul unui ciclu? 20. Cum acţionează o instrucţiune continue fără etichetă în corpul unui ciclu? 21. În ce situaţie se foloseşte o instrucţiune break cu etichetă? 22. În ce situaţie se foloseşte o instrucţiune continue cu etichetă? 23. La ce serveşte clauza catch şi unde se plasează? 24. La ce serveşte clauza finally şi unde se plasează? 1. 2. 3. 4. 5. 6.

98

Programarea orientata pe obiecte în limbajul Java

Tipul referinţă. Utilizarea claselor din pachetul java.lang. Tablouri. Utilizarea parametrilor din linia de comandă
Tipul referinţă; declararea variabilelor referinţă; Clase, câmpuri, metode; Constructori; crearea obiectelor prin operatorul new; Moştenirea şi polimorfismul; ierarhia de clase Java; Atribuirea de valori variabilelor-referinţă; Pachetele de clase din Java API; declaratia import; Utilizarea claselor din pachetul java.lang Clasa Object; Clasele de excepţii; Clasele String şi StringBuffer; Clasa Class; Clasele acoperitoare de tip; Clasa Math; Clasa System; Tablouri Tablouri unidimensionale; Tablouri multidimensionale; Utilizarea parametrilor din linia de comandă Întrebări. 99 101 102 102 103 104 105 105 107 108 111 112 119 120 121 122 126 133 134

Tipul referinţă
În limbajul Java există două categorii de tipuri de date: tipuri primitive şi clase. Tipurile primitive sunt predefinite în limbaj, în sensul că numele, mulţimea de date, mulţimea de operaţii şi reprezentarea datelor în memoria internă a maşinii virtuale Java pentru fiecare tip sunt definite în insăşi specificaţia limbajului Java şi nu mai pot fi modificate de programatori. În program, datele de tipuri primitive apar sub forma de variabile.

Fiecare variabilă are un nume, un tip şi o valoare şi este plasată într-o anumita locaţie de memorie. În locaţia de memorie respectivă se găseşte chiar valoarea variabilei. Pe programator nu il interesează insă adresa de memorie la care se găseşte variabila, ci doar numele, tipul şi valoarea acesteia.

99

Severin Bumbaru Clasele sunt tipuri de date structurate specifice programării orientate pe obiecte. Clasele se definesc de programatori şi se grupează în pachete de clase. La crearea unui program nou, programatorul poate utiliza clasele din pachetele deja existente, sau poate creea propriile sale clase. În program, fiecare clasă poate avea mai multe instanţe, numite obiecte.

Comparând clasele cu tipurile primitive, constatăm că clasa este o extensie a conceptului de tip, iar obiectul este o extensie a conceptului de variabila. Deosebirea este că o variabilă simplă este o locaţie de memorie care conţine o valoare primitivă, în timp ce obiectul este tot o zonă de memorie, care conţine însă o structură de date. În memoria maşinii virtuale Java, obiectele sunt plasate într-o zonă de memorie specială, numită memorie dinamică (în engleză: heap, adică "gramadă"). Localizarea în memorie a obiectelor se face cu ajutorul unor variabile speciale, numite variabile referinţă. Valoarea unei variabile referinţă nu este obiectul însuşi, ci o referinţă la acest obiect. Din această cauză, se consideră că variabilele referintă aparţin unui tip de date numit tip referinţă. Mulţimea de valori a acestui tip este mulţimea referinţelor, adică mulţimea locaţiilor (adreselor) din memoria dinamică. Operaţiile permise asupra datelor de tip referinţa sunt cele date de operatorii de atribuire (=), egalitate (==) şi inegalitate (!=) care au aceleaşi semnificaţii şi mod de utilizare ca pentru toate celelalte tipuri de date.

Referinţele din Java sunt similare pointerilor din unele limbaje de programare tradiţionale, cum sunt Pascal şi C. Există, totuşi, deosebiri importante: - valorile variabilelor-pointer sunt adrese din memorie care pot fi cunoscute de programator (de exemplu pot fi afişate) De asemenea, unei variabile-pointer i se poate atribui ca valoare o adresa dată explicit. În schimb, adresele la care se găsesc în memorie obiectele indicate de referinţele din Java nu pot fi cunoscute de utilizator; - în limbajele tradiţionale, pointerii pot indica nu numai adresele în memorie ale unor structuri de date, ci şi pe cele ale unor variabile simple. În java, referinţele pot fi folosite numai pentru obiecte; - în limbajele traditionale (de exemplu în C/C++) se pot face asupra pointerilor operatii aritmetice, ceeace nu este permis asupra referinţelor în Java.

Declararea variabilelor referinţă
Declararea variabilelor referinţă se poate face la fel cu declararea variabilelor de tipuri primitive. Singura deosebire constă în faptul că, în locul tipului primitiv, se foloseşte un nume de clasa. De exemplu, declaraţia
String s1, s2, s3; arată că s1, s2 si s3 sunt

variabile referinţă către obiecte din clasa String, adică din

clasa şirurilor de caractere. Atentie: s1, s2 si s3 nu sunt şiruri de caractere (adică obiecte din clasa String) ci sunt doar referinţe către şiruri de caractere. Aceasta înseamnă că valoarea fiecăreia deintre ele nu este un obiect, ci o referinţă la obiect. Obiectul propriu-zis nu se găseşte în memorie în acelaşi loc

100

Programarea orientata pe obiecte în limbajul Java cu variabila, ci undeva în memoria dinamică. Prin contrast, în cazul declararii unor variabile primitive, de exemplu
int i1,i2,i3; variabilele i1, i2 si i3

sunt numere de tip int, adică au ca valori astfel de numere.

Vom arăta ulterior şi modul în care pot fi iniţializate variabilele referinţă.

Clase, câmpuri, metode
Clasa este o structură de date, asociată cu o colecţie de proceduri sau funcţii, metode, care utilizează datele din această structură. Datele unei clase se numesc câmpuri, sau variabile membre. Câmpurile pot fi statice (ale clasei) sau nestatice (ale instanţei). Când clasa este instanţiată, în memoria maşinii virtuale Java se construieşte un obiect (o instanţă a clasei respective). Obiectul conţine numai câmpuri nestatice. Câmpurile statice se păstreaza în memorie într-un singur loc, care este rezervat clasei respective. Metoda este o funcţie, care întoarce o valoare şi poate avea, de asemenea, efect lateral. Ca şi câmpurile, metodele pot fi statice(ale clasei) şi nestatice (ale instanţei). Metodele statice pot invocă numai câmpurile statice ale clasei respective, în timp ce metodele nestatice pot invoca atât câmpurile statice, cât şi pe cele nestatice (ale unei instanţe a clasei respective). Dacă valoarea întoarsă de metodă este void, metoda respectivă este o procedură şi trebuie să aibă obligatoriu efect lateral. Invocarea unei metode statice (a clasei) se face printr-o expresie de forma
nume_clasa.nume_metoda(parametri_efectivi)

a cărei valoare este valoarea întoarsă de funcţia respectivă. O astfel de invocare de funcţie poate fi deci folosită ca o componentă într-o altă expresie. De exemplu, expresia Math.sqrt(a) serveste pentru a calcula rădăcina patrată a lui a, în care scop este invocată funcţia sqrt, care este o metodă statică a clasei Math (clasa funcţiilor matematice uzuale). Putem folosi această invocare de metodă într-o expresie mai complicată, de exemplu
x=2*Math.sin(a)+3;

Invocarea unei metode nestatice (a instanţei) se face sub forma
referinţa_la_obiect.nume_metodă(parametri_efectivi)

deci numele metodei nu mai este calificat prin (însoţit de) numele clasei, ci prin cel al variabilei referinţă la obiectul respectiv, sau prin o expresie care are ca valoare o astfel de referinţă. De exemplu, daca r1 este o variabilă referinţă care indică un anumit obiect din memorie, iar met(a)este o metodă nestatică a clasei căreia îi aparţine acest obiect, atunci r1.met(a) are ca efect invocarea metodei met pentru obiectul indicat de r1. În acest fel, metoda met(a) va folosi atât câmpurile (nestatice ale) obiectului indicat de referinţa r1, cât şi câmpurile (statice ale) clasei căreia îi aparţine acest obiect. Dacă o metoda are efect lateral, ea poate fi invocată şi sub forma de instrucţiune. Această instrucţiune constă numai din expresia de invocare a metodei, urmată de punct_şi_virgulă. În acest caz, valoarea întoarsă de metodă (valoarea expresiei) este ignorată, folosindu-se numai

101

Severin Bumbaru efectul lateral al metodei respective. Un exemplu cunoscut deja este instrucţiunea
System.out.println(sir);

Este evident că metodele care întorc void (procedurile) pot fi invocate numai sub formă de instrucţiuni, fiind folosite pentru efectul lor lateral.

Constructori
Constructorul este o procedură specială, prin care se construiesc obiecte dintr-o anumită clasă. Constructorul are întotdeauna acelaşi nume cu clasa. În schimb, o clasă poate avea mai mulţi constructori, care pot să difere între ei prin numărul şi/sau tipul argumentelor. Constructorul alocă în memoria dinamică spaţiul necesar pentru un obiect din clasa căreia îi aparţine şi iniţializează câmpurile acestui obiect cu valori care depind de argumentele sale.

Crearea obiectelor prin operatorul new
Invocarea unui constructor se face prin operatorul new, urmat de numele constructorului respectiv şi de lista de argumente a acestuia. Operatorul new este unar, iar operandul lui este un constructor, deci forma generala a expresiei de invocare a unui constructor este
new nume_constructor(parametri_efectivi)

Valoarea acestei expresii este o referinţă la obiectul nou construit. Efectul lateral al acestei expresii este construirea în memorie a unui obiect din clasa căreia îi aparţine constructorul, iar valoarea acestei expresii este referinţa la obiectul nou construit. De exemplu, expresia
new String("acesta este un sir")

are ca efect (lateral) construirea în memorie a şirului de caractere "acesta este un sir", iar valoarea expresiei este referinţa către acest şir. Să considerăm acum următoarele două instrucţiuni:
String s1=new String("abcd_1234"), s2; s2=new String("alpha");

Prima dintre ele este o declaraţie, prin care se specifică faptul că s1 şi s2 sunt variabile referinţă, ale căror valori sunt referinţe la obiecte din clasa String. Totodată, se alocă în memorie şirul "abcd_1234" şi se atribuie ca valoare iniţială a variabilei s1 referinţa la acest şir. A doua dintre instrucţiunile de mai sus, este o instrucţiune de atribuire, în care variabilei s2 i se atribuie valoarea expresiei new String("alpha"). La executarea acestei expresii, se obţine ca efect lateral alocarea în memorie a şirului "alpha", iar referinţa la acest şir se atribuie ca valoare a variabilei s2

Moştenirea şi polimorfismul
Moştenirea este una din proprietăţile fundamentale ale claselor în programarea orientată pe obiecte. Ea constă în faptul că dintr-o clasă se pot deriva alte clase. Clasa de baza se mai numeste şi superclasă, iar clasele derivate se numesc şi subclase.

102

Programarea orientata pe obiecte în limbajul Java Fiecare clasă derivata moşteneşte câmpurile şi metodele superclasei. Aceasta înseamnă că toate câmpurile şi metodele existente în superclasă sunt utilizabile şi în subclasă, dar în aceasta din urmă pot exista, de asemenea, câmpuri şi/sau metode suplimentare. Polimorfismul este o alta proprietate fundamentală a claselor. În limbajul Java este posibil ca o metodă a superclasei să fie redefinită în subclasă. Aceasta înseamnă că ea va avea în subclasă acelaşi nume şi aceeasi lista de parametri şi acelaşi tip de valoare întoarsă ca în superclasă, dar va avea un comportament diferit.

Să considerăm, de exemplu, că exista clasa Poligon, în care există metoda double arie(). Aceasta metodă este o funcţie, a cărei valoare este aria poligonului. Sa considerăm acum că Triunghi şi Patrat sunt doua clase derivate din clasa Poligon. În ambele clase derivate va exista metoda double arie(), dar este evident că modul de calcul al ariei triunghiului diferă de cel al calculării ariei patratului şi, cu atât mai mult, de modul de calcul al ariei unui poligon oarecare.

Ierarhia de clase Java
În general, în programarea orientată pe obiecte este permisă moştenirea multiplă, adică o clasă poate avea mai multe superclase. În limbajul Java este permisă numai moştenirea simplă, deci fiecare clasă poate avea numai o singură superclasă. Această ierarhie de clase este unică, adică orice clasă are obligatoriu o superclasă şi numai una. Singura excepţie o constituie clasa Object, care este rădăcina ierarhiei de clase Java şi nu are superclasă. Vom arata ulterior că lipsa moştenirii multiple este compensată în Java prin faptul că fiecare clasa poate avea mai multe interfeţe.

Atribuirea de valori variabilelor-referinţă
Unei variabile referinţă i se pot atribui ca valori referinţă la obiecte din clasa variabilei respective sau din clasele derivate. De exemplu, având în vedere că clasa String este derivată din clasa Object, urmatoarele instrucţiuni sunt corecte:
Object ob; String s=new String("abcd"); ob=s;

În primele două instrucţiuni, s-au declarat variabilele-referinţă ob din clasa Object şi s din clasa String, ultima fiind iniţializată cu o referinţă către şirul "abcd", care a fost creat folosind operatorul new. În a treia instrucţiune, variabilei-referinţă ob i se atribuie valoarea variabilei-referinţă s. În consecinţă, acum variabila ob din clasa Object are ca valoare o referinţa către obiectul "abcd" din clasa String.

103

Severin Bumbaru

Pachetele de clase din Java API
Clasele sunt grupate în pachete (engleză: package). Pachetul este o colecţie de clase reutilizabile destinate unui anumit domeniu de utilizare, care sunt puse la dispoziţia programatorului sub formă compilată (bytecode). Ar putea fi numit şi "bibliotecă de clase", dar autorii platformei Java au preferat denumirea de pachet. Pachetul poate avea subpachete. Daca pachetul p are subpachetul q, atunci p.q este numele complet (numele calificat) al subpachetului q. Acest subpachet poate avea, la rândul sau, alte subpachete. Java API (Application Programming Interface - interfaţa de programare de aplicaţii) este descrierea unui set standard de pachete necesare programării în Java. Pentru pachetele conţinute în Platforma Java 2 Standard Edition (J2SE) această documentaţie poate fi gasită pe Internet la urmatoarea adresă: java.sun.com/products/j2se/1.3/docs/api/index.html - la firma Sun Microsystems; Principalele pachete de clase sunt: java.lang - conţine clasele de bază necesare programării în limbajul Java; java.io - conţine clasele necesare pentru programarea operaţiilor de intrare/ieşire; java.util - conţine clase pentru anumite structuri de date tipice (listă, stivă etc) şi alte clase utile; java.awt si javax.swing - conţin clase necesare pentru realizarea interfeţelor grafice; java.applet - pentru programarea appleturilor. Există însă şi multe alte pachete, necesare în diferite domenii de aplicaţie.

Declaraţia import
Pentru a putea utiliza într-un fişier-sursa Java un anumit pachet (subpachet) de clase, la începutul fişierului respectiv trebuie pusă declaraţia în al pachetului respectiv. De exemplu, pentru a utiliza orice clase din pachetul java.io se pune declaraţia
import java.io.*; import nume_pachet.*; care nume_pachet este numele calificat

Pentru clasele din pachetul java.lang nu este necesară o declaraţie de import, acestea fiind importate implicit. Pentru a importa numai o anumita clasă dintr-un pachet, se foloseste declaraţia
import nume_pachet.NumeClasa; De exemplu, pentru a importa clasa File din import java.io.File;

pachetul java.io se foloseşte declaraţia

Declaraţia
import nume_pachet.*;

se numeşte declaraţie de import la cerere. Ea nu are semnificaţia că se importă toate clasele din pachetul (subpachetul) respectiv, ci numai acele clase care sunt utilizate efectiv în program.

104

Programarea orientata pe obiecte în limbajul Java

Utilizarea claselor din pachetul java.lang
Pachetul java.lang conţine clasele fundamentale şi există pe orice platformă Java. Pentru acest pachet nu este necesară declaraţia import, clasele sale fiind importate implicit. Lista completă a claselor din pachetul java.lang şi descrierea lor se găsesc în documentaţia Java API de pe Internet. Noi vom prezenta aici câteva clase mai frecvent utilizate.

Clasa Object
Clasa Object este rădăcina ierarhiei de clase a platformei Java. Este singura clasă care nu are o superclasă. Orice altă clasa este derivată direct sau indirect din clasa Object. Conceptual, instanţele clasei Object sunt obiecte oarecare, fără atribute precizate. Variabilele referinţă la Object se folosesc atunci când, la elaborarea programului, se consideră că lor li se pot da ca valori referinţe la orice fel de obiecte, indiferent de clasa căreia îi aparţin. În clasa Object sunt declarate, de asemenea, metode care se consideră ca trebuie să existe în toate celelalte clase. Unele din aceste metode vor trebui redefinite în clasele derivate, pentru a efectua acţiuni specifice acestor clase.

Metodele clasei Object
Clasa Object nu are date membre. În consecinţă, obiectele acestei clase nu au stare (cu excepţia faptului ce ele există sau nu). Clasa are un singur constructor, Object(), care nu are parametri. Obiectele acestei clase nu conţin date. În schimb, clasa dispune de un set de metode. Conform cu principiul moştenirii, toate celelalte clase posedă metodele clasei Object. Metodele clasei Object sunt descrise în Java API. Noi vom prezenta aici numai o parte dintre ele, care vor fi utilizate în cele ce urmează. Metoda equals() este declarată sub forma:
public boolean equals(Object obj)

ceeace înseamnă că nivelul de acces este public, valoarea întoarsă (valoarea obţinută la evaluarea acestei funcţii) este de tip boolean, iar unicul argument aparţine clasei Object. Expresia a.equals(b) în care a şi b sunt două obiecte, întoarce valoarea true dacă a este identic cu b şi valoarea false în caz contrar. Aşa cum este ea definită în clasa Object, metoda equals() întoarce valoarea true numai dacă cele doua obiecte comparate sunt cu adevărat identice, adică au aceeaşi adresă în memorie, ceeace se poate scrie şi sub forma expresiei a==b. În clasele derivate, această metoda poate fi redefinită, respectând principiul polimorfismului, astfel încât să compare cele două obiecte după conţinut, chiar dacă ele nu au aceeaşi referinţă.

105

Severin Bumbaru Metoda hashCode() este declarată sub forma
public int hashCode()

şi întoarce codul de dispersie al obiectului căruia i se aplică. Codul de dispersie (engleză: hash code) este un număr întreg (de tip int) care are următoarele proprietăţi: - dacă se aplică de mai multe ori aceluiaşi obiect, metoda hashCode() întoarce de fiecare dată acelaşi cod de dispersie; - dacă două obiecte sunt egale între ele în sensul definit prin metoda equals(), atunci codurile de dispersie obţinute aplicând metoda hashCode() fiecăruia în parte trebuie să fie, de asemenea, egale; - este posibil ca, aplicând metoda hashCode() la doua obiecte diferite, să se obţină coduri de dispersie egale, însa este preferabil ca această situaţie să se întâlnească cât mai rar cu putinţă.

Metoda hashCode() este utilă atunci când obiectele trebuie plasate în tabele de dispersie, cum sunt cele care fac parte din clasa java.util.Hashtable şi java.util.HashMap. Metoda toString() este declarată sub forma
public String toString()

şi întoarce reprezentarea sub forma de şir de caractere (de obiect din clasa String) a obiectului căruia i se aplică. Asa dar, expresia a.toString(), în care a este un obiect, întoarce reprezentarea sub forma de şir a obiectului a. În cazul obiectelor aparţinând clasei Object, aceasta metodă întoarce un şir de forma java.lang.Object@<cod_dispersie> în care <cod_dispersie> este codul de dispersie al obiectului respectiv, exprimat în hexazecimal. În clasele derivate aceasta metodă este redefinită, astfel încât să se obţină o reprezentare sub formă de şir a conţinutului obiectului respectiv.

Metoda clone() este declarată sub forma:
protected Object clone()

şi are rolul de a întoarce o clonă a obiectului căruia i se aplică, deci întoarce un obiect identic cu acesta, dar situat la o altă adresă de memorie şi deci având o altă referinţă. În consecinţă, după ce se execută instrucţiunea
b=a.clone();

în care a şi b sunt referinţe la obiecte, expresia a==b va avea valoarea false, deoarece valorile variabilelor referinţă a şi b sunt diferite, în timp ce expresia a.equals(b) va întoarce în mod normal valoarea true, deoarece cele două obiecte comparate au conţinuturi identice.

106

Programarea orientata pe obiecte în limbajul Java În schimb, după ce se execută instrucţiunea
b=a;

variabila b va avea ca valoare aceeaşi referinţă ca şi variabila a, adica a şi b vor indica acelaşi obiect din memorie, astfel că expresia a==b întoarce valoarea true. Întrucât metoda clone() este protejată (are nivelul de acces protected) ea nu poate fi utilizată decât pentru obiectele din clasa Object. În schimb, ea poate fi redefinită la clasele descendente, astfel încât sa devină publică. Metodele prezentate aici (exceptând, desigur, metoda clone()) sunt testate în programul din fişierul TestObject.java. Metoda getClass() este declarată sub forma
public final Class getClass()

O expresie de forma a.getClass(), în care a este o referinta la un obiect, întoarce ca valoare un obiect din clasa Class, care conţine informaţii despre clasa obiectului indicat de variabila a. Deocamdată vom folosi această metoda numai pentru a afişa numele clasei, ca în ultimele trei instrucţiuni ale programului din fişierul TestObject.java.

Clase de excepţii
În pachetul java.lang există şi numeroase clase de excepţii. Instanţele acestor clase sunt creeate de către maşina virtuală Java atunci când se produce o excepţie, adică o situaţie anormală în procesul de calcul. Toate aceste clase sunt descrise în documentatia Java API. Vom menţiona aici numai două dintre ele, celelalte urmând să fie indicate la descrierea claselor care conţin metode care pot genera excepţii. În limbajul Java se face distincţie între excepţie şi eroare. Se consideră ca excepţiile sunt incidente care pot fi captate prin mecanismul try .. catch şi pot fi deci tratate prin program, în timp ce erorile sunt incidente grave, care - de regulă - nu pot fi tratate prin program ci produc oprirea executării acestuia.

Clasa Exception
Această clasă este rădăcina ierarhiei claselor de excepţii. În consecinţă, atunci când dorim ca in clauza catch sa fie captată orice fel de excepţie, scriem această clauză sub forma
catch(Exception e) { instructiuni_de_tratare_a_exceptiei e }

unde e este numele simbolic (identificatorul) dat excepţiei captate. Clasa are doi constructori:
public Exception()

creează un obiect din clasa Exception (deci "o excepţie") care nu conţine nici un mesaj.

107

Severin Bumbaru
public Exception(String s)

creează un obiect din clasa Exception, care conţine un mesaj sub forma şirului s. Prin acest mesaj se indică, de regulă, ce incident a condus la generarea excepţiei respective. Vom arăta ulterior cum putem folosi aceşti constructori pentru a genera propriile noastre excepţii. Clasa Exception nu are propriile ei metode.Totuşi, atât pentru clasa Exception, cât şi pentru toate celelalte clase de excepţii se pot folosi metodele superclasei Object. În particular, se poate folosi pentru instanţele oricărei clase de excepţii metoda toString(), care pune instanţa respectivă sub forma unui şir de caractere. Acest şir conţine, de regulă, un mesaj privind felul exceptiei respective.

Clasa ArithmeticException
Instanţele acestei clase sunt generate de maşina virtuală Java atunci când are loc o situaţie anormală la efectuarea unei operaţii aritmetice, cum ar fi, de exemplu, împărţirea la zero a unui număr întreg. Instanţa respectivă conţine un mesaj care indică ce fel de excepţie s-a produs. Exemple de utilizare a claselor de excepţii s-au dat deja în capitolul "Tratarea excepţiilor".

Clasele String şi StringBuffer
Pentru şirurile de caractere, pe care le vom numi în viitor simplu "şiruri", există în pachetul java.lang doua clase: String şi StringBuffer. Obiectele clasei String conţin şirurile propriu-zise, iar cele ale clasei StringBuffer conţin zone tampon pentru şiruri.

Clasa String
Toate şirurile în limbajul Java, inclusiv literalii-şir, de exemplu "ABCdef123", sunt obiecte ale clasei String. Obiectele din această clasă sunt constante, adică şirurile conţinute în ele nu pot fi modificate. Dacă este necesar să se folosească şiruri modificabile, se recurge la clasa StringBuffer. În afară de metodele pe care le oferă, clasa String conţine şi suportul necesar pentru operatorul de concatenare'+'. Prin concatenarea a două şiruri se obţine un nou şir, rezultat din punerea celor două unul în continuarea celuilalt. De exemplu, expresia "ABC"+"defg" are ca valoare şirul "ABCdefg". Dăm aici o parte din constructorii şi metodele mai frecvent utilizate ale clasei String. O prezentare mai amplă există în indexul de clase. Vă recomandăm să o consultaţi, pentru a cunoaşte ce constructori şi metode conţine. Amintim că prezentarea completă se găseşte în documentaţia Java API de pe Internet.

108

Programarea orientata pe obiecte în limbajul Java Construirea unui obiect din clasa şir se face cel mai frecvent cu constructorul String(String s). Acest constructor creeaza în memorie o copie a şirului s primit ca argument. Dintre metodele foarte utile menţionăm aici următoarele:
int compareTo(String anotherString) Compara acest şir cu şirul anotherString

primit ca argument. Dacă cele două şiruri sunt identice, întoarce 0 (zero). Dacă acest şir îl precede pe anotherString întoarce o valoare negativă, iar daca îi succede lui anotherString întoarce o valoare pozitivă. Compararea şirurilor se face în ordine lexicografică (în ordinea în care ar fi plasate într-un dicţionar).
int compareToIgnoreCase(String str)

Compară lexicografic acest şir cu sirul str primit ca argument, ignorând deosebirea dintre literele majuscule şi cele minuscule.
int length()

Întoarce lungimea acestui şir (numărul de caractere conţinute).
String trim()

Întoarce un nou şir, obţinut din acest şir prin eliminarea spaţiilor de la început şi de la sfârşit.

int indexOf(int ch)

Întoarce indicele la care se găseşte în şir prima apariţie a caracterului ch. Dacă acest caracter nu există în şir, întoarce -1.
int indexOf(int ch, int fromIndex)

Similar cu metoda precedentă, dar căutarea in sir începe de la poziţia fromIndex.
int indexOf(String str)

Întoarce indicele poziţiei de la care în acest şir apare prima dată subşirul str.
int indexOf(String str, int fromIndex)

Similar cu metoda precedentă, dar căutarea în acest şir începe de la poziţia fromIndex.
int lastIndexOf(int ch)

Întoarce indicele ultimei poziţii pe care apare caracterul ch în acest şir.
int lastIndexOf(int ch, int fromIndex)

Întoarce indicele ultimei poziţii pe care se găseşte caracterul ch, dacă se face căutarea înapoi, începând de la poziţia fromIndex.
int lastIndexOf(String str)

Întoarce indicele ultimei apariţii în acest şir a subşirului str.
int lastIndexOf(String str, int fromIndex)

Similar cu metoda precedentă, dar căutarea înapoi se face de la poziţia fromIndex.
String substring(int beginIndex)

109

Severin Bumbaru Întoarce un nou şir, care conţine caracterele acestui şir, începând de la poziţia beginIndex, până la sfârşit.
String substring(int beginIndex, int endIndex) Întoarce subşirul situat între pozitiile beginIndex şi endIndex-1

inclusiv.

Exemplu În programul din fişierul TestStr.java se exemplifică utilizarea clasei String.

Clasa StringBuffer
Obiectele clasei StringBuffer implementează şiruri care pot fi modificate atât ca lungime, cât şi sub aspectul caracterelor pe care le conţin. În engleză buffer înseamnă zonă tampon. Aşa dar, un "StringBuffer" este modelul unei zone tampon de memorie, în care se pot adăuga noi caractere la şirul existent şi în care se pot înlocui total sau parţial caracterele existente cu altele. Principalele operaţii asupra unei astfel de "zone tampon" sunt metodele append() şi insert(), prin care se poate adăuga un şir nou în coada celui existent, sau se înserează acest şir nou în interiorul celui existent. Un obiect StringBuffer (zona tampon pentru caractere) se caracterizează prin lungime şi capacitate. Lungimea este numărul de caractere conţinut efectiv, iar capacitatea este dimensiunea la un moment dat a tabloului de caractere conţinut în obiectul respectiv. Ambele se pot modifica în timp, ca urmare a aplicării unor metode. Construirea unui nou StringBuffer se poate face cu unul din constructorii StringBuffer(), StringBuffer(int length) sau StringBuffer(String str). Primul dintre ei construieşte o zonă tampon de caractere, având o capacitate iniţială predefinită; al doilea una de capacitate iniţială length, iar al treilea construieşte o zonă tampon care conţine iniţial şirul str. Cele mai importante metode sunt: public int length() - întoarce lungimea curentă a şirului (numărul de caractere existente efectiv în buffer); public int capacity() - întoarce capacitatea curentă a buffer-ului; public StringBuffer append(char c) - adaugă la buffer caracterul c; public StringBuffer insert(int offset, char c) - însereaza în buffer caracterul c pe pozitia offset; public String toString() - întoarce un şir care are acelaşi conţinut cu cel din acest StringBuffer.

Există, de fapt, o familie de metode append(), care diferă între ele prin tipul argumentului:
append(boolean b), append(byte b), append(short s), append(int i), append(long l), append(char c), append(float f), append(double d),append(String str), append(Object obj).

110

Programarea orientata pe obiecte în limbajul Java Toate aceste metode adaugă în coada zonei tampon argumentul lor, convertit într-un şir. Există, de asemenea, o familie de metode insert(), care diferă prin tipul celui de al doilea argument:
insert(int offset, boolean b), insert(int offset, byte b), insert(int offset, short s), insert(int offset, int i), insert, int offset, long l), insert(int offset, char c), insert(int offset, float f), insert(int offset, double d), insert(int offset, String str), insert(int offset, Object obj).

Toate aceste metode înserează în zona tampon, pe poziţia offset, cel de al doilea argument convertit în şir. O prezentare mai amplă a clasei StringBuffer este dată în indexul de clase, iar descrierea completă se găseşte pe Internet în documentaţia Java API. Exemplu În fişierul TestStrB.java este dat un exmplu de program în care se testează clasa StringBuffer.

Clasa Class
O caracteristică importantă a limbajului şi platformei Java este că clasele şi interfeţele utilizate în program sunt prezente în memoria maşinii virtuale Java în timpul executării programului, sub forma de instanţe ale clasei Class. În consecinţă, se pot obţine în timpul executării unui program informaţii despre clasele cărora le aparţin obiectele din memorie. Clasa Class nu are un constructor public. În schimb, putem obţine un obiect din această clasă folosind metoda getClass() a clasei Object. Există şi instanţe ale clasei Class pentru tipurile de date primitive. Acestea sunt conţinute sub forma de câmpuri statice în clasele acoperitoare ale tipurilor primitive respective. Iată câteva dintre metodele clasei Class:
public String getName() - întoarce numele calificat al unei entităţi (clase, interfeţe, tip primitiv) reprezentată de un obiect din clasa Class; public boolean isAssignableFrom(Class cls) - întoarce true dacă clasa căreia i se aplică metoda este o superclasă a clasei cls, primită ca argument; public boolean isInterface() - întoarce true dacă metoda este aplicată unei instanţe a clasei Class care reprezintă o interfaţă; public boolean isPrimitive() - întoarce true dacă metoda este aplicată unui obiect din clasa Class care reprezintă un tip de date primitiv; public Class getSuperclass() - întoarce o instanţă a clasei Class care reprezintă superclasa obiectului căruia i se aplică această metodă.

În fişierul TestClass.java este dat un program de testare a obţinerii obiectelor Class şi a aplicării unora dintre metodele acestora. Descrierea completă a clasei Class este dată în documentaţia Java API. 111

Severin Bumbaru

Clasele acoperitoare de tip
În pachetul java.lang, pentru fiecare din tipurile de date primitive există o clasă acoperitoare de tip (engleză: Type Wrapper). Aceste clase pun la dispoziţia programatorului atât variabile finale (constante), cât şi metode necesare pentru a lucra cu tipul de date respectiv. Clasele acoperitoare de tip formează ierarhia din figura 1.

- Figura 1 În aceasta figură, clasele acoperitoare de tip sunt reprezentate cu negru. Clasa Number este o clasa abstractă, din care sunt derivate toate clasele acoperitoare pentru tipuri de date numerice. Fiecare din celelalte clase acoperă un anumit tip de date primitiv. Clasa Character acoperă tipul char, iar clasa Integer acoperă tipul int. Toate celelalte clase acopera tipul primitiv al carui nume îl poartă. Spre deosebire de numele tipurilor primitive, care sunt scrise cu litere mici, numele claselor acoperitoare încep (ca la orice alte clase) cu majuscule. Toate clasele acoperitoare sunt clase finale, deci din ele nu mai pot fi derivate alte clase. Fiecare instanţă a unei clase acoperitoare conţine un câmp de date nestatic în care se pastrează o valoare aparţinând tipului primitiv de date corespunzator clasei respective. De exemplu, un obiect din clasa Boolean conţine un câmp de tip boolean care, la rândul lui, conţine valoarea logică true sau false. În mod similar, un obiect din clasa Double conţine un câmp de tip double în care există un număr în virgulă mobilă în dublă precizie. În afară de câmpul de date nestatic, fiecare clasă acoperitoare conţine mai multe câmpuri de date statice finale, în care se păstrează diferite constante (variabile finale) specifice tipului de date corespunzător. Unele dintre metode sunt prezente în toate clasele acoperitoare, altele sunt specifice fiecărei clase sau unui grup de astfel de clase. Prezentăm aici succint principalele metode prezente în toate clasele:
public boolean equals(Object obj) - este o redefinire a metodei equals()din clasa Object ; compară obiectul propriu cu obiectul obj primit ca argument şi întoarce true dacă acestea sunt identice, sau false în caz contrar; public String toString() - este o redefinire a metodei toString() din clasa Object ; întoarce valoarea primitivă conţinută în obiectul respectiv, convertită într-un şir de

112

Programarea orientata pe obiecte în limbajul Java caractere, reprezentând forma externă a valorii respective; public int hashCode() - este o redefinire a metodei hashCode() din clasa Object; intoarce codul de dispersie al obiectului propriu. Clasele acoperitoare oferă, de asemenea, metode prin care se pot converti datele din tipurile respective din forma externă (de şiruri de caractere) în cea interna şi invers.

Clasa Boolean
Clasa Boolean acoperă tipul primitiv boolean. Clasa are doi constructori publici: Boolean(boolean value) şi Boolean(String s). Primul primeşte ca argument o expresie cu valoare de tip boolean, iar al doilea primeşte un şir de caractere, care conţine forma externă a unei valori de tip boolean. În afara de metodele equals(), toString() şi hashCode(), care au fost deja menţionate, clasa Boolean mai oferă următoarele metode specifice: public boolean booleanValue() - întoarce valoarea primitivă de tip boolean conţinută în obiectul propriu; public static Boolean valueOf(String s) - întoarce o noua instanţă a clasei boolean, corespunzătoare şirului s: dacă şirul s este "True" întoarce TRUE, altfel întoarce FALSE. Are, practic, acelaşi efect cu aplicarea operatorului new cu constructorul Boolean(String s). Clasa Boolean moşteneşte, de asemenea, metodele clasei Object. În programul din fişierul TestBoolean.java este testată clasa Boolean.

Clase acoperitoare pentru tipurile de date numerice
Clasa abstractă Number
Este superclasa tuturor tipurilor de date numerice, cu excepţia tipului char.Contine următoarele metode: public abstract byte byteValue() - întoarce numărul convertit la tipul primitiv byte, ceeace poate avea ca efect rotunjirea sau trunchierea; public abstract short shortValue() - întoarce numărul convertit la tipul primitiv short, ceeace poate avea ca efect rotunjirea sau trunchierea; public abstract int intValue() - întoarce numărul convertit în tipul primitiv int, ceeace poate avea ca efect rotunjirea; public abstract long longValue() - întoarce numărul convertit la tipul primitiv long, ceeace poate avea ca efect rotunjirea; public abstract float floatValue() - întoarce numarul convertit la tipul primitiv float; public abstract double doubleValue() - întoarce numarul convertit la tipul primitiv double.

113

Severin Bumbaru Remarcăm că aceste metode se aplica tuturor claselor acoperitoare pentru numere, realizând conversia numărului conţinut într-o instanţă a unei clase acoperitoare numerice către tipul primitiv dorit de utilizator.

Clasele Byte, Short, Integer şi Long
Instanţele acestor clase acoperă valori din tipurile primitive byte, short, int şi long. Între aceste clase există o mare asemănare în ce priveăte câmpurile de date statice şi metodele. Vom prezenta aci principalele facilităţi oferite de clasa Integer. Pentru o prezentare completă a acestei clase, cât şi pentru informaţii privind celelalte clase, recomandăm consultarea documentaţiei originale Java API. Menţionăm că majoritatea metodelor statice din clasa Integer nu există în celelalte clase acoperitoare pentru tipuri de date intregi, dar nici nu este necesar, deoarece se pot folosi aceste metode şi cu argumente efective de tip byte sau short. Clasa Integer este clasa acoperitoare pentru tipul primitiv int. Clasa conţine următoarele câmpuri de date statice:
public static final int MAX_VALUE

- conţine valoarea maximă pentru tipul de - conţine valoarea minimă pentru tipul de date

date int, adică valoarea 2147483647;
public static final int MIN_VALUE

int, adică -2147483648;
public static final Class TYPE despre tipul primitiv int.

- conţine un obiect din clasa Class cu informaţii

Clasa are doi constructori:
public Integer(int value)

- construieşte instanţa clasei Integer care conţine

valoarea primitivă value;
public Integer(String s) - converteşte şirul s, primit ca argument, într-un număr de tip int şi construieşte obiectul Integer care conţine acest număr; dacă şirul s nu reprezintă forma externă a unui număr întreg, se obţine excepţia NumberFormatException. Remarcăm deci că se face analiza sintactică a şirului s pentru a se verifica dacă acesta este cu adevărat un număr întreg.

Clasa oferă un numeroase metode, atât de instanţă, cât şi statice, care sunt utile când se lucrează cu numere întregi. Dintre acestea menţionăm: a/ metodele equals(), toString() şi hashCode() existente în toate clasele acoperitoare; b/ metodele byteValue(), shortValue(), intValue(), longValue(), floatValue()si doubleValue() existente în toate subclasele clasei Number; c/ metode statice, utile în diverse operaţii cu numere întregi: public static int parseInt(String s) - face analiza sintactică a şirului s, considerat ca forma externă a unui număr întreg în sistemul de numeraţie zecimal, şi întoarce valoarea de tip int corespunzatoare; dacă şirul s nu are forma corectă a unui număr întreg, se

114

Programarea orientata pe obiecte în limbajul Java obţine excepţia NumberFormatException;

public static int parseInt(String s, int radix) - acţioneaza asemănător cu metoda precedentă, cu deosebirea că se consideră că şirul s este un număr în sistemul de numeraţie cu baza radix; public static String toBinaryString(int i) - întoarce numărul întreg i convertit într-un şir care îl reprezintă în sistemul de numeraţie binar; public static String toHexString(int i) - întoarce numărul întreg i convertit într-un şir hexazecimal; public static String toOctalString(int i) - întoarce numărul întreg i convertit într-un şir în sistemul de numeraţie octal; public static String toString(int i) - întoarce numărul întreg i convertit într-un şir în sistemul de numeraţie zecimal (se deosebeşte de metoda toString(), moştenită de la clasa Object, prin faptul ca nu este o metodă a instanţei, ci una statică); public static String toString(int i, int radix) - întoarce un şir care conţine numărul intreg i convertit în sistemul de numeraţie cu baza radix; public static Integer valueOf(String s) - acţionează asemănător cu metoda parseInt(String s), dar întoarce un obiect din clasa Integer; public static Integer valueOf(String s, int radix) - similara cu metoda precedentă, dar se consideră că şirul s este un număr întreg în baza de numeraţie radix; public static Integer decode(String s) - analizează şirul s şi îl decodifică; dacă acesta respectă unul din formatele admise pentru numere întregi, adică este forma externă a unui număr întreg în unul din sistemele zecimal, hexazecimal (incepe cu 0x) sau octal (incepe cu 0), atunci il converteşte în valoare internă de tip int şi întoarce obiectul de clasa Integer care contine aceasta valoare; altfel, întoarce excepţia NumberFormatException.

d/ metode ale instanţei (în plus, faţă de cele menţionate la punctele a/ şi b/): - compară propriul obiect din clasa Integer cu obiectul anotherInteger primit ca argument. Dacă ele sunt egale, întoarce zero; daca întregul propriu este mai mic decât argumentul, întoarce o valoare negativă, iar dacă este mai mare întoarce o valoare pozitivă; public int compareTo(Object obj) - dacă obiectul-argument obj este tot un Integer, atunci această metodă acţionează la fel cu cea precedentă; altfel, se obţine excepţia ClassCastException.
public int compareTo(Integer anotherInteger)

Exemplu: Programul din fişierul TestInt.java testează utilizarea unora dintre metodele clasei Integer. Executând acest program se poate constata că analiza sintactică a şirului primit ca argument decurge în acelaşi mod la metodele parseInt(String), valueOf(String) şi la constructorul Integer(String). Se acceptă numai şirurile care reprezintă cu adevărat numere întregi cuprinse în domeniul de valori al tipului int. Se admite ca numărul să fie precedat de semnul - (minus), dar nu de semnul +. Se observă, de asemenea, că la conversia de la Integer către byte sau short este posibil să se piardă cifrele cele mai semnificative prin trunchiere.

Clasele Float şi Double
Instanţele claselor Float şi Double "acoperă" valori ale tipurilor primitive corespunzătoare, respectiv float şi double. Ele oferă, de asemenea, metode de conversie a şirurilor în 115

Severin Bumbaru numere reale (cu analiza sintactică corespunzătoare) şi a numerelor reale în şiruri. Vom prezenta aici clasa Double, dar aceleaşi facilităţi există şi în clasa Float. Câmpuri de date statice: Clasa Double (şi similar clasa Float) oferă următoarele câmpuri de date statice: public static final double MAX_VALUE - cea mai mare valoare pozitivă de tip double; public static final double MIN_VALUE - cea mai mică valoare pozitivă de tip double; public static final double NaN - valoarea NaN (Not a Number) pentru tipul double; public static final double POSITIVE_INFINITY - valoarea Infinitypentru tipul double; public static final double NEGATIVE_INFINITY - valoarea -Infinity pentru tipul double; public static final Class TYPE - un obiect din clasa Class cu informaţii despre tipul primitiv double. Constructori: Clasa Double are doi constructori: Double(double value) - construieşte o instanţă a clasei Double care conţine valoarea primitivă value; Double(String s) - construieşte o instanţă a clasei Double care conţine valoarea primitivă de tip double a cărei formă externă este şirul s primit ca argument; dacă acest şir nu este corect sintactic, se obtine excepţia NumberFormatException. Metode: Clasa Double oferă numeroase metode utile în lucrul cu date de tip double, dintre care mentionăm: a/ metodele equals(), toString() si hashCode() existente în toate clasele acoperitoare; b/ metodele byteValue(), shortValue(), intValue(), longValue(), floatValue() si doubleValue() existente în toate subclasele clasei Number; c/ metode statice, utile în diverse operaţii cu numere reale: public static double parseDouble(String s) - analizează sintactic şirul s primit ca argument şi - dacă este corect - întoarce valoarea primitivă double corespunzatoare; altfel generează excepţia NumberFormatException; public static String toString(double d) - converteşte în sir numărul primitiv primit ca argument;

public static Double valueOf(String s) - acţioneaza asemănător cu metodă parseDouble(String), dar intoarce un obiect din clasa Double;

116

Programarea orientata pe obiecte în limbajul Java
public static long doubleToLongBits(double value)

- întoarce o valoare de tip long care este reprezentarea pe 64 biţi, conform standardului IEEE 754, a numărului de tip double primit ca argument; public static double longBitsToDouble(long bits) - întoarce o valoare de tip double, a cărei reprezentare ca long (întreg pe 64 biţi) conform standardului IEEE 754 este cea primită ca argument; public static boolean isInfinite(double v) - întoarce true dacă argumentul v este infinit; public static boolean isNaN(double v) - întoarce true dacă argumentul v este NaN; d/ metode ale instanţei (în plus, faţă de cele menţionate la punctele a/ şi b/):

public int compareTo(Double anotherDouble) - compară propriul obiect din clasa Double cu obiectul anotherDouble primit ca argument; dacă ele sunt egale, întoarce zero; dacă numărul propriu este mai mic decât argumentul întoarce o valoare negativă, iar dacă este mai mare întoarce o valoare pozitivă; public int compareTo(Object obj) - dacă obiectul-argument obj este tot un Double, atunci această metodă acţionează la fel cu cea precedentă; altfel, se obţine excepţia ClassCastException. public boolean isInfinite() - întoarce true dacă valoarea conţinută în obiectul propriu este infinită; public boolean isNaN() - întoarce true dacă valoarea conţinuta în obiectul propriu este NaN;

Exemplu: Programul din fişierul TestDouble.java testează principalele metode ale clasei Double. Remarcăm că, în acest caz, a fost considerat corect şi un sir care reprezintă un număr real precedat de semnul +. Pentru reprezentarea formatului intern al numărului s-a folosit metoda doubleToLongBits(), combinată cu metodele toBynaryString() şi toHexString()din clasa Long.

Clasa Character
Fiecare obiect al clasei Character "acoperă" o valoare primitiva de tip char. În plus, clasa Character oferă metode utile în lucrul cu caractere. Reamintim că, în limbajul Java, caracterele sunt reprezentate pe 16 biţi, în sistemul Unicode. Câmpuri statice: Clasa Character conţine numeroase câmpuri de date statice, prezentate în documentaţia Java API. Cele mai multe dintre ele sunt coduri numerice ale diferitelor categorii de caractere (litere majuscule, litere minuscule, cifre zecimale, simboluri matematice etc), folosite în metodele care verifică dacă un anumit caracter corespunde categoriei respective. Printre ele există însă şi câmpul care
public static final Class TYPE conţine un obiect din clasa Class cu informaţii

despre tipul primitiv char.

117

Severin Bumbaru Constructori: Clasa Character are un singur constructor public Character(char value) - construieşte un obiect al clasei Character care conţine caracterul primit ca argument.

Metode statice: Clasa Character oferă numeroase metode statice utile în lucrul cu caractere, dintre care menţionăm: public static boolean isDigit(char ch) - întoarce true dacă argumentul ch este cifră; public static boolean isLetter(char ch) - întoarce true dacă argumentul este literă; public static boolean isLowerCase(char ch) - întoarce true dacă ch este literă mică; public static boolean isUpperCase(char ch) - întoarce true daca ch este literă majusculă; public static boolean isLetterOrDigit(char ch) - verifică dacă ch este literă sau cifră; public static boolean isWhitespace(char ch) - verifică dacă ch este un spaţiu liber sau un alt caracter asimilabil acestuia, de exemplu caracter de trecere la linie nouă, de întoarcere a carului, de tabulare orizontală sau verticală, de separare a fişierelor sau înregistrărilor etc (vezi documentaţia). public static boolean isSpaceChar(char ch) - verifică dacă ch este caracterul spaţiu; public static char toLowerCase(char ch) - întoarce caracterul ch convertit în litera mică; dacă nu este literă, îl lasă neschimbat; public static char toUpperCase(char ch) - întoarce caracterul ch convertit în litera majusculă; dacă nu este literă, îl lasă neschimbat; public static int getNumericValue(char ch) - întoarce valoarea numerica Unicode a caracterului ch ca un întreg nenegativ; public static int digit(char ch, int radix) - întoarce valoarea numerică a caracterului ch considerat ca cifra a sistemului de numeraţie cu baza radix; dacă în sistemul respectiv nu există o astfel de cifră, întoarce -1; public static char forDigit(int digit, int radix) - întoarce caracterul prin care este reprezentată cifra de valoare digit în sistemul de numeraţie cu baza radix; daca în sistemul respectiv nu exista o asemenea cifră, întoarce caracterul nul ('\u0000'); Metode nestatice: În afara de metodele generale equals(), toString() şi hashCode(), clasa Character oferă, de asemenea, următoarele metode: public char charValue() - întoarce caracterul primitiv conţinut în obiectul propriu din clasa Character; public int compareTo(Character anotherCharacter) - compară caracterul din propriul obiect cu cel primit ca argument şi întoarce valoare nulă dacă sunt egale, negativă daca primul este mai mic decât al doilea şi valoare pozitivă dacă este mai mare;

118

Programarea orientata pe obiecte în limbajul Java - dacă argumentul obj este un obiect din clasa Character, actionează similar cu metoda precedentă; altfel generează excepţia ClassCastException;
public int compareTo(Object obj)

Exemplu: În fişierul TestChar.java este dat un program de testare a metodelor oferite de clasa Character.

Clasa Void
Clasa Void nu este instanţiabilă (nu poate avea obiecte) şi "acoperă" tipul primitiv void. Ea conţine numai un singur câmp static
public static final Class TYPE

care conţine un obiect din clasa Class cu informaţii despre tipul primitiv void.

Clasa Math
Clasa Math este foarte utilă în calcule ştiinţifice şi inginereşti. Ea conţine un număr mare de funcţii matematice (funcţii trigonometrice, logaritmice, exponenţiale etc) şi două constante matematice: numărul e şi numărul pi. Constantele sunt reprezentate sub forma următoarelor două câmpuri statice finale ale clasei: public static final double E - numărul e (baza logaritmilor naturali); public static final double PI - numarul pi (raportul dintre perimetrul şi diametrul cercului). Aceste constante se folosesc în expresiile din program sub forma Math.E şi Math.PI. Funcţiile matematice se prezintă în această clasă sub forma de metode statice. Menţionăm aici numai câteva din aceste funcţii, cu precizarea că arcele (unghiurile) se exprimă în radiani: public static double sin(double a) - sinusul trigonometric sin a. public static double cos(double a) - cosinusul trigonometric cos a. public static double tan(double a) - tangenta trigonometrică tg a. public static double asin(double a) - arcsin a. public static double acos(double a) - arccos a. public static double atan(double a) - arctg a. a public static double exp(double a) - funcţia exponentială e . public static double log(double a) - logaritmul natural ln a. public static double sqrt(double a) - radacina patrată a lui a. Lista completă a funcţiilor este dată în indexul claselor.

119

Severin Bumbaru Utilizarea în expresiile din program a acestor funcţii se face, ca la toate metodele statice, calificând numele funcţiei prin numele clasei. De exemplu, sin(2*u+3) se va scrie Math.sin(2*u+3).

Clasa System
Clasa System conţine câmpuri şi metode utile pentru realizarea legăturii dintre aplicaţie şi sistemul de execuţie Java (cel care implementează maşina virtuală Java). Această clasă nu poate fi instanţiată. Câmpuri statice: Clasa System are trei câmpuri statice care reprezintă unităţile standard de intrare/ieşire ale sistemului: public static final InputStream in - este intrarea standard a sistemului. De regulă, aceasta este tastatura, dar poate fi şi alt dispozitiv indicat de utilizator. public static final PrintStream out - este ieşirea standard a sistemului. De regulă este unitatea de afişare standard (ecranul) dar poate fi şi alt dispozitiv indicat de utilizator. Afişarea datelor pe ecran se face, de regulă folosind metoda System.out.println(sir) sau System.out.print(sir). public static final PrintStream err - unitatea standard de ieşire pentru erori. De regulă este aceeaşi ca pentru obiectul out, dar poate fi şi alt dispozitiv indicat de utilizator. Metode: Dăm aici numai metodele care sunt utilizate de noi în acest curs. Descrierea completă a tuturor metodelor clasei System poate fi gasită în documentaţia Java API. public static void exit(int status)- provoaca incheierea executării programului. Argumentul acestei metode este un cod de stare care se transmite maşinii virtuale Java. Prin convenţie, 0 înseamnă încheiere normală a executării aplicaţiei, iar un cod diferit de zero indică încheiere anormală (cu cod de eroare). Metoda se foloseşte în program sub forma instructiunii System.exit(stare);.

public static void setIn(InputStream in)- schimbă unitatea de intrare standard.

Noua unitate de intrare standard va fi obiectul din clasa InputStream dat prin argumentul in.
public static void setOut(PrintStream out)- schimba unitatea de ieşire standard

pentru date. Noua unitate de ieşire va fi obiectul din clasa OutputStream dat prin argumentul out.
public static void setErr(PrintStream err)- schimbă unitatea de ieşire standard

pentru erori. Noua unitate de iesire va fi obiectul din clasa OutputStream dat prin argumentul err.
public static long currentTimeMillis()- întoarce timpul

curent în milisecunde. Acesta este un număr de tip long, care exprimă timpul în milisecunde măsurat cu începere de

120

Programarea orientata pe obiecte în limbajul Java la data de 1 ianuarie 1970 ora 0.

Tablouri
Conceptul de tablou
Tabloul (în engleză Array) este o structură de date de acelasi tip, numite componente ale tabloului, care sunt specificate prin indici. În programare, tabloul poate fi privit ca o colecţie indexată de variabile de acelaşi tip. Exemple tipice de tablouri sunt vectorul şi matricea din matematică. Un vector este o colecţie indexată de componente cu un singur indice. De exemplu x=[x0, x1, ... , xn-1] este un vector cu n componente. Componentele au acelaşi "nume" cu vectorul, dar se disting prin indici, care specifică poziţia componentei respective în cadrul tabloului. În limbajul Java, la fel ca în limbajele C/C++, indicii încep de la 0. Întrucât componentele vectorului sunt dispuse pe o singură direcţie în spaţiu, spunem că este un tablou unidimensional. La nivel conceptual, se consideră că tabloul ocupă o zonă compactă de memorie, în care componentele sale sunt aşezate în ordinea crescătoare a indicilor, din care cauză mai este numit şi masiv. Matricea este un tablou bidimensional. Componentele matricei sunt ordonate pe două direcţii în spaţiu, iar poziţia fiecărei componente este indicată prin doi indici: primul specifică linia, iar al doilea coloana în care se găseşte componenta respectivă. Iată un exemplu de matrice cu 4 linii şi 5 coloane:

a00 a10 a20 a30

a01 a11 a21 a31

a02 a12 a22 a32

a03 a13 a23 a33

a04 a14 a24 a34

În acest exemplu, numele matricei, ca şi numele fiecărui element al ei, este a. Poziţia componentei în cadrul matricei este specificată prin cei doi indici. Conform convenţiei de indexare din limbajul Java, indicii incep de la zero. Pot exista şi tablouri cu mai mult de două dimensiuni. Astfel, un tablou tridimensional poate fi imaginat ca un volum (o carte), având mai multe pagini, fiecare pagină fiind un tablou bidimensional (o matrice). În acest caz, primul indice specifică linia, al doilea - coloana, iar al treilea - pagina în care se găseşte componenta respectivă. În mod similar, un tablou cu patru dimensiuni poate fi privit ca o serie de volume; fiecare componentă, în acest caz, are patru indici, cel de al patrulea fiind numărul volumului în cadrul seriei. Putem, desigur, continua raţionamentul şi pentru tablouri cu mai mulţi indici.

121

Severin Bumbaru

Tablourile în limbajul Java
În limbajul Java, tablourile (engl.: Arrays) sunt considerate obiecte care aparţin unor clase descendente din clasa Object. În consecinţă, variabilele care au ca valori tablouri sunt variabile referinţă, iar alocarea tablourilor în memorie se face dinamic, prin operatorul new, la fel ca în cazul celorlalte obiecte. Mai mult, unor variabile referinţă la Object li se pot da ca valori referinţe la tablouri, deoarece clasa Object este superclasa oricărei alte clase, deci şi a oricarui tip de tablou. Tipul tabloului coincide cu tipul componentelor sale. Componentele pot aparţine unor tipuri de date primitive, sau unor clase.

Tablouri cu un singur indice (unidimensionale)
Aceste tablouri corespund conceptului matematic de vector. Nu le vom numi totuşi astfel, pentru a nu face confuzie cu obiectele clasei Vector din pachetul java.util. Tabloul unidimensional este constituit dintr-un ansamblu de componente indexate (cu un singur indice), căruia i se asociază şi o variabila de tip int numita length, care reprezintă lungimea tabloului (numărul de componente). Indicii elementelor de tablou sunt cuprinşi în intervalul [0, length-1]. Utilizarea unui indice situat în afara acestui interval generează o excepţie. Întrucât tablourile sunt obiecte, pentru indicarea lor în program se folosesc variabile referinţă.

Declararea şi iniţializarea tablourilor cu un singur indice
Variabilele referinţă la tablouri cu un singur indice pot fi declarate în două moduri: a/ într-o declaraţie de variabile se pune simbolul [] (o pereche de paranteze drepte) după numele variabilei referinţă la tablou. Ca exemplu, să considerăm declaraţiile următoare:
int a, b, c[], d, e[]; String s1, ts1[], s2; În aceste declaraţii, a, b, şi d sunt variabile simple de tip double, deci ele pot primi valori simple de acest tip, iar s1 şi s2 sunt variabile referinţă la obiecte din clasa String (la şiruri de caractere). În schimb, c[] şi e[] sunt variabile referinţă la tablouri de tip int (tablouri la care toate componentele sunt de tip int), iar ts1[] este o variabilă referinţă la un tablou cu componente din clasa String.

b/ Parantezele se pun după numele tipului de date sau al clasei, în care caz toate variabilele din declaraţia respectiva sunt considerate drept referinţe la tablouri. De exemplu, în declaraţiile
int[] i, j; long [] k, m; float []u, v; String[] ww; variabilele i, j, k, m, u, v, ww

sunt referinţe la tablouri cu componente de tipuri corespunzătoare fiecarei declaraţii. Remarcăm că nu are importanţă dacă parantezele sunt puse imediat după numele tipului sau clasei (fară spaţiu liber) sau între acestea există unul sau mai multe spaţii. Iniţializarea tablourilor unidimensionale se poate face, de asemenea, în două moduri: a/ indicând valorile componentelor tabloului, separate prin virgule şi cuprinse între acolade, ca în exemplele următoare: 122

Programarea orientata pe obiecte în limbajul Java
int a=27, b=-15, c[]={-3,72,-21},d=-5,e[]={231,-98}; String s1="un sir", ts1[]={"sirul 0", "sirul 1", "sirul 2"}, s2="alt sir"; float[] u={-1.24076f, 0.03254f, 27.16f}, v={2.7698E-12f, -3.876e7f};

Remarcăm că în ultima declaraţie nu s-au mai pus paranteze după numele variabilelor, deoarece ele apar după numele tipului. b/ folosind operatorul new, urmat de numele tipului sau al clasei, însoţit de dimensiunea tabloului (numărul de elemente din tablou) scrisă între paranteze drepte, ca în exemplele următoare:
double aa[]=new double[3]; String str[]=new String[2];

În primul caz, se alocă în memorie spaţiu pentru un tablou cu 3 componente de tip double, iar variabilei aa i se dă ca valoare referinţa la acest tablou. În al doilea caz, se alocă în memorie un tablou de variabile referinţă la obiecte din clasa String, iar variabilei str i se dă ca valoare referinţa la acest tablou. Iniţializarea unei variabile referinţă la tablou cu componente aparţinând unei anumite clase se poate face atât cu tablouri din clasa respectivă, cât şi din clase descendente ale acesteia. De exemplu, în declaraţia
Object tab1[]=new Object[2], tab2[]=new String[3], tab3[]={"aaa","bbb","ccc"}; variabila tab1 este initializata cu o referinţă la un tablou de componente din clasa Object, în timp ce variabilele tab2 şi tab3 sunt iniţializate cu referinţe la tablouri de şiruri, clasa String(ca orice alta clasă) fiind descendentă a clasei Object.

În programul din fişierul InitTab1.java se testează declaraţiile şi iniţializările de mai sus şi altele similare şi se afişează valorile componentelor tablourilor iniţializate. Se observă că, în cazul folosirii operatorului new pentru alocarea de tablouri, componentele acestora se iniţializează la valorile lor implicite: 0 pentru date numerice şi null pentru obiecte.

Atribuirea de valori variabilelor referinţă la tablou
Unei variabile referinţă la tablou i se poate atribui ca valoare o referinţă la un tablou de acelasi tip, sau dintr-o clasă descendentă a acestuia. Această referinţă poate fi obţinută prin operatorul new (în care caz se alocă în memorie un nou tablou), fie printr-o expresie care are ca valoare o referinţă la un tablou de tip corespunzator deja existent în memorie. În programul din fişierul Tab1.java se dau astfel de exemple.

123

Severin Bumbaru

Utilizarea tablourilor
Componentele tablourilor pot fi utilizate ca orice variabile simple. Referinţa la o componentă de tablou se face prin numele tabloului, insoţit de indicele componentei pus între paranteze drepte. De exemplu, u[3] este componenta de indice 3 a tabloului u, iar aa[i] este componenta de indice i a tabloului aa. Indicele poate fi orice expresie de tip întreg, cu condiţia ca valoarea acesteia să nu iasa din cadrul domeniului de indici admis pentru tabloul respectiv. Un exemplu de utilizare a variabilelor indexate s-a dat deja în programul din fişierul InitTab1.java , când au fost afişate valorile componentelor tablourilor. Alte exemple se dau în programul din fişierul Tab1.java. Este instructiv să urmărim în figurile următoare cum evoluează datele din memorie în timpul executării acestui program.

- Fig. 1 În figura 1 sunt reprezentate datele din memorie după executarea instrucţiunilor
double a[]={3.276, -5.83, 12.8}, b[]; String ts1[]={"aa","bb"}, ts2[]; b=a; ts2=ts1;

Prin prima declaraţie s-au creat în memorie variabilele referinţă la tablouri de tip double a[] şi b[], s-a creat, de asemenea, un tablou cu trei componente de tip double şi s-a dat ca valoare variabilei a referinţa la acest tablou. Prin a doua declaratie, s-au creat variabilele referinţă la tablouri de tip String ts1[] si ts2[] şi un tablou de tip String cu două componente, iar variabilei ts1 i s-a dat ca valoare o referinţă la acest tablou. Prin instrucţiunea b=a i s-a atribuit variabilei b[] aceeaşi valoare-referinţă ca cea din a[], iar prin ultima instrucţiune s-a atribuit variabilei ts2[] acceaşi valoare-referinţă ca a lui ts1[]. Remarcăm deosebirea importantă dintre tabloul de tip double şi cel de tip String. Primul dintre ele are drept componente date de tip primitiv. În consecinţă, "celulele" tabloului conţin chiar valorile de tip double ale componentelor corespunzătoare. În schimb, cel de al doilea este un tablou de obiecte din clasa String, deci componentele lui sunt, de fapt, variabile referinţă la obiecte String, iar aceste obiecte sunt reprezentate în memorie separat. În ambele cazuri, componentele tabloului sunt tratate ca nişte variabile al căror tip este corespunzător declaraţiei. Ştim însă că variabilele de tipuri primitive au ca valori chiar date primitive, în timp ce pentru obiecte se folosesc variabile referinţă. În figura 2 este reprezentată situaţia creeată după ce s-au executat instrucţiunile de atribuire

124

Programarea orientata pe obiecte în limbajul Java
b[0]=-12.7; b[1]=283.6;

- Fig. 2 Întrucât variabilele referinţă a[] şi b[] indică acelaşi tablou, este normal ca valorile componentelor a[i] sunt şi acum aceleaşi cu ale componentelor b[i], ceeace se constată şi din afişarea prin program a datelor respective. În figura 3 este reprezentată situaţia creată după executarea instrucţiunii b=new double[4].

- Fig. 3 Prin operatorul new s-a alocat în memorie un nou tablou cu 4 componente double, iar lui b[] i s-a dat ca valoare referinţa la acest tablou. Imediat după iniţializare componentele noului tablou au valoarea zero, deoarece aceasta este valoarea implicită pentru tipurile de date numerice. În schimb, valoarea variabilei referinţă a[] a ramas aceeaşi, pe care a avut-o anterior. Acestor componente putem sa le dăm acum valori prin program. În acelaşi program se testează şi situaţia în care se încearcă accesul la tabloul b[] cu un indice care iese din domeniul admis [0 ... b.lenght-1]. Se constată că se produce excepţia java.lang.ArrayIndexOutOfBoundsException.

Conversii de tip pentru referinţe la tablouri
Tablourile cu componente aparţinând unor tipuri de date primitive sunt considerate că sunt obiecte ale unor clase cu numele
tip[]

De exemplu, un tablou cu componente double (deci care a fost declarat ca double[]), aparţine clasei double[], care este descendentă a clasei Object (nu a clasei Object[], care conţine tablourile de obiecte). În mod asemanător, un tablou cu componente dintr-o anumită Clasa este considerat ca aparţinând clasei Clasa[] care, de asemenea, este

125

Severin Bumbaru descendenta a clasei Object. De exemplu, daca s-au făcut declaraţiile
int a[]={54, 23, -17}, b[]; String str1[]={"abc","def"}, str2[], str3[]; Object ob1, ob3, tob1[];

sunt permise fara conversie explicită atribuiri de forma:
tob1=str1; ob1=a; ob3=str1; În primul caz, tob1 este o referinţă la un tablou cu componente din clasa Object, iar str1 este referinţă la un tablou cu componente din clasa String, care este descendenta a clasei Object. În următoarele două cazuri, ob1 si ob3 sunt referinţe la Object, iar tablourile a[] şi str1[] aparţin claselor int[] şi, respectiv, String[], care sunt şi ele descendente ale clasei Object.

În schimb, atribuirile următoare necesită conversie explicită (prin cast), deoarece se fac de la superclasă la clasă:
str2=(String[])tob1; b=(int[])ob1; str3=(String[])ob3;

Pentru a face referinţă la componente din tablourile referite de variabilele ob1 sau ob3 este necesară, de asemenea, conversie explicită, deoarece ob1 siob3 nu au fost declarate ca tablouri. Se va scrie deci: ((int[])ob1).length, ((int[])ob1)[k], ((String[])ob3).length, ((String[])ob3)[j]. Nu trebuie, insa, facuta conversie explicită în cazul componentelor tabloului referit prin tob1[], deoarece vsrisbila tob1 a fost declarată ca referinţă la tablou, iar clasa Object este superclasă a clasei String. Este, deci permisă referinţa tob1[k]. Exemplele de mai sus, si altele, sunt testate în programul din fişierul ConvTip1.java. În acelaşi program, se testează şi numele claselor-tablouri întoarse de metoda getName() a clasei Class. Explicarea codificărilor respective este dată în documentaţia java API, la descrierea acestei metode.

Tablouri cu doi sau mai mulţi indici (multidimensionale)
Tabloul cu N indici este considerat în limbajul Java drept un tablou de referinţe la tablouri cu N-1 indici. Vom exemplifica aceasta pentru cazul tabloului cu doi indici (bidimensional) şi vom extinde apoi conceptul la tablouri cu mai mult de două dimensiuni.

Tablouri cu doi indici
Tablourile bidimensionale din limbajul Java sunt o generalizare a conceptului de matrice, În matematică, toate liniile unei matrice au acelaşi număr de componente, în timp ce în Java

126

Programarea orientata pe obiecte în limbajul Java numărul de componente poate fi diferit de la o linie la alta (deci liniile "matricei" pot avea lungimi diferite). Tabloul cu doi indici este privit ca un tablou ale cărui componente sunt referinţe la tablouri cu câte un singur indice. Putem, deci, să ne imaginăm un "tablou coloană" care conţine în fiecare componentă o referinţă către un "tablou linie". Fiecare din aceste "tablouri linie" are propria sa variabilă length şi deci propria sa lungime. În schimb, variabila length a "tabloului coloană" reprezintă lungimea acestui tablou, adică numărul de linii. "Tipul tabloului" este, de fapt, tipul componentelor "liniilor" acestuia.

127

Severin Bumbaru

Declararea şi iniţializarea tablourilor cu doi indici
Tabloul bidimensional poate fi declarat în mod asemănător celui unidimensional, dar punând după numele variabilei două perechi de paranteze drepte. Rămâne valabilă şi regula că, dacă perechea de paranteze drepte se pune după numele tipului sau al clasei, ea se aplică tuturor variabilelor din declaraţia respectivă. Fie, de exemplu, următoarele declaraţii:
double u, v[], w[][]; int[] a, b[]; char[][] h1, h2; String ts[][];

Conform acestor declaraţii: - u este o variabilă simplă de tip double; - v este un tablou unidimensional, iar w un tablou bidimensional, ambele cu componente de tip double; - a este un tablou unidimensional, iar b un tablou bidimensional, ambele cu componente de tip int; - h1 şi h2 sunt tablouri bidimensionale cu componente de tip char; - ts este un tablou bidimensional, având drept componente referinţe la String. Iniţializarea tablourilor bidimensionale se face, de asemenea, asemănător cu a celor unidimensionale, având însă şi unele trăsături specifice. Iată cum putem introduce în declaraţiile de mai sus iniţializări folosind forma cu acolade sau operatorul new:
double u=5.3127, v[]={1,25, -3.12, 2,6}, w[][]={{2.31,-4.15},{0.35,-12.6,-573.2},{10.9}}; int[] a={3,2,7}, b[]={{43,28,92,-6},{},{-1,17,29}}; char[][] h1={{'a','*'},{'+','$'}}, h2=new char[2][3], h3=new char[4][]; String ts[][]={{"abc","defg"},{"AB","CD","EF","GH"}};

Remarcam ca, la forma de iniţializare cu acolade, tabloul bidimensional este tratat ca un tablou (acoladele exterioare) care are drept componente alte tablouri (acoladele interioare). Acestea din urmă reprezintă "liniile" tabloului bidimensional şi pot avea lungimi diferite, unele din ele putând avea chiar lungimea zero (în acest caz acoladele respective nu conţin nimic), ca în cazul liniei a doua a tabloului b. La folosirea operatorului new există urmatoarele posibilităţi de iniţializare: a/ se indică atât numărul de linii, cât şi numărul de coloane, de ex. new int[7][4]; în acest caz, se iniţializează o matrice, în care toate liniile au acelaşi număr de componente. În exemplul nostru, matricea are 7 linii şi 4 coloane, toate cele 7x4=28 componente fiind iniţializate cu valorile implicite ale tipului respectiv (în cazul de faţă zero); b/ se indică numai numărul de linii, de exemplu: new int[7][]. În acest caz se iniţializeaza, de fapt, numai "vectorul coloană" care contine referinţe (deocamdata nule) către linii care, deocamdată, nu există, urmând sa fie atribuite ulterior. În figura 1 este reprezentată matricea w dupa iniţializare.

128

Programarea orientata pe obiecte în limbajul Java

- Figura 1 Se observă că variabila w conţine numai o referinţă către un tablou cu trei componente care, la rândul lor, conţin referinţe către trei tablouri care conţin cele trei linii ale tabloului bidimensional indicat de variabila w. In aceasta situaţie, cele trei "linii" ale tabloului bidimensional pot să fie situate în locuri diferite din memorie, fără a mai forma o zonă compactă. În schimb, fiecare "linie" este, în acest caz, un tablou compact, ale cărui componente sunt valori primitive de tip double. Să considerăm acum tabloul
String ts[][]={{"abc","defg"},{"AB","CD","EF","GH"}};

Acest tablou este reprezentat in figura 2.

- Figura 2 Componentele tabloului sunt aici, de fapt, referinţe la obiecte din clasa String. La nivel conceptual însă, noi privim acest tablou ca şi când ar avea drept componente însăşi aceste obiecte. Pentru a le distinge mai uşor, în figura 2 obiectele din clasa String au fost trasate cu culoare albastră. Iniţializările de mai sus sunt testate în programul din fişierul Tab2.java.

Operaţii cu tablouri bidimensionale

Numărul de componente
Ştim că fiecărui tablou unidimensional îi este asociată o variabilă length, care are ca valoare "lungimea" tabloului, adică numărul de componente ale acestuia. Ce reprezintă variabila length în cazul unui tablou bidimensional? Sa privim din nou figura 1. Întrucat w.length este numărul de componente din tabloul unidimensional referit de variabila w, înseamnă că el este egal cu numărul de linii al tabloului bidimensional. În schimb, numărul de componente

129

Severin Bumbaru din linia referită de componenta w[i] a tabloului w este dat de variabila w[i].length. De exemplu, numărul de componente din linia de indice 0 este w[0].length. În programul din fisierul Tab2.java se determină astfel numărul de linii şi numărul de componente din fiecare linie pentru fiecare din tablourile bidimensionale iniţializate în programul respectiv.

Referirea la componentele tabloului
Pentru a indica o anumită componentă a unui tablou bidimensional, se foloseşte variabila referinţă la tablou, însoţită de indicii componentei respective. De exemplu, w[i][j] este componenta situata în linia i şi coloana j a tabloului referit de variabila w. Indicii pot fi literali întregi, variabile sau expresii de tip întreg, de exemplu w[2*k-1][j-3]. Referirile la componentele de tablou au fost folosite, de exemplu, în programul din fişierul Tab2.java la afişarea tablourilor. În acelaşi program se poate urmări şi cum sunt afişate componentele tablourilor, în situaţia când liniile acestor tablouri au lungimi diferite. Componentele de tablou astfel referite pot fi folosite în orice expresii din program, la fel ca variabilele simple. Iată doua exemple de instrucţiuni, în care se folosesc componentele tabloului bidimensional w:
t=2*w[1][0]*Math.cos(3.5*w[1][1]-0.23); w[j][k+1]=2*w[j-1][k]-w[j][k-1];

Bineînţeles, trebuie avut grijă ca indicii să nu iasă din domeniile admise pentru fiecare din ei deoarece, în caz contrar, vor apare excepţii la executarea programului.

Referirea la tablou şi la liniile tabloului
Variabila referinţă la tablou, neînsoţită de indici, este o referire la întregul tablou. Dacă folosim însa variabila referinţă la un tablou bidimensional însoţită de un singur indice, aceasta este o referinţă la o linie a tabloului respectiv. De exemplu, în cazul tabloului bidimensional w, w[i] este o referinţă la linia i a tabloului indicat de variabila w. Variabilele w şi w[i] pot fi folosite şi ele în diferite expresii. Astfel s-a procedat mai sus, în expresiile w.length si w[i].length. Să urmărim acum un exemplu, în care se ilustrează folosirea acestor referinţe în instrucţiuni de atribuire. Să urmarim programul din fişierul Tab2a.java, făcând şi schemele tablourilor din memorie in diferite etape de execuţie a acestuia. Se fac mai intâi următoarele declaraţii cu iniţializări de tablouri:
int a[][]={{-5,12,52},{8,-4},{},{105}}, b[]={31,-3,17,24}

Rezultatul aceztor initializari este reprezentat in figura 3.

130

Programarea orientata pe obiecte în limbajul Java

- Figura 3Se fac apoi urmatoarele atribuiri:
a[0][2]=2*b[1]-a[1][1]; a[2]=new int[3]; a[2][0]=b[3]; a[2][2]=a[3][0]; a[3]=b; b=a[1]; b[1]=88;

Situatia creată în memorie după această secvenţă de atribuiri este reprezentată in figura 4.

- Figura 4 În aceasta figură, pentru a fi urmărite mai uşor, modificarile au fost făcute cu culoare roşie. A fost creat, prin operatorul new, un nou tablou cu trei elemente, care a devenit noua linie a[2]. S-au atribuit valori pentru a[2][0] si a[2][2], iar componenta a[2][1] a rămas la valoarea implicită 0 cu care a fost iniţializată. Fosta linie a[3], formată numai dintr-o singură componentă cu valoarea 105, a rămas fără referinţă şi va fi eliminată de către colectorul de reziduuri de memorie. Noua linie a[3] este acum fostul tablou b, iar tabloul unidimensional indicat de variabila referinta b este acum acelasi cu linia a[1]. Toate aceste modificari pot fi verificate executând programul din fişierul Tab2a.java .

Conversii de tip la tablouri cu doi indici
Regulile aplicate pentru conversii la tablourile bidimensionale sunt aceleaşi cu cele pentru tablouri unidimensionale. Să urmărim următorul exemplu, care este testat în programul din fişierul Tab2b.java. Fie declaraţiile:
Object ob1, ob2, tob2[][]; int ti1[][]={{0,1},{10,11,12,13},{20,21,22}},ti2[][]; String ts[][]={{"aa","ab"},{"ba","bb","bc"},{"ca","cb"}},ts1[][];

131

Severin Bumbaru

Cu aceste declaraţii, următoarele atribuiri
ob1=ti1; ob2=ts;

sunt corecte, deoarece clasele ti1[][] şi ts[][] sunt descendente ale clasei Object. În schimb, atribuirile
ti2=ob1; ts1=ob2;

nu sunt corecte, deoarece se fac în sens "descendent". Ştiind, totusi, că variabilele ob1 şi ob2 conţin efectiv referinţe către obiecte din clasele int[][] şi respectiv String[][], putem face conversii explicite sub forma:
ti2=(int[][])ob1; ts1=(String[][])ob2;

Numărul de linii al tabloului indicat de variabila referinţă ob1 este ((int[][])ob1).length, iar numarul de componente din linia i a aceluiasi tablou este ((int[][])ob1)[i].length. De asemenea, componenta situată în linia i şi coloana j este((int[][])ob1)[i][j]. Să încercăm acum atribuirea ti2=(int[][])ob2; În acest caz, compilatorul nu poate sesiza eroarea, deoarece în momentul compilării nu are informaţie despre adevarata clasa căreia îi aparţine obiectul indicat de variabila ob2. În schimb, la execuţie se constată că obiectul indicat de ob2 aparţine clasei String[][], în timp ce variabila ti1 apartine clasei int[][], aşa că se va genera excepţia ClassCastException.

Tablouri cu mai mulţi indici
Modul de tratare al tablourilor cu doi indici poate fi extins şi la tablouri cu mai mulţi indici (tablouri multidimensionale). Se are în vedere ca tabloul cu N indici conţine referinţe la tablouri cu N-1 indici. De exemplu un tablou cu trei indici (tridimensional) poate fi considerat că conţine mai multe "pagini", iar fiecare "pagină" este un tablou cu doi indici (bidimensional). În acest caz, primii doi indici specifica linia şi, respeectiv, coloana, iar al treilea indice specifica "pagina" în care se găseşte o anumită componentă. Un tablou cu patru indici poate fi asemănat cu un set de "volume", în care fiecare "volum" este un tablou cu trei indici, iar cel de al patrulea indice specifica numarul "volumului" din acest set. Putem sa ne imaginam astfel si semnificaţii ale unor tablouri cu mai mult de patru indici. Totuşi, în practică, cele mai frecvent folosite tablouri au cel mult trei indici.

Tablouri eterogene
Prin definiţie, componentele unui tablou trebuie să fie toate de acelaşi tip. În programarea orientată pe obiecte, această restricţie a fost "relaxată", în sensul că un tablou poate avea drept componente şi obiecte aparţinând claselor descendente din clasa de bază. Faptul că clasa Object este superclasă a oricărei alte clase din limbajul Java, inclusiv a claselor de tablouri, permite să se creeze tablouri eterogene, adică tablouri cu componente care aparţin unor clase foarte diferite. Acestea pot fi structuri complicate de date, realizate pornind de la structura de "tablou de obiecte". Să consideram, de exemplu, structura de date din figura 5.

132

Programarea orientata pe obiecte în limbajul Java

- Figura 5 În programul din fişierul TabEterogen.javase construieşte această structură de date eterogenă şi se afişează unele dintre componentele ei.

Utilizarea parametrilor din linia de comandă
A venit timpul să ne ocupăm de argumentul args[] al metodei
public static main(String args[]) Remarcăm ca argumentul formal args[] al acestei metode este un tablou unidimensional, ale cărui componente sunt din clasa String, deci sunt şiruri de caractere. La lansarea în

execuţie a aplicaţiei, acest argument preia drept componente de tablou parametrii din linia de comandă prin care s-a făcut lansarea. Aceşti parametri pot fi, astfel, folosiţi în program după necesităţi. Exemplu: să considerăm următorul program din fişierul Parametri.java:

class Parametri { public static void main(String args[]) { if(args.length==0) System.out.println("Nu ati introdus parametri in linia de comanda"); else { System.out.println("Aplicatia are urmatorii parametri:); for(int i=0; i<args.length; i++) System.out.println(args[i]); } } }

Lungimea tabloului args (numărul de componente) este, după cun ştim, args.length. În acest program, dacă numărul de parametri este nul, se afişează mesajul "Nu aţi introdus 133

Severin Bumbaru parametri în linia de comandă". În caz contrar, se afişează toţi parametrii. Să considerăm că lansarea acestui program s-a făcut prin următoarea linie de comandă:
java Parametri abcd EFG-HIjk 1376 7.3086 -15

La executarea programului se afişează:

Aplicatia are urmatorii parametri: abcd EFG-HIjk 1376 7.3086 -15

Constatăm, deci, că unicul separator între parametri este spaţiul liber, iar numerele sunt preluate tot sub forma de şiruri de caractere. Putem rula acum acest program în mod repetat cu diferite seturi de parametri, pentru a urmări cum se comportă. Putem constata de asemenea că, dacă între doi parametri există mai multe spaţii libere succesive, ele sunt tratate ca şi un singur spaţiu.

Întrebări
Nivel 1
1. Ce deosebire este între tipul referinţă şi tipurile primitive? 2. Unde sunt plasate în memorie obiectele? 3. Care sunt operaţiile permise asupra variabilelor-referinţă? 4. Cum se declara variabilele referinţă? 5. Ce este un câmp? 6. Ce deosebire este între câmpurile statice şi cele nestatice? 7. Ce este o metodă? 8. Ce deosebire este între metodele statice şi nestatice? 9. Cum este invocată o metoda statică? 10. Cum este invocată o metoda a instanţei? 11. Ce este un constructor? 12. Cum este invocat un constructor? 13. Ce este moştenirea? 14. Ce este o superclasă? 15. Ce legatură este între o clasă şi clasele derivate din aceasta? 16. Ce este polimorfismul? 17. Ce fel de moştenire este permisă în Java? 18. Care este rădăcina ierarhiei de clase în Java? 19. Ce este un pachet? 20. Ce este Java API? 21. Ce conţine pachetul java.lang?

134

Programarea orientata pe obiecte în limbajul Java 22. La ce serveste declaratia import si ce forma are? 23. Cum este importat pachetul java.lang? 24. Fie a şi b două obiecte. Ce deosebire este între a.equals(b) şi a==b? 25. Ce rol are metoda toString()? 26. Ce este o excepţie? 27. Ce este clasa Exception? 28. Care este clasa şirurilor de caractere? 29. Ce este concatenarea şirurilor şi prin ce operator se realizează? 30. Cum se compară două şiruri? 31. Ce este ordinea lexicografică? 32. Cum se determina lungimea unui şir? 33. La ce foloseşte metoda trim()? 34. Ce fel de obiecte aparţin clasei StringBuffer? 35. Ce deosebire este între lungimea şi capacitatea unui StringBuffer? 36. Care sunt principalele metode ale clasei StringBuffer? 37. Ce este o clasă acoperitoare de tip? 38. Poate fi instanţiată clasa Number? Justificaţi răspunsul. 39. Care sunt subclasele clasei Number? 40. Cum se poate determina cea mai mare valoare pe care poate să o aibă un număr de tip int? 41. Cum se poate converti un numar întreg din forma externă în cea internă? 42. Ce câmpuri statice conţine clasa Double? 43. Cum se poate converti un număr în virgulă mobilă din forma externă în cea internă? 44. În ce cod sunt reprezentate caracterele în Java? 45. Ce este clasa Character? 46. Ce este clasa Math? 47. Cum se poate calcula sinusul trigonometric al unui număr? 48. Cum se poate calcula rădăcina patrată a unui număr? 49. Cum se poate calcula logaritmul natural al unui număr? 50. Scrieţi în limbajul Java expresia prin care se calculeaza rădăcina patrată din 2u2+eu. 51. Ce câmpuri statice conţine clasa System? 52. Ce efect are metoda exit() din clasa System? 53. Ce este un tablou? 54. Cum se declară în Java un tablou unidimensional? 55. Ce semnificaţie are declaraţia int a, b[], c;? ce sunt a, b şi c? 56. Ce semnificatie are declaratia int[] a, b, c;? ce sunt a, b şi c? 57. Cum se iniţializează în Java un tablou unidimensional? 58. Prin ce instrucţiune se crează în Java un tablou cu 7 componente de tip double? 59. Cum se poate determina numărul de elemente dintr-un tablou unidimensional? 60. Cum sunt preluaţi parametrii din linia de comandă? 61. Cum pot fi determinate valorile numerice ale parametrilor din linia de comandă?

Nivel 2
1. Ce se găseşte în zona de memorie afectată unei variabile care aparţine unui tip primitiv? 2. Ce se găseşte în zona de memorie afectată unei variabile referinţă?

135

Severin Bumbaru 3. Ce deosebire există între o valoare primitivă şi un obiect? 4. Fie declaraţia Integer i1, i2; Ce sunt i1 şi i2? 5. Fie declaratia int i1, i2; Ce sunt i1 şi i2? 6. Care este efectul lateral al invocării unui constructor? 7. Care este valoarea intoarsă de aplicarea operatorului new? 8. Ce este o declaratie de import la cerere şi cum acţionează? 9. Ce este codul de dispersie şi prin ce metodă se obţine? 10. Ce rol are metoda clone()? 11. Ce deosebire este în Java între excepţie şi eroare? 12. Cum se determină poziţia unui caracter într-un şir? 13. Cum se determină poziţia unui subşir într-un şir? 14. Prin ce metode se poate face adăugarea de valori la un StringBuffer? 15. Prin ce metode se poate face înserarea de valori într-un StringBuffer? 16. Ce sunt instanţele clasei Class? 17. Cum se poate determina numele clasei căreia îi aparţine un obiect? 18. Cum se poate determina în timpul execuţiei programului superclasa clasei unui obiect? 19. Prin ce metoda poate fi convertit din forma externă în cea internă un numar scris întro bază oarecare? 20. Prin ce metoda se poate obţine reprezentarea externă în sistemul binar a unui întreg? 21. Prin ce metodă poate fi obţinută forma externă hexazecimală a unui întreg? 22. Prin ce metodă poate fi obţinută forma externă octală a unui întreg? 23. Cum se poate verifica dacă un numar în virgula mobilă are valoarea infinită? 24. Cum se poate verifica dacă valoarea unei variabile în virgulă mobilă este un număr? 25. Cum se poate verifica dacă un caracter este literă? 26. Cum se poate verifica dacă un caracter este cifră? 27. Cum se poate face conversia literelor dintr-un şir din minuscule în majuscule? 28. Cum se poate face conversia literelor dintr-un şir din majuscule în minuscule? 29. Ce este clasa Void? 30. Scrieţi în limbajul Java expresia: e-|u|sin(2u+3)cos4u+ln(|u|+1). 31. Prin ce metodă se poate determina timpul curent şi în ce unităţi se exprimă acesta? 32. Prin ce metoda se poate schimba dispozitivul de intrare standard? 33. Prin ce metoda se poate schimba dispozitivul de ieşire standard? 34. Cărui concept matematic îi corespunde conceptul de tablou unidimensional? 35. Cărui concept matematic îi corespunde conceptul de tablou bidimensional? 36. Ce legatură există între conceptul de clasă şi cel de obiect? 37. Cărei clase îi aparţine un tablou? 38. Cum se construieşte o matrice cu 7 linii şi 3 coloane cu elemente de tip double? 39. Este obligstoriu ca toate liniile unui tablou bidimensional să aibă aceeaşi lungime? 40. Cum se poate determina lungimea unei linii dintr-un tablou bidimensional? 41. Cum se poate modifica în timpul execuţiei lungimea unei linii dintr-un tablou bidimensional?

136

Programarea orientata pe obiecte în limbajul Java

Declararea claselor
Declararea claselor; Declararea câmpurilor. Câmpuri statice şi câmpuri ale instanţei. Iniţializarea câmpurilor. Valori iniţiale implicite; Declararea metodelor. Metode statice şi metode ale instanţei; Metode cu acelaşi nume; signatura metodei; Transferul parametrilor către metode la invocarea metodelor; Metode care întorc o referinţă la un obiect construit în corpul lor; Metode care au ca argumente şi/sau ca valori întoarse referinţe la tablouri; Metode recursive; comparaţie între iteraţie şi recursie; Metode care generează excepţii; instrucţiunea throw şi clauza throws; Clase publice Un exemplu: clasa complex Declararea clasei complex Utilizarea clasei complex Distrugerea obiectelor de către colectorul de reziduuri; Metoda finalize Întrebări.

Declararea claselor
Până în prezent, s-a arătat modul în care putem utiliza în programul nostru clase existente în biblioteci (în pachetele de clase). Vom studia în continuare cum putem crea propriile noastre clase. Cea mai simplă formă a unei declaraţii de clasă este următoarea:
class NumeClasa { declaratii_de_membri }

Observăm că declaraţia începe cu cuvântul-cheie class, urmat de numele clasei şi de corpul clasei, cuprins între acolade.

137

Severin Bumbaru Numele clasei este un identificator. Se obisnuieşte ca numele clasei sa înceapă cu literă majusculă. Dacă numele este compus din mai multe cuvinte, fiecare din acestea începe cu majusculă. Corpul clasei cuprinde declaraţii de membri ai clasei respective. Acestea pot fi: - declaraţii de câmpuri; - declaraţii de constructori; - declaraţii de metode. Nu este obligatoriu ca într-o clasă să existe toate aceste categorii de declaraţii. Pot exista, de exemplu, clase în care apar numai declaraţii de metode. În principiu, pot exista şi clase care conţin numai câmpuri şi nu conţin metode, deşi astfel de situaţii apar foarte rar în practică.

Declararea câmpurilor
Declaraţiile de câmpuri servesc pentru a descrie structura de date specifică clasei respective. Câmpurile se mai numesc şi variabile membre şi pot fi ale clasei sau ale instanţei (ale obiectului). Se preferă denumirea de câmpuri, pentru a le deosebi de variabilele locale ale metodelor. Câmpurile instanţei se declară la fel ca variabilele locale ale metodelor, numai că declaraţia respectivă nu apare în blocul unei metode, ci în corpul clasei. De exemplu:
int m=172, n=2*m-4, r;

Aceste câmpuri pot avea valori diferite pentru fiecare instanţă a clasei respective. În consecinţă, câmpurile instanţei sunt plasate în zona de memorie rezervată instanţei respective, astfel că ele sunt distincte pentru fiecare instanţă. Câmpurile clasei se numesc şi câmpuri statice. Declararea unor astfel de câmpuri se face asemănător cu cea a câmpurilor de instanţă, dar declaraţia are în faţă, în acest caz, modificatorul static.De exemplu:
static double u=3.65, v=2.87*u-3.1, x;

La iniţializarea câmpurilor de instanţă se pot folosi atât valori ale câmpurilor statice, cât şi ale altor câmpuri de instanţă. În schimb, la iniţializarea câmpurilor statice se pot folosi numai valori ale altor câmpuri statice.

Câmpurile statice (ale clasei) sunt plasate în memorie în zona rezervată clasei căreia îi aparţin şi nu în cea rezervata instanţelor. În consecinţă, câmpurile clasei există în memorie într-un singur exemplar, care este accesibil fiecărei instanţe. Valorile iniţiale implicite ale câmpurilor: dacă nu sunt iniţializate explicit, câmpurile statice şi cele nestatice primesc valori implicite astfel: - câmpurile booleene primesc valoarea false; - câmpurile numerice primesc valoarea 0 (chiar şi cele de tip char, care este tot tip numeric!); - câmpurile referinţa primesc valoarea null. Remarcăm, deci, că există o deosebire între crearea câmpurilor (variabilelor membre) şi crearea variabilelor locale. La crearea câmpurilor, acestora li se atribuie implicit o valoare iniţială, în timp ce la crearea variabilelor locale acestora trebuie sa li se atribuie valori în mod

138

Programarea orientata pe obiecte în limbajul Java explicit. Dacă o variabilă locală apare într-o expresie fără să aibă o valoare atribuită anterior, compilatorul Java semnaleaza această situaţie ca o eroare de programare.

Exemplu de clasă care nu conţine constructori expliciţi sau metode:

Acest exemplu este dat în scop didactic, pentru a ilustra declararea, iniţializarea şi utilizarea câmpurilor. Crearea unei clase fără metode nu corespunde principiului de baza al programării orientate pe obiecte, conform căruia o clasă conţine atât date, cât şi metodele prin care acestea sunt tratate. În consecinţă, declararea unor clase fără metode se evită în practica programării orientate pe obiecte, chiar dacă o astfel de declaraţie este permisă. De altfel, chiar daca declaraţia de clasă nu conţine constructori sau metode, ea are un constructor implicit (fără parametri) şi moşteneşte metodele superclasei, în particular metodele clasei Object. În exemplul din fişierul TestClasa1.java se declară şi se utilizează o clasă care nu conţine constructori si metode, ci numai câmpuri de date. O astfel de clasă este folosită, deci, ca o structură din limbajele neorientate pe obiecte (cum este struct în C sau record în Pascal). În acest fişier sunt declarate două clase: clasa Proba1, care este o simplă structură de date fără metode proprii, şi clasa TestClasa1, care conţine metoda main si serveşte ca aplicaţie în care se testează clasa Proba1. Clasa Proba1 este declarată astfel:

/* Declararea clasei Proba1 Clasa contine numai campuri de date */ class Proba1 { static int m=9, n=m-3; int a=7, b=m*a+1, c, d; char c1='@', c2; String s1="un sir", s2=s1+" extins", s3; }

În această clasă, s-au definit câmpurile statice (ale clasei) m şi n şi câmpurile nestatice (de instanţă) a, b, c şi d - toate de tip int. S-au declarat, de asemenea, câmpurile c1 şi c2 de tip char şi câmpurile s1, s2 şi s3 care contin referinţe la instanţe ale clasei String. Unele din aceste câmpuri sunt iniţializate, altele au valori iniţiale implicite. La iniţializarea câmpului de instanţă b s-a folosit şi valoarea câmpului static a. Câmpurile c şi d sunt iniţializate implicit cu valoarea 0.

Amintim o deosebire importantă între modul cum sunt plasate în memorie valorile primitive şi obiectele (instanţele claselor): valorile primitive sunt plasate chiar în zona de memorie rezervată variabilelor respective, în timp ce instanţele claselor sunt plasate în memoria

139

Severin Bumbaru dinamică. În consecinţă: - valorile câmpurilor primitive statice m şi n sunt plasate în memorie o singură dată, în spaţiul rezervat clasei Proba1; - valorile câmpurilor primitive nestatice a, b, c, d, c1 şi c2 sunt plasate în câmpurile corespunzătoare ale instanţelor clasei Proba1, deci acestea vor avea câte o valoare pentru fiecare instanţă; - în campurile s1, s2 si s3 (care, în cazul nostru, sunt tot nestatice, deci se plaseaza în fiecare instanţă), ca valori se pun numai referinţe la instanţe ale clasei String. În consecinţă, şirurile "un şir" şi "un şir extins" se vor creea în memoria dinamică, sub forma de obiecte ale clasei String, iar în câmpurile s1 şi s2 ale fiecărei instanţe a clasei Proba1 se vor pune numai referinţe la aceste şiruri. Câmpul s3 va fi iniţializat cu referinţa null. Clasa Proba1 poate fi utilizată în alte clase Java la fel ca o structură de date (înregistrare) "tradiţională" cum ar fi struct în limbajul C, sau record în limbajul Pascal. Un exemplu de aplicaţie, în care se utilizează clasa Proba1, este clasa TestClasa1 din fişierul TestClasa1.java, pe care o reproducem în continuare.

/* Programul in care se utilizeaza clasa Proba1 ca o structura obisnuita */ class TestClasa1 { public static void main(String args[]) { /* Se declara doua referinte catre instante ale clasei Proba1, iar prima din ele se si initializeaza */ Proba1 p1=new Proba1(), p2; /* Se mai creaza o instanta si se atribuie lui p2 */ p2=new Proba1(); /* Se afiseaza unele campuri ale instantelor p1 si p2; */ System.out.println("Campuri din p1: n="+p1.n+" m="+p1.m+" a="+ p1.a+" b="+p1.b+" c="+p1.c+" c1="+p1.c1+" c2="+p1.c2+ " (int)p1.c2="+(int)p1.c2); System.out.println("Campuri din p2: n="+p2.n+" m="+p2.m+" a="+ p2.a+" b="+p2.b+" c="+p2.c+"\n s1="+p2.s1+" s2="+p2.s2+ " s3="+p2.s3); /* Afisarea campurilor statice calificandu-le cu numele clasei */ System.out.println("Afisarea campurilor statice: m="+Proba1.m+ " n="+Proba1.n); /* Modificam atribuim p1.a si p2.a valori diferite, apoi reafisam toate campurile */ p1.a=12; p2.a=-9; System.out.println("Dupa modificarea valorilor campurilor a:"); System.out.println("Campuri din p1: n="+p1.n+" m="+p1.m+" a="+ p1.a+" b="+p1.b+" c="+p1.c); System.out.println("Campuri din p2: n="+p2.n+" m="+p2.m+" a="+ p2.a+" b="+p2.b+" c="+p2.c); /* Modificam campul static p1.m si reafisam p1.m, p2.m si Proba1.m */ p1.m=-12; System.out.println("Dupa modificare: p1.m="+p1.m+" p2.m="+p2.m+ " Proba1.m="+Proba1.m);

140

Programarea orientata pe obiecte în limbajul Java
/* Modificam campul static n folosind expresia Proba1.n si afisam */ Proba1.n=-25; System.out.println("Dupa modificare: p1.n="+p1.n+" p2.n="+p2.n+ " Proba1.n="+Proba1.n); /* Atribuim o valoare campului de instanta p1.c */ p1.c=1234; System.out.println("Dupa atribuire: p1.c="+p1.c+" p2.c="+p2.c); } }

La compilarea fişierului sursă TestClasa1.java, vor fi create două fişiere bytecode, câte unul pentru fiecare clasă, numite în mod corespunzător Proba1.class şi TestClasa1.class. Pentru executarea aplicaţiei se va folosi comanda
java TestClasa1

Clasa Proba1 nu poate fi pusă în execuţie în acest mod, deoarece ea nu conţine metoda main. Această clasă poate fi folosită numai în cadrul altei clase. Rezultatele afişate pe ecran la executarea acestei aplicaţii sunt următoarele:

Campuri din p1: n=6 m=9 a=7 b=64 c=0 c1=@ c2= (int)p1.c2=0 Campuri din p2: n=6 m=9 a=7 b=64 c=0 s1=un sir s2=un sir extins s3=null Afisarea campurilor statice: m=9 n=6 Dupa modificarea valorilor campurilor a: Campuri din p1: n=6 m=9 a=12 b=64 c=0 Campuri din p2: n=6 m=9 a=-9 b=64 c=0 Dupa modificare: p1.m=-12 p2.m=-12 Proba1.m=-12 Dupa modificare: p1.n=-25 p2.n=-25 Proba1.n=-25 Dupa atribuire: p1.c=1234 p2.c=0

Urmărind aceste rezultate, putem constata că: - câmpurile numerice neiniţializate explicit (inclusiv cele de tip char) sunt iniţializate implicit la valoarea zero; - valorile câmpurilor statice pot fi utilizate calificând numele câmpului respectiv, atât cu numele unei instanţe (de exemplu, p1.m), cât şi calificandu-le cu numele clasei (de exemplu, Proba1.m); - valorile câmpurilor nestatice pot fi utilizate numai calificând numele câmpului respectiv cu numele unei instanţe (de exemplu, p2.a); - dacă se atribuie o nouă valoare unui câmp nestatic al unei instanţe, valorile câmpurilor cu acelaşi nume ale celorlalte instanţe rămân nemodificate; - dacă se atribuie o nouă valoare unui câmp static, această modificare se constată în toate înstanţele, deoarece câmpul respectiv este unic (există numai în cadrul clasei).

Declararea metodelor
În programarea orientată pe obiecte, clasa conţine, în mod normal, nu numai câmpuri de date, ci şi metodele prin care se tratează aceste câmpuri.

141

Severin Bumbaru Sub aspect conceptual, metoda este o funcţie sau o procedură, care foloseşte drept date atât valorile argumentelor sale, cât şi câmpurile clasei căreia îi aparţine metoda respectivă.

În principiu, deosebirea dintre funcţie şi procedură este că funcţia "întoarce" o valoare, în timp ce metoda nu întoarce o valoare, ci are numai efect lateral. O funcţie fără efect lateral este realizarea în program a conceptului matematic de funcţie şi este folosită în expresiile de calcul numai pentru valoarea ei. În limbajele C/C++ şi Java, procedurile se scriu, din punct de vedere sintactic, la fel ca funcţiile, dar se consideră că ele intorc o valoare specială numită void. Acesta este un artificiu pentru a indica faptul că o asemenea "funcţie" nu întoarce o valoare. În limbajele de programare tradiţionale, o funcţie (sau procedura) foloseşte ca date argumentele sale şi variabilele globale. În limbajul Java nu există variabile globale şi nici funcţii independente (care să nu fie metode ale unor clase). În schimb, fiecare metodă poate folosi ca date atât argumentele saleşi variabilele locale, cât şi câmpurile clasei căreia îi aparţine metoda respectivă. Cea mai simplă formă sintactică a declaraţiei de metodă este următoarea:
tip_valoare_intoarsa nume_metoda(declaratii_de_argumente) { corpul_metodei }

în care:
tip_valoare_intoarsa

- este tipul valorii primitive sau clasa valorii-referinţă întoarsă de

aceasta metodă;
nume_metoda- este un identificator care, în mod uzual, începe cu literă mică şi constituie numele metodei; declaratii_de argumente - este o listă de declaraţii de argument separate prin virgulă, deci ea poate avea forma: declaratie_argument1, declaratie_argument2, ... declaratie_argumentN

dar poate fi şi vidă. Lista declaraţiilor de argument este cuprinsă între paranteze rotunde. Fiecare declaraţie de argument are forma
tip_argument nume_argument

în care tipul sau clasa argumentului respectiv; un identificator care, în mod uzual, începe cu literă mică. După această listă de argumente se deschide acolada unui bloc, care conţine corpul metodei, adică secvenţa de instrucţiuni prin care se calculează valoarea funcţiei respective şi/sau dacă este cazul - se efectuează acţiunile care constituie efectele laterale ale acesteia.
tip_argument - este nume_argument- este

Instrucţiunea return
Dacă funcţia întoarce o valoare (diferită de void), aceasta se indică prin instrucţiunea
return expresie;

Efectul acestei instrucţiuni este următorul: se evalueaza expresia expresie şi se încheie executarea funcţiei respective, întorcând valoarea astfel obţinută. În consecinţă, chiar dacă după instrucţiunea return mai apar în corpul funcţiei respective şi alte instrucţiuni, acestea nu vor mai fi executate. Dacă metoda nu întoarce o valoare (întoarce void), folosirea instrucţiunii return nu este absolut necesară, încheierea execuţiei făcându-se când se ajunge la acolada prin care se

142

Programarea orientata pe obiecte în limbajul Java sfârseşte blocul funcţiei. Totuşi, dacă este necesar să se încheie în mod abrupt executarea corpului funcţiei, se poate folosi instrucţiunea return fără expresie.

Metode statice
La declararea metodelor statice, în faţa tipului valorii întoarse se pune modificatorul static. Metodele care conţin în declaraţie acest modificator se numesc statice sau ale clasei, spre deosebire de metodele obişnuite care sunt ale instanţei. În corpul metodelor statice se pot folosi numai câmpurile statice ale clasei respective şi se pot invoca numai alte metode statice ale acestei clase. Exemplu În fişierul Cercuri.java se declară două clase pentru cercuri: clasa Cerc1, în care aria şi circumferinţa se calculeaza prin metode ale instanţei, şi clasa Cerc2, care conţine metode statice.

/* Declararea clasei Cerc1 cu metode ale instantei */ class Cerc1 { static final double PI=3.141592653589793; double r; double arie() { return PI*r*r; } double circumferinta() { return 2*PI*r; } } /* Declararea clasei Cerc2 cu metode ale clasei (statice) */ class Cerc2 { static final double PI=3.141592653589793; static double arie(double r) { return PI*r*r; } static double circumferinta(double r) { return 2*PI*r; } }

Clasa Cerc1 conţine câmpul static final PI şi câmpul de instanţă r (raza cercului). Aceasta întrucat numărul PI este o constantă valabilă pentru orice cerc, în timp ce raza cercului diferă de la o instanţă la alta. Cele doua metode de instanţă, arie() şi circumferinta(), nu au argumente, dar folosesc ca date câmpurile statice şi de instanţă declarate în clasa respectivă.

143

Severin Bumbaru Invocarea acestor metode se face calificându-le cu numele instanţei. Clasa Cerc2 conţine, de asemenea, câmpul static final PI, dar nu conţine câmpul de instanţă r. În schimb, metodele sale sunt statice şi primesc raza cercului ca argument. Invocarea acestor metode se face calificându-le cu numele clasei. În fişierul Cercuri.java există şi clasa Cercuri, în care se folosesc clasele Cerc1 şi Cerc2 declarate anterior. Iată această aplicaţie:

/* Aplicatia in care se utilizeaza cele doua clase de cercuri declarate mai sus */ class Cercuri { public static void main(String args[]) { double r1=1, r2=7.32; Cerc1 c1=new Cerc1(), c2=new Cerc1(); c1.r=r1; c2.r=r2; System.out.println("Folosind metodele de instanta din clasa Cerc1:"); System.out.println("Pentru cercul c1: aria="+c1.arie()+ " circumferinta="+c1.circumferinta()); System.out.println("Pentru cercul c2: aria="+c2.arie()+ " circumferinta="+c2.circumferinta()); System.out.println("Folosind metodele statice din clasa Cerc2:"); System.out.println("Pentru raza r1: aria="+Cerc2.arie(r1)+ " circumferinta="+Cerc2.circumferinta(r1)); System.out.println("Pentru raza r2: aria="+Cerc2.arie(r2)+ " circumferinta="+Cerc2.circumferinta(r2)); } }

Remarcăm că: - instanţierea clasei Cerc1 s-a făcut folosind constructorul implicit Cerc1(); vom explica aceasta la capitolul "declararea constructorilor"; - atribuirea de valoare razei r a instanţelor din clasa Cerc1 s-a făcut calificând numele câmpului cu numele referinţei la instanţă, de exemplu în instrucţiunea de atribuire c1.r=r1; - invocarea metodelor de instanţă s-a făcut calificând numele metodei cu referinţa la instanţa respectivă: de exemplu c1.arie() calculează aria cercului cu referinţa c1 si, deci, va folosi implicit raza c1.r1; - invocarea metodelor statice ale clasei Cerc2 s-a făcut calificându-le cu numele clasei şi transmiţându-le valoarea razei cercului ca argument, de exemplu Cerc2.arie(r1); - la compilarea fişierului sursă Cercuri.java se obţin trei fişiere de bytecode: Cerc1.class, Cerc2.class si Cercuri.class, corespunzătoare celor trei clase declarate.

Metode cu acelaşi nume. Signatura metodei
În aceeaşi clasă pot exista mai multe metode cu acelaşi nume, cu condiţia ca ele să difere prin numărul şi/sau tipul argumentelor. Pentru a deosebi între ele astfel de metode, s-a introdus conceptul de signatură.

144

Programarea orientata pe obiecte în limbajul Java Signatura metodei constă din numele acesteia, însoţit de lista de argumente. În consecinţă, două metode pot avea acelaşi nume, dacă diferă între ele prin signatură. Putem da exemple din clasele existente în pachetele deja studiate.

Să considerăm, de exemplu, următoarele metode ale clasei String:
int int int int indexOf(int ch) indexOf(int ch, int fromIndex) indexOf(String str) indexOf(String str, int fromIndex)

Se observă imediat că prima şi a doua diferă prin numărul argumentelor, prima şi a treia diferă prin tipul unicului argument, iar a doua şi a patra diferă prin tipul unuia din cele două argumente.

Transferul de parametri către metode
La invocarea unei metode, este necesar să se transmită de la metoda care invocă la metoda invocată parametrii (argumentele) acesteia. De exemplu, la executarea invocării Math.sin(a), este necesar să se transmită către metoda sin argumentul acesteia, a. În teoria şi practica programării se cunosc diferite moduri în car se poate face transmiterea argumentelor către o funcţie sau procedură: - transmitere prin valoare: de la programul apelant către funcţie (procedură) se transmit valorile argumentelor; - transmitere prin adresa: de la programul apelant către funcţie (procedură) se transmit adresele la care se găsesc în memorie valorile argumentelor; - transmitere prin nume: de la programul apelant către funcţie (procedură) se transmit numele argumentelor; - transmitere prin referinţă: de la programul apelant la funcţie (procedură) se transmit referinţe către argumente. În limbajul Java, transmiterea parametrilor (argumentelor) metodelor se face prin valoare. Aceasta înseamnă că: - dacă argumentul aparţine unui tip de date primitiv, se transmite chiar valoarea primitivă a argumentului respectiv; - dacă argumentul aparţine unui tip-referinţă (este instanţă a unei clase), se transmite - de fapt - o referinţă către un obiect din clasa respectiva sau dintr-o clasa derivată din aceasta.

Exemplu Să considerăm metoda int indexOf(String str, int fromIndex) din clasa String. Primul argument al acestei metode este o referinţă la un obiect din clasa String, iar al doilea argument este o valoare primitivă de tip int. În limbajul Java, cei doi parametri str si fromIndex sunt consideraţi variabile locale ale metodei indexOf, iar transmiterea parametrilor este echivalentă cu operaţia de atribuire. În consecinţă, dacă se invocă această metodă sub forma indexOf("abc", 3), variabilei-referinţă str i se atribuie ca valoare referinţa la sirul "abc" (şi nu insuşi şirul "abc", care rămâne la locul lui în memoria

145

Severin Bumbaru dinamică), în timp ce variabilei fromIndex de tip int i se atribuie valoarea primitivă 3. Să considerăm acum că într-un program există instrucţiunile:
int k=7, j; String s1="un exemplu de sir", s2="exem"; j=s1.indexOf(s2,k); Evaluarea metodei indexOf va decurge astfel: se transmit de la programul apelant către metoda indexOf valorile argumentelor s2 si k. Numai că s2 este o variabila-referinţă, deci valoarea ei este referinţa la şirul "exem" şi nu însuşi acest şir. În schimb, valoarea variabilei k este chiar valoarea primitivă 7. În consecinţă, în şirul indicat de s1, se va căuta subşirul a cărui referinţă este s2, începand căutarea de la poziţia de indice 7.

În teoria programării, argumentele (parametrii) care apar în declaraţia unei metode (funcţii, proceduri) se numesc parametri formali, iar valorile prin care se substituie aceşti parametri formali la invocarea metodei respective se numesc parametri efectivi. Astfel, în exemplul de mai sus, str şi fromIndex sunt parametri formali ai metodei indexOf, in timp ce "abc", 3, valoarea lui s2, şi valoarea lui k sunt parametri efectivi. Consecinţa faptului că parametrii metodelor se transmit prin valoare este următoarea: chiar dacă în corpul unei metode se atribuie unui argument formal al acesteia o nouă valoare, această atribuire nu modifică valoarea argumentului efectiv corespunzător din programul apelant. Daca însă, în cazul unui argument-referinţă, nu se modifica referinţa însăşi, ci valorile câmpurilor obiectului indicat de către aceasta, modificarea respectivă se va transmite şi la programul apelant, constituind efectul lateral al metodei respective.

Exemplu În fişierul TestParam.java este dat următorul program, în care se testează un caz de metodă care îşi modifică parametrii:
/* Testarea modului de transmitere a parametrilor catre metode */ /* O clasa oarecare, continand un camp a */ class Proba2 { int a; } class TestParam { /* O metoda in care se modifica valorile propriilor parametri formali */ static void modParam(int k, Proba2 p1, Proba2 p2) { System.out.println("La intrarea in modParam k="+k+" p1.a="+p1.a+ " p2.a="+p2.a); k=-111; // S-a modificat valoarea parametrului k de tip int p1.a=-222; // S-a modificat valoarea unui camp al obiectului cu // referinta p1, dar nu insasi referinta p1 p2=new Proba2(); // S-a modificat insasi referinta p2, catre // o noua instanta a clasei Proba2 p2.a=-333; // S-a atribuit valoare campului a al noii // instante referite prin p2 System.out.println("In modParam dupa modificarile de parametri:\n"+ "k="+k+" p1.a="+p1.a+" p2.a="+p2.a); }

146

Programarea orientata pe obiecte în limbajul Java

/* Metoda principala */ public static void main(String args[]) { // Se declara si se initializeaza variabilele int m=123; Proba2 pr1=new Proba2(), pr2=new Proba2(); pr1.a=333; pr2.a=444; System.out.println("In main inainte de a invoca modParam:\n"+ "m="+m+" pr1.a="+pr1.a+" pr2.a="+pr2.a); // Se invoca metoda modParam modParam(m, pr1, pr2); // Se afiseaza valorile parametrilor dupa revenirea din modParam System.out.println("In main dupa ce s-a invocat modParam:\n"+ "m="+m+" pr1.a="+pr1.a+" pr2.a="+pr2.a); } }

Executând acest program, obţinem afişate pe ecran următoarele rezultate:

In main inainte de a invoca modParam: m=123 pr1.a=333 pr2.a=444 La intrarea in modparam k=123 p1.a=333 p2.a=444 In modParam dupa modificarile de parametri k=-111 p1.a=-222 p2.a=-333 In main dupa ce s-a invocat modParam: m=123 pr1.a=-222 pr2.a=444

Remarcăm că, deşi în metoda modParam s-au modificat valorile lui k, p1.a si p2.a, la revenirea din modParam în main valorile lui m şi pr2.a (care corespund respectiv lui k şi p2.a din modParam) au rămas cele anterioare invocării acestei metode, în timp ce pr1.a (corespunzătoare lui p1.a) s-a modificat. Iată cum se explică cele constatate: 1/ Parametrul formal k este de tipul primitiv int. În consecinţă, la invocarea metodei modParam, parametrul efectiv corespunzator se transmite prin valoare, adică se poate considera că variabilei locale k din modParam i s-a atribuit valoarea lui m, respectiv 123. În schimb, valoarea variabilei m din main a rămas nemodificată. 2/ Parametrul formal p1 este de tip referinţă la o instanţă a clasei Proba2. La invocarea metodei modParam, acestui parametru i s-a atribuit valoarea variabilei referinţa pr1 din main, adică o referinţă către obiectul din clasa Proba2 care a fost creat în main şi are câmpul a=333. În modParam se modifică valoarea câmpului a din acest obiect. Este deci normal să constatăm că, la revenirea din modParam, valoarea câmpului pr1.a s-a modificat, deoarece pr1.a==pr2.a. 3/ Parametrul formal p2, la fel ca p1, este o referinţă la o instanţă a clasei Proba2. La intrarea în modParam, lui p2 i se atribuie valoarea parametrului efectiv pr1 din main, adică o referinţă la obiectul în care campul pr1.a are valoarea 444. În metoda modParam se modifică valoarea parametrului p2, adică se creeaza o nouă instanţă a clasei Proba2 şi se atibuie lui p2 ca valoare o referinţă la aceasta nouă instanţă. Când se face apoi atribuirea p2.a=-333, se modifică valoarea câmpului a din această nouă instanţă, fără a se modifica valoarea câmpului pr2.a al obiectului referit iniţial. În consecinţă, la revenirea în main constatăm că pr2.a a rămas la valoarea anterioară invocarii metodei modParam.

147

Severin Bumbaru

Metode care întorc o referinţă la un obiect construit în corpul lor
În limbajul Java este posibil ca o metodă să întoarcă o referinţă la un obiect construit în interiorul ei. Un astfel de exemplu este dat în fişierul TestRef.java, care conţine următorul program:
/* Testarea unei metode care intoarce un sir */ class TestRef { static String metoda1() { String str=new String("un sir"); System.out.println("In metoda1 str="+str); return str; } static void metoda2(String s) { String s1=new String("alt sir"); s=s1; System.out.println("In metoda2 s="+s); } public static void main(String args[]) { String sir1=null, sir2=null; sir1=metoda1(); metoda2(sir2); System.out.println("In main sir1="+sir1+"\nsir2="+sir2); } }

În metoda1 se creează prin operatorul new un nou şir, iar referinţa str la acesta este "întoarsă" de această metodă la executarea instrucţiunii return. În metoda2 se creează de asemenea un nou şir, dar referinţa la acesta se atribuie argumentului s al metodei. La executarea acestui program se obţin următoarele rezultate:

In metoda1 str=un sir In metoda2 s=alt sir In main sir1=un sir sir2=null

Se observă că metoda1 a întors corect referinţa către şirul "un sir" care a fost creat în interiorul ei. În schimb, metoda2 nu a avut ca efect lateral transmiterea catre variabilareferinţă sir2 din main a referinţei către obiectul "alt sir" creat în această metodă deoarece, după cum s-a arătat anterior, transmiterea parametrilor se face prin valoare şi deci modificarea în corpul metodei a valorii parametrului formal s nu afectează valoarea parametrului efectiv str2 prin care aces ta a fost substituit la invocarea metodei respective.

148

Programarea orientata pe obiecte în limbajul Java

Metode care au ca argumente şi/sau ca valori întoarse referinţe la tablouri
În limbajul Java, tablourile sunt obiecte. În consecinţă, dacă parametrii formali ai unei metode sunt tablouri, numele lor sunt, de fapt, variabile-referinţă. De exemplu, în signatura metodei main
main(String args[]) parametrul formal args[] este o referinţă la un tablou, ale cărui elemente aparţin clasei String (sunt şiruri de caractere). În consecinţă, tot ce s-a prezentat în secţiunea anterioară cu

privire la folosirea ca parametri formali a variabilelor- referinţă este valabil şi pentru tablouri. Pentru exemplificare, considerăm programul următor, din fişierul TestTab.java.
/* Metode care au ca parametri si ca valori intoarse referinte la tablouri */ class TestTab { static int[] alpha(int a[], double b[]) { System.out.print("La intrarea in metoda alpha\n Tabloul a: "); for(int i=0; i<a.length; i++) System.out.print(a[i]+" "); System.out.print("\nTabloul b: "); for(int i=0; i<b.length; i++) System.out.print(b[i]+" "); /* Se creaza tabloul c si se dau valori elementelor lui */ int c[]=new int[4]; for(int i=0; i<c.length; i++) c[i]=2*i+1; System.out.println("\nTabloul c alocat si initializat in aceasta "+ "metoda:"); for(int i=0; i<c.length; i++) System.out.print(c[i]+" "); /* Se modifica valorile primelor doua elemente ale tabloului b */ b[0]=-777.77; b[1]=-999.99; /* Se creaza un nou tablou, iar referinta catre el se atribuie parametrului formal a: */ a=new int[3]; a[0]=1000; a[1]=1001; a[2]=1002; System.out.println("Inainte de iesirea din metoda alpha:"); System.out.print("Tabloul a: "); for(int i=0; i<a.length; i++) System.out.print(a[i]+" "); System.out.print("\nTabloul b: "); for(int i=0; i<b.length; i++) System.out.print(b[i]+" "); System.out.println(); return c; } public static void main(String args[]) { int p[]={10, 11, 12, 13, 14}, q[]; double w[]={1.1, 1.2, 1.3, 1.4, 1.5, 1.6}; q=alpha(p,w); System.out.println("In main dupa revenirea din alpha:"); System.out.print("Tabloul p: "); for(int i=0; i<p.length; i++) System.out.print(p[i]+" "); System.out.print("\nTabloul q: "); for(int i=0; i<q.length; i++) System.out.print(q[i]+" "); System.out.print("\nTabloul w: ");

149

Severin Bumbaru

for(int i=0; i<w.length; i++) System.out.print(w[i]+" "); System.out.println(); } }

Rezultatele afişate la executarea acestui program sunt următoarele:

La intrarea in metoda alpha Tabloul a: 10 11 12 13 14 Tabloul b: 1.1 1.2 1.3 1.4 1.5 1.6 Tabloul c alocat si initializat in aceasta metoda: 1 3 5 7 Inainte de iesirea din metoda alpha: Tabloul a: 1000 1001 1002 Tabloul b: -777.77 -999.99 1.3 1.4 1.5 1.6 In main dupa revenirea din alpha: Tabloul p: 10 11 12 13 14 Tabloul q: 1 3 5 7 Tabloul w: -777.77 -999.99 1.3 1.4 1.5 1.6

Urmărind executarea programului, constatăm că: - deşi parametrului formal a i s-a atribuit în interiorul metodei alpha o referinţă la alt tablou, nou construit, aceasta nu a afectat tabloul referit în metoda main prin parametrul efectiv corespunzător p; - întrucât în metoda alpha s-au modificat valorile elementelor tabloului referit prin parametrul formal b, aceste modificări apar şi în metoda main în tabloul referit de parametrul efectiv corespunzatorw (deoarece b şi w sunt, de fapt, referinţe la acelaşi tablou); - referinţa la tabloul c, creat în corpul metodei alpha, a fost intoarsă de această metodă şi a fost atribuită în metoda main variabilei referinţă q; în consecinţă, c şi q sunt referinţe la acelaşi tablou. Remarcăm că, la ieşirea din metoda alpha, variabila locală c este eliminată de pe stiva sistemului. Cu toate acestea, tabloul creat în metoda alpha şi referit de această variabilă locală nu este eliminat din memorie, deoarece către el indică în continuare variabila-referinţă q din metoda main.

Metode recursive. Comparaţie între iteraţie şi recursie
O metodă (funcţie sau procedură) care se invocă pe sine însăşi se numeşte metodă recursivă. Două sau mai multe metode care se invocă una pe alta (metoda A invocă metoda B şi reciproc) se numesc mutual recursive. Limbajul Java permite utilizarea metodelor recursive şi mutual recursive. Vom ilustra aceasta prin exemple. Vom arăta, de asemenea că, de regulă, aceleaşi funcţii pot fi calculate atât recursiv, cât şi iterativ (folosind cicluri). În general, în limbajele funcţionale se utilizează predominant funcţiile recursive, în timp ce în limbajele procedurale se prefera iteraţia, deşi în unele dintre ele (cum este şi limbajul Java) se pot folosi atât iteraţia, cât şi recursia.

150

Programarea orientata pe obiecte în limbajul Java Remarcam că, la fel ca în cazul ciclurilor iterative, în metodele recursive trebuie să existe o condiţie de oprire a repetării. În caz contrar recursia ar continua până la depăşirea spaţiului de memorie alocat pentru memorarea datelor intermediare (numit stivă). Comparând metodele recursive cu cele iterative se constată că: - metodele recursive sunt "mai elegante", fiind şi mai uşor de inţeles de către om decât cele iterative; - din punct de vedere computaţional, metodele iterative sunt mai eficiente, deoarece solicită mai puţină memorie şi sunt mai rapide decât cele recursive. Deosebirea este cu atât mai mare, cu cât numărul de invocari succesive, respectiv de repetări, este mai mare. Exemplul 1: Un exemplu tipic de funcţie recursivă este calcularea factorialului. Din punct de vedere matematic, factorialul este o funcţie factorial(n) = 1*2*3*...*n care poate fi definită recursiv astfel: factorial(0)=1; factorial(n)=n*factorial(n-1) pentru n>0; Pentru n<0 funcţia factorial nu este definită. Calcularea acestei funcţii poate fi facută prin metoda următoare:
public static long factorial(int n) throws Exception { if(n<0) throw Exception("factorial(n): n<0"); if(n==0) return 1; return n*factorial(n-1); }

În corpul metodei se verifică, mai întâi dacă argumentul n se încadrează în domeniul admis, iar în caz contrar se generează o excepţie. Dacă argumentul este valabil, se aplică formulele de calcul recursiv al factorialului date mai sus. Recursia se încheie când se ajunge la n==0. Atunci când o metodă invoca alta metodă (fie ea recursivă sau nu), datele metodei care invocă, inclusiv adresa instrucţiunii care urmeaza celei care a făcut invocarea, sunt puse întro structură de memorie numita stivă, după care se transmit către metoda invocată argumentele şi i se dă acesteia controlul. Stiva (engleza: stack) este o structura de memorie în care ultima dată introdusă este prima extrasă (în engleză: LIFO - Last In First Out). La revenirea din metoda invocată se obţine valoarea întoarsă de aceasta şi se extrag din stivă datele puse acolo înainte de invocare, continuându-se calculul. Să urmarim acest proces în cazul invocării metodei factorial(n) pentru n=4: factorial(4)=4*factorial(3); se pune 4 în stivă şi se invocă factorial(3); stiva conţine numărul 4; factorial(3)=3*factorial(2); se pune 3 în stivă şi se invocă factorial(2); stiva conţine numerele 3 4; factorial(2)=2*factorial(1); se pune 2 în stivă şi se invocă factorial(1); stiva conţine numerele 2 3 4; 151

Severin Bumbaru factorial(1)=1*factorial(0); se pune 1 în stivă şi se invocă factorial(0); stiva conţine numerele 1 2 3 4; factorial(0)=1; recursia s-a incheiat, deoarece funcţia nu se mai invocă pe ea însăşi. Se continuă calculul fucţiei factorial(1)care a invocat-o pe factorial(0). În acest scop se scoate din stivă 1 şi se calculează factorial(1)=1*1=1; stiva conţine 2 3 4. S-a încheiat calculul lui factorial(1) şi se revine în factorial(2)cu valoarea intoarsa 1; se continuă factorial(2), extrăgând din stivă pe 2: factorial(2)=2*1=2; stiva conţine 3 4; factorial(3)=3*2=6; stiva conţine 4; factorial(4)=4*6=24; stiva este vidă, deci calculul funcţiei recursive s-a incheiat, intorcându-se valoarea 24. Se observa că, cu cât recursia este mai "profundă" (funcţia recursivă se invocă de mai multe ori pe sine însăşi), cu atât este necesară o stivă de capacitate mai mare. Daca recursia este prea "profundă", este posibil ca stiva să nu aibă capacitate suficientă. În acest caz se obţine eroarea de depăşire de stivă StackOverflowError (atenţie: este o eroare, nu o excepţie). Calcularea factorialului se poate face şi iterativ, folosind următoarea funcţie, în care factorialul se calculează printr-un ciclu for:
public static long factorial(int n) throws Exception { long fact; if(n<0) throw new Exception("factorial(n): n<0"); fact=1; for(int i=2; i<=n; i++) fact*=i; return fact; }

Ambele metode de calculare a factorialului sunt testate în aplicaţia din fişierul TestRecursii.java. În acest fişier se declară două clase: clasa Recursii, care conţine metode statice recursive şi mutual-recursive şi clasa Iteraţii, care conţine metode iterative. În acelaşi fişier există şi clasa-aplicaţie TestRecursii, care testează metodele din celelalte două clase. Pentru a se compara metodele din punctul de vedere al timpului de calcul, în aplicaţia TestRecursii s-a procedat astfel: înainte şi după invocarea fiecărei metode s-a determinat timpul sistemului în milisecunde, folosind metoda System.currentTimeMillis(), apoi s-a făcut diferenţa. Se constata astfel că timpul de calcul al factorialului pe cale recursivă este mai mare decat pe cale iterativă. Exemplul 2: Un alt exemplu tipic de funcţie recursivă este funcţia lui Fibonacci definită astfel: fibonacci(0)=0; fibonacci(1)=1; fibonacci(n)=fibonacci(n-1)+fibonacci(n-2) pentru n>1. În fişierul TestRecursii.java sunt declarate două metode statice pentru funcţia Fibonacci: una recursiva, în clasa Recursii, şi alta iterativă, în clasa Iteratii. Cele două metode sunt apoi testate şi comparate în clasa TestRecursii. Se poate constata că, la valori mici ale

152

Programarea orientata pe obiecte în limbajul Java argumentului n, metoda recursivă este chiar mai rapidă decât cea iterativă. În schimb, la valori mari ale lui n, timpul de calcul al metodei recursive creşte foarte rapid, devenind sensibil mai mare decât al celei iterative. Exemplul 3: În clasa Recursii din fisierul TestRecursii.java sunt declarate şi două funcţii mutualrecursive: fct1(n,x)=2*fct2(n, 0.4*x+0.3)+x pentru n>=0 fct2(0,y)=y fct2(n,y)=y*fct1(n-1, 1.27*y-0.89)-1 pentru n>0 Se observă că funcţia fct1() invocă funcţia fct2() şi reciproc.

Metode care generează excepţii
Instrucţiunea throw
Cunoaştem deja că, la apariţia anumitor anomalii în executarea programului, maşina virtuală Java generează excepţii. Excepţiile sunt obiecte din clasa Exception s-au dintr-o subclasă a acesteia. Este posibil ca programatorul să prevadă, în anumite puncte ale programului, generarea unor excepţii, folosind în acest scop instrucţiunea throw, care are forma următoare:
throw new ConstructorExceptie(lista_argumente);

În limba engleză, throw este imperativul de la "a arunca". În această instrucţiune, se foloseşte operatorul new pentru a genera un obiect al clasei de excepţii căreia îi aparţine constructorul invocat, după care acest obiect este "aruncat", fie pentru a fi "prins" (captat) printr-o structura try .. catch şi tratat prin program, fie pentru a fi preluat de maşina virtuală Java care, în acest caz, opreşte execuţia programului. Exemplu Instrucţiunea
if(a>1000) throw new Exception("Exceptie 102: a="+a);

"aruncă" o excepţie din clasa Exception, care contine un mesaj sub forma argumentului furnizat constructorului.

Clauza throws
În mod normal, excepţiile generate într-o metodă sunt tratate prin structuri try .. catch. chiar în metoda în care au fost generate. Este însă posibil ca metoda respectivă să "arunce" mai departe excepţiile generate în corpul ei. Pentru a indica această proprietate, la declararea metodei, după paranteza care conţine lista declaraţiilor argumentelor formale se pune clauza

153

Severin Bumbaru
throws ClasaDeExceptii,

în care se indică numele clasei excepţiei care este "aruncată" către metoda invocatoare (Cuvantul throws este persoana a treia singular a verbului to throw, "a arunca"). Exemplu În programul din fişierul TestExceptie.java există metoda static int factorial(int n), care calculează factorialul argumentului n. În această metodă este folosită instrucţiunea throw de doua ori: pentru cazul în care argumentul este negativ şi pentru cel în care argumentul este prea mare (rezultatul depăşeşte valoarea maximă pentru tipul int al valorii întoarse). În ambele cazuri, în instrucţiunea throw se foloseşte constructorul clasei Exception, furnizându-i ca argument un mesaj care arată ce eroare s-a produs. Întrucat aceste excepţii nu sunt tratate în metoda factorial, în declaraţia metodei factorial s-a folosit clauza throws. În metoda main se captează şi se afişează atât aceste excepţii, cât şi excepţia NumberFormatException generată de metoda int Integer.parseInt(String s) atunci când argumentul acesteia nu este forma externă a unui număr întreg.

/* Testarea unei metode care genereaza exceptii. Lansarea in executie a aplicatiei se face prin comanda: java TestExceptie <numar_intreg> Daca <numar_intreg> este un numar intreg cuprins intre 0 si 12 se afiseaza factorialul acestui numar. Altfel, se afiseaza un mesaj de eroare */ class TestExceptie { /* O metoda in care se genereaza exceptii prin clauza throw */ static int factorial(int n) throws Exception { if(n<0) throw new Exception("factorial: argument negativ n="+n); if(n>12) throw new Exception("factorial: argument prea mare: "+n); int fact=1; for(int k=1; k<=n; k++) fact*=k; return fact; } /* In metoda main se capteaza si trateaza exceptiile generate de metodele factorial si parseInt */ public static void main(String args[]) { if(args.length==0) { System.out.println("Lipsa argument in linia de comanda"); System.exit(1); } try { int n=Integer.parseInt(args[0]); int m=factorial(n); System.out.println("Factorialul lui "+n+" este "+m); } catch(Exception e) { System.out.println(e); }

154

Programarea orientata pe obiecte în limbajul Java
} }

Clase publice
Clasele publice sunt clase care pot fi utilizate şi în alte pachete, decât cel din care fac parte. Fiecare clasă publică se declară într-un fişier separat, care are obligatoriu acelaşi nume cu cel al clasei şi extensia java. În declaraţia de clasă, în faţa numelui clasei se pune modificatorul public. Dăm în continuare ca exemplu clasa Complex.

155

Severin Bumbaru

Declararea clasei Complex
Clasa Complex este un exemplu de clasă prin care se modelează conceptul matematic de număr complex. După cum este cunoscut, numărul complex a+b.ieste compus din două numere reale a şi b, numite respectiv partea reală şi partea imaginară. Simbolul i este numit unitatea imaginară şi are proprietatea i2= -1. În fişierul Complex.java este dat un exemplu de declaraţie a clasei Complex. Iată această declaraţie:
/* Clasa numerelor complexe */ public class Complex { private double re; // partea reala private double im; // partea imaginara /* Constructori */ public Complex() { re=0; im=0; } public Complex(double parteaReala) { re=parteaReala; im=0; } public Complex(double parteaReala, double parteaImaginara) { re=parteaReala; im=parteaImaginara; } public Complex(Complex z) { re=z.re; im=z.im; } /* Metode */ public double real() { // intoarce partea reala return re; } public double imag() { // intoarce partea imaginara return im; } public double modul() { // intoarce modulul numarului complex return Math.sqrt(re*re+im*im); } public double arg() { // intoarce argumentul numarului complex return Math.atan2(im,re); } public static Complex complex(double modul, double argument) throws Exception { if(modul<0) throw new Exception("Complex: modul negativ: "+modul); Complex z=new Complex();

156

Programarea orientata pe obiecte în limbajul Java

z.re=modul*Math.cos(argument); z.im=modul*Math.sin(argument); return z; } public Complex plus(Complex a) { Complex z=new Complex(); z.re=re+a.re; z.im=im+a.im; return z; } public Complex plus(double d) { return new Complex(re+d, im); } public static Complex plus(double d, Complex z) { return new Complex(d+z.re, z.im); } public Complex minus(Complex a) { Complex z=new Complex(); z.re=re-a.re; z.im=im-a.im; return z; } public Complex minus(double d) { return new Complex(re-d, im); } public static Complex minus(double d, Complex z) { return new Complex(d-z.re, -z.im); }

public Complex inmultitCu(Complex a) { Complex z=new Complex(); z.re=re*a.re-im*a.im; z.im=re*a.im+im*a.re; return z; } public Complex inmultitCu(double d) { return new Complex(re*d, im*d); } public static Complex inmultitCu(double d, Complex z) { return new Complex(d*z.re, d*z.im); } public Complex impartitLa(Complex a) throws Exception { Complex z=new Complex(); double w=a.re*a.re+a.im*a.im; // patratul modulului numitorului if(w==0) throw new Exception("Complex impartitLa: impartire la zero");

157

Severin Bumbaru

z.re=(re*a.re+im*a.im)/w; z.im=(im*a.re-re*a.im)/w; return z; } public Complex impartitLa(double d) throws Exception { if(d==0) throw new Exception("Complex impartitLa: impartire la zero"); return new Complex(re/d, im/d); } public static Complex impartitLa(double d, Complex z) throws Exception { double w=z.re*z.re+z.im*z.im; if(w==0) throw new Exception("Complex impartitLa: impartire la zero"); return new Complex(d*z.re/w, -d*z.im/w); } public String toString() { StringBuffer b=new StringBuffer("("+re); if(im>=0) b.append('+'); b.append(im+"*i)"); return b.toString(); } public boolean equals(Object obj) { if(this==obj) return true; if(getClass()!=obj.getClass()) return false; if(re==(((Complex)obj).re)&&(im==(((Complex)obj).im))) return true; return false; } public int hashCode() { return (int)(100000*modul()+100*arg()); } }

Declaraţia clasei incepe prin public class Complex, deci este o clasa publică. Clasa conţine două câmpuri de tip double, reprezentând respectiv partea reală şi partea imaginară a numărului complex. S-au prevăzut mai mulţi constructori. Primul dintre aceştia nu are argumente şi creează un număr complex la care, atât partea reală, cât şi cea imaginară sunt nule. Având în vedere că s-a declarat un constructor cu două argumente de tip double, reprezentând partea reală şi cea imaginară a numărului complex nou creat, nu a mai fost posibil să se creeze încă un constructor, care să aibă ca argumente modulul şi argumentul noului număr complex (acestea fiind tot numere reale). Din această cauză, el a fost înlocuit prin metoda statică
public static Complex complex(double modul double argument)

al cărei nume începe cu literă mică. Au fost redefinite metodele toString, equals şi hashCode ale superclasei Object, pentru a ţine seama de specificul clasei Complex. Au fost declarate, de asemenea, metode pentru

158

Programarea orientata pe obiecte în limbajul Java efectuarea de calcule între două numere complexe, între un număr complex şi unul real şi intre unul real şi unul complex. Ultimele au fost declarate ca metode statice, deoarece operandul din stânga nu aparţine clasei complex. În toate metodele, în care pot să apară situaţii anormale, s-a prevazut generarea de excepţii.

Utilizarea clasei complex
Testarea diferitelor situaţii de utilizare a clasei complex este exemplificată în fişierul TestComplex.java.

Colectorul de reziduuri
Dacă un obiect nu mai este necesar, el poate fi distrus, adică eliminat din memorie. În maşina virtuală Java, există un colector de reziduuri de memorie (engleză: garbage collector) care eliberează automat spaţiul de memorie ocupat de obiectele către care nu mai există nici o referinţă. în consecinţă, programatorul nu mai este pus în situaţia să prevadă explicit în program distrugerea obiectelor şi, deci clasele nu mai conţin destructori, ca în alte limbaje de POO. În programul din fişierul Referinte.java se dă un exemplu de situaţie în care unele obiecte rămân fără referinţe. Dacă, după ce s-a ajuns în situaţia din Figura 2, se execută instrucţiunile:
e=b; a="Sir nou"; c=new String(a);

se ajunge în situaţia din Figura 3.

- Figura 3 S-au făcut, faţă de situaţia din Figura 2, următoarele transformări: - referinţei e i s-a atribuit aceeaşi valoare ca referinţei b; - s-a construit un obiect cu conţinutul "Şir nou", iar variabila aa primit ca valoare referinţa la acest obiect; - s-a construit un obiect cu acelaşi continut ca cel indicat de a, iar variabila c indică acum

159

Severin Bumbaru acest nou obiect. În consecinţă, două din obiectele "un şir" construite initial au rămas fără referinţe. Nu mai există nici o cale în program de a folosi aceste obiecte, deci ele trebuie să fie eliminate. Acest rol este îndeplinit de colectorul de reziduuri. Programatorul nu are însă posibilitatea de a decide în ce moment se va produce eliminarea efectivă a acestor obiecte, decizia aparţinand numai colectorului de reziduuri.

Metoda finalize
În clasa Object exista metoda
protected void finalize() throws Throwable

Aceasta metodă este invocată de colectorul de reziduuri, atunci când acesta determină că nu mai există referinţe către obiectul respectiv. În clasa Object, această metodă nu efectuează nimic. Metoda finalize poate fi redefinită în orice altă clasă, pentru a elibera resurse sau a efectua orice alte acţiuni necesare înainte de distrugerea obiectului respectiv. De exemplu, dacă obiectul respectiv a deschis anumite fişiere sau conexiuni externe, în metoda finalize se poate efectua închiderea lor. Metoda finalize nu este apelată explicit în programul de aplicaţie. Apelarea metodei finalize se face numai de către colectorul de reziduuri (garbage collector), dar nu imdeiat ce un obiect a rămas fără referinţă, ci abia atinci când acest obiect a "intrat în atenţia" colectorului. Este posibil ca executarea aplicaţiei să se incheie înainte ca "finalizarea" unor obiecte să aibă loc. Exemplu În aplicaţia din fişierul Finalizari.java este declarată clasa ProbaFinaliz, în care este redefinită metoda finalize() din clasa Object. În clasa Finalizari din acelaşi fisier se construiesc două obiecte din clasa ProbaFinaliz, după care se elimină referinţele către aceste obiecte. Având în vedere că imediat după aceea se încheie executarea aplicaţiei, cel mai probabil este că nu va avea loc invocarea de către colectorul de reziduuri a metodei finalize().

Întrebări
Nivel 1
1. 2. 3. 4. Care este cea mai simplă formă a unei declaraţii de clasă? Ce sunt membrii unei clase? Ce este numele clasei din punct de vedere sintactic? Cu ce începe numele unei clase?

160

Programarea orientata pe obiecte în limbajul Java 5. Ce conţine corpul clasei? 6. Ce asemănare este între câmpuri şi variabilele locale? 7. Ce deosebire este între câmpuri şi variabille locale? 8. Ce sunt câmpurile statice şi prin ce se deosebesc de cele nestatice? 9. Pot exista clase care nu conţin metode? 10. Care este cea mai simplă formă a unei declaraţii de metodă? 11. Ce este numele metodei din punct de vedere sintactic? 12. Ce este corpul metodei şi ce conţine el? 13. Cum se declară argumentele metodei? 14. Cum se declară tipul valorii întoarse? 15. Ce formă şi ce semnificaţie are instrucţiunea return? 16. Ce este o metodă statică? 17. Ce restricţii trebuie respectate la declararea unei metode statice? 18. Ce este signatura metodei? 19. Pot exista în aceeaşi clasă mai multe metode cu acelaşi nume? 20. Cum se face transferul argumentelor (parametrilor) de la metoda invocatoare la cea invocată? 21. Cum pot fi generate excepţii în corpul unei metode? 22. Ce formă are instrucţiunea throw şi la ce serveşte? 23. La ce serveste clauza throws? 24. Ce deosebire este între throw şi throws? 25. Ce sunt clasele publice? 26. Cum se declara o clasă publică? 27. Ce este colectorul de reziduuri?

Nivel 2
Care sunt valorile iniţiale implicite ale câmpurilor? Ce deosebire este între iniţializarea câmpurilor şi iniţializarea variabilelor locale? Ce deosebire este intre o funcţie şi o procedură? Prin ce se deosebeşte o metodă prin care se realizează o procedură de una care realizează o funcţie? 5. În ce mod se invocă, în mod normal, o metodă prin care se realizează o funcţie propriu-zisă (a carei valoare întoarsă nu este void)? 6. În ce mod poate fi invocata o metoda care întoarce void? 7. Există în limbajul Java variabile globale? 8. Ce deosebire este între parametrii formali ai unei metode şi cei efectivi? 9. Ce se întâmplă dacă în corpul unei metode se modifică valoarea unui argument formal? 10. Ce se întâmplă dacă în corpul unei metode se modifică conţinutul unui obiect referit de către un parametru formal al metodei respective? 11. Ce se întâmplă dacă în corpul unei metode se modifică valoarea unui parametru formal de tip referinţă (în sensul că i se dă ca valoare o referinţă la alt obiect)? 12. În ce situaţie, la revenirea dintr-o metodă, poate să aibă loc un efect lateral? 13. Este posibil ca o metodă să întoarcă o referinţă la un obiect construit în corpul acesteia? 14. Ce se întâmplă dacă, în corpul unei metode care are ca argument formal un tablou, se modifică elementele acestui tablou? 15. Ce se întâmplă dacă, în corpul unei metode care are ca argument formal un tablou, i se 1. 2. 3. 4.

161

Severin Bumbaru dă acestui argument ca valoare o referinţa la alt tablou? 16. Poate o metodă să întoarcă drept valoare o referinţă la un tablou creat în interiorul ei? 17. Ce este o metodă recursivă? 18. Ce deosebiri există între iteraţie şi recursie? 19. În ce scop se redefineşte într-o clasă metoda toString() a clasei Object? 20. În ce scop se redefineşte într-o clasă metoda equals(Object ob) a clasei Object? 21. Poate fi declarată o metodă care creează un obiect nou al clasei căreia îi apartine metoda respectivă? Prin ce se deosebeşte ea de un constructor? 22. Ce este metoda finalize()? 23. În ce scop este redefinită metoda finalize()? 24. În ce situaţii este invocată metoda finalize()?

162

Programarea orientata pe obiecte în limbajul Java

Caracteristicile obiectelor şi claselor şi reflectarea lor în declararea claselor. Interfeţe. Clase imbricate
Caracteristici ale obiectelor şi claselor şi reflectarea lor în declararea claselor: Încapsularea; câmpuri şi metode publice şi private; Modificatori de acces pentru câmpuri şi metode; Declararea constructorilor; Agregarea; Moştenirea; Membrii protejati ai clasei; Referinţele this şi super; Declararea clasei derivate; clauza extends; Declararea constructorului clasei derivate; Redefinirea metodelor; Ascunderea (acoperirea) câmpurilor; Ascunderea metodelor statice; Metode finale; Declararea propriilor clase de excepţii; Clase finale; Polimorfismul; Un exemplu: clasa Persoana şi subclasa Student; Declararea clasei Persoana; Declararea clasei Student; Utilizarea celor două clase; Instanţierea clasei care conţine metoda main; Clase abstracte; Interfeţe; Clase imbricate şi clase interioare Întrebări 163 166 167 167 170 171 171 171 171 172 172 175 175 177 177 177 177 180 180 181 183 184 186 186 190 195

Caracteristicile obiectelor şi claselor
În majoritatea surselor bibliografice asupra POO, se consideră drept principale caracteristici ale obiectelor încapsularea, moştenirea şipolimorfismul. La acestea se mai pot adaugă şi alte caracteristici importante, cum sunt identitatea, agregarea şi clasificarea. Identitatea (engleză: Identity) se referă la faptul că datele sunt grupate în entităţi discrete, numite obiecte. Fiecare obiect din POO modelează starea şi comportamentul unui anumit

163

Severin Bumbaru obiect din lumea reală, care poate fi un obiect fizic (de exemplu automobil, calculator, furnal, om, animal etc.) sau unul conceptual (de exemplu figură geometrică, orar etc.). Fiecare obiect are propria lui identitate, astfel că două obiecte sunt considerate distincte, chiar daca atributele lor (cum ar fi numele, culoarea etc.), sunt identice. Pentru a face aceasta distincţie, obiectul este indicat printr-o referinţă unică. Modul în care este reprezentata această referinţă poate sa difere în diverse limbaje de programare (de ex. adresă de memorie, nume etc.), important însă este că fiecare obiect are o singură referinţă şi nu există două obiecte distincte cu aceeaşi referinţă. Încapsularea (engleză: encapsulation) este proprietatea obiectelor de a-şi ascunde o parte din date şi metode. Din exteriorul obiectului sunt accesibile ("vizibile") numai datele şi metodele publice. Putem deci sa ne imaginăm obiectul ca fiind format din două straturi, ca în Figura 1.

- Figura 1 Obiectul se comportă ca şi când ar avea doua "învelişuri": unul "transparent", care permite accesul la datele şi metodele publice ale obiectului, şi un al doilea inveliş "opac", care cuprinde datele şi metodele invizibile (inaccesibile) din exterior. Starea obiectului depinde atât de datele publice, cât şi de cele încapsulate. Metodele publice ale obiectului au acces la datele şi metodele încapsulate (ascunse) ale acestuia. In consecinţă, starea obiectului poate fi modificata atât prin modificarea directă, din exterior, a valorilor variabilelor publice, fie prin utilizarea unor metode publice care modifica valorile variabilelor încapsulate. În mod similar, valorile variabilelor încapsulate pot fi obţinute numai utilizand metode publice ale obiectului respectiv. Încapsularea obiectelor prezintă avantaje importante în programare, deoarece măreşte siguranţa şi fiabilitatea programelor, prin eliminarea posibilităţii modificării accidentale a valorilor acestora, ca urmare a accesului neautorizat din exterior. Din această cauză, programatorii evită în general să prevadă într-un obiect date publice, preferand ca accesul la date să se facă numai prin metode. Partea vizibilă (publică) a obiectului constituie interfaţa acestuia cu "lumea exterioară". Este posibil ca două obiecte diferite să aibă interfeţe identice, adică să prezinte în exterior aceleaşi date şi metode. Datorită faptului că partea încapsulată diferă, astfel de obiecte pot avea comportament diferit. Agregarea (engleză: aggregation) este proprietatea obiectelor de a putea încorpora alte obiecte. Aşa dar, "datele" conţinute într-un obiect pot fi nu numai date primitive, ci şi obiecte. Se pot astfel crea obiecte cu structuri din ce în ce mai complexe.

164

Programarea orientata pe obiecte în limbajul Java Clasificarea (engleză: classification) este proprietatea obiectelor care au aceeaşi structură de date şi acelaşi comportament (aceleaşi metode) de a putea fi grupate într-o clasă. Clasa este o abstractizare, care conţine acele proprietăţi ale obiectelor, care sunt importante într-o aplicaţie sau într-o categorie de aplicaţii, şi le ignoră pe celelalte. De exemplu, în aplicaţii privind situaţia profesională a studenţilor, clasa Student conţine astfel de atribute ca numele şi prenumele studentului, facultatea, anul de studii, examenele promovate şi notele obţinute, dar ignoră atribute ca înălţimea, culoarea ochilor sau a părului, greutatea etc., care nu sunt necesare în aplicaţia respectivă. Spre deosebire de agregare, care este o relaţie între obiecte, clasificarea este o relaţie între concepte reprezentate prin clase. În functie de nivelul de abstractizare, clasele pot forma o structura ierarhică. De exemplu, clasa mijloacelor de transport cuprinde subclasele mijloacelor de transport terestre, navale şi aeriene. Clasa mijloacelor de transport terestre le cuprinde pe cele rutiere şi feroviare. Clasa mijloacelor de transport rutiere cuprinde clasele automobilelor şi autocamioanelor etc. Fiecare subclasă a unei clase este ea însăşi o clasă. O astfel de ierarhie de clase este reprezentată în Figura 2.

- Figura 2 Cu cat o clasă se afla în această ierarhie pe un nivel mai înalt, cu atât ea este mai abstractă, deci descrie un număr mai mic de atribute ale obiectelor care îi aparţin. Clasa A este rădăcina acestei ierarhii, fiind situată pe cel mai înalt nivel de abstractizare. Clasele B, C şi D sunt subclase ale lui A, iar clasele E si F sunt subclase ale clasei B. În acelaşi timp, clasa Aestesuperclasă a clasei B, iar aceasta este superclasă a clasei F. În programarea orientată pe obiecte se spune, de asemenea, că clasa B este derivată din clasa A, iar clasa F este derivată din clasa B sau, în general, o subclasă este derivată din superclasa sa. Un obiect care aparţine unei clase se numeşte şi instanţă a clasei (este o instanţiere, o realizare particulara a clasei respective). De exemplu, clasa Barbat şi clasa Femeie sunt subclase ale clasei Om. În schimb, Ion_Popescu este o instanţăa clasei Barbat (un obiect care aparţine acestei clase), iar Maria_Preda este o instanţă a clasei Femeie. În principiu, o clasă poate avea o infinitate de instanţe. Practic, desigur, numărul lor este finit. Toate instanţele unei clase au aceleaşi atribute (aceleaşi "câmpuri"), dar valorile acestora pot să difere de la o instanţă la alta.

165

Severin Bumbaru Moştenirea (engleză: inheritance) este proprietatea unei clase de a conţine toate atributele (câmpurile) şi metodele superclasei sale. În consecinţă, trecerea de la clasă la subclasă se face prin adăugarea de atribute şi/sau de metode. De exemplu, clasa Barbat şi clasa Femeie au ambele toate atributele clasei Om, dar fiecare din acestea are şi atribute specifice. În general, în programarea orientată pe obiecte, moştentirea poate fi simplă sau multiplă. În cazul moştenirii simple fiecare clasă are cel mult o superclasă, ca în Figura 2. În cazul moştenirii multiple o clasă poate avea mai multe superclase, ca în Figura 3.

- Figura 3 În această figură se observă că clasa D moşteneşte clasele A şi B, iar clasa H moşteneşte clasele D, E siF. Aceasta corespunde situaţiei din lumea reală în care, de exemplu, un student_sportiveste în acelaşi timp şi student, şi sportiv, deci deţine atributele ambelor clase. Deşi există limbaje de programare, ca limbajul C++, care admit moştenirea multiplă, aceasta creează unele dificultăţi. Astfel, în exemplul din Figura 3, metodele şi datele clasei B sunt moştenite de clasa H pe două căi: pe traseul BDH şi pe traseul BEH. Pentru a se evita astfel de situaţii, în limbajul Java se admite numai moştenirea simplă. Lipsa moştenirii multiple este compensată în limbajul Java prin introducerea conceptului de interfaţă care va fi prezentat ulterior. Polimorfismul (engleză: polymorphism) permite ca aceeaşi operaţie să se realizeze în mod diferit în clase diferite. Să considerăm, de exemplu, că în clasa Figura_geometrica există metoda arie(), care calculeaza aria figurii respective. Clasele Cerc, Triunghi, Patrat sunt subclase ale clasei Figura_geometrica si vor moşteni, deci, de la aceasta metoda arie(). Este însă evident că aria cercului se calculează în alt mod decât aria patratului sau cea a triunghiului. Pentru fiecare din instanţele acestor clase, la calcularea ariei se va aplica metoda specifică clasei respective.

Încapsularea
Încapsularea este una din proprietăţile fundamentale ale claselor în programarea orientată pe obiecte. Câmpurile, constructorii şi metodele dintr-o clasă pot fi încapsulate, astfel încât să nu fie vizibile din exteriorul clasei sau instanţei în care se află. Aceasta se realizează folosind la declararea câmpului, constructorului sau metodei respective modificatorul private.

166

Programarea orientata pe obiecte în limbajul Java De exemplu, prin declaraţia de câmpuri
private double a, b, c;

se creează câmpurile private a, b, c care nu sunt vizibile din exteriorul instanţei în care se afla. În mod similar, prin declaraţia de metodă
private int ex1(int k) { return 2*k+1); }

se declară metoda privată ex1. Câmpurile şi metodele declarate astfel se numesc private. În opoziţie cu acestea, sunt câmpurile şi metodele publice, care sunt declarate folosind modificatorul public şi sunt vizibile din orice altă clasă.

Modificatori de acces pentru câmpuri şi metode
În limbajul Java există trei modificatori de acces pentru câmpuri şi metode: - private - pentru a specifica câmpuri sau metode private; - public - pentru a specifica câmpuri sau metode publice; - protected - pentru a specifica câmpuri sau metode protejate (care vor fi prezentate în secţiunea despre moştenire). Membrii privati ai claselor (câmpuri sau metode) sunt accesibili numai din clasa respectivă. Membrii publici sunt accesibili din orice clasă. Membrii protejati sunt accesibili numai din clasa în care se află sau din subclasele acesteia. Dacă la declararea câmpurilor sau metodelor nu se folosesc modificatori de acces, acestea sunt considerate vizibile numai din clasele care sunt situate în acelaşi pachet cu clasa căreia îi aparţin. În particular, pentru clasele declarate de către noi, aceste câmpuri şi metode sunt vizibile numai din clasele situate pe disc în acelaşi director. Se spune, în astfel de cazuri, că modul de acces este prietenos (engleză: friendly) sau de pachet (engleză: package).

Declararea constructorilor
Constructorii sunt utilizati impreuna cu operatorul new pentru a creea instanţe ale claselor. Constructorii pot fi impliciţi sau expliciţi. Constructorii, ca şi metodele, sunt niste subprograme. Faţă de metode, constructorii prezintă următoarele trăsături specifice: - numele constructorului este întotdeauna acelaşi cu al clasei căreia îi aparţine; - constructorul nu întoarce o valoare. În consecinţă, la declararea constructorului nu se specifică tipul valorii întoarse, ca la metode; - constructorii nu pot fi statici; - invocarea constructorilor se face numai prin operatorul new. Ca şi în cazul metodelor, o clasă poate avea mai multi constructori, care sa difere între ei prin signatură.

Constructorul implicit
Dacă într-o clasă nu este declarat explicit nici un constructor, ea are un constructor implicit. Acest constructor nu are argumente, iar efectul lui constă în iniţializarea tuturor câmpurilor instanţei care se creează cu valorile implicite corespunzătoare tipurilor câmpurilor respective.

167

Severin Bumbaru ATENŢIE: dacă clasa are unul sau mai multi constructori declaraţi explicit, ea nu mai are constructor implicit. Exemplu: în aplicaţia Cercuri din fişierul Cercuri.java, la crearea instanţelor clasei Cerc1 s-a folosit constructorul implicit al acestei clase.

Constructori expliciţi
Declararea constructorului se face sub forma
modificator_acces nume_clasa(declaratii_de_argumente) { corpul_constructorului }

Se observă imediat că declararea constructorului se face la fel cu a unei metode, cu deosebirea că lipseşte tipul valorii întoarse, iar numele constructorului este cel al clasei. Modificatorul de acces al constructorului este, cel mai frecvent, public. El poate, totuşi, sa lipsească dacă se consideră că instanţierea clasei se va face numai în metode ale claselor din acelaşi pachet. În corpul constructorului pot fi programate orice fel de acţiuni care trebuie executate imediat după ce se alocă în memorie spaţiu pentru o instanţă a clasei respective. Cel mai frecvent, în corpul constructorilor se iniţializează valorile câmpurilor instanţei nou create.

Exemplu În fişierul Cercuri1.java este declarată clasa Cerc în modul următor:

class Cerc { public static final double PI=3.141592653589793; private double r; public Cerc(double raza) { r=raza; } public double raza() { return r; } public double arie() { return PI*r*r; } public double circumferinta() { return 2*PI*r; } public static double arie(double r) { return PI*r*r; } public static double circumferinta(double r) { return 2*PI*r;

168

Programarea orientata pe obiecte în limbajul Java

} }

Clasa Cerc are atât metodele de instanţă, cât şi metodele statice pentru calcularea ariei şi circumferinţei, care existau în clasele Cerc1 şi Cerc2 din exemplul dat în fişierul Cercuri.java. În plus, remarcăm următoarele: - câmpul r1 (raza cercului) a fost declarat privat, deci el nu este accesibil din exteriorul clasei; - s-a declarat un constructor care, la crearea unui obiect din clasa Cerc, iniţializează câmpul r1. Întrucât clasa Cerc nu conţine nici o metodă prin care să se modifice valoarea acestui câmp, raza cercului nu mai poate fi modificată după ce acesta a fost creat, aşa cum era posibil în cazul instanţelor clasei Cerc1. În acelaşi fişier sursă, este declarata şi clasa Cercuri1, în care se utilizeaza clasa Cerc. Aplicaţia menţionată este declarată astfel:

/* Aplicatia in care se utilizeaza cele doua clase de cercuri declarate mai sus */ class Cercuri1 { public static void main(String args[]) { double r1=1, r2=7.32; /* Instantierea a doua cercuri */ Cerc c1=new Cerc(r1), c2=new Cerc(r2); /* Utilizarea metodelor de instanta */ System.out.println("Utilizand metodele de instanta:"); System.out.println("Pentru cercul c1:\n aria="+c1.arie()+ " circumferinta="+c1.circumferinta()+" raza="+c1.raza()); System.out.println("Pentru cercul c2:\n aria="+c2.arie()+ " circumferinta="+c2.circumferinta()+" raza="+c2.raza()); /* Utilizarea metodelor statice */ System.out.println("Folosind metodele statice ale clasei Cerc:"); System.out.println("Pentru raza r1: aria="+Cerc.arie(r1)+ " circumferinta="+Cerc2.circumferinta(r1)); System.out.println("Pentru raza r2: aria="+Cerc.arie(r2)+ " circumferinta="+Cerc2.circumferinta(r2)); System.out.println("Pentru raza r=9.23 aria="+Cerc.arie(9.23)+ " circumferinta="+Cerc.circumferinta(9.23)); /* Acelasi calcul, facut calificand numele metodelor statice prin referinte la instante ale clasei Cerc */ System.out.println("Aria cercului cu raza 9.23: "+ c1.arie(9.23)+" "+c2.arie(9.23)); } }

Se observă că iniţializarea celor două cercuri (instanţe ale clasei Cerc) c1 şi c2 s-a făcut folosind constructorul Cerc(double raza). După ce a fost construit cercul, raza lui nu mai poate fi modificată. Calculara ariei cercului c1 se face invocând metoda c1.arie(), care aparţine instanţei c1. În acest caz, metoda nu are argumente, deoarece se va folsi raza cercului c1. Aria unui cerc oarecare poate fi calculată însă şi invocând metoda staticăCerc.arie(raza),

169

Severin Bumbaru

căreia însă i se dă ca argument raza cercului. Chiar dacă această metodă statică este invocată prin intermediul referinţei la o instanţă sub forma c1.arie(raza), nu se va folosi în calcul raza instanţei c1, ci raza primită ca argument. Compilatorul face distincţie intre metodele arie() şi arie(double raza) întrucât au semnături diferite.

Agregarea
Agregarea este o caracteristică fundamentală a programării orientate pe obiecte, conform căreia un obiect poate conţine alte obiecte. În limbajul Java, la declararea claselor, este permis ca atât câmpurile clasei, cât şi ale instanţei să aibă ca valori obiecte din alte clase. Se poate porni astfel de la clase simple, care au drept câmpuri date din tipuri primitive, şi construi pas cu pas clase cu structuri din ce in ce mai complicate, realizate prin agregarea claselor cu structuri mai simple. Un exemplu de agregare este clasa Pers din fisierul Pers.java, pe care îl reproducem aici.

class Pers { private String numePers, prenumePers; private int anNasterePers; Pers(String nume, String prenume, int anNastere) { numePers=nume; prenumePers=prenume; anNasterePers=anNastere; } String nume() { return numePers; } String prenume() { return prenumePers; } int anNastere() { return anNasterePers; } }

Instanţele clasei Pers conţin datele unei persoane. Se observă că obiectele din clasa Pers conţin câmpurile nume şi prenume, care sunt referinţe la obiecte ale clasei String. Remarcăm de asemenea că, întrucât câmpurile de date sunt private, iniţializarea lor se face folosind un constructor. După crearea obiectului, aceste câmpuri nu mai pot fi modificate. Testarea clasei Pers se face în aplicaţia din fişierul TestPers.java.

Moştenirea
Conceptul de moştenire

170

Programarea orientata pe obiecte în limbajul Java Moştenirea este o trăsătură fundamentală a programării orientate pe obiecte, conform căreia: - dintr-o clasă se pot deriva alte clase. Dacă din clasa A este derivată clasa B, atunci A se numeşte clasă de bază, sau superclasă, iar B se numeşte clasă derivată, sau subclasă; - subclasa mosteneste câmpurile şi metodele superclasei; - metodele superclasei pot fi redefinite în subclasă prin alte metode cu aceeaşi signatură; - metodele statice ale superclasei pot fi ascunse în subclasă; - câmpurile superclasei pot fi ascunse în subclasă prin câmpuri cu acelaşi nume dar care pot avea, eventual, alt tip. - câmpurile unei clase pot fi ascunse chiar şi în metodele clasei respective, dacă aceste metode au argumente sau variabile locale cu acelaşi nume.

Membrii protejaţi ai clasei
Există trei moduri de acces la membrii claselor (atât la câmpuri, cât şi la metode): public, privat si protejat. Până în prezent am folosit numai modificatorii de acces public şi private, deoarece nu am declarat clase derivate. Introducem acum şi modificatorul de acces protected. Câmpurile sau metodele declarate cu acest modificator de acces sunt vizibile (accesibile) în propria clasă şi în clasele derivate din aceasta. Iată, deci, care sunt cei trei modificatori de acces la membrii unei clase folositi în declaraţiile de clase: - public - pentru câmpuri şi metode care sunt accesibile din orice clasă (inclusiv din clase aparţinând altor pachete); - protected - pentru câmpuri şi metode care sunt accesibile în propria clasă şi în subclasele acesteia, dar nu sunt vizibile din alte clase; - private - pentru câmpurile şi metodele care sunt accesibile numai din propria clasă. Acestea nu sunt accesibile din nici o alta clasă (nici chiar din subclasele propriei lor clase). Amintim că, în lipsa modificatorului de acces, câmpurile şi metodele respective sunt accesibile din clasele aceluiaşi pachet. Acest mod de acces este cunoscut sub numele de prietenos (engleză: friendly) sau de pachet (engleză: package).

Referinţele this şi super
În orice clasă pot fi utilizate două referinţe predefinite: this - este o referinţă la "această" instanţă, adică la instanţa (obiectul) din care se face referinţa respectivă; super - este o referinţă la superclasă.

Declararea clasei derivate. Clauza extends
În declaraţia clasei derivate (subclasei), numele clasei care se declară este urmat de clauza extends, în care se indică numele superclasei. În consecinţă, clasa derivată poate fi declarată astfel:
class NumeClasa extends NumeSuperclasa { declaratii_de_membri }

171

Severin Bumbaru Amintim că în limbajul Java orice clasă are o superclasă şi numai una. Excepţie face clasa Object, care este rădăcina ierarhiei de clase. Daca lipseşte clauza extends, superclasa implicită este Object.

Declararea constructorului clasei derivate
Pentru a se da valori iniţiale câmpurilor superclasei, în declaraţia constructorului subclasei poate fi invocat constructorul superclasei prin instrucţiunea
super(lista_parametri_efectivi);

unde lista parametrilor efectivi este cea a constructorului superclasei. Aceasta instrucţiune, dacă există, trebuie să fie prima instrucţiune din corpul constructorului clasei derivate. În lipsa ei, va fi invocat constructorul fără parametri al superclasei. Este posibil, de asemenea, ca într-un constructor să se invoce alt constructor al aceleeaşi clase, sub forma
this(lista_parametri_efectivi);

Redefinirea metodelor
Metodele de instanţă (nestatice) ale unei clase pot fi redefinite în subclasele acesteia. Redefinirea unei metode se face declarând în subclasă o metodă având aceeaşi signatură cu una din superclasă. Atunci când se redefineşte o metoda protejată, modificatorul de acces al acesteia poate fi menţinut, sau poate fi transformat în public. În subclasă pot fi folosite, totuşi, şi metodele superclasei care au fost redefinite, dacă la invocarea acestor metode se foloseste referinţa super.

Exemplu: În fişierul S1.java este declarata clasa S1, iar in fişierul CD1.java este declarata clasa CD1, care extinde clasa S1 (este, deci, derivată din aceasta). Reproducem aici declaraţiile celor două clase:
/* Clasa S1 din care vor fi derivate alte clase */ public class S1 { public int a; protected double b; private int c; public final static int alpha=12; public S1(int a, double b, int c) { // constructorul clasei S1 this.a=a; this.b=b; this.c=c; } private double f1() { // o metoda privata return a*c+b; }

172

Programarea orientata pe obiecte în limbajul Java

protected int f2() { // o metoda protejata return a+2*c; } public double f3() { // o metoda publica in care se folosesc f1 si f2 return 2*f1()+f2(); } public String toString() { // redefinirea metodei Object.toString() return "(S1: "+a+" "+b+" "+c+")"; } public static int f4(int k) { return 2*k+alpha; } } /* O clasa derivata din S1, in care se redefinesc unele metode */ class CD1 extends S1 { public CD1(int a, double b, int c) { super(a,b,c); } public int f2() { // redefinirea metodei f2() din superclasa return 2*a; } public double f3() { // redefinirea metodei f3() din superclasa return f2()+super.f3(); } }

Remarcăm următoarele: - în metodele clasei S1 pot fi folosite toate câmpurile declarate în această clasă, fie ele publice, protejate sau private; de asemenea, pot fi invocate toate metodele din această clasă; - în clasa S1 a fost redefinită metoda toString din clasa Object, astfel incât să se întoarca un şir care conţine valorile câmpurilor a, b şi c; - metoda statică f4() din clasa S1 utilizează numai câmpul static alpha al acestei clase; - în constructorul clasei CD1 a fost invocat constructorul clasei S1 sub forma super(a,b,c). În acest mod au fost iniţializate toate câmpurile din superclasa S1, inclusiv câmpul privat c, care nu este direct accesibil din clasa CD1; - în clasa CD1 au fost redefinite metodele f2 şi f3 din superclasa S1 şi a fost iarăşi redefinită metoda toString; - în mod normal, în clasa CD1 se folosesc metodele f2 şi f3 redefinite în această clasă. Este totuşi posibil ca în clasa CD1 să se apeleze metodele superclasei, dacă în expresia de invocare se foloseşte referinta super. Astfel, în metoda f3 din clasa CD1 există expresia super.f3(), prin care se invoca metoda f3 din superclasa S1; - la redefinirea metodei f2, modul de acces a fost modificat din protected in public; - în metodele clasei CD1 nu au putut fi folosite câmpurile şi metodele private ale superclasei S1. Testarea claselor S1 si CD1 se face în clasa TestCD1, care se găseşte în fişierul TestD1.java. Iată aceasta clasă:

173

Severin Bumbaru

/* Testarea claselor S1 si CD1 */ class TestCD1 { public static void main(String args[]) { S1 s=new S1(1, 1.1, 2); CD1 cd=new CD1(10, 10.2, 20); System.out.println("s="+s+" cd="+cd); System.out.println("s.f2()="+s.f2()+" s.f3()="+s.f3()); System.out.println("cd.f2()="+cd.f2()+" cd.f3()="+cd.f3()); System.out.println("CD2.f4(3)="+CD2.f4(3)+" S1.f4(3)="+S1.f4(3)); System.out.println("cd.f4(3)="+cd.f4(3)+" s.f4(3)="+s.f4(3)); } }

Executând aplicaţia TestCD1 se obţin urmatoarele rezultate:

s=(S1: 1 1.1 2) cd=(S1: 10 10.2 20) s.f2()=5 s.f3()=11.2 cd.f2()=20 cd.f3()=460.4 CD2.f4(3)=18 cd.f4(3)=18

Remarcăm că: - crearea instanţelor claselor S1 şi CD1 s-a făcut aplicând operatorul new asupra constructorilor claselor respective; - în instrucţiunea
System.out.println("s="+s+" cd="+cd);

este de două ori invocată implicit metoda toString, pentru a converti în şiruri afişabile obiectele s şi cd; prima dată este invocată metoda s.toString() din clasa S1. A doua oară ar trebui sa fie invocată metoda cd.toString() din clasa CD1 dar, intrucât metoda nu a fost redefinită în aceasta clasă, a fost aplicată efectiv metoda toString() din superclasa S1; - în expresiile s.f2() si s.f3() sunt invocate metodele din clasa S1, careia îi aparţine s, iar in expresiile cd.f2() şi cd.f3() sunt invocate metodele corespunzătoare din clasa CD1; - metoda statică f4() din clasa S1 a fost invocată în patru moduri: calificând numele metodei f1 cu numele clasei S1 (căreia îi aparţine de fapt această metodă), cu numele subclasei CD1 (care moşteneşte această metodă) şi cu numele instanţelor s şi cd ale acestor clase. Cum era de aşteptat, în toate cele patru cazuri s-a obţinut acelaşi rezultat. Puteti compila cele trei fişiere menţionate şi pune în executie aplicaţia TestCD1 pentru a verifica respectarea afirmaţiilor de mai sus. Puteţi, de asemenea, face modificări în metodele celor trei clase menţionate, astfel încât să verificaţi: - ce se întâmplă dacă într-o metoda a clasei CD1 se încearcă folosirea câmpurilor sau metodelor private ale superclasei S1; - ce se întâmplă dacă în clasa TestDC1 se încearcă folosirea câmpurilor sau metodelor private sau protejate din clasele S1 şi CD1.

Ascunderea câmpurilor

174

Programarea orientata pe obiecte în limbajul Java Câmpurile declarate într-o clasă pot fi ascunse prin câmpuri cu acelaşi nume declarate în subclasă, chiar dacă acestea au tipuri diferite. Aceasta înseamna că, în mod normal, în metodele clasei se folosesc câmpurile declarate în clasa respectivă, şi nu cele cu acelaşi nume ale superclasei. În subclasă pot fi, totuşi, folosite şi câmpurile superclasei, dacă sunt calificate cu referinţa super.

Ascunderea metodelor statice
Metodele statice nu aparţin instanţelor, ci clasei. Din această cauză, dacă într-o subclasă se declara o metoda statică cu aceeaşi signatură ca o metodă a superclasei, atunci se spune că metoda din subclasă o ascunde pe cea din superclasă (nu o redefineşte). Modul de lucru cu metodele ascunse este similar cu cel în care se lucrează cu câmpurile ascunse. Vom înţelege mai bine deosebirea dintre redefinire şi ascundere când vom studia polimorfismul. Exemplu În fişierul CD2.java este declarată clasa CD2, care extinde clasa S1 din exemplul precedent. În clasa CD2 există declaraţia public double a, b, m; prin care se creează câmpuri ale instanţelor acestei clase, dintre care a şi b ascund câmpurile cu aceleaşi nume ale superclasei. De asemenea, în clasa CD2 este declarată metoda statică f4(int h), care ascunde metoda statică cu aceeaşi signatura din superclasa S1.
/* O clasa in care se ascund unele din campurile superclasei */ class CD2 extends S1 { public double a, b, m; // campurile a, b le ascund pe cele cu // aceleasi nume din superclasa public CD2(int a1, double b1, int c1, double a2, double b2, double m2) { super(a1,b1,c1); // initializarea campurilor din superclasa a=a2; b=b2; m=m2; // initializarea campurilor proprii } public void afisare() { System.out.println("Campuri din CD2: a="+a+" b="+b+" m="+m); System.out.println("Campuri din S1: a="+super.a+" b="+super.b); } public static int f4(int h) { // ascunde metoda statica f4() din // superclasa return h+alpha; } public String toString() { return "(CD2: "+super.toString()+" "+a+" "+b+" "+m+")"; } }

Remarcăm că: - în constructorul clasei CD2 este mai întâi invocat constructorul superclasei, prin care se iniţializează câmpurile din S1, după care se iniţializează şi câmpurile din CD2; - întrucât metoda afisare() este declarată în clasa CD2, ea operează cu câmpurile a, b şi m

175

Severin Bumbaru declarate în CD2. Totuşi, în acesastă metodă se poate lucra şi cu câmpurile a şi b din S1, folosindu-se în mod corespunzător expresiile super.a şi super.b; - în redefinirea metodei toString() s-a folosit expresia super.toString() pentru a invoca metoda cu aceeasi signatură din superclasa S1. Observăm că pentru a utiliza în aceasta metodă în mod direct câmpurile a şi b din S1 (care sunt ascunse în clasa CD2), ar fi trebuit utilizate expresiile super.a şi super.b; Testarea clasei CD2 se face în aplicaţia din fişierul TestCd2.java, care are conţinutul următor:

/* Testarea clasei CD2 */ class TestCD2 { public static void main(String args[]) { CD2 cd=new CD2(1, 2.45, 3, 10.23, 12.5, 17.08); System.out.println("Imediat dupa ce s-a creat instanta clasei CD2:"); cd.afisare(); System.out.println("cd.toString(): "+cd.toString()); cd.a=-17.83; cd.b=28.16; cd.m=127.9; System.out.println("dupa ce s-au schimbat valorile "+ "campurilor din CD2:"); cd.afisare(); System.out.println("cd.toString(): "+cd.toString()); System.out.println("S1.f4(3)="+S1.f4(3)+" CD2.f4(3)="+CD2.f4(3)+" CD1.f4(3)=cd1.f4(3)); } }

Executând aplicaţia TestCD2 obţinem următoarele rezultate:

Imediat dupa ce s-a creat instanta clasei CD2: Campuri din CD2: a=10.23 b=12.5 m=17.08 Campuri din S1: a=1 b=2.45 cd.toString()=(CD2: (S1: 1 2.45 3) 10.23 12.5 17.08) dupa ce s-au schimbat valorile campurilor din CD2: Campuri din CD2: a=-17.83 b=28.16 m=127.9 Campuri din S1: a=1 b=2.45 cd.toString()=(CD2: (S1: 1 2.45 3) -17.83 28.16 127.9) S1.f4(3)=18 CD2.f4(3)=15 CD1.f4(3)=18

Remarcăm că: - în metoda afişare() din clasa CD2 se operează corect atât cu câmpurile a şi b din clasa CD2, cât şi cu cele ascunse de acestea din clasa S1; - în metoda toString() din CD2 se operează direct cu câmpurile a şi b declarate în această clasă şi cu metodele declarate în CD2 şi se opereaza indirect (folosind referinţa super) cu câmpurile şi metodele din superclasa S1 care sunt ascunse sau redefinite în CD2; - în expresiile S1.f4(3) şi CD1.f4(3) este invocată metoda statică a clasei S1, în timp ce în expresia CD2.f4(3) este invocată metoda cu aceeaşi signatură a clasei CD2, care o ascunde pe cea din clasa S1.

176

Programarea orientata pe obiecte în limbajul Java

Metode finale
Metodele finale sunt metode care nu mai pot fi redefinite în clasele derivate. Astfel de metode se declară cu modificatorul final. De exemplu, în clasa Object, metoda getClass este o metoda finală, fiind declarată sub forma public final Class getClass(). Aceasta inseamnă că în nici o altă clasă nu este posibil ca aceasta metodă să fie redefinită. Când declarăm propriile noastre clase, avem şi noi posibilitatea să declarăm că unele din metodele lor sunt finale.

Declararea propriilor clase de excepţii
Deşi Java API oferă o varietate destul de mare de clase de excepţii, uneori programatorul poate simţi nevoia să işi creeze propriile sale clase de exceptii. Aceasta se poate realiza derivând clasele respective din clasa Exception, existentă în pachetul java.lang. Corpul declaraţiei clasei de excepţii conţine numai declaraţii de constructori, în care se apelează constructorul corespunzător al superclasei. Exemplu: În fişierul TestExceptie.java se dă ca exemplu declararea şi utilizarea clasei ExceptieDomeniuFactorial, care este generată la calcularea funcţiei factorial(n), dacă argumentul acesteia n este negativ. S-au creat doi constructori: unul fără argument, altul având un argument din clasa String. Dintre aceştia a fost folosit unul singur. Puteţi modifica programul pentru a-l utiliza şi pe al doilea.

Clase finale
Dacă se doreşte ca o clasă să nu poată avea subclase, la declararea acesteia se foloseşte modificatorul final. De exemplu, dacă se dorea ca clasa CD1, dată ca exemplu mai sus, să fie finala, ea trebuia declarata sub forma
public final class CD1 extends S1

sau, dacă nu se dorea sa fie publica, sub forma
final class CD1 extends S1

Polimorfismul
Polimorfismul se manifestă atunci când unei variabile-referinţă pentru superclasă i se atribuie ca valoare o referinţă către o instanţă a unei subclase a acesteia, în care una sau mai multe metode ale superclasei au fost redefinite. În limbajul Java se consideră că metodele sunt legate dinamic (engleză: dynamic method binding). Aceasta înseamnă că stabilirea metodei care va fi invocată se face în momentul execuţiei, fiind invocată metoda din clasa căreia îi aparţine efectiv obiectul indicat de referinţa respectivă. Fie A o clasă, iar B o subclasă a clasei A şi fie declaraţiile
A a1, a2, a3; B b1; Object ob1, ob2, ob3;

Unei variabile referinţă dintr-o clasă, i se pot atribui ca valori referinţe la instanţe ale 177

Severin Bumbaru subclaselor sale. În consecinţă, pot exista instrucţiuni de atribuire de forma
a1=new A(); a2=new B(); b1=new B(); a3=b1; ob1=new B(); ob2=a1; ob3=b1;

Ultimele două atribuiri sunt posibile deoarece, în limbajul Java, toate clasele sunt subclase ale clasei Object. Se pune întrebarea: dacă o metodă din clasa A, fie aceasta metoda1(), a fost redefinită în clasa B, care din ele va fi executata în expresia a2.metoda1()? Observăm că, în această expresie, variabila a2 aparţine clasei A, în timp ce obiectul indicat de această variabilă aparţine efectiv clasei B. Conform principiului polimorfismului, va fi executata metoda1() din clasa B.

Atenţie: polimorfismul se aplică numai metodelor de instanţă. În cazul câmpurilor statice şi metodelor statice, care sunt redeclarate în subclasa, nu are loc o redefinire, ci o ascundere. În consecinţă, legarea acestora este statică (la compilare), iar la execuţie se vor folosi câmpurile şi metodele statice din clasa căreia ii aparţine variabila-referinţa, nu din clasa căreia îi aparţine instanţa indicată de aceasta. În atentia programatorilor de C++ În limbajul C++ polimorfismul se manifestă numai pentru metodele virtuale. Metodele care nu au fost declarate cu modificatorul virtual sunt legate static (la compilare), deci la execuţie se invocă în astfel de cazuri metoda clasei căreia îi aparţine variabila-referinţa (sau pointerul) şi nu cea căreia îi aparţine obiectul indicat. Folosind conceptele din C++, se poate considera că în limbajul Java toate metodele de instanţă sunt implicit virtuale (sunt legate dinamic), în timp ce metodele statice nu sunt virtuale (sunt legate static).

Conversia unei referinţe la clasă într-o referinţă la subclasă
Am arătat mai sus că atribuiri de forma ob1=new B() sau ob2=a1 (unde ob1 şi ob2 sunt variabile-referinţă la Object, iar a1 este variabila-referinţă la A) sunt permise, deoarece unei variabile referinţă dintr-o clasa i se pot da ca valori referinţe la instanţe ale unor subclase. În schimb, dacă încercăm să utilizăm expresia ob1.metoda1() sau ob2.metoda1() obţinem erori de compilare, deoarece metoda1() nu există în clasa Object. Pentru a evita astfel de erori, este necesar să convertim explicit (prin cast) referinţa la Object în referinţă la clasa A sau B, în care există metoda1(). În acest scop, vom folosi expresiile ((B)ob1).metoda1() sau, respectiv, ((A)ob2).metoda1(). Având în vedere că B este subclasă a lui A, iar variabila ob1 indică efectiv un obiect din clasa B, atât în expresia ((B)ob1).metoda1(), cât şi în expresia ((A)ob1).metoda1() se va invoca metoda1() din clasa B, adică din clasa căreia îi aparţine efectiv obiectul indicat de variabila-referinţă ob1.

Exemplu În fişierul Polimorf1.java se da următorul exemplu de aplicaţie în care se testează polimorfismul, folosind clasa S1 şi subclasele ei CD1 şi CD2 definite anterior:
class Polimorf1 { public static void main(String args[]) {

178

Programarea orientata pe obiecte în limbajul Java

Object ob1, ob2, ob3, ob4; S1 s1a, s1b, s1c; CD1 cd1a; CD2 cd2a; String s1="sirul1"; s1a=new S1(1, 2.5, 3); cd1a=new CD1(-1, -2.5, -3); cd2a=new CD2(10, 20.5, 30, 100, 200.5, 300); s1b=cd1a; s1c=cd2a; ob1=s1; ob2=s1a; ob3=cd1a; ob4=cd2a; System.out.println("s1a="+s1a+" s1b="+s1b); System.out.println("s1c="+s1c); System.out.println("s1a.f2()="+s1a.f2()+" s1a.f3()="+s1a.f3()); System.out.println("s1b.f2()="+s1b.f2()+" s1b.f3()="+s1b.f3()); System.out.println("s1c.f2()="+s1c.f2()+" s1c.f3()="+s1c.f3()); System.out.println("ob1="+ob1+" ob2="+ob2+" ob3="+ob3); System.out.println("ob4="+ob4); System.out.println("cd1a.f2()="+cd1a.f2()+" cd1a.f3()="+cd1a.f3()); System.out.println("cd2a.f2()="+cd2a.f2()+" cd2a.f3()="+cd2a.f3()); System.out.println("((S1)ob2).f2()="+((S1)ob2).f2()+ " ((S1)ob2).f3()="+((S1)ob2).f3()); System.out.println("((S1)ob3).f2()="+((S1)ob3).f2()+ " ((S1)ob3).f3()="+((S1)ob3).f3()); System.out.println("((CD1)ob3).f2()="+((CD1)ob3).f2()+ " ((CD1)ob3).f3()="+((CD1)ob3).f3()); System.out.println("((S1)ob4).f2()="+((S1)ob4).f2()+ " ((S1)ob4).f3()="+((S1)ob4).f3()); System.out.println("((CD2)ob4).f2()="+((CD2)ob4).f2()+ " ((CD2)ob4).f3()="+((CD2)ob4).f3()); } }

La executarea acestei aplicaţii se obţin pe ecran următoarele rezultate:

s1a=(S1: 1 2.5 3) s1b=(S1: -1 -2.5 -3) s1c=(CD2: (S1 10 20.5 30) 100.0 200.5 300.0) s1a.f2()=7 s1a.f3()=18.0 s1b.f2()=-2 s1b.f3()=-3.0 s1c.f2()=70 s1c.f3()=711.0 ob1=sirul1 ob2=(S1: 1 2.5 3) ob3=(S1: -1 -2.5 -3) ob4=(CD2: (S1 10 20.5 30) 100.0 200.5 300.0) cd1a.f2()=-2 cd1a.f3()=-3.0 cd2a.f2()=70 cd2a.f3()=711.0 ((S1)ob2).f2()=7 ((S1)ob2).f3()=18.0 ((S1)ob3).f2()=-2 ((S1)ob3)f3()=-3.0 ((CD1)ob3.f2()=-2 ((CD1)ob3).f3()=-3.0 ((S1)ob4).f2()=70 ((S1)ob4).f3()=711.0 ((CD2)ob4).f2()=70 ((CD2)ob4).f3()=711.0

Remarcăm următoarele: - atribuiri de forma s1b=cd1a sau ob1=s1 sunt corecte, întrucât unei variabile-referinţă dintro clasă i se pot da ca valori referinţe la instanţe ale subclaselor acesteia; - expresiile s1a.f2() şi ((S1)ob2).f2() au aceeaşi valoare (7), intrucat in ambele cazuri este invocata metoda f2() din clasa S1;

179

Severin Bumbaru - în expresia ((S1)ob2).f2() s-a folosit castul pentru a converti referinţa la Object intr-o referinţă la S1, întrucat în clasa Object nu există o metoda f2(); - expresiile s1b.f2(), cd1a.f2(), ((S1)ob3).f2() şi ((CD1)ob3).f2()au aceeasi valoare (-2), deoarece în toate cele trei cazuri metoda f2() se aplică asupra aceluiaşi obiect; - în expresia s1b.f2() nu a fost necesar să se folosească o conversie explicită (cast) de forma ((CD1)s1b).f2(), deoarece o metoda cu signatura f2() există şi în clasa S1, căreia îi aparţine variabila s1b. Totuşi, în momentul execuţiei, se aplică efectiv metoda f2() din clasa CD2, căreia îi aparţine obiectul indicat de s1b; - în mod similar, expresiile s1c.f2(), cd2a.f2(), ((S1)ob4).f2() si ((CD2)ob4).f2() au aceeaşi valoare (70); - în instrucţiunea System.out.println("s1a="+s1a+" s1b="+s1b); se foloseşte implicit metoda toString()pentru a se converti obiectele indicate de variabilele-referinţă s1a şi s1b în şiruri. Se foloseşte în ambele cazuri metoda toString() din clasa S1, deoarece în clasa CD1 nu a fost redefinită o astfel de metodă; - în instrucţiunea System.out.println("s1c="+s1c); se foloseşte implicit metoda toString() din clasa CD2, căreia îi aparţine efectiv obiectul indicat de variabila s1c;

Un exemplu: clasa Persoana si subclasa Student
Studentul are toate atributele unei persoane, dar are şi atribute şi metode specifice. În consecinţă, clasa Student poate fi derivată din clasa Persoana. În continuare dăm exemple de declarare şi de testare a celor două clase.

Declararea clasei Persoana
În fişierul Persoana.java, reprodus aici, este declarată clasa Persoana.
public class Persoana { private String nume, prenume; private int anNastere; public Persoana(String nume, String prenume, int anNastere) { this.nume=nume; this.prenume=prenume; this.anNastere=anNastere; } public Persoana(Persoana pers) { nume=pers.nume; prenume=pers.prenume; anNastere=pers.anNastere; } public String nume() { return nume; } public String prenume() { return prenume; }

180

Programarea orientata pe obiecte în limbajul Java
public int anNastere() { return anNastere; } public int varsta(int anCurent) { return anCurent-anNastere; } public String toString() { return nume+" "+prenume+" "+anNastere; } public int hashCode() { return nume.hashCode()+prenume.hashCode()/1000+anNastere%100; } }

Clasa contine trei câmpuri private: două referinţe la String (nume şi prenume) şi un câmp de tip int (anNaştere). Întrucât câmpurile sunt private, ele nu sunt vizibile din alte clase (nici chiar din subclasele clasei Persoana). Clasa Persoana are doi constructori. Primul constructor primeşte ca argumente valorile celor trei câmpuri: nume, prenume, anNaştere. Întrucât argumentele au aceleaşi nume cu câmpurile corespunzătoare, în corpul constructorului numele de câmpuri sunt calificate cu referinţa this. Aceasta se putea evita schimband numele argumentelor. Al doilea constructor are ca argument o instanţă a clasei Persoana, fiind ceeace se numeşte un "constructor de copiere". Vom vedea utilitatea lui in continuare, la declararea clasei Student. Metodele nume(), prenume() şi anNastere() au rolul de a întoarce fiecare valoarea câmpului corespunzator. Fiind publice, ele pot fi utilizate în alte clase. Aşa dar, este posibil să obţinem în altă clasă valorile câmpurilor unui obiect Persoana, dar nu putem modifica aceste valori. Remarcăm că este posibil ca numele unei metode să coincidă cu cel al unui câmp. Nu are loc nici o confuzie, întrucât numele metodei este insoţit întotdeauna de paranteze, ca în expresia p1.nume(), care are ca valoare numele persoanei p1. Metoda varsta(int anCurent) întoarce vârsta persoanei, fiind dat ca argument anul pentru care se calculează. Este un exemplu de metodă care întoarce un atribut al persoanei (vârsta), care nu este memorat într-un câmp, ci se obţine prin calcul. Metodele toString() şi hashCode() redefinesc metodele corespunzătoare ale superclasei Object.

Declararea clasei Student
Clasa publică Student este declarată în fişierul Student.java şi este reprodusă aici.
public class Student extends Persoana { private String fac, gr; private int anStudii; public Student(String nume, String prenume, int anNastere, String facultate, int an, String grupa) { super(nume, prenume, anNastere); fac=facultate; anStudii=an; gr=grupa;

181

Severin Bumbaru
} public Student(Persoana pers, String facultate, int an, String grupa) { super(pers); fac=facultate; anStudii=an; gr=grupa; } public Student(Student stud) { super(stud.nume(), stud.prenume(), stud.anNastere()); fac=stud.fac; anStudii=stud.anStudii; gr=stud.gr; } public String facultate() { return fac; } public int an() { return anStudii; } public String grupa() { return gr; } public String toString() { return super.toString()+" fac: "+fac+" an: "+anStudii+ " grupa: "+gr; } }

Clasa Student moşteneşte câmpurile şi metodele superclasei Persoana. În plus, conţine următoarele câmpuri private: două referinţe la String (fac - facultatea şi gr - grupa) şi un int (anStudii). Au fost declaraţi trei constructori. Primul are ca argumente valorile tuturor câmpurilor, atât ale celor moştenite de la superclasă, cât şi ale celor proprii. Întrucât câmpurile superclasei sunt private, nu a fost posibilă iniţializarea lor directă, prin operaţii de atribuire, ci prin apelarea constructorului superclasei sub forma
super(nume, prenume, anNastere);

Al doilea constructor are ca prim argument o referinţă la persoană, iar ultimele trei argumente sunt aceleaşi ca la constructorul precedent. Pentru iniţializarea câmpurilor superclasei a fost invocat constructorul acesteia sub forma
super(pers); în care pers este referinţa la o Persoana. S-a utilizat aici constructorul de copiere Persoana(Persoana pers). Remarcăm că nu am fi putut utiliza instrucţiunea super(pers.nume, pers.prenume, pers.anNastere);

deoarece câmpurile clasei Persoana sunt private şi nu sunt, deci, accesibile din clasa Student. În lipsa constructorului de copiere, am fi putut utiliza in schimb instructiunea
super(pers.nume(), pers.prenume(), pers.anNastere()); deoarece metodele nume(), prenume() şi anNastere() sunt publice.

Metodele publice facultate(), an() şi grupa() întorc valorile câmpurilor specifice clasei Student, respectiv fac, anStudii şi gr. Metoda toString() redefineşte metoda cu aceeaşi signatură a superclasei. 182

Programarea orientata pe obiecte în limbajul Java

Utilizarea claselor Persoana şi Student
Aplicaţia din fişierul TestStud.java are ca obiectiv testarea claselor Persoana şi Student.
class TestStud { public static void main(String args[]) { Persoana p1=new Persoana("Vasiliu","George",1981), p2; Student s1=new Student(p1, "NIE",2,"2221a"), s2=new Student("Ionescu","Maria", 1980, "NIE", 2, "2322b"), s3; Object ob1, ob2, ob3; Persoana tabPers[]=new Persoana[3]; ob1=s1; ob2=p2=s2; ob3=p1; System.out.println("p1="+p1); System.out.println("s1="+s1); System.out.println("s2="+s2); System.out.println("ob1="+ob1); System.out.println("p2="+p2); System.out.println("ob2="+ob2); System.out.println("ob3="+ob3); System.out.println("Numele lui p1: "+p1.nume()); System.out.println("Numele lui s1: "+s1.nume()); System.out.println("Numele lui ob1: "+((Persoana)ob1).nume()); s3=(Student)ob2; System.out.println("s3="+s3); System.out.println("In anul 2000, s3 are "+s3.varsta(2000)+" ani"); System.out.println("p2.hashCode()="+p2.hashCode()); System.out.println("s1.hashCode()="+s1.hashCode()); tabPers[0]=p1; tabPers[1]=s1; tabPers[2]=s2; System.out.println("Tabloul de persoane tabPers contine:"); System.out.println("Clasa lui p1: "+p1.getClass().getName()); System.out.println("Clasa lui ob2: "+ob2.getClass().getName()); for(int i=0; i<tabPers.length; i++) System.out.println(tabPers[i]); } }

Se testează funcţionarea metodelor polimorfe toString() din clasele Persoană şi Student, funcţionarea castului de la Object la Student si funcţionarea metodei hashCode(), redefinită în clasa Persoana şi mostenită de clasa Student. Se testează, de asemenea, metoda getClass(), moştenita de la clasa Object. Se construieşte şi se utilizează un tablou de referinţe la Persoana.

O variantă în care se utilizează câmpuri protejate
În fişierele Persoana1.java, Student1.java si Test1.java sunt date variante ale fişierelor comentate mai sus, în care sunt făcute următoarele modificări: - în clasa Persoana1 câmpurile nu mai sunt private, ci protejate; - în clasa Persoana1 s-a introdus în plus un constructor fără argumente. Acest constructor este necesar pentru a fi invocat implicit de constructorul clasei Student1, aşa cum vom arăta mai jos; - în primul constructor al clasei Student1 nu a mai fost invocat explicit constructorul clasei 183

Severin Bumbaru Persoana. Aceasta înseamnă că, la construirea instanţei Student1, a fost invocat implicit constructorul fără argumente Persoana1() al superclasei. Atribuirea de valori câmpurilor protejate ale superclasei se face, apoi, direct în corpul constructorului clasei Persoana1. S-au folosit, în acest scop, instrucţiuni de forma this.nume=nume, întrucât numele câmpului coincide cu cel al argumentului. Referinţa this este utilizată corect, intrucât câmpul superclasei Persoana1 a fost moştenit de clasa Student1, ne fiind acoperit în aceasta. Se putea folosi, însă, şi referinţa super sub forma super.nume=nume, deoarece câmpul nume aparţine, de fapt, superclasei; - în al doilea constructor al clasei Student1 nu a fost invocat explicit, de asemenea, constructorul superclasei. Atribuirea de valori câmpurilor protejate ale superclasei Persoana1 s-a făcut direct în corpul clasei Student1, prin instrucţiuni de forma nume=pers.nume, unde Pers este argumentul din clasa Persoana1; - s-au înlocuit peste tot, în cele trei fişiere, numele de clase Persoana şi Student prin Persoana1 şi Student1. Complilând fişierul TestStud1.java şi executând aplicaţia TestStud1 constatăm că se obţin aceleaşi rezultate ca în cazul aplicaţiei TestStud. Întrucât clasele Persoana1 şi Student1 sunt publice, compilarea explicită a fişierelor respective nu este necesară, deoarece ea se face automat când se compilează fişierul TestStud1.java.

Instanţierea clasei care conţine metoda main
Clasa care conţine metoda main este clasa principală a unei aplicaţii, deci este prima clasă care se încarcă în maşina virtuală Java la punerea în execuţie a aplicaţiei respective. În acelaşi timp, ea este o clasă ca oricare alta, care poate avea şi instanţe. Metoda main(), fiind statică, nu poate utiliza direct decât câmpurile statice şi metodele statice ale acestei clase. Totuşi, în metoda main() sau în alte metode, pot fi create instanţe ale acestei clase, putându-se astfel utiliza şi câmpurile şi metodele de instanţă ale acestora. Exemplu În fişierul Aplic.java este dat un exemplu de aplicaţie, în care clasa Aplic, care conţine metoda main(), are câmpuri şi metode statice şi de instanţă şi constructor.
/* O clasa care contine metoda main() si poate fi instantiata */ class Aplic { static int a=10; // un camp static (al clasei) double b; // camp al instantei String s; // camp al instantei Aplic(double b, String s) { // constructorul clasei Aplic this.b=b; this.s=s; } double f1(double x) { // o metoda (functie) de instanta return a*b+1; // se utilizeaza campul static si cel de instanta } static int f2(int m) { // o metoda (functie) statica return m+a; // se utilizeaza numai campul static

184

Programarea orientata pe obiecte în limbajul Java

} public static void main(String args[]) { String s0="sir implicit 0", s1="sir implicit 1"; Aplic a0, a1; // Doua referinte la instante ale clasei Aplic if(args.length>=1) s0=args[0]; if(args.length>=2) s1=args[1]; /* Se creaza doua instante ale clasei Aplic */ a0=new Aplic(12.47, s0); a1=new Aplic(-15.28, s1); /* Campul static a si metoda statica f2 pot fi utilizate direct, sau calificandu-le cu numele clasei sau cu referinta la o instanta */ System.out.println("a="+a+" Aplic.a="+Aplic.a+" a0.a="+a0.a+ " a1.a="+a1.a); System.out.println("f2(2)="+f2(2)+" Aplic.f2(2)="+Aplic.f2(2)+ " a0.f2(2)="+a0.f2(2)+" a1.f2(2)="+a1.f2(2)); /* Campurile si metodele de instanta se folosesc prin intermediul referintelor la instantele respective */ System.out.println("a0.b="+a0.b+" a0.s="+a0.s); System.out.println("a1.b="+a1.b+" a1.s="+a1.s); System.out.println("a0.f1(1.5)="+a0.f1(1.5)+ " a1.f1(1.5)="+a1.f1(1.5)); /* Modificam campul static a si il afisam in diferite moduri */ a=20; System.out.println("Dupa modificari:"); System.out.println("a="+a+" Aplic.a="+Aplic.a+" a0.a="+a0.a+ " a1.a="+a1.a); /* Modificam campul b din fiecare instanta si afisam */ a0.b=32.5; a1.b=-121.8; System.out.println("a0.b="+a0.b+" a1.b="+a1.b); } }

Remarcăm urmatoarele: - clasa Aplic are câmpuri statice şi de instanţă şi alte metode statice şi de instanţă, pe lângă metoda main(); - clasa Aplic are şi un constructor (putea avea chiar mai mulţi, sau numai pe cel implicit); - în metoda main() se creează două instanţe ale clasei Aplic, cu referinţele a0 şi a1; - în metodele statice main()şi f2(), câmpul static a şi metoda statica f2() pot fi utilizate direct, sau calificându-le cu numele clasei sau cu o referinţă la o instanţă a acesteia. În toate cazurile se obtine acelaşi rezultat; - câmpurile şi metodele de instanţă nu pot fi utilizate direct în metoda statică main(), dar pot fi utilizate cele ale instanţelor, calificandu-le cu referinţele la aceste instanţe, de exemplu a0.b sau a0.f1(1.5); - în metodele de instanţă pot fi utilizate direct atât câmpurile clasei, cât şi ale instanţei. Astfel, în metoda de instanţă f1() se foloseşte expresia a*b+1, în care apar câmpul static a şi câmpul de instanţă b.

185

Severin Bumbaru

Clase abstracte
Clasele abstracte conţin în declaraţia lor modificatorul abstract. Clasele abstracte nu pot fi instanţiate. Remarcăm însa că pot exista clase care nu pot fi instanţiate deşi nu sunt abstracte, cum sunt, de exemplu, clasele care nu au decât constructori privaţi. Cu toate că clasa abstractă nu poate fi instanţiată, se pot declara variabile aparţinând unor clase abstracte. Acestor variabile li se pot da însa ca valori numai referinţe către instanţe ale unor subclase concrete. De exemplu, daca A este o clasă abstracta, iar B este o subclasă concreta a clasei A, atunci este corectă declaraţia
A a1=new B();

Din punct de vedere conceptual, clasa abstractă modelează un concept general, care trebuie apoi dezvoltat prin subclase. Subclasele unei clase abstracte pot fi, la rândul lor, abstracte sau concrete. De exemplu, clasa abstractă FiguraPlana poate avea ca subclase clasele Cerc, Elipsa, Poligon etc. Din punct de vedere al progrămarii, clasa abstractă conţine cel puţin o metodă abstractă, adică o metodă pentru care s-a declarat numai antetul, fără să i se definească şi corpul. O clasă poate să conţină o metodă abstractă în mai multe moduri: a/ în corpul clasei este declarată explicit o metodă abstractă; b/ clasa mosteneşte de la superclasa ei o metodă abstractă, care nu este definita nici în corpul clasei curente; c/ clasa implementeaza o interfaţă, dar nu defineşte una sau mai multe din metodele acesteia. Dacă apare o astfel de situaţie, iar clasa nu este declarată în mod explicit abstractă, se genereaza o eroare la compilare. Declararea unei metode abstracte: - antetul metodei trebuie sa conţina modificatorul abstract; - corpul metodei se înlocuieşte prin caracterul ';' (punct şi virgulă).

Exemplu: În fişierul FiguriPlane.java sunt declarate clasa abstractă FiguraPlana şi clasele instanţiabile Cerc şi Patrat. Este declarată, de asemenea clasa-aplicaţie în care se testează aceste clase.

186

Programarea orientata pe obiecte în limbajul Java

Interfeţe
Conceptul de interfaţă
Conform principiului încapsulării, fiecare clasă are "interfaţa" sa intrinsecă, prin care poate fi accesata din exterior. Aceasta "interfaţă" conţine toate datele şi metodele publice ale clasei respective. Pentru a compensa lipsa moştenirii multiple, în limbajul Java s-a admis că o clasă poate avea mai multe interfeţe şi că mai multe clase pot implementa aceeaşi interfaţă. S-a introdus astfel o nouă categorie de componente, numite interfeţe, care se declară în mod asemănător cu nişte clase abstracte, dar nu sunt înglobate, aşa cum sunt clasele abstracte, în ierarhia unică de clase .

Interfaţa este o specificaţie care descrie metodele publice şi variabilele finale publice pe care trebuie sa le aibă o clasă care implementeaza interfaţa respectivă. Dacă o clasă implementează mai multe interfeţe, ea conţine toate metodele publice şi variabilele finale publice ale acestora. Interfaţa nu este o clasă, dar poate fi utilizată de programator ca şi când ar fi o clasă abstractă. Se pot declara variabile referinţă la o interfaţă în mod asemănător cu declararea variabilelor referinţa la obiecte aparţinând unei clase, adică sub forma:
interfaţa nume_variabila1[=initializare1], ..., nume_variabilaN[=initializareN];

în care interfaţa este numele unei interfeţe, iar celelalte elemente ale declaraţiei sunt aceleaşi ca în cazul declarării de referinţe la obiecte. Interfeţele pot fi şi ele organizate ierarhic, aplicându-se principiul moştenirii. În schimb, pentru interfeţe, ierarhia nu mai este unică, aşa cum este în cazul claselor, şi se admite moştenirea multiplă. Remarcam că, la fel ca şi clasele abstracte, interfeţele nu pot fi instanţiate. În schimb, unei variabile referinţă la o interfaţă i se pot da ca valori referinţe către obiecte din orice clasă care implementează acea interfaţă sau este descendentă a unei astfel de clase. Pentru a le distinge mai uşor, în documentaţia Java API numele interfeţelor sunt scrise cu litere cursive (stil italic), în timp ce numele claselor sunt scrise cu litere normale (drepte). De exemplu, în pachetul java.lang există interfaţa Cloneable care este implementată de toate clasele din SDK care implementează metoda clone(), deci ale căror obiecte pot fi clonate. Sa considerăm acum exemplul din Figura 1, în care clasele B şi C implementeaza interfeţele I1 şi I2, iar clasele C si D implementează interfeţele I2 şi I3.

187

Severin Bumbaru

- Figura 1 În acest exemplu, interfetele I1 şi I2 sunt derivate din interfata I0 deci moştenesc variabilele finale şi metodele acesteia, putând avea fiecare, de asemenea, date finale, câmpuri şi metode proprii. Clasa B implementează interfaţa I1, deci conţine variabilele finale şi metodele interfeţei I1 şi pe cele mostenite de aceasta de la interfaţa I0. Clasa C implementează interfeţele I1 şi I2, deci conţine variabilele finale şi metodele interfeţelor I1 şi I2, inclusiv pe cele moştenite de acestea de la interfaţa I0. Totodată, clasele A, B şi C moştenesc datele şi metodele clasei A. Faptul că variabilele finale şi metodele interfeţei I0 se propagă la clasa C atât pe traseul I0-I1-C, cât şi pe traseul I0-I2-C nu mai produce dificultăţi, ca în cazul moştenirii multiple a claselor, deoarece implementarea efectivă a acestor variabile şi metode se face în clasa C şi nu in interfeţe. Pentru exemplul din Figura 1, să considerăm acum următoarele declaraţii şi instrucţiuni de atribuire valabile, în care A(), B(),C() si D() sunt constructori ai claselor respective:
A v1=new A(), v2; B v3=new B(), v4; C v5=new C(), v6; I0 w1=new B(),w2; I1 w3=new C(), w4; I2 w5, w6; v2=v5; w2=new D(); v4=v3; w4=v3; v6=new C(); w5=v6; w2=w4;

Constatăm că variabilele referinţă la interfeţe (w1, w2, w3 etc.) sunt utilizate la fel ca şi variabilele referinta la obiecte ale claselor. Dacă, insă, ştiind că variabila w1 are ca valoare o referinţă la un obiect din clasa B, vom dori sa folosim instrucţiunea
v4=w1;

vom constata că apare o eroare de compilare, deoarece v4 este referinţă la un obiect al unei clase, în timp ce w1 este o referinţă la interfaţă. În această situaţie este necesară o conversie explicită (cast), astfel că vom folosi instrucţiunea
v4=(B)w1;

188

Programarea orientata pe obiecte în limbajul Java

Declararea interfeţelor
O declaraţie de interfaţă introduce un nou tip referinţă, ai cărui membri sunt câmpuri statice finale şi metode abstracte. În consecinţă, interfaţa se aseamănă cu o clasa abstractă pură, care nu conţine decât metode abstracte şi câmpuri statice finale şi nu se încadrează în ierarhia unică de clase descendente din Object (amintim ca o clasa abstractă, în afară de una sau mai multe metode abstracte, poate conţine şi câmpuri de date şi metode concrete şi este descendentă a clasei Object). Deşi nu se încadrează în ierarhia claselor, interfeţele se pot constitui in diverse ierarhii de interfeţe, aplicându-li-se principiul mostenirii. O clasă poate implementa mai multe interfeţe. Cel mai important avantaj al folosirii interfeţelor este că mai multe clase, de pe diferite ramuri ale arborelui ierarhic al claselor, pot fi "văzute" printr-o singură interfaţă. Se pot declara variabile referinţă la interfaţă la fel cum se pot declara variabile referinţă la clasă. Interfaţa este abstractă şi deci nu poate fi instanţiată. În schimb, unei variabile referinţă la interfaţă i se pot atribui ca valori referinţe la obiecte din orice clasă care implementează interfaţa respectivă. Declaraţia de interfaţă are forma generală următoare:
[public] interface NumeInterfata [extends lista_superinterfete] { declaratii_de_membri_ai_interfetei }

În afară de modificatorul public, se poate folosi şi modificatorul abstract, numai că acesta este implicit, deci folosirea lui este de prisos. Spre deosebire de clase, la care moştenirea multiplă este interzisă (o clasă poate avea numai o singura superclasă), în cazul interfeţelor moştenirea multiplă este permisă. În consecinţă, clauza extends poate conţine o listă de nume de interfeţe separate prin virgule. Corpul interfeţei conţine una sau mai multe declaraţii de membru al interfeţei. Declaraţiile de membri ai interfeţei pot fi: a/ Declaraţia de câmpuri statice finale, numită şi declaraţie de constante, sub forma:
tip NUME_CAMP1=valoare1,NUME_CAMP2=valoare2,... , NUME_CAMP_N=valoareN;

în care tip este un tip primitiv. Modificatorii public, static şi final pentru câmpurile interfeţei sunt impliciţi, deci folosirea lor este de prisos. Aceasta înseamnă că toate câmpurile unei interfeţe sunt publice, statice şi finale, deci ele trebuie să fie iniţializate la declarare şi nu mai pot fi modificate ulterior. Se obisnuieşte ca numele de câmp ale interfeţelor să fie scrise numai cu majuscule.

Exemplu:
interface CuloareDeBaza { int ROSU=1, VERDE=2, ALBASTRU=4; }

189

Severin Bumbaru
interface Culoare extends CuloareDeBaza { int GALBEN=3, ORANGE=5, INDIGO=6, VIOLET=7; }

b/ Declaraţia de metodă abstractă, care constă din antetul metodei urmat de simbolul ';' (punct şi virgulă). Ea este deci la fel ca o declaraţie de metodă abstractă din corpul unei clase, cu următoarele observaţii: - modificatorii public şi abstract sunt impliciti, deci folosirea lor este de prisos (dar este permisă); - modificatorul final nu poate fi folosit, deoarece se declară o metodă abstractă; - corpul metodei este înlocuit prin simbolul punct şi virgulă, ca la orice metodă abstractă.

Exemplu:
interface Interf1 { double metoda1(); int metoda2(int a, int b, double c); }

Ambele metode din exemplul de mai sus sunt implicit publice şi abstracte. Orice clasă, care implementează aceasta interfaţă, trebuie sa conţină declaraţii de metode cu aceeaşi semnătură cu cele din interfaţă. La definirea acestor metode îm cadrul claselor, folosirea identificatorului public este obligatorie.

Clase imbricate şi clase interioare
În limbajul Java se permite ca o clasă să aibă ca membri alte clase. Acestea se numesc clase imbricate sau clase încuibate (engleza: nested classes). Ca şi ceilalţi membri, clasele imbricate pot fi statice sau nestatice şi se declară în corpul clasei care le încorporează. Clasele imbricate nestatice se numesc clase interioare (engleză: inner classes). O clasă care încorporează (imbrică) alte clase se declară astfel:
[public] class NumeClasa { declaratii_de_membri_ai_clasei [modificatori_de_camp] class NumeClasaImbricata { declaratii_de_membri_ai_clasei_imbricate } declaratii_de_membri_ai_clasei }

Se recurge la aceasta tehnică atunci când utilizarea unei clase are sens numai în cadrul altei clase. Aşadar, prin imbricare se creează o legatură strânsa între două clase. Este posibil, desigur, ca o clasa să conţină mai multe clase imbricate. Remarcăm că declaraţia clasei imbricate este tratata ca oricare alta declaraţie de membru al clasei, putând avea şi modificatori de câmp (de exemplu static). Fiind membru al clasei, clasa imbricată are acces la toţi membrii clasei care o conţine, chiar şi la cei privaţi. Dacă clasa imbricată este statică, ea nu poate referi direct decât membrii statici

190

Programarea orientata pe obiecte în limbajul Java ai clasei care o conţine. Clasa imbricată statică există, la fel ca şi câmpurile sau metodele statice, numai în cadrul clasei care o conţine, nu şi în instanţele acesteia. În consecinţă, pentru a referi membrii acestei clase, se foloseşte pentru calificare numele clasei. Clasa interioară (clasa imbricată nestatică) are câte o instanţă în interiorul fiecărei instanţe a clasei care o conţine. În consecinţă, accesul la membrii ei se poate face folosind drept calificator referinţa la o instanţă. Clasa interioară are acces la toţi membrii clasei care o conţine, atât la cei statici, cât şi la cei de instanţă (nestatici).

Exemplu: În secţiunea Interfeţe a acestui curs, a fost dat un exemplu de aplicaţie (din fişierul Integrare.java), în care se face integrarea funcţiilor prin metoda trapezelor. În fişierul Integrare.java, toate clasele sunt declarate în mod obişnuit, ca nişte clase autonome. În consecinţă, după compilare se obţin urmatoarele fişiere cu extensia class: Functie1.class, FunctieReala.class, Integrare.class, Integrator.class, IntegrareTrapeze.class. Clasa IntegrareTrapeze poate fi folosită în diferite aplicaţii, deci are sens să fie declarată ca o clasa separată. În schimb, clasa Funcţie1 este specifica numai unei singure aplicaţii. Este deci preferabil să fie declarata ca o clasă interioară a acestei aplicaţii, aşa cum se face în fisierul Integrare1.java. Clasa Functie1, din acest exemplu, a fost declarată ca o clasa interioară (clasă imbricată nestatică) a clasei-aplicaţie Integrare1. În consecinţă, accesul la membrii ei se poate face numai printr-o instanţă a clasei Integrare1. În acest scop, s-a procedat astfel: a/ s-a creat în metoda main() o instanţă a clasei Integrare1 prin instrucţiunea
Integrare1 int1=new Integrare1();

b/ s-a creat o instanţă a clasei Functie1 prin instrucţiunea
Functie1 f1=int1.new Functie1(arg1,arg2,arg3,arg4);

c/ Referinţa f1 la o instanţă a clasei interioare f1 a fost apoi utilizată în mod obişnuit, ca argument al metodei integrare(); Atragem atenţia asupra modului cum s-a făcut instanţierea clasei interioare Functie1: operatorul new a fost calificat cu referinta int1 la o instanţă a clasei Integrare1, care o conţine. Procedeul de mai sus a fost necesar, întrucat metoda main() este statică, astfel că nu poate accesa direct un membru nestatic al clasei, ci numai prin crearea unei instanţe a acesteia. Pentru comparaţie, în acelaşi fişier s-a declarat (tot în clasa-aplicaţie Integrare1) şi o clasa imbricată statică numită Functie2. Remarcăm că instanţierea acesteia s-a făcut în mod "obişnuit" prin instrucţiunea
Functie2= func2=new Functie2(arg1,arg2,arg3); iar utilizarea funcţiei statice f2() conţinute în aceasta s-a facut sub forma "obişnuită" Functie2.f2(arg1,arg2) . De asemenea, utilizarea funcţiei nestatice f() a acestei clase s-a făcut "obişnuit", sub forma func2.f(arg), iar instanţa func2 a fost transmisă şi ca argument

al metodei de integrare. După compilarea fişierului Integrare1.java, constatăm că au fost creeate următoarele fişiere cu extensia class: FunctieReala.class, Integrare1.class, Integrare1$Functie1.class,

191

Severin Bumbaru Integrare1$Functie2.class, IntegrareTrapeze.class, Integrator.class. Remarcam ca numele fişieror de bytecode care conţin clase imbricate sau interioare încep prin numele clasei care le conţine, urmat de caracterul $ şi de numele clasei imbricate (interioare) respective. În acest fel, devine posibil ca în două aplicaţii distincte să existe clase imbricate sau interioare cu acelaşi nume, fără să se creeze confuzii.

192

Programarea orientata pe obiecte în limbajul Java

Exemplu de declarare, implementare şi utilizare a unor interfeţe
Considerăm că dorim să realizăm diferite clase de integratoare numerice, care calculează prin diferite metode valoarea integralei pe intervalul [inf, sup] a funcţiei reale f(x). Dorim ca, în oricare din aceste integratoare, să existe metoda de calculare a integralei:
double integrala(double inf, double sup, FunctieReala func)

în care inf şi sup sunt, respectiv, marginea inferioara şi cea superioara a intervalului de integrare, iar func este o referinţă la un obiect care contine funcţia de integrat f(x). Având în vedere că nu avem, deocamdată, în vedere o metoda de integrare numerică particulară, vom declara interfaţa

interface Integrator { double integrala(double inf, double sup, FunctieReala func); }

în care metoda de integrare apare ca o metoda abstractă. Cu aceasta ocazie remarcam că, spre deosebire de limbajele de programare tradiţionale, cum sunt Pascal sau C/C++, în limbajul Java nu există posibilitatea ca o funcţie (metodă) să aibă ca argument o referinţa la altă funcţie (metodă). În schimb, putem să-i oferim ca argument o referinţa la un obiect care conţine funcţia respectivă. Astfel, în cazul nostru, func este referinţă la un obiect care conţine funcţia de integrat f(x). Având în vedere că dorim ca integratoarele noastre să fie valabile pentru orice funcţie reală f(x), vom declara o interfaţă:

interface FunctieReala { double f(double x); }

care conţine metoda abstractă f(x) pe care trebuie să o conţină orice obiect pe care îl oferim ca argument metodei de integrare din interfaţa Integrator. Să realizăm acum un integrator concret, pentru o anumita metodă particulară de integrare. Pentru simplitate, vom implementa integrarea prin metoda trapezelor. Ştiind că integrala poate fi interpretată geometric ca aria suprafeţei cuprinse între graficul funcţiei respective şi axa ox, putem împărţi intervalul de integrare [inf, sup] în n subintervale de lungime egala h=(sup-inf)/n, ca în Figura 1.

193

Severin Bumbaru

- Figura 1 Înlocuind pe fiecare din aceste subintervale curba f(x) prin coarda ei, obtinem n trapeze. Considerand că ariile cuprinse între curbă şi coarda curbei în fiecare din subintervale sunt mici, aproximăm aria de sub curbă prin suma ariilor celor n trapeze. Se obţine astfel pentru calcularea integralei I cunoscuta formulă a integrării numerice prin metoda trapezelor dată în Figura 2.

- Figura 2 Pentru calcularea integralei este necesar să se impună şi numărul de paşi n, pe care îl vom nota în continuare cu nrPasi. Putem astfel declara clasa IntegrareTrapeze, care implementeaza interfaţa Integrator, în modul următor:

class IntegrareTrapeze implements Integrator { int nrPasi; // Numarul de pasi de integrare /* Constructorul initializeaza numarul de pasi de integrare */ public IntegrareTrapeze(int nrPasi) throws Exception { if(nrPasi<=0) throw new Exception("IntegrareTrapeze: nrPasi<=0"); this.nrPasi=nrPasi; } /* Metoda de modificare a numarului de pasi de integrare */ public void puneNrPasi(int nrPasi) throws Exception { if(nrPasi<=0) throw new Exception("IntegrareTrapeze: nrPasi<=0"); this.nrPasi=nrPasi; } /* Implementarea metodei abstracte din interfata Integrator */ public double integrala(double inf, double sup, FunctieReala func) { double h, s; h=(sup-inf)/nrPasi; // h este pasul de integrare /* Calcularea sumei ordonatelor s */ s=0; for(int i=1; i<nrPasi; i++) s+=func.f(inf+i*h);

194

Programarea orientata pe obiecte în limbajul Java

/* Calcularea valorii integralei si intoarcerea rezultatului */ return ((func.f(inf)+func.f(sup))/2+s)*h; } }

La calcularea integralei s-a aplicat formula din Figura 2, luând în consideraţie că xi=inf+i*h. Remarcăm că funcţia f(x) nu a fost încă precizată. S-a indicat numai sub forma func.f(inf+i*h) că se foloseşte funcţia (metoda) f(x) a obiectului func, care implementează interfaţa FunctieReala. Integratorul nostru are deci o utilizare foarte largă, putând integra funcţia f(x) din orice obiect care implementează această interfaţă. Remarcăm, de asemenea, că putem aplica şi alte metode de integrare numerică, dacă creem pentru fiecare din aceste metode o clasa care implementează interfaţa Integrator. Să considerăm acum că dorim să integrăm funcţia
f(x)=a*sin(b*x+c)+d*cos(b*x)

în careparametrii a, b, c şi d sunt numere reale. Pentru aceasta vom creea o clasă care implementează interfaţa FunctieReala şi, totodată, conţine drept câmpuri parametrii funcţiei:

class Functie1 implements FunctieReala { private double a, b, c, d; // parametrii functiei /* Constructorul clasei introduce valorile parametrilor functiei */ public Functie1(double a, double b, double c, double d) { this.a=a; this.b=b; this.c=c; this.d=d; } /* implementarea metodei abstracte din interfata */ public double f(double x) { return a*Math.sin(b*x+c)+d*Math.cos(b*x); } }

Putem acum să scriem o aplicaţie în care se calculează integrala acestei funcţii. În fişierul Integrare.javasunt date atât declaraţiile de interfeţe şi clase prezentate mai sus, cât şi cea a clasei-aplicaţie în care se folosesc acestea.

Întrebări
Nivel 1
1. 2. 3. 4. 5. 6. 7. 8. Ce este încapsularea? Care sunt modificatorii de acces pentru câmpuri şi metode? Ce sunt constructorii? Ce particularităţi prezintă declaraţiile de constructori? Poate avea o clasă mai multi constructori? cum se disting aceştia? Ce este constructorul implicit? Ce este agregarea? Ce este moştenirea?

195

Severin Bumbaru 9. Ce relaţie există între o clasă şi superclasa ei? 10. Câte superclase poate avea o clasă în limbajul Java? 11. Ce sunt membrii protejaţi ai clasei? 12. Ce sunt this si super? 13. Ce particularităţi prezintă constructorul unei clase derivate? 14. Ce se înţelege prin redefinirea metodelor? 15. Ce fel de metode pot fi redefinite? 16. Ce este o metodă finală? 17. Ce se înţelege prin ascunderea câmpurilor? 18. În ce situaţie o metoda poate fi ascunsă? 19. Este posibilă declararea unei clase de excepţii proprie? 20. Ce este o clasă finală? 21. Ce este polimorfismul? 22. În ce situaţii se manifestă polimorfismul? 23. Cum se face conversia unei referinţe dintr-o clasa în referinţă dintr-o subclasă? 24. Poate fi instanţiata clasa care conţine metoda main()? 25. Ce este o clasă abstractă? 26. Ce este o metodă abstractă? 27. Cum se declară o clasă abstractă? 28. Ce este o interfaţă? 29. Ce efect are faptul că o clasă implementează o interfaţă? 30. Ce este o clasa imbricată? 31. Ce este o clasa interioară?

Nivel 2
Enumeraţi caracteristicile obiectelor şi claselor Ce se înţelege prin identitatea obiectelor? Ce se înţelege prin clasificare, ca proprietate a obiectelor? Ce este moştenirea? Ce fel de moştenire este permisă în limbajul Java în cazul claselor? Ce se înţelege prin ascunderea câmpurilor? Cum poate fi accesat dintr-o clasă un câmp ascuns al superclasei? Este posibil să utilizăm într-o clasă un membru privat al superclasei? Este posibil să declarăm într-o clasă o metodă cu aceeaşi signatură ca o metodă privată din superclasă? 10. Ce se întâmplă dacă un argument sau o altă variabilă locală a unei metode are acelaşi nume cu un câmp al clasei respective? 11. Cum putem utiliza într-o metodă a unei clase un câmp ascuns al propriei clase? 12. Sa considerăm că în subclasa B a clasei A a fost redefinită metoda met(); cum putem utiliza în B metoda met() din A? 13. Ce este o metoda statică? 14. Ce se întâmplă dacă într-o clasă se declară o metodă statică cu aceeaşi signatura ca a unei metode statice din superclasă? 15. Cum poate fi invocată o metodă statică? 16. Cum poate fi invocată o metodă de instanţă? 17. Daţi un exemplu de declarare a unei clase de excepţii. 18. Ce metode, în limbajul Java, nu sunt supuse polimorfismului? 1. 2. 3. 4. 5. 6. 7. 8. 9.

196

Programarea orientata pe obiecte în limbajul Java 19. Daţi un exemplu de situaţie în care trebuie folosit castul pentru a converti o referinţă. 20. În ce scop au fost definite în clasa Object metodele equals() şi toString()? 21. Să considerăm că clasa A conţine atât metoda main(), cât şi un câmp nestatic a şi o metodă nestatica met(). Cum putem utiliza în main câmpul a şi metoda met()? 22. Ce asemănări şi deosebiri există între interfeţe şi clasele abstracte? 23. Poate exista moştenire multiplă în cazul interfeţelor? 24. Care sunt proprietăţile implicite ale câmpurilor unei interfeţe? 25. Care sunt proprietăţile implicite ale metodelor unei interfeţe? 26. Poate fi declarată ca finală o metodă a unei interfeţe? Justificati răspunsul. 27. Ce avantaj prezintă declatrarea interfeţei Integrator ca o interfaţă şi nu ca o clasă? 28. Ce avantaj prezintă folosirea unei interfeţe ca argument al unei metode? 29. Ce deosebire există între clasa imbricată şi clasa interioară?

197

Severin Bumbaru

Interfeţe utilizator grafice şi programarea orientată pe evenimente
Interfeţe utilizator grafice; Interfaţa utilizator grafică a unei aplicaţii Clasa Component Un exemplu introductiv de interfaţă utilizator grafică Programarea orientată pe evenimente Modelul de evenimente de pe platforma Java 2 Clase de evenimente Interfeţe şi clase adaptoare pentru ascultători de evenimente Evenimentele generate de o fereastră Terminarea aplicatiei la inchiderea ferestrei; utilizarea clasei WindowAdapter Evenimente de mouse Evenimente de tastă Adăugarea unei componente la fereastra principală a aplicaţiei Gestionarea poziţionării componentelor Clasa BorderLayout Clasa FlowLayout Clasa GridLayout Clasa BoxLayout Poziţionarea absolută a componentelor (fără gestionar de poziţionare) Gruparea componentelor prin utilizarea containerelor auxiliare Clasele Panel si JPanel Clasa Box Întrebări. 198 200 200 201 203 204 205 206 206 208 209 213 216 219 220 222 226 228 231 233 234 235 237

Interfeţe utilizator grafice
Interfaţa utilizator, numită şi interfaţa om-maşină, este mijlocul de comunicare între un sistem informatic şi utilizator. În programare, conceptul de interfaţă utilizator a apărut în legătură cu utilizarea programelor în regim interactiv. Un asemenea regim permite utilizatorului să comunice cu procesul de calcul în timpul desfăşurării acestuia. În prezent, se cunosc două tipuri principale de interfeţe utilizator: - interfaţa prin linie de comandă;

198

Programarea orientata pe obiecte în limbajul Java - interfaţa grafică. În modul de comunicare prin linie de comandă, operatorul uman transmite calculatorului comenzi, pe care le introduce de la tastatură sub forma unor linii de text, folosind un anumit limbaj de comandă. Exemplele cele mai cunoscute sunt limbajele de comandă ale sistemelor de operare MS-DOS şi Unix. Interfaţa utilizator grafică (engleză: GUI - graphical user interface) este o interfaţă ommaşină care permite operatorului să dea comenzi şi să introducă date prin acţionarea asupra unor obiecte grafice vizualizate pe ecran: butoane, pictograme, meniuri etc.

Interfaţa utilizator grafică a fost introdusă pentru prima dată la Palo Research Center al firmei Xerox în anii 1970 şi a fost apoi aplicată de firma Apple pe calculatoarele Macintosh în 1984. În prezent, cele mai răspândite interfeţe utilizator grafice sunt sistemul Windows al firmei Microsoft şi cele de tip X-Window folosite în sistemele de operare din categoria Unix. Un avantaj important al platformei Java 2 este că oferă pachete de clase, care permit programatorului realizarea comodă şi eficientă a interfeţelor grafice. În prezent, există două grupuri de pachete de clase pentru interfeţe grafice, care corespund la două principii diferite de realizare a interfeţei: 1. AWT (engleză: Abstract Windowing Toolkit - setul de dezvoltare de ferestre abstracte) este un set de clase care permit realizarea de interfeţe grafice în care se folosesc obiectele grafice specifice platformei pe care se execută programul. Aceasta înseamnă că, dacă programul se execută - de exemplu - sub Windows, diferitele obiecte grafice (butoane, meniuri, bare de defilare etc.) vor avea aspectul specific sistemului Windows, în timp ce dacă se executa sub X-Window vor avea aspectul specific acestui sistem.

Principalele pachete utilizate în acest set sunt: java.awt,java.applet,java.awt.color si java.awt.event. Acestea se completează şi cu alte subpachete ale pachetului java.awt, care sunt descrise în documentaţia Java API. 2. JFC (engleză: Java Foundation Classes - clase de bază Java), este un set de pachete de clase, care extind posibilităţile oferite de AWT, pentru realizarea de interfeţe utilizator de calitate superioară. Dintre acestea, cele mai larg folosite constituie setul cunoscut sub numele de cod Swing (sau JFC/Swing), care oferă posibilitatea de a realiza interfeţe grafice al căror aspect nu depinde de platforma pe care se execută programul.

Principalele pachete din JFC/Swing sunt javax.swing şi javax.swing.event, la care se adaugă multe alte subpachete ale pachetului javax.swing descrise în documentaţia Java API. Este important să menţionăm că pachetele din JFC/Swing nu se substituie, ci le completează pe cele din AWT. Modul de realizare al interfeţei grafice folosind AWT este prezentat destul de amplu in lucrarea "Interfaţa grafica utilizator în limbajul Java". În cursul de faţă ne vom referi în special realizarea interfeţei grafice utilizator folosind JFC/Swing şi vom folosi 199

Severin Bumbaru clasele din AWT numai acolo unde va fi absolut necesar.

Se ştie că autorii limbajului Java şi-au propus ca programele scrise în acest limbaj să poată fi executate pe orice platformă. Sub acest aspect, realizarea interfeţei grafice a ridicat probleme dificile deoarece, pe diferite platforme, atât setul de obiecte grafice utilizate, cât şi aspectul acestora, pot fi diferite. La realizarea setului de dezvoltare AWT s-a acceptat ideea că în program se lucrează cu nişte obiecte grafice abstracte, care sa fie comune tuturor platformelor. Obiectul grafic abstract, aşa cum este el luat în consideraţie în AWT, se caracterizează prin anumite atribute şi un anumit comportament, dar aspectul său pe ecran poate sa difere de la o platformă la alta. Avantajul utilizării AWT este că utilizatorul vede pe ecran obiectele care compun interfaţa grafică sub forma cu care el este obişnuit pe platforma pe care lucrează. La aceasta se adaugă şi faptul că AWT se înţelege şi se utilizează relativ usor. Dezavantajul este că o astfel de interfaţă nu poate folosi decât obiectele grafice existente pe toate platformele (mulţimea de clase de obiecte grafice din AWT este "intersecţia" mulţimilor de clase grafice de pe diferite platforme), ceeace restrânge posibilitatea programatorilor de a realiza interfeţe utilizator diversificate şi originale. Autorii setului de dezvoltare JFC/Swing au decis să realizeze un set de clase de interfaţă care sunt complet independente de platformă. În acest scop, s-a renunţat la metodele native, clasele Swing fiind programate total în Java. Unele din aceste clase le extind sau le dublează pe cele din AWT, iar altele sunt complet noi. Mai mult, se dă posibilitatea programatorului să dezvolte propriile sale clase de obiecte grafice prin extinderea celor existente. Este drept insă că aceasta se plăteşte prin creşterea complexităţii interfeţei grafice şi a nivelului de competenţă cerut de la programator.

Interfaţa utilizator grafică a unei aplicaţii
Orice interfaţă utilizator grafică este un obiect grafic structurat, în alcătuirea căruia intră componente grafice. În sistemele AWT şi JFC/Swing, toate componentele grafice fac parte din clase care constituie un arbore, a cărui rădăcină este clasa Component. Deosebim componente atomice şi containere. Containerele sunt componente grafice care conţin alte componente. Containerele aparţin unor clase care formează o ierarhie, a cărei rădăcină este clasa Container. Clasa Container este derivată din clasa Component. Aceasta înseamnă ca orice container este o el însuşi componentă, deci un container poate să conţină alte containere.

Clasa Component
Având în vedere că întreaga ierarhie de clase de obiecte de interfaţă utilizator grafică folosite în Java (atât cele din AWT, cât şi din JFC/Swing) are ca rădăcină clasa Component, este util să începem studiul cu această clasă. Componenta este un obiect grafic care este afişabil pe ecran şi poate interacţiona cu utilizatorul.

200

Programarea orientata pe obiecte în limbajul Java Clasa Component este o clasă abstractă, derivată direct din clasa Object.

Dintre numeroasele metode ale acestei clase, prezentăm aici numai câteva mai frecvent folosite: - testează dacă această componentă este vizibilă, atunci când este vizibil şi containerul în care se găseşte; public void setVisible(boolean visible) - face componenta sa fie vizibilă sau nu, după cum argumentul este true sau false; public boolean isShowing() - determină dacă componenta este efectiv afişată pe ecran; public Point getLocation() - întoarce locaţia componentei (poziţia colţului din stânga sus în containerul-părinte) sub forma unei instanţe a clasei Point; public void setLocation(Point location) - setează noua poziţie a componentei în cadrul containerului părinte; public Dimension getSize() - întoarce dimensiunea componentei; public void setSize(int latime, int inaltime) - setează noile dimensiuni ale componentei.
public boolean isVisible()

În aceste metode se utilizează şi clasele auxiliare Point şi Dimension din pachetul java.awt. Instanţele clasei Point conţin coordonatele unui punct. Se consideră că originea sistemului de coordonate se află în colţul din stânga sus, axa Ox este orientată către dreapta, iar axa Oy este orientata în jos. Instanţele clasei Dimension conţin dimensiunile componentei: lăţimea şi înălţimea. O prezentare mai amplă a metodelor acestei clase este dată în indexul de clase. Descrierea completă este dată în documentaţia Java API.

Un exemplu introductiv de interfaţă utilizator grafică
Întrucât interfaţa grafică conţine componente, ea este un container. Vom studia ulterior diferite clase de containere dar, deocamdată, ne vom opri asupra clasei JFrame, care se foloseşte în aplicaţiile Java. În sistemul JFC/Swing, interfaţa utilizator grafică a unei aplicaţii este o instanţă a clasei JFrame din pachetul javax.swing, sau este o subclasă a acesteia. În mod obişnuit, instanţele clasei JFrame sunt ferestre care au la partea superioara o bară de titlu. În partea stângă a barei de titlu există un buton, la apăsarea căruia se desfaşoară un menu cu urmatoarele opţiuni: Restore, Move,Size, Minimize, Maximize, Close. Cînd meniul este desfăşurat, acste opţiuni pot fi selectate cu mouse-ul sau acţionând tasta corespunzătoare literei subliniate. La partea dreaptă a barei de titlu exista trei butoane, care au acelasi efect ca opţiunile Minimize, Maximize şi Close din meniul menţionat. Selecţionarea opţiunilor sau acţionarea butoanelor din bara de titlu produce asupra ferestrei efectul corespunzător. Fereastra poate fi deplasată pe ecran dacă punem cursorul mouse-lui

201

Severin Bumbaru pe bara de titlu şi o tragem, ţinând butonul de mouse apăsat. Dimensiunile ferestrei pot fi modificate trăgând cu mouse-ul oricare din laturile sau din colţurile ei. Modul cel mai simplu de a realiza o aplicaţie cu interfaţă grafică este să folosim direct o instanţă a clasei javax.swing.JFrame, care extinde clasa Frame din AWT. Clasa are o structură destul de complicată şi este descrisă complet în documentaţia Java.API. Pentru a nu intra de la început în detalii, vom da, deocamdată, numai cateva informaţii strict necesare pentru folosirea acestei clase, urmând să revenim ulterior cu indicaţii suplimentare. Principalul constructor este:
JFrame(String titlu) unde titlu este titlul ferestrei.

Clasa JFrame are numeroase metode, atât proprii, cât şi moştenite de la superclasele ei. În afară de metodele moştenite de la clasa Component, deocamdată, vom mai menţiona aici numai două, care sunt moştenite de la clasa Frame:
public String getTitle() - întoarce titlul ferestrei; public void setTitle(String titlu) - pune ferestrei

un titlu (în locul celui

existent). La crearea ei, instanţa clasei JFrame nu este vizibilă. Pentru a o face vizibilă se foloseşte metoda public void setVisible(boolean visible) a clasei Component. Constructorii şi metodele clasei Frame se gasesc în indexul de clase. Descrierea completă se găseşte în documentaţia Java API.

Exemplu: introducerea unui JFrame într-o aplicaţie În fişierul TestJFrame1.java este dat un exemplu de aplicaţie care are o interfaţă utilizator din clasa JFrame. Iată conţinutul acestui fişier:

import java.awt.*; import javax.swing.*; class TestJFrame1 { static JFrame iug; public static void main(String args[]) throws Exception { iug=new JFrame("Exemplu de JFrame"); iug.setSize(300,100); iug.setLocation(new Point(100,50)); iug.setVisible(true); System.out.println("Titlul ferestrei este: "+iug.getTitle()); System.out.println("Coltul din stanga sus este in punctul: "+ iug.getLocation()); System.out.println("Dimensiunile ferestrei: "+iug.getSize()); } }

202

Programarea orientata pe obiecte în limbajul Java

În prima linie este importat pachetul javax.swing, care conţine principalele clase ale JFC/Swing, inclusiv clasa JFrame. În clasa TestJFrame1 (care este aplicaţia noastră), s-a declarat câmpul static iug, care este o referinţă la JFrame. În prima instrucţiune din metoda main se creeaza o instanţă a clasei JFrame şi se dă câmpului iug ca valoare referinţa la instanţa nou creată. Se folosesc apoi metodele setSize(), setLocation() şi setVisible() din clasa Component, pentru a stabili dimensiunile ferestrei şi pozitia ei pe ecran şi a o face vizibilă. Se afişeaza apoi în fereastra terminalului de la care s-a lansat aplicaţia (la ieşirea standard a sistemului) titlul, coordonatele şi dimensiunile ferestrei, folosind metodele adecvate din clasele JFrame şi Component. La lansarea în execuţie a acestei aplicaţii, în colţul din stânga sus al ecranului apare o fereastră de dimensiuni 300x100 pixeli, cu titlul "Exemplu de JFrame", iar la ieşirea standard apar toate mesajele afişate prin instrucţiunile System.out.println(...) din metoda main. Putem acum să acţionăm asupra ferestrei cu mouse-ul, având grijă să nu dam comanda Close (nici din menu, nici acţionând butonul din dreapta sus marcat cu X): putem să deplasăm fereastra tragând cu mouse-ul de bara de titlu, să o iconificăm (minimizăm), deiconificăm, maximizam, restaurăm. Putem, de asemenea, să modificăm dimensiunile ferestrei, trăgând cu mouse-ul de marginile acesteia. La sfârşit, putem da comanda Close (din meniul ferestrei sau apasând butonul marcat cu X) şi fereastra se închide. Constatăm însă că închiderea ferestrei nu are drept consecinţa şi încheierea executării aplicaţiei: în fereastra terminalului din care am lansat aplicaţia nu a apărut promptul sistemului de operare. Pentru a încheia executarea aplicaţiei, trebuie să dăm de la terminal comanda <Control>-C. Pentru a avea o interfaţă grafică functională, este necesar: - să adaugăm la interfaţă componente grafice; - să asigurăm interacţiunea utilizatorului cu aceste componente şi cu însăşi fereastra aplicaţiei. Ne vom ocupa de aceste aspecte în secţiunile următoare.

Programarea orientată pe evenimente
Când se lucrează cu o interfaţă grafică, pe ecranul staţiei de lucru apar diferite obiecte grafice: ferestre, meniuri, butoane, bare de defilare etc. Operatorul poate utiliza tastatura staţiei de lucru, la fel ca în cazul progrămarii tradiţionale, dar, în plus, utilizează un dispozitiv de selecţie numit mouse, cu ajutorul căruia poate alege diferite obiecte grafice de pe ecran şi poate da anumite comenzi prin apăsarea unuia din butoanele acestui dispozitiv. Se numeste eveniment orice modificare care are loc, fie în starea dispozitivelor de intrare, fie în cea a obiectelor grafice de pe ecran: apăsarea sau eliberarea unei taste, deplasarea mouseului, apăsarea sau eliberarea unui buton al mouse-ului, deschiderea sau închiderea unei ferestre, efectuarea unui clic de mouse pe un obiect de control (buton, caseta de validare, bara de defilare etc.), intrarea cursorului de mouse în câmpul activ al unui obiect grafic sau

203

Severin Bumbaru părăsirea acestuia etc. Pot exista, desigur, şi alte tipuri de evenimente, dar aici ne interesează numai cele legate de interfaţa grafică a aplicaţiei. Interacţiunea dintre operator şi aplicaţie într-un sistem bazat pe evenimente decurge astfel: operatorul provoacă generarea unui eveniment, acţionând asupra tastaturii, mouse-ului sau a altui dispozitiv de intrare, iar programul "răspunde" la acest eveniment prin executarea unei anumite acţiuni. Acest mod de lucru impune o nouă concepţie în proiectarea programelor, numită programarea orientată pe evenimente.

În programarea procedurală tradiţională, procesul de calcul este în întregime ghidat de instrucţiunile programului. Imediat ce s-a încheiat executarea unei instrucţiuni, se trece la instrucţiunea următoare, respectând fluxul de instrucţiuni al programului respectiv. Aceasta se referă şi la interacţiunea dintre program şi utilizator. Chiar dacă programul este interactiv, initiaţiva privind ce date trebuie introduse şi în ce moment se introduc acestea aparţine programului. Operatorului uman nu îi rămâne decât să se conformeze solicitărilor programului şi să introducă date atunci când ele sunt cerute de acesta. Este evident că un asemenea rol nu este deloc convenabil pentru om, care doreşte să aibă el iniţiativa acţiunilor. Apariţia interfeţelor grafice a permis introducerea unei noi concepţii în interacţiunea dintre operator şi aplicatie, astfel ca iniţiativa să îi revină operatorului uman, iar programul să execute comenzile acestuia. S-a trecut, astfel, de la programarea procedurală tradiţională la programarea orientată pe evenimente (engleză: Event-Oriented Programming), cunoscuta şi sub numele de programare ghidată de evenimente (engleză: Event Driven Programming). Există diferite modele de generare, captare şi tratare a evenimentelor. În lucrarea de faţă va fi prezentat modelul de evenimente specific platformei Java 2.

Modelul de evenimente de pe Platforma Java 2
In JDK 1.0 s-a folosit un model de evenimente bazat pe moştenire, reprezentat prin clasa Event. Această clasă este menţinuta în continuare, din motive de compatibilitate a programelor din JDK 1.0 cu cele din versiunile ulterioare, dar nu mai este recomandată. Începând cu JDK 1.1 s-a introdus un nou model de evenimente, bazat pe delegare (engleză: Delegation Event Model). Conform acestui model, distingem trei catedorii de obiecte care au relaţii cu evenimentele: - obiecte care generează evenimente, numite surse de evenimente (engleza: Event Source); - obiecte care captează şi tratează evenimentele, numite ascultători de evenimente (engleză: Event Listener); - evenimentele propriu-zise (engleză: Event), care sunt tot obiecte, generate de surse şi captate de ascultători. Un eveniment generat de o sursă poate fi interceptat de mai mulţi ascultători. Este posibil ca un obiect să se "asculte" pe sine însuşi, deci să îşi trateze propriile evenimemte. Tocmai această legatură strânsa între sursele şi ascultătorii de evenimente este caracteristica esenţiala a "modelului delegării": ea constă în faptul că sursa transmite evenimentele generate de ea

204

Programarea orientata pe obiecte în limbajul Java numai către ascultătorii care au fost ataşaţi la sursa respectivă, iar ascultătorul primeşte evenimente numai de la sursele la care a fost ataşat.

În modelul de evenimente din JDK 1.0 sursa transmitea evenimentele fără o destinaţie anume, iar consumatorii de evenimente trebuiau sa depisteze singuri ce fel de evenimente sunt acestea şi de la ce sursă provin şi, în funcţie de aceasta, să decidă dacă interceptează sau nu evenimentul respectiv. Remarcăm, deci, că se aplică cu consecvenţă principiul programării orientate pe obiecte, în sensul că aplicaţia este constituită dintr-un ansamblu de obiecte care interacţionează. Putem spune că evenimentul este o nouă forma de mesaj, care se transmite între obiectele-sursă şi obiectele-ascultători. Fiecare ascultator trebuie "adăugat" la sursă. În acest fel, sursa "ştie" căror ascultători să le transmită evenimentele pe care le generează. Este important sa reţinem faptul că, în acest model de evenimente, sursele sunt întotdeauna componente ale interfeţei grafice. Când cursorul mouse-ului intră în interiorul unei componente sau iese din aceasta, sau când facem click de mouse pe o componenta, evenimentul nu este generat direct de către mouse, ci de către componenta respectivă a interfeţei. În mod similar, când apăsăm sau eliberăm o tastă, evenimentul nu este generat direct de către tastă, ci de către acea componentă a interfeţei grafice, care este activă în momentul respectiv. Evenimentele pot fi generate de o componentă şi atunci când au loc modificari ale stării acesteia ca urmare a executării unor instrucţiuni din program (de exemplu când se deschide sau se închide o fereastră, etc.).

Clase de evenimente
Clasele de evenimente se găsesc în pachetele java.awt.event şi javax.swing.event şi formează o ierarhie de clase, care are ca radacină clasa abstractă java.awt.AWTEvent. Aceasta, la rândul ei, extinde clasa java.util.EventObject din pachetul java.util. Orice eveniment conţine următoarele metodele declarate în clasa java.util.EventObject:
public Object getSource()

- care întoarce o referinţă către obiectul care a generat - care întoarce o reprezentare sub forma de şir a

evenimentul respectiv;
public String toString()

obiectului. Orice eveniment AWT (generat de componente AWT sau JFC/Swing) conţine un câmp protejat de tip int numit id, a cărui valoare indică tipul evenimentului. Toate clasele de evenimente conţin câmpuri statice finale de tip int, al căror nume indică tipul de eveniment şi a căror valoare este valoarea corespunzătoare a câmpului id. Valoarea acestui câmp se poate obţine prin metoda
public int getId()

Evenimentele pot fi de nivel coborât (low level event) sau semantice. Numele claselor de evenimente de nivel coborât indică fie componenta, fie dispozitivul de intrare care le-a generat, de exemplu: ComponentEvent, WindowEvent, MouseEvent, KeyEvent. Numele claselor de evenimente semantice indică mai curând tipul de eveniment, decât sursa acestuia,

205

Severin Bumbaru de exemplu: ActionEvent, TextEvent. Descrierea claselor de evenimente este dată complet în documentaţia Java API.

Interfeţe şi clase adaptoare pentru ascultători de evenimente
Evenimentele generate de surse, sunt captate şi tratate de ascultători. Sursele sunt componente ale interfeţei grafice care, de regulă, sunt preluate din pachetele de clase ale platformei Java 2, deci nu sunt programate de către programatorul de aplicaţie, ci doar utilizate de acesta. În schimb, programatorul de aplicaţie trebuie să creeze clasele de ascultători de evenimente specifice aplicaţiei pe care o dezvoltă. Metodele acestor clase trebuie sa reacţioneze la apariţia evenimentelor şi să execute acţiunile corespunzătoare, conform cu obiectivele aplicaţiei. În consecinţă, platforma Java 2 nu pune la dispoziţie clase ascultătoare predefinite, ci doar interfeţele pe care trebuie să le implementeze aceste clase, pentru a putea trata evenimentele pe care le recepţionează. Există câte o interfaţă de ascultător pentru fiecare clasă de eveniment. De exemplu, pentru ComponentEvent există interfaţa ComponentListener, pentru WindowEvent exista intefaţa WindowListener etc. Pentru a se uşura munca programatorilor, pentru unele interfeţe care conţin mai multe metode, se oferă şi prototipuri de clase care implementează interfaţa respectivă, numite adaptoare. De exemplu, clasa WindowAdapter implementează interfaţa WindowListener, clasa MouseAdapter implementează interfaţa MouseListener etc. Adăugarea ascultătoarelor la sursele de evenimente se face prin metode de adăugare corespunzătoare, existente în clasele de surse. De exemplu, pentru a se adăuga la o instanţă a clasei Window sau a subclaselor acesteia (de exemplu JFrame) un ascultător de evenimente de fereastră, în clasa Window există metoda
public void addWindowListener(WindowListener l)

Descrierea completă a interfeţelor şi adaptoarelor pentru clasele de ascultători de evenimente este dată în documentaţia Java API.

Evenimentele generate de o fereastră
Pentru a ne familiariza cu modelul de evenimente al platformei Java 2, vom urmări acum evenimentele generate de o fereastră şi modul cum acestea pot fi tratate. Vom folosi în acest scop o interfaţă din clasa javax.swing.JFrame. Aceasta este derivată din clasa java.awt.Frame care, la rândul ei, este derivată din clasa java.awt.Window. Evenimentele prezentate în această secţiune sunt generate de orice instanţă a clasei Window şi ale subclaselor sale. Evenimentele generate de fereastră sunt instanţe ale clasei WindowEvent şi sunt ascultate de instanţe ale unor clase care implementează interfaţa WindowListener sau extind clasa WindowAdapter. Toate aceste clase şi interfeţe se găsesc în pachetul java.awt.event.

Metodele interfeţei WindowListener sunt următoarele: public void windowOpened(WindowEvent e)- fereastra a fost deschisă public void windowClosing(WindowEvent e)- fereastra se închide

206

Programarea orientata pe obiecte în limbajul Java
public public public public public void void void void void windowClosed(WindowEvent e)- fereastra a fost inchisă windowIconified(WindowEvent e)- fereastra a fost iconificată windowDeiconified(WindowEvent e)- fereastra a fost deiconificată windowActivated(WindowEvent e)- fereastra a fost activată windowDeactivated(WindowEvent e)- fereastra a fost dezactivată

Pentru a se trata evenimentele generate de fereastră, este necesar să se declare o clasă care implementează interfaţa WindowListener. În această clasă se definesc toate metodele interfeţei, astfel încât acestea să execute acţiunile adecvate evenimentelor corespunzătoare.

Exemplu În fişierul Evenim1.java este dat ca exemplu următorul program, în care se urmăresc evenimentele generate de o fereastră din clasa JFrame.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class Evenim1 { static JFrame iug; // referinta la interfata grafica static AscultFereastra af; // referinta la ascultator /* Clasa imbricata pentru ascultatorul de fereastra */ static class AscultFereastra implements WindowListener { public void windowOpened(WindowEvent e) { System.out.println("Fereastra a fost deschisa: "+e); } public void windowClosing(WindowEvent e) { System.out.println("Fereastra se inchide: "+e); } public void windowClosed(WindowEvent e) { System.out.println("Fereastra a fost inchisa: "+e); } public void windowIconified(WindowEvent e) { System.out.println("Fereastra a fost iconificata: "+e); } public void windowDeiconified(WindowEvent e) { System.out.println("Fereastra a fost deiconificata: "+e); } public void windowActivated(WindowEvent e) { System.out.println("Fereastra a fost activata: "+e); } public void windowDeactivated(WindowEvent e) { System.out.println("Fereastra a fost dezactivata: "+e); } } // se incheie clasa ascultatorului de fereastra /* Metoda principala */ public static void main(String args[]) throws Exception { af=new AscultFereastra(); // instantierea ascultatorului iug=new JFrame("Urmarire evenimente fereastra"); iug.setSize(300,100); iug.setLocation(new Point(100,50)); iug.setVisible(true);

207

Severin Bumbaru

iug.addWindowListener(af); // adaugarea ascultatorului System.out.println("Titlul ferestrei este: "+iug.getTitle()); System.out.println("Coltul din stanga sus este in punctul: "+ iug.getLocation()); System.out.println("Dimensiunile ferestrei: "+iug.getSize()); } }

Pentru ascultătorul de fereastră a fost creată clasa imbricată statică AscultFereastra, în care sunt implementate toate metodele interfeţei WindowListener. În cazul de faţă, aceste metode nu fac altceva, decât că afişează pe terminal un mesaj privind evenimentul care s-a produs. Acest mesaj conţine şi evenimentul interceptat e, convertit în şir. În metoda main(), se construiesc instanţele claselor JFrame şi AscultFereastra, după care se adaugă ascultătorul af la fereastra iug prin metoda addWindowListener(). Se stabilesc, de asemenea, dimensiunea şi poziţia ferestrei şi se face fereastra vizibilă. După ce a fost pusă aplicaţia în execuţie, pe ecran apare o fereastră cu titlul "Urmarire evenimente fereastra". La terminal putem urmări succesiunea evenimentelor care se produc. Ca şi în exemplul precedent, ieşirea din aplicaţie se face de la tastatura, prin comanda <Control>-C. Remarcăm că, în exemplul de mai sus, a fost necesar ca, în clasa care implementează interfata WindowListener, să se definească toate metodele acesteia. Dacă ne sunt necesare numai unele din aceste metode, este preferabil sa obţinem clasa AscultFereastra prin extinderea clasei WindowAdapter. Această ultimă clasă conţine toate metodele interfeţei WindowListener, dar corpurile lor sunt vide, deci metodele nu fac nimic. În acest fel, în clasa derivată este suficient să redefinim numai metodele care ne sunt necesare, ca în exemplul din secţiunea următoare.

Terminarea aplicaţiei la închiderea ferestrei
Pentru ca la acţionarea butonului de închidere din colţul din dreapta-sus al ferestrei să se închidă nu numai fereastra respectivă, ci si aplicaţia, este necesar ca metoda windowClosing() a ascultătorului de fereastră să conţină invocarea metodei System.exit(0) din clasa System. Dacă nu dorim să utilizam şi alte metode de tratare a evenimentelor generate de fereastră, vom deriva clasa de ascultare a ferestrei AF din clasa WindowAdapter. Exemplu În fişierul Inchidere.java se dă o modificare a clasei Evenim1 din exemplul precedent.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class Inchidere { static JFrame iug; // referinta la fereastra static AF af; // referinta la ascultator /* Clasa imbricata pentru ascultatorul de fereastra */ static class AF extends WindowAdapter {

208

Programarea orientata pe obiecte în limbajul Java

public void windowActivated(WindowEvent e) { System.out.println("Fereastra a fost activata"); } public void windowDeactivated(WindowEvent e) { System.out.println("Fereastra a fost dezactivata"); } public void windowClosing(WindowEvent e) { System.out.println("Fereastra se inchide"); System.exit(0); // incheierea executarii aplicatiei } } /* Metoda principala */ public static void main(String args[]) throws Exception { af=new AF(); // instantierea ascultatorului iug=new JFrame("Urmarire evenimente fereastra"); iug.setSize(300,100); iug.setLocation(new Point(100,50)); iug.setVisible(true); iug.addWindowListener(af); // adaugarea ascultatorului System.out.println("Titlul ferestrei este: "+iug.getTitle()); System.out.println("Coltul din stanga sus este in punctul: "+ iug.getLocation()); System.out.println("Dimensiunile ferestrei: "+iug.getSize()); } }

În acest program, în afară de modificarea numelor claselor, s-au mai făcut următoarele modificări: - clasa de ascultare a evenimentelor AF extinde clasa WindowAdapter, deci implementează in mod indirect interfaţa WindowListener, prin intermediul acesteia; - s-au definit numai trei din cele şapte metode ale intefeţei WindowListener; celelalte metode sunt moştenite de la clasa WindowAdapter, deci nu fac nimic; - în metoda windowClosing()s-a introdus instrucţiunea System.exit(0); În consecinţă, când acţionăm butonul de închidere al ferestrei, sau selectăm optiunea Close din meniu, se încheie executarea aplicaţiei.

Evenimente de mouse
Evenimentele de mouse sunt instanţe ale clasei java.awt.event.MouseEvent şi sunt generate de către orice componentă a interfeţei grafice, atunci când asupra ei se acţioneaza cu mouseul. Clasa MouseEvent este derivată din clasa java.awt.event.InputEvent. Evenimentele de mouse sunt intrarea cursorului de mouse într-o componentă sau ieşirea din aceasta, apăsarea unui buton de mouse sau eliberarea lui, efectuarea unui click de mouse (simplu sau multiplu) pe suprafaţa componentei, mişcarea mouse-ului. Pentru ascultarea evenimentelor de mouse se folosesc interfeţele java.awt.event.MouseListener,java.awt.event.MouseMotionListener şi javax.swing.event.MouseInputListener. Ascultătoarele de mouse se pot obţine, de asemenea, prin extinderea claselor java.awt.event.MouseAdapter,java.awt.event.MouseMotionAdapter şi

209

Severin Bumbaru javax.swing.event.MouseInputAdapter.

În AWT se face distincţie între evenimentele de mouse discrete şi evenimentele care caracterizează mişcarea continuă a mouse-lui. a/ Evenimente de mouse - a fost apăsat un buton al mouse-ului (MOUSE_PRESSED); - a fost eliberat un buton al mouse-ului (MOUSE_RELEASED); - s-a făcut click de mouse, adică un buton al acestuia a fost apăsat şi eliberat imediat (MOUSE_CLICKED); - cursorul mouse-ului a intrat într-o componentă (MOUSE_ENTERED); - cursorul mouse-ului a ieşit din componentă (MOUSE_EXITED). Ascultarea acestor evenimente se face cu instanţe ale claselor care implementeaza interfaţa java.awt.event.MouseListener. b/ Evenimente de mişcare a mouse-ului - mouse-ul s-a mişcat pe suprafaţa componentei (MOUSE_MOVED); - mouse-ul a fost "tras" pe suprafaţa componentei, adică a fost mişcat ţinând un buton apăsat (MOUSE_DRAGGED). Aceste evenimente sunt ascultate cu instanţe ale claselor care implementează interfaţa java.awt.event.MouseMotionListener. În JFC/Swing s-a introdus în plus interfaţa javax.swing.event.MouseInputListener, care ascultă ambele categorii de evenimente de mouse, moştenind cele două interfeţe menţionate mai sus.

Exemplu În fişierul EvMouse.java este dat un exemplu de aplicaţie în care se ascultă evenimentele de mouse generate de o instanţă a clasei JFrame şi se afişează la terminal conţinutul acestor evenimente. În acest scop, în clasa imbricată AMouse au fost implementate toate metodele interfeţei MouseListener, dar aceste metode nu fac altceva, decât să afişeze la terminal evenimentul recepţionat.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class EvMouse { static JFrame iug; // referinta la fereastra static AF af; // referinta la ascultator de fereastra static AMouse am; // referinta la ascultator de mouse /* Clasa imbricata pentru ascultatorul de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } }

210

Programarea orientata pe obiecte în limbajul Java

/* Clasa imbricata pentru ascultatorul de mouse */ static class AMouse implements MouseListener { public void mouseClicked(MouseEvent e) { System.out.println(e); } public void mousePressed(MouseEvent e) { System.out.println(e); } public void mouseReleased(MouseEvent e) { System.out.println(e); } public void mouseEntered(MouseEvent e) { System.out.println(e); } public void mouseExited(MouseEvent e) { System.out.println(e); } } /* Metoda principala */ public static void main(String args[]) throws Exception { af=new AF(); // instantierea ascultatorului am=new AMouse(); // instantierea ascultatorului de mouse iug=new JFrame("Urmarire evenimente de mouse"); iug.setSize(300,100); iug.setLocation(new Point(100,50)); iug.setVisible(true); iug.addWindowListener(af); // adaugarea ascultatorului de fereastra iug.addMouseListener(am); // adaugarea ascultatorului de mouse } }

Executând acest program, putem urmări pe ecran succesiunea de evenimente care se produc atunci când acţionăm cu mouse-ul asupra ferestrei aplicaţiei.

Evenimentul de mouse conţine următoarele informaţii: - tipul evenimentului (câmpul id); - coordonatele punctului în care s-a produs evenimentul; - modul în care s-a produs evenimentul, caracterizat printr-un numar întreg, în care fiecare bit are o anumită semnificaţie. Acest număr poate fi decodificat cu ajutorul măştilor existente în clasa java.awt.event.InputEvent; - contorul de click-uri (util când au loc mai multe clickuri succesive); - numărul de ordine al ferestrei (în ordinea în care ferestrele au fost creeate). Aceste informaţii pot fi obţinute folosind metodele şi măştile din clasele java.awt.event.MouseEvent şi java.awt.event.InputEvent. Exemplu În fişierul EvMouse1.java este dat un exemplu de aplicaţie, în care se urmăresc situaţiile în care este apăsat unul dintre butoanele mouse-ului, atunci când cursorul acestuia se găseşte pe suprafaţa ferestrei aplicaţiei. Întrucât nu se folosesc toate metodele interfeţei MouseListener, clasa ascultătorului de mouse AMouse s-a creat prin extinderea clasei java.awt.event.MouseAdapter.

211

Severin Bumbaru

import java.awt.*; import java.awt.event.*; import javax.swing.*; class EvMouse1 { static JFrame iug; // referinta la fereastra static AF af; // referinta la ascultator de fereastra static AMouse am; // referinta la ascultator de mouse /* Clasa imbricata pentru ascultatorul de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa imbricata pentru ascultatorul de mouse */ static class AMouse extends MouseAdapter { public void mousePressed(MouseEvent e) { int x=e.getX(), y=e.getY(), n=e.getClickCount(), mod=e.getModifiers(), buton=0; String mesaj="S-a apasat butonul "; if((InputEvent.BUTTON1_MASK & mod) != 0) buton=1; else if((InputEvent.BUTTON2_MASK & mod) !=0) buton=2; else if((InputEvent.BUTTON3_MASK & mod) !=0) buton=3; mesaj+=buton+" x="+x+" y="+y+" contor="+n+" mod="+mod; if(e.isControlDown()) mesaj+=" Ctrl"; if(e.isAltDown()) mesaj+=" Alt"; if(e.isShiftDown()) mesaj+=" Shift"; System.out.println(mesaj); } } /* Metoda principala */ public static void main(String args[]) throws Exception { af=new AF(); // instantierea ascultatorului am=new AMouse(); // instantierea ascultatorului de mouse iug=new JFrame("Urmarire evenimente de mouse"); iug.setSize(300,100); iug.setLocation(new Point(100,50)); iug.setVisible(true); iug.addWindowListener(af); // adaugarea ascultatorului de fereastra iug.addMouseListener(am); // adaugarea ascultatorului de mouse } }

În metoda mousePressed() a clasei AMouse se determină coordonatele punctului în care s-e găseşte cursorul la apăsarea butonului, invocând metodele getX() şi getY() ale clasei MouseEvent. Se determină numărul de apăsări succesive rapide, invocând metoda getClickCount() a aceleeaşi clase. Pentru a afla ce buton a fost apăsat, se procedează astfel: - se determină codul modificatorilor, invocând metoda getModifiers() din clasa InputEvent; - se aplică asupra acestui cod măştile butoanelor, folosind operatorul de intersecţie pe biţi

212

Programarea orientata pe obiecte în limbajul Java

&. Butonul de mouse a cărui mască, intersectată cu codul mod, dă rezultat nenul este cel apăsat. Codul modificatorilor mod poate fi folosit şi pentru a determina daca, în timp ce s-a apăsat butonul de mouse, era apăsată şi una din tastele auxiliare Ctrl, Alt sau Shift, folosind măştile corespunzătoare acestor taste. Totuşi, aici s-a preferat utilizarea metodelor isControlDown(), isAltDown() şi isShiftDown() ale clasei InputEvent. Toate măştile folosite în această aplicaţie sunt definite în clasa java.awt.event.InputEvent.

Evenimente de tastă
De câte ori se apasă sau se eliberează o tastă, componenta activă a interfeţei grafice generează un eveniment de tastă din clasa java.awt.event.KeyEvent. Acesta poate fi tratat cu un "ascultător de taste" care implementează interfaţa java.awt.event.KeyListener sau care extinde clasa java.awt.event.KeyAdapter. Se disting următoarele evenimente generate la acţionarea tastelor: - a fost apasată o tastă (KEY_PRESSED); - a fost eliberată o tastă (KEY_RELEASED); - a fost "tipărit" un caracter, adică a fost apasată şi eliberată o tastă care transmite un caracter tipăribil (KEY_TYPED). Aceste situaţii pot fi detectate cu metodele corespunzătoare ale interfeţei KeyListener. Evenimentul generat de tastă conţine, în afară de codul tastei acţionate, şi informaţii privind starea tastelor auxiliare Ctrl, Alt şi Shift în momentul producerii evenimentului respectiv. Aceste stări pot fi detectate cu metodele corespunzătoare ale interfeţei superclasei java.awt.event.InputEvent.

Exemplu În fişierul EvTaste.java se dă un exemplu de aplicaţie, în care se urmăreşte apariţia unor evenimente de tastă, care sunt generate de fereastra aplicaţiei, atunci când ea este activă şi se actionează o tastă.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class EvTaste { static AF af=new AF(); // ascultatorul de fereastra static ATaste at=new ATaste(); // ascultatorul de taste static IUG iug=new IUG("Urmarirea evenimentelor de tasta"); /* Clasa imbricata pentru interfata utilizator */ static class IUG extends JFrame { /* Constructorul interfetei utilizator */

213

Severin Bumbaru

IUG(String titlu) { super(titlu); setSize(300,100); setLocation(new Point(100,50)); setVisible(true); addWindowListener(af); // adaugarea ascultatorului de fereastra addKeyListener(at); // adaugarea ascultatorului de taste } } /* Clasa imbricata pentru ascultatorul de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa imbricata pentru ascultatorul de taste */ static class ATaste implements KeyListener { public void keyTyped(KeyEvent e) { System.out.println(e); } public void keyPressed(KeyEvent e) { System.out.println(e); } public void keyReleased(KeyEvent e) { System.out.println(e); } } /* Metoda principala */ public static void main(String args[]) { } }

Ascultătorul de taste este realizat prin clasa imbricată ATaste, care implementează interfaţa KeyListener. Aşa cum sunt ele redefinite aici, metodele ascultătorului de taste afişează evenimentul respectiv la terminal. Punând în execuţie această aplicaţie, putem urmări la terminal succesiunea evenimentelor care sunt generate atunci când acţionam tastele. Remarcăm, de asemenea, că aceste evenimente sunt generate numai atunci când fereastra aplicaţiei este activă.

Pentru a detecta în ce condiţii a fost generat evenimentul (codul tastei care a fost apasată, inscripţia de pe tastă, ce taste auxiliare erau apăsate), putem utiliza metodele claselor KeyEvent şi superclasei acesteia InputEvent.

Exemplu În fişierul EvTaste1.java este dat un exemplu de aplicaţie în care se folosesc metodele din clasele KeyEvent şi InputEvent pentru a detecta situaţiile în care au apărut evenimentele captate de către ascultătorul de taste. Acesta a fost realizat prin extinderea clasei

214

Programarea orientata pe obiecte în limbajul Java

KeyAdapter.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class EvTaste1 { static AF af=new AF(); // ascultatorul de fereastra static ATaste at=new ATaste(); // ascultatorul de taste static IUG iug=new IUG("Urmarirea evenimentelor de tasta"); /* Clasa imbricata pentru interfata utilizator */ static class IUG extends JFrame { /* Constructorul interfetei utilizator */ IUG(String titlu) { super(titlu); setSize(300,100); setLocation(new Point(100,50)); setVisible(true); addWindowListener(af); // adaugarea ascultatorului de fereastra addKeyListener(at); // adaugarea ascultatorului de taste } } /* Clasa imbricata pentru ascultatorul de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa imbricata pentru ascultatorul de taste */ static class ATaste extends KeyAdapter { public void keyPressed(KeyEvent e) { int cod=e.getKeyCode(), mod=e.getModifiers(); String mesaj="S-a apasat tasta de cod "+cod+" "+e.getKeyText(cod); if(mod!=0) mesaj+=" cu modificatorii: "+e.getKeyModifiersText(mod); System.out.println(mesaj); } public void keyTyped(KeyEvent e) { System.out.println("S-a introdus caracterul "+e.getKeyChar()); } } /* Metoda principala */ public static void main(String args[]) { } }

Punând în execuţie această aplicaţie, putem urmări la terminal evenimentele de apăsare a unei taste (KEY_PRESSED) şi de introducere a unui caracter (KEY_TYPED) afişate sub formă decodificată. Pentru detalii privind metodele disponibile în clasele KeyAdapter, KeyEvent şi InputEvent se poate consulta documentaţia Java API.

215

Severin Bumbaru

Adăugarea unei componente la fereastra principală a aplicaţiei
Până în prezent, am arătat că, atunci când folosim JFC/Swing, fereastra principală a aplicaţiei este o instanţă a clasei javax.swing.JFrame sau a unei subclase a acesteia. Pentru a construi o interfaţă utilizator grafică este necesar ca în fereastra principală să introducem diverse componente. Acestea pot fi componente simple, ca etichete, butoane, casete de validare, câmpuri de text etc., sau pot fi containere, care conţin - la rândul lor - alte componente. Pentru realizarea acestor componente simple şi containere se folosesc clasele din pachetele java.awt şi javax.swing, care sunt descrise în documentaţia Java API. Adăugarea de componente nu se face direct la instanţele clasei JFrame. În JFrame există un Container (un obiect dintr-o subclasă a clasei Container) numit contentPane, la care se pot adăuga componente. O referinţă la acest Container se obţine prin metoda public Container getContentPane(), existentă în clasa JFrame. Adăugarea de componente se face prin una din metodele add() ale clasei Container. Dacă, de exemplu, comp este referinţă la o componentă, atunci expresia getContentPane().add(comp), folosită într-o metodă a clasei JFrame sau a subclaselor ei, adaugă componenta comp la containerul contentPane conţinut în JFrame. O prezentare succintă a clasei JFrame este dată în Indexul de clase, iar cea completă se găseşte în Java API, completată cu cea din Tutorialul Java.

Exemplul 1 În fişierul AdComp.java este un exemplu de aplicaţie, în care se testează adăugarea la fereastra principală a unui buton şi se face contorizarea numărului de apăsări pe butonul respectiv. În acest exemplu se arată: - cum se adaugă la fereastra aplicaţiei un buton (o instanţă a clasei JButton); - cum se tratează evenimentele de acţiune generate la apasarea butonului. În locul unui buton se putea folosi orice altă componentă.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class AdComp { static String textButon="Numar de actionari: "; static int contorActionari=0; static AF af=new AF(); static AB ab=new AB(); static JButton buton=new JButton(textButon+contorActionari); static IUG iug=new IUG("Un buton de contorizare a apasarilor"); /* clasa imbricata pentru interfata utilizator grafica */

216

Programarea orientata pe obiecte în limbajul Java

static class IUG extends JFrame { IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(300, 80); setLocation(200, 150); getContentPane().add(buton); // adaugarea butonului addWindowListener(af); // adaugarea ascultatorului de fereastra buton.addActionListener(ab); // adaugarea ascult. de actiune setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa ascultatoare de actiuni asupra butonului */ static class AB implements ActionListener { public void actionPerformed(ActionEvent e) { contorActionari++; buton.setText(textButon+contorActionari); } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Remarcăm următoarele: - S-au declarat trei clase imbricate: . clasa IUG, care este interfaţa utilizator grafică; . clasa AF, care este ascultătorul evenimentelor de fereastră, necesar pentru închiderea aplicaţiei; . clasa AB, care asculta şi tratează evenimentele de acţiune generate de buton. - În constructorul clasei IUG se adaugă la containerul de conţinut al ferestrei principale (contentPane) butonul button, din clasa JButton. În acest scop, se foloseşte instrucţiunea
getContentPane().add(buton);

- În acelaşi constructor, se adaugă la buton ascultătorul de evenimente de fereastră af şi ascultătorul de evenimente de acţiune ab. Obiectele indicate de referinţele buton af şi ab au fost construite înainte de a se construi interfaţa grafică. - Metoda actionPerformed(Event e) din clasa imbricată AB este invocată atunci când se produce un "eveniment de acţiune". Întrucat în programul nostru sursa evenimentelor de acţiune (la care a fost inregistrată instanţa ab a clasei AB) este butonul buton, evenimentele de acţiune vor fi generate atunci când se apasă pe buton (se face click de mouse pe suprafaţa butonului). În această metodă se incrementează variabila contorAcţionări şi se pune în buton un nou text, care indică valoarea contorului. În acest scop se foloseşte instrucţiunea buton.setText(text). Când punem în execuţie această aplicaţie, pe ecran apare o fereastră cu titlul "Un buton de

217

Severin Bumbaru contorizare a apăsărilor", în interiorul căreia se găseşte un buton pe care apare inscripţia "Număr de acţionări: 0". De fiecare dată, când facem click de mouse pe acest buton, constatăm că s-a modificat textul continut, indicand noua valoare a contorului de acţionări. Exemplul 2 Folosirea unei clase de buton de contorizare proprie, derivată din clasa JButton Având în vedere ca butonul de contorizare a apăsărilor poate fi, în principiu, folosit şi în mai multe aplicaţii, putem să îl declarăm ca o clasa publică într-un fişier separat, ca în fişierul ButonContor.java, reprodus aici:

/* Buton care contorizeaza numarul de apasari si afiseaza acest numar pe suprafata sa, dupa numele butonului. In acest scop, butonul isi asculta propriile evenimente de actiune. */ import java.awt.event.*; import javax.swing.*; public class ButonContor extends JButton implements ActionListener { private int contor; // contorul de apasari private String nume; // numele butonului public ButonContor(String nume) { // constructorul butonului contor=0; this.nume=nume+" #"; setText(this.nume+contor); addActionListener(this); // butonul se asculta pe sine } public void actionPerformed(ActionEvent e) { contor++; setText(nume+contor); } public int numarApasari() { // se intoarce numarul de apasari return contor; } public void reset() { // se pune contorul la zero contor=0; setText(nume+contor); } }

În fişierul AdComp1.java se dă o modificare a aplicaţiei din exemplul precedent, în care, în locul unei instanţe a clasei JButton, se foloseşte o instanţă a clasei ButonContor. În acest caz nu a mai fost necesar să se creeze o clasă de ascultare a butonului, întrucât butonul din clasa ButonContor se ascultă pe el însuşi.

import java.awt.event.*; import javax.swing.*;

218

Programarea orientata pe obiecte în limbajul Java

class AdComp1 { static AF af=new AF(); static ButonContor buton=new ButonContor("Contor apasari"); static IUG iug=new IUG("Un buton de contorizare a apasarilor"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(300, 80); setLocation(200, 150); getContentPane().add(buton); // adaugarea butonului addWindowListener(af); // adaugarea ascultatorului de fereastra setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Gestionarea poziţionării componentelor
Am arătat în secţiunea precedentă cum se poate plasa într-un container (în particular într-un contentPane) o singură componentă. În mod normal însă, într-un comtainer se plasează mai multe componente. În acest caz, este foarte important să se stabilească modul în care aceste componente sunt poziţionate pe suprafaţa containerului. În acest scop, a fost introdus conceptul de gestionar de poziţionare (în engleză: Layout Manager). Gestionarul de poziţionare este o clasă care asigură poziţionarea şi redimensionarea automată a componentelor situate într-un container, atât la crearea containerului, cât şi la modificarea dimensiunilor acestuia. Orice gestionar de poziţionare implementează interfaţa java.awt.LayoutManager sau subinterfaţa acesteia java.awt.LauoutManager2. În cele ce urmează, vom studia cele mai larg utilizate clase de gestionare a poziţionării, existente în pachetele java.awt şi javax.swing. Fiecare clasă de container are un gestionar de poziţionare implicit. Acesta este BorderLayout pentru Frame şi JFrame.contentPane şi FlowLayout pentru clasa Panel. Modificarea gestionarului de pozitionare se face prin metoda
public void setLayout(LayoutManager manager)

219

Severin Bumbaru din clasa java.awt.Container.

Clasa BorderLayout
Începem cu această clasă, deoarece este gestionarul de poziţionare implicit pentru conţinutul instanţelor clasei JFrame. În acest caz, se consideră că suprafaţa containerului este imparţită în cinci zone numite, respectiv, NORTH, SOUTH, WEST, EAST si CENTER. În fiecare din aceste zone se poate plasa numai o singură componentă, care poate fi însă ea însăşi un container. Adăugarea de componente la container se face, în acest caz, folosind metoda add(componenta, BorderLayout.ZONA), unde componenta este referinţa la componenta adaugată, iar ZONA este una din cele cinci zone menţionate mai sus. Dacă se foloseşte metoda add(componenta), fără a indica zona, componenta respectivă este plasată implicit în zona CENTER, aşa cum s-a procedat în secţiunea precedenta, în exemplul din fişierul AdComp.java. Exemplu În fişierul Butoane.java este dat un exemplu, în care se plasează în fereastra aplicaţiei câte un buton în fiecare din zonele NORTH,WEST, CENTER şi EAST şi o etichetă (instanţă a clasei javax.swing.JLabel) în zona SOUTH. Pe suprafaţa fiecărui buton este afişat numărul de apăsări succesive. În eticheta de la partea de jos se afişează numărul total de acţionări asupra tuturor butoanelor.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class Butoane { static AF af=new AF(); // ascultatorul de fereastra static AA aa=new AA(); // ascultatorul de actiuni static IUG iug=new IUG("Adaugare de butoane"); static int totalActionari=0; static class IUG extends JFrame { JLabel lab=new JLabel("Actionati butoanele"); ButonContor b1, b2, b3, b4; IUG(String titlu) { super(titlu); Container cp=getContentPane(); setSize(400,300); setLocation(200,150); addWindowListener(af); b1=new ButonContor("N"); b1.addActionListener(aa); cp.add(b1, BorderLayout.NORTH); b2=new ButonContor("E"); b2.addActionListener(aa); cp.add(b2, BorderLayout.EAST); b3=new ButonContor("W"); b3.addActionListener(aa); cp.add(b3, BorderLayout.WEST); b4=new ButonContor("C");

220

Programarea orientata pe obiecte în limbajul Java

b4.addActionListener(aa); cp.add(b4, BorderLayout.CENTER); cp.add(lab, BorderLayout.SOUTH); setVisible(true); } } static class AA implements ActionListener { public void actionPerformed(ActionEvent e) { totalActionari++; iug.lab.setText("Total actionari: "+totalActionari); } } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public static void main(String args[]) { } }

Remarcăm următoarele: - clasa ButonContor extinde clasa JButton şi implementează interfaţa ActionListener, fiind definită de noi ca o clasa publică în fişierul ButonContor.java. În constructorul acestei clase există instrucţiunea addActionListener(this), astfel că fiecare buton îşi ascultă propriile evenimente de acţiune; - în metoda actionPerformed(ActionEvent e) din clasa ButonContor se incrementează contorul intern al butonului respectiv şi se afişează valoarea contorului pe suprafaţa butonului; - în constructorul clasei imbricate IUG se adaugă câte un ButonContor în fiecare din zonele NORTH, WEST, EAST si CENTER ale ferestrei principale. - pentru a fi totalizat numărul de apăsări al tuturor butoanelor, la fiecare buton se adaugă, de asemenea, ascultătorul de acţiune ac, instanţă a clasei AC, care incrementează câmpul total al clasei Butoane; - eticheta din zona SOUTH face parte din clasa javax.swing.JLabel. Instanţele acestei clase au ca principal rol afişarea pe suprafaţa lor a unui text şi/sau a unei pictograme. În exemplul nostru se afişează numarul total de acţionări ale butoanelor (valoarea câmpului total al clasei Butoane); - adăugarea la fereastra aplicaţiei a butoanelor şi a etichetei se face în constructorul clasei IUG. Punând în execuţie această aplicaţie, putem vedea cum se plasează cele cinci componente pe suprafaţa ferestrei. Putem urmări, de asemenea, cum se modifică automat dimensiunile componentelor atunci când modificăm cu mouse-ul dimensiunile ferestrei. Dacă facem click de mouse pe oricare din butoane, se modifică atât contorul afişat pe butonul respectiv, cât şi numărul total de acţionari afişat pe eticheta din partea inferioară a ferestrei.

221

Severin Bumbaru

Clasa FlowLayout
Gestionarul de poziţionare java.awt.FlowLayout plasează componentele pe suprafaţa containerului una după alta, în ordinea în care acestea sunt adăugate, de la stânga la dreapta şi de sus în jos. Când s-a terminat o linie, se trece la linia următoare. Numărul de componente pe o linie depinde de lăţimea componentelor şi de lăţimea containerului în care sunt acestea plasate. Este posibil să se specifice modul de aliniere a componentelor: la stânga, la dreapta sau la centru. Aceasta se face fie specificând alinierea ca argument al constructorului, fie invocând metoda
public void setAlignement(int align)

în care argumentul align poate fi FlowLayout.LEFT, FlowLayout.RIGHT sau FlowLayout.CENTER.

Exemplul 1 În fişierul ButoaneF.java este dat un exemplu de aplicaţie, în care se testează gestionarul de poziţionare FlowLayout. În fereastra aplicaţiei sunt plasate mai multe butoane din clasa ButonContor şi un buton de anulare a tuturor contoarelor. Numărul de butoane de contorizare este dat la punerea în execuţie a aplicatiei, ca parametru în linia de comanda. Modificând cu mouse-ul dimensiunile ferestrei, putem constata cum se modifică în mod corespunzator dimensiunile şi amplasarea butoanelor. Îtrucât la crearea gestionarului de poziţionare nu s-a precizat alinierea componentelor, modul de aliniere este implicit CENTER.
/* Testarea gestionarului de pozitionare FlowLayout. In fereastra aplicatiei apar mai multe butoane din clasa ButonContor si un buton de anulare, a carui apasare anulaza toate contoarele. La lansarea in executie se da ca parametru in linia de comanda numarul de butoane de contorizare */ import java.awt.*; import java.awt.event.*; import javax.swing.*; class ButoaneF { static AF af=new AF(); // ascultatorul de fereastra static Anulare anulare=new Anulare(); // ascultatorul // butonului de anulare static IUG iug; // fereastra aplicatiei static int numarButoane; static class IUG extends JFrame { JLabel lab=new JLabel("Actionati butoanele"); ButonContor bc[]; JButton br=new JButton("Reset"); IUG(String titlu) { // constructorul ferestrei aplicatiei super(titlu);

222

Programarea orientata pe obiecte în limbajul Java

Container cp=getContentPane(); setSize(300,150); setLocation(200,150); addWindowListener(af); cp.setLayout(new FlowLayout()); // setarea gestionarului // de pozitionare bc=new ButonContor[numarButoane]; for(int i=0; i<numarButoane; i++) { bc[i]=new ButonContor("B"+(i+1)); // crearea butoanelor cp.add(bc[i]); // adaugarea butoanelor la contentPane } cp.add(br); // adaugarea butonului de anulare br.addActionListener(anulare); // adaugarea ascultatorului setVisible(true); } } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class Anulare implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i=0; i<numarButoane; i++) iug.bc[i].reset(); } } public static void main(String args[]) throws Exception { if(args.length!=1) { System.out.println("Utilizare: java ButoaneF <numarButoane>"); System.exit(1); } numarButoane=Integer.parseInt(args[0]); // crearea tabloului de // butoane iug=new IUG("FlowLayout cu "+numarButoane+ " butoane de contorizare"); // crearea ferestrei aplicatiei } }

Remarcăm că aplicaţia ButoaneF este o modificare a aplicaţiei Butoane din fişierul Butoane.java şi se deosebeşte de aceasta prin următoarele: - în metoda main se preia un argument din linia de comandă, care trebuie să fie numărul de butoane de acţionare solicitat de utilizator, după care se construieşte fereastra aplicaţiei; - s-a introdus tabloul bc[] care conţine referinţe la instanţele clasei ButonContor. Atât acest tablou, cât şi butoanele respective, se creează în constructorul clasei IUG; - după adăugarea butoanelor de contorizare, se adaugă la fereastra aplicaţiei butonul de anulare br, din clasa JButton; - pentru ascultarea acţionării butonului de anulare, a fost declarată clasa imbricată Anulare, care implementează interfaţa ActionListener.

223

Severin Bumbaru Exemplul 2 În fişierul Aliniere.java este dată o modificare a aplicaţiei din exemplul precedent, în care sau introdus trei butoane suplimentare, care au ca efect modificarea alinierii butoanelor pe suprafaţa containerului. În acest scop: - Pentru a se afla care buton a fost acţionat, în metoda actionPerformed() a clasei imbricate de ascultare a evenimentelor de acţiune Actiuni s-a invocat metoda getActionCommand()a clasei ActionEvent. Această metodă întoarce un String care, în mod obisnuit, este textul de pe butonul respectiv. Este posibil, totuşi, să se modifice acest şir folosind metoda setActionCommand(String command), care există în toate clasele care generează evenimente de acţiune, de exemplu în clasa AbstractButton, din care este derivată şi clasa JButton. - Modificarea alinierii se face invocând pentru gestionarul de poziţionare metoda setAlignment(int align) a clasei FlowLayout. Dupa ce s-a aplicat această metodă, se invocă pentru conţinutul ferestrei (contentPane) metoda doLayout() a clasei Container, pentru a se aplica efectiv noua poziţionare.
/* Testarea alinierii componentelor cand se foloseste gestionarul de pozitionare FlowLayout. In fereastra aplicatiei apar mai multe butoane din clasa ButonContor, trei butoane de aliniere si un buton de anulare. La lansarea in executie se da ca parametru in linia de comanda numarul de butoane de contorizare Actionarea unuia din butoanele de aliniere are ca efect modificarea corespunzatoare a alinierii componentelor pe suprafata containerului Actionarea butonului de anulare produce anuloarea tuturor contoarelor. */ import java.awt.*; import java.awt.event.*; import javax.swing.*; class Aliniere { static AF af=new AF(); // ascultatorul de fereastra static Actiuni actiuni=new Actiuni(); // ascultatorul // butoanelor de aliniere si de anulare static IUG iug; // fereastra aplicatiei static int numarButoane; static FlowLayout layout=new FlowLayout(); static class IUG extends JFrame { JLabel lab=new JLabel("Actionati butoanele"); ButonContor bc[]; JButton br=new JButton("Anulare"); JButton bas=new JButton("Stanga"), bac=new JButton("Centru"), bad=new JButton("Dreapta"); IUG(String titlu) { // constructorul ferestrei aplicatiei super(titlu);

224

Programarea orientata pe obiecte în limbajul Java

Container cp=getContentPane(); setSize(300,150); setLocation(200,150); addWindowListener(af); cp.setLayout(layout); // setarea gestionarului de pozitionare bc=new ButonContor[numarButoane]; for(int i=0; i<numarButoane; i++) { bc[i]=new ButonContor("B"+(i+1)); // crearea butoanelor cp.add(bc[i]); // adaugarea butoanelor la contentPane } cp.add(bas); cp.add(bac); cp.add(bad); // adaugare butoane // de aliniere cp.add(br); // adaugarea butonului de anulare br.addActionListener(actiuni); // adaugarea ascultatorului bas.addActionListener(actiuni); bac.addActionListener(actiuni); bad.addActionListener(actiuni); setVisible(true); } } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class Actiuni implements ActionListener { public void actionPerformed(ActionEvent e) { String comanda=e.getActionCommand(); if(comanda.equals("Anulare")) for(int i=0; i<numarButoane; i++) iug.bc[i].reset(); else { if(comanda.equals("Stanga")) layout.setAlignment(FlowLayout.LEFT); else if(comanda.equals("Centru")) layout.setAlignment(FlowLayout.CENTER); else if(comanda.equals("Dreapta")) layout.setAlignment(FlowLayout.RIGHT); iug.getContentPane().doLayout(); } } } public static void main(String args[]) throws Exception { if(args.length!=1) { System.out.println("Utilizare: java Aliniere <numarButoane>"); System.exit(1); } numarButoane=Integer.parseInt(args[0]); // crearea tabloului de // butoane iug=new IUG("FlowLayout cu "+numarButoane+ " butoane de contorizare"); // crearea ferestrei aplicatiei } }

225

Severin Bumbaru

Clasa GridLayout
Gestionarele de poziţionare din clasa java.awt.GridLayout plasează componentele în celulele unei grile rectangulare. În consecinţă, toate componentele de pe aceeaşi coloană sunt aliniate vertical. Clasa are doi constructori:
public GridLayout(int rows, int cols)

- are ca argumente numărul de linii şi de - are în plus ca

coloane al grilei;
public GridLayout(int rows, int cols, int hgap, int vgap)

argumente spaţiul orizontal şi, respectiv, vertical între componente. Deşi în constructor se indică atât numărul de linii, cât şi cel de coloane ale grilei, în realitate numai numărul de linii este respectat, în timp ce numărul de coloane este practic ignorat. La stabilirea amplasării componentelor în container, dacă numarul lor total este mai mic sau egal cu cel de linii, toate componentele vor fi aşezate vertical, una sub alta. Dacă numărul de componente este mai mare decât numărul de linii, numărul de coloane se stabileşte automat, prin împărţirea numărului de componente la cel de linii, cu rotunjire în plus. Plasarea efectivă a componentelor în celulele grilei astfel creeate se face apoi de la stânga la dreapta şi de sus in jos, la fel ca în cazul gestionarului FlowLayout, dar respectând alinierea verticală şi orizontală impusă de grilă.

Exemplu În fişierul Grila.java se dă un exemplu de aplicaţie, în care se testează gestionarul de poziţionare GridLayout. La punerea în execuţie a aplicaţiei, se dau ca parametri în linia de comandă numărul total de butoane de contorizare, numărul de linii al grilei şi numărul de coloane. Se poate observa ordinea în care sunt butoanele aşezate pe grilă şi modul în care se schimbă aspectul ferestrei, dacă se modifică cu mouse-ul dimensiunile acesteia. Se poate verifica şi ce se întâmplă dacă numărul de coloane dat ca parametru este mai mic sau mai mare decât cel necesar.
/* Testarea alinierii componentelor cand se foloseste gestionarul de pozitionare GridLayout. In fereastra aplicatiei apar mai multe butoane din clasa ButonContor si un buton de anulare. Punerea in executie a aplicatiei se face prin comanda java Grila <numarContoare> <nrLinii> <nrColoane> Actionarea butonului de anulare produce anuloarea tuturor contoarelor. */ import java.awt.*; import java.awt.event.*; import javax.swing.*; class Grila { static AF af=new AF(); // ascultatorul de fereastra

226

Programarea orientata pe obiecte în limbajul Java

static Actiuni actiuni=new Actiuni(); // ascultatorul // butonului de anulare static IUG iug; // fereastra aplicatiei static int numarButoane, nrLinii, nrColoane; static class IUG extends JFrame { JLabel lab=new JLabel("Actionati butoanele"); ButonContor bc[]; JButton br=new JButton("Anulare"); IUG(String titlu) { // constructorul ferestrei aplicatiei super(titlu); Container cp=getContentPane(); setSize(400,250); setLocation(200,150); addWindowListener(af); cp.setLayout(new GridLayout(nrLinii, nrColoane)); // setarea // gestionarului de pozitionare bc=new ButonContor[numarButoane]; for(int i=0; i<numarButoane; i++) { bc[i]=new ButonContor("B"+(i+1)); // crearea butoanelor cp.add(bc[i]); // adaugarea butoanelor la contentPane } cp.add(br); // adaugarea butonului de anulare br.addActionListener(actiuni); // adaugarea ascultatorului setVisible(true); } } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class Actiuni implements ActionListener { public void actionPerformed(ActionEvent e) { String comanda=e.getActionCommand(); if(comanda.equals("Anulare")) for(int i=0; i<numarButoane; i++) iug.bc[i].reset(); } } public static void main(String args[]) throws Exception { /* Verificarea numarului de argumente din linia de comanda */ if(args.length!=3) { System.out.println( "Utilizare: java <numarButoane> <nrLinii> <nrColoane>"); System.exit(1); } /* Preluarea argumentelor din linia de comanda */ numarButoane=Integer.parseInt(args[0]); nrLinii=Integer.parseInt(args[1]); nrColoane=Integer.parseInt(args[2]); /* Crearea ferestrei aplicatiei */ iug=new IUG("GridLayout cu "+numarButoane+ " butoane de contorizare"); } }

227

Severin Bumbaru

Clasa BoxLayout
Gestionarul de poziţionare javax.swing.BoxLayout plasează toate componentele containerului pe o singură direcţie, care poate fi orizontală sau verticală. Constructorul clasei este
public BoxLayout(Container target, int axis) în care target este o referinţa la containerul ţintă (al cărui gestionar de poziţionare este creat), iar axis poate fi BoxLayout.X_AXIS sau BoxLayout.Y_AXIS şi indică direcţia (axa)

după care vor fi plasate componentele.

Exemplul 1 În fişierul ButoaneBox.java se dă un exemplu de aplicaţie, în care se testează clasa BoxLayout. În acest caz, gestionarul BoxLayout a fost folosit direct în containerul contentPane al ferestrei aplicaţiei. La lansarea aplicaţiei în execuţie, se indică numărul de butoane de contorizare şi direcţia după care acestea vor fi plasate (x sau y). Se poate urmări efectul modificării cu mouse-ul a dimensiunilor ferestrei. Se observă că dimensiunile componentelor rămân constante, fără a mai fi influenţate de dimensiunile ferestrei.
/* Testarea gestionarului de pozitionare BoxLayout. In fereastra aplicatiei apar mai multe butoane din clasa ButonContor si un buton de anulare, a carui apasare anulaza toate contoarele. La lansarea in executie se da ca parametru in linia de comanda numarul de butoane de contorizare si numele axei in lungul careia vor fi plasate componentele (X sau Y). De exemplu, comanda java ButoaneBox 5 Y are ca efecte punerea in executie a aplicatiei cu 5 butoane contor si un buton de anulare, toate plasate unul sub altul */

import java.awt.*; import java.awt.event.*; import javax.swing.*; class ButoaneBox { static AF af=new AF(); // ascultatorul de fereastra static Anulare anulare=new Anulare(); // ascultatorul // butonului de anulare static IUG iug; // fereastra aplicatiei static int numarButoane; static int axa; static class IUG extends JFrame { JLabel lab=new JLabel("Actionati butoanele");

228

Programarea orientata pe obiecte în limbajul Java

ButonContor bc[]; JButton br=new JButton("Reset"); IUG(String titlu) { // constructorul ferestrei aplicatiei super(titlu); Container cp=getContentPane(); setSize(350,350); setLocation(200,150); addWindowListener(af); cp.setLayout(new BoxLayout(getContentPane(), axa)); bc=new ButonContor[numarButoane]; for(int i=0; i<numarButoane; i++) { bc[i]=new ButonContor("B"+(i+1)); // crearea butoanelor cp.add(bc[i]); // adaugarea butoanelor la contentPane } cp.add(br); // adaugarea butonului de anulare br.addActionListener(anulare); // adaugarea ascultatorului setVisible(true); } } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class Anulare implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i=0; i<numarButoane; i++) iug.bc[i].reset(); } } public static void main(String args[]) throws Exception { if(args.length!=2 ) { System.out.println( "Utilizare: java ButoaneBox <numarButoane> <axa>"); System.exit(1); } String numeAxa=args[1].toUpperCase(); if(numeAxa.equals("Y")) axa=BoxLayout.Y_AXIS; else if(numeAxa.equals("X")) axa=BoxLayout.X_AXIS; else { System.out.println("Axa nu poate fi decat X sau Y"); System.exit(1); } numarButoane=Integer.parseInt(args[0]); iug=new IUG("BoxLayout cu "+numarButoane+ " butoane de contorizare"); } }

În loc de a folosi gestionarul BoxLayout într-un container oarecare, se preferă să se foloseasca clasa javax.swing.Box, care are acest gestionar implicit.

229

Severin Bumbaru Exemplul 2 În fişierul TestBox.java se dă o variantă a aplicaţiei din exemplul precedent, în care se utilizează drept container pentru butoane o instanţă a clasei Box. În constructorul ferestrei aplicaţiei, după ce se creeaza această instanţă a clasei Box, se adaugă la ea succesiv toate componentele, după care este ea însăşi adaugată la conţinutul ferestrei aplicaţiei (la contentPane). În această situaţie, gestionarul de poziţionare BoxLayout este folosit prin intermediul instanţei clasei Box, în care sunt plasate componentele.
/* Testarea clasei javax.swing.Box In fereastra aplicatiei apar mai multe butoane din clasa ButonContor si un buton de anulare, a carui apasare anulaza toate contoarele. La lansarea in executie se da ca parametru in linia de comanda numarul de butoane de contorizare si numele axei in lungul careia vor fi plasate componentele (X sau Y). De exemplu, comanda java TestBox 5 Y are ca efecte punerea in executie a aplicatiei cu 5 butoane contor si un buton de anulare, toate plasate unul sub altul */

import java.awt.*; import java.awt.event.*; import javax.swing.*; class TestBox { static AF af=new AF(); // ascultatorul de fereastra static Anulare anulare=new Anulare(); // ascultatorul // butonului de anulare static IUG iug; // fereastra aplicatiei static int numarButoane; static int axa; static class IUG extends JFrame { JLabel lab=new JLabel("Actionati butoanele"); ButonContor bc[]; JButton br=new JButton("Reset"); Box box; IUG(String titlu) { // constructorul ferestrei aplicatiei super(titlu); setSize(350,350); setLocation(200,150); addWindowListener(af); box=new Box(axa); // se creeaza o instanta a clasei Box // orientata pe directia axa bc=new ButonContor[numarButoane]; for(int i=0; i<numarButoane; i++) { bc[i]=new ButonContor("B"+(i+1)); // crearea butoanelor box.add(bc[i]); // adaugarea butoanelor la caseta box } box.add(br); // adaugarea butonului de anulare br.addActionListener(anulare); // adaugarea ascultatorului getContentPane().add(box); // adaugarea casetei box la

230

Programarea orientata pe obiecte în limbajul Java

// contentPane setVisible(true); } } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class Anulare implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i=0; i<numarButoane; i++) iug.bc[i].reset(); } } public static void main(String args[]) throws Exception { if(args.length!=2 ) { System.out.println( "Utilizare: java Box <numarButoane> <axa>"); System.exit(1); } String numeAxa=args[1].toUpperCase(); if(numeAxa.equals("Y")) axa=BoxLayout.Y_AXIS; else if(numeAxa.equals("X")) axa=BoxLayout.X_AXIS; else { System.out.println("Axa nu poate fi decat X sau Y"); System.exit(1); } numarButoane=Integer.parseInt(args[0]); iug=new IUG("Box cu "+numarButoane+ " butoane de contorizare"); } }

Poziţionarea absolută a componentelor în container
În general, este recomandabil ca pentru amplasarea componentelor în containere să se folosească gestionari de poziţionare. În acest fel se asigură modificarea automată a poziţiei şi dimensiunilor componentelor, atunci când se modifică dimensiunile containerului. Există totuşi situaţii, în care se preferă ca poziţiile şi dimensiunile componentelor să fie fixe şi să fie indicate prin program. Din această cauză, în afară de amplasarea flexibilă a componentelor în containere, folosind clasele de gestionare a poziţionării, este posibil să se proiecteze şi interfeţe grafice în care poziţionarea componentelor se face folosind coordonatele şi dimensiunile absolute ale acestora. În acest scop: a/ pentru a se indica faptul că nu se foloseşte un gestionar de poziţionare, se recurge la instrucţiunea: setLayout(null); b/ pentru stabilirea coordonatelor colţului din stânga sus şi dimensiunilor fiecărei componente se foloseşte metoda
public void setBounds(int x, int y, int width, int height);

231

Severin Bumbaru din clasa java.awt.Component. În acest mod, fiecare componentă este plasată în container cu colţul stânga-sus în punctul de coordonate (x,y)şi are dimensiunile date prin lăţimea width şi înălţimea height. În locul metodei setBounds se pot folosi şi metodele
public void setLocation(Point location) public void setSize(int latime, int inaltime)

care există, de asemenea, în clasa Component şi permit setarea separată a poziţiei şi dimensiunilor componentelor. Poziţia şi dimensiunile astfel stabilite ale componentelor rămân neschimbate, chiar dacă se modifică dimensiunile containerului care le conţine.

Exemplu: În fişierul PozAbs.java se dă un exemplu de aplicaţie, în care se recurge la poziţionarea absolută a componentelor pe suprafaţa ferestrei principale a aplicaţiei, care este o instanţă a clasei JFrame. În fereastră (mai exact în containerul contentPane al ferestrei) se plasează următoarele componente: - o etichetă cu textul "Butoane de contorizare"; - patru butoane de contorizare ale clasei ButonContor, creată de noi anterior; - o etichetă cu textul "Anularea tuturor contoarelor >>>"; - un buton din clasa JButton cu inscriptia "Reset", care serveşte aici pentru a comanda punerea la zero a tuturor contoarelor. Pentru a realiza poziţionarea absolută a acestor componente, se seteaza la null gestionarul de poziţionare al containerului contentPane conţinut în fereastra aplicaţiei, iar pentru fiecare componentă se indică poziţia şi dimensiunile, invocând metoda setBounds().

/* Pozitionarea absoluta a componentelor in container */ import java.awt.*; import java.awt.event.*; import javax.swing.*; class PozAbs { static AF af=new AF(); // ascultatorul de fereastra static AA aa=new AA(); // ascultatorul de actiuni static IUG iug=new IUG("Pozitionare absoluta"); static class IUG extends JFrame { JLabel et1=new JLabel("Butoane de contorizare"); JLabel et2=new JLabel("Anularea tuturor contoarelor >>>"); JButton br=new JButton("Reset"); int numarContoare=4; ButonContor bc[]=new ButonContor[numarContoare]; /* Constructorul ferestrei aplicatiei */

232

Programarea orientata pe obiecte în limbajul Java

IUG(String titlu) { super(titlu); Container cp=getContentPane(); setSize(420,220); setLocation(200,150); addWindowListener(af); cp.setLayout(null); // se elimina gestionarul de pozitionare din // contentPane et1.setBounds(150, 5, 160,10); // pozitia si dimensiunile etichetei cp.add(et1); // adaugarea etichetei for(int i=0; i<numarContoare; i++) { bc[i]=new ButonContor("B"+i); // crearea butoanelor-contor bc[i].setBounds(10+100*i, 20, 90,70); // stabilirea pozitiei // si dimensiunilor butoanelor-contor cp.add(bc[i]); // adaugarea butoanelor-contor la contentPane } et2.setBounds(10, 140, 200,10); // locul si dimens. etichetei 2 cp.add(et2); // adaugarea etichetei 2 la contentPane br.setBounds(210, 120, 70, 60); // pozitia si dimensiunile // butonului de anulare br.addActionListener(aa); // ascultarea butonului de anulare cp.add(br); // adaugarea butonului de anulare la contentPane setVisible(true); } } /* Ascultatorul actiunilor butonului de anulare. Cand este apasat acest buton, se pun la zero toate contoarele */ static class AA implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i=0; i<iug.numarContoare; i++) iug.bc[i].reset(); } } /* Ascultarea evenimentelor de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public static void main(String args[]) { } }

Gruparea componentelor
Pentru a poziţiona componentele în structuri mai complicate decât cele oferite de clasele de gestionare a poziţiei existente, se poate recurge la gruparea mai multor componente în containere auxiliare, care se plasează apoi în containerul principal. Drept containere auxiliare, se folosesc cel mai frecvent instanţe ale claselor java.awt.Panel, javax.swing.JPanel şi javax.swing.Box.

233

Severin Bumbaru

Clasele Panel si Jpanel
Clasa java.awt.Panel (panou) reprezinta cel mai simplu container, care este o suprafaţă dreptunghiulară fără bordură. Gestionarul de poziţionare implicit este FlowLayout, dar se poate pune oricare altul. Panourile sunt frecvent folosite pentru a plasa pe ele diferite componente, formând un grup care se plaseaza apoi într-un alt container. Clasa javax.swing.JPanel este varianta de Panel folosită în JFC/Swing, fiind un container genreric. Exemplu În fişierul Panouri.java este dat un exemplu în care fereastra aplicaţiei, cu gestionar de pozitionare GridLayout, conţine în cele patru celule ale reţelei două panouri şi două butoane de anulare. Un panou conţine 5 butoane de contorizare amplasate cu gestionar BorderLayout, iar alt panou conţine trei butoane de contorizare amplasate cu FlowLayout (gestionarul implicit). Fiecare buton de anulare pune la zero contoarele de apăsări dintr-un panou.

import java.awt.*; import java.awt.event.*; import javax.swing.*; class Panouri { static ButonContor bc[]=new ButonContor[8]; static JButton br1=new JButton("Reset 1"), br2=new JButton("Reset 2"); static Anulare anulare=new Anulare(); static IUG iug=new IUG("Gruparea componentelor pe panouri"); static class IUG extends JFrame { JPanel p1=new JPanel(); // p1 are implicit FlowLayout JPanel p2=new JPanel(new BorderLayout()); // p2 primeste BorderLayout IUG(String titlu) { super(titlu); Container cp=getContentPane(); setSize(500,400); addWindowListener(new AF()); cp.setLayout(new GridLayout(2,2)); /* Crearea butoanelor */ for(int i=0; i<8; i++) bc[i]=new ButonContor("B"+i); /* Completarea cu butoane a panoului p2 */ p2.add(bc[0], BorderLayout.NORTH); p2.add(bc[1], BorderLayout.WEST); p2.add(bc[2], BorderLayout.CENTER); p2.add(bc[3], BorderLayout.EAST); p2.add(bc[4], BorderLayout.SOUTH); /* Completarea panoului p1 */ for(int i=5; i<8; i++) p1.add(bc[i]); /* Adaugarea de ascultatori la butoanele de anulare */ br1.addActionListener(anulare); br2.addActionListener(anulare); /* Adaugarea de panouri si butoane la controlPane */ cp.add(p2); cp.add(p1); cp.add(br1);

234

Programarea orientata pe obiecte în limbajul Java

cp.add(br2); setVisible(true); } } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class Anulare implements ActionListener { public void actionPerformed(ActionEvent e) { String actiune=e.getActionCommand(); if(actiune.equals(br1.getText())) for(int i=0; i<5; i++) bc[i].reset(); else if(actiune.equals(br2.getText())) for(int i=5; i<8; i++) bc[i].reset(); } } public static void main(String args[]) { } }

Clasa Box
Clasa javax.swing.Box are ca instanţe containere speciale, care nu pot folosi decât gestionarul de poziţionare javax.swing.BoxLayout. Orice incercare de a-i pune alt gestionar produce o eroare de execuţie din clasa AWTError. Într-un container din clasa Box, componentele pot fi amplasate numai pe o singură direcţie: orizontală sau verticală.

Clasa dispune de metode statice pentru crearea de "casete" (instanţe ale clasei Box) orizontale sau verticale:
public static Box createHorizontalBox() public static Box createVerticalBox()

Se pot creea, de asemenea "componente invizibile" de dimensiuni fixe sau variabile, care pot fi folosite la distanţarea componentelor vizibile. În acest scop, se folosesc metodele public static Component createRigidArea(Dimension d) - creeaza o "componentă ascunsă" de dimensiune fixă; public static Component createHorizontalStrut(int width) - creeaza o "componentă ascunsă" de lăţime fixă, dar de înălţime variabilă; public static Component createVerticalStrut(int height) - creeaza o "componentă ascunsă" de înalţime fixă, dar de lăţime variabilă; public static Component createGlue() - creaza o "componentă ascunsă" cu ambele dimensiuni variabile;

235

Severin Bumbaru Combinarea adecvată de containere din clasa Box şi folosirea în acestea a diferitelor componente vizibile şi ascunse permite crearea de interfeţe grafice cu aspecte foarte diversificate.

Exemplu În fişierul Casete.java este dată o aplicaţie în care se folosesc trei casete din clasa Box: două verticale şi una orizontală.

/* Exemplu de utilizare a containerelor din clasa javax.swing.Box */ import java.awt.*; import java.awt.event.*; import javax.swing.*; class Casete { static ButonContor bc[]=new ButonContor[5]; static JButton br1=new JButton("Reset 1"), br2=new JButton("Reset 2"); static Anulare anulare=new Anulare(); static IUG iug=new IUG("Gruparea componentelor in casete"); static class IUG extends JFrame { Box c1=Box.createVerticalBox(); Box c2=Box.createVerticalBox(); Box c3=Box.createHorizontalBox(); IUG(String titlu) { super(titlu); setSize(280,160); addWindowListener(new AF()); /* Crearea butoanelor */ for(int i=0; i<5; i++) bc[i]=new ButonContor("B"+i); /* Completarea cu butoane a casetei c1 */ c1.add(Box.createVerticalStrut(18)); // deplasare in jos c1.add(bc[0]); c1.add(bc[1]); c1.add(Box.createVerticalStrut(10)); // deplasare in jos c1.add(br1); /* Completarea casetei c2 */ for(int i=2; i<5; i++) c2.add(bc[i]); c2.add(Box.createVerticalStrut(5)); // deplasare in jos c2.add(br2); /* Adaugarea de ascultatori la butoanele de anulare */ br1.addActionListener(anulare); br2.addActionListener(anulare); /* Adaugarea casetelor c1 si c2 la caseta c3 */ c3.add(Box.createGlue()); // pentru alinierea la centru c3.add(c1); c3.add(Box.createHorizontalStrut(10)); c3.add(c2); /* Adaugarea casetei c3 la contentPane */ getContentPane().add(c3); setVisible(true);

236

Programarea orientata pe obiecte în limbajul Java

} } static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class Anulare implements ActionListener { public void actionPerformed(ActionEvent e) { String actiune=e.getActionCommand(); if(actiune.equals(br1.getText())) for(int i=0; i<2; i++) bc[i].reset(); else if(actiune.equals(br2.getText())) for(int i=2; i<5; i++) bc[i].reset(); } } public static void main(String args[]) { } }

Remarcăm următoarele: - butoanele au fost plasate în cele două casete verticale, c1 şi c2; - întrucât numărul de butoane din cele două casete este diferit, în caseta c1 s-a introdus o componentă ascunsă pentru alinierea la partea inferioară. De asemenea, în ambele casete verticale s-au introdus în faţa ultimului buton componente ascunse pentru distanţare; - cele două casete verticale au fost introduse în cea orizontală; - componenta ascunsă cu dimensiuni variabile, introdusă în faţa primei casete din c3, a fost pusă pentru a se obţine alinierea pe centru a casetelor c1 şi c2. În lipsa acesteia, caseta c1 ar fi fost aliniată la stânga; - caseta c3 a fost introdusă în contentPane. Urmăriţi ce se întâmplă când modificaţi cu mouse-ul dimensiunile ferestrei aplicaţiei.

Întrebări
Nivel 1
1. 2. 3. 4. 5. 6. 7. 8. 9. Ce este interfaţa utilizator? Ce este interfaţa utilizator grafică? Ce sunt obiectele grafice? Ce este AWT? Ce este JFC/Swing? Ce rol are clasa Component şi din ce pachet face parte? Ce deosebire este între componentele atomice şi containere? Care este clasa folosită în JFC/Swing pentru fereastra principală a aplicaţiei? Ce conţin la partea superioară instanţele clasei JFrame?

237

Severin Bumbaru 10. Ce sunt evenimentele în cazul interfeţei utilizator grafice? 11. Ce este programarea orientată pe evenimente? 12. Care este modelul de evenimente folosit pe platforma Java 2? 13. Cum sunt generate evenimentele? 14. Cum sunt utilizate evenimentele? 15. Ce deosebire este între evenimentele de nivel jos şi cele semantice? 16. Care este rădăcina claselor de evenimente generate de obiectele interfeţei grafice? 17. Ce clase de evenimente cunoasteţi? 18. În ce pachete se găsesc clasele de evenimente? 19. Ce sunt interfeţele pentru ascultarea de evenimente şi la ce folosesc? 20. Care este rolul unei clase ascultătoare de evenimente? 21. Ce este un adaptor pentru ascultarea de evenimente? 22. Cum poate fi comandată prin interfaţa utilizator grafică încheierea executării aplicaţiei? 23. Care este componenta instanţei clasei JFrame la care se adaugă componentele interfeţei grafice? 24. Ce este un gestionar de poziţionare? 25. Care sunt interfeţele gestionarilor de poziţionare? 26. Care este gestionarul de poziţionare implicit pentru contentPane? 27. Care este gestionarul de poziţionare implicit pentru Panel şi JPanel? 28. Cum sunt amplasate componentele în cazul gestionarului BorderLayout? 29. Cum sunt amplasate componentele în container în cazul gestionarului FlowLayout? 30. Cum sunt aliniate componentele în cazul gestionarului FlowLayout? 31. Ce fel de evenimente generează un buton? 32. Cum sunt amplasate componentele în cazul gestionarului GridLayout? 33. Cum se stabileste numarul de linii şi de coloane pentru gestionarul GridLayout? 34. Cum sunt plasate componentele într-un BoxLayout?

Nivel 2
1. Ce categorii de interfeţe utilizator cunoasteţi? 2. Ce este un limbaj de comandă şi ce astfel de limbaje cunoasteţi? 3. Care sunt în prezent cele mai larg folosite interfeţe utilizator grafice? 4. Prin ce pachete se realizează AWT pe platformele Java? 5. Prin ce pachete se realizează JFC/Swing pe platforma Java 2? 6. In ce constă deosebirea de abordare între AWT şi JFC/Swing? 7. Ce metode ale clasei Component cunoasteţi? 8. Ce metode ale clasei JFrame cunoasteţi? 9. Cum este ghidat procesul de calcul în programarea procedurală tradiţională? 10. Cine are iniţiativa acţiunilor în programarea orientată pe evenimente? 11. Care sunt obiectele care intervin atunci când se generează un eveniment? 12. Ce sunt evenimentele de mouse şi cum sunt ele generate? 13. Ce sunt evenimentele de tastă şi cum sunt ele generate? 14. Care sunt evenimentele generate de o fereastră şi cum sunt ele ascultate? 15. Care sunt evenimentele de mouse? 16. Cum sunt ascultate evenimentele de mouse? 17. Care sunt evenimentele de tasta? 18. Cum sunt ascultate evenimentele de tasta? 19. În ce stare trebuie sa fie fereastra pentru a genera evenimente de tastă?

238

Programarea orientata pe obiecte în limbajul Java 20. În ce clasă există metoda getContentPane() şi la ce foloseşte? 21. Cum se adaugă un ascultător la o sursă? 22. Poate un obiect grafic să-şi asculte propriile evenimente? Cum se programează aceasta? 23. Care sunt zonele unui BorderLayout şi câte componente pot fi plasate în fiecare zonă? 24. Ce se întâmplă cu componentele unui container cu gestionar BorderLayout dacă se modifică dimensiunile acestuia? 25. Dacă într-un container există mai multe butoane, cum se poate afla care din ele a fost acţionat atunci când este receptat un eveniment de acţiune? 26. Ce se întâmplă cu componentele unui container cu gestionar FlowLayout, dacă se modifică laţimea acestuia? 27. Ce se întâmplă în cazul gestionarului GridLayout, dacă numarul de componente conţinute este mai mare decât numărul de celule ale grilei stabilite la crearea containerului? 28. În ce ordine se plasează componentele adăugate la un container cu gestionar GridLayout? 29. Ce legatură există între clasele Box şi BoxLayout? 30. Ce se înţelege prin poziţionarea absolută a componentelor? 31. Cum se setează gestionarul de poziţionare pentru a realiza poziţionarea absolută? 32. Ce metode se folosesc pentru poziţionarea absolută a componentelor? 33. Ce fel de containere auxiliare se folosesc pentru gruparea componentelor? 34. Ce este un Panel sau un JPanel? 35. Ce reprezintă clasa Box? 36. Cum pot fi create instanţele clasei Box? 37. Ce componente ascunse se pot pune într-un container din clasa Box?

239

Severin Bumbaru

Componente grafice din JFC/Swing
Componente ale interfeţei utilizator grafice Clasa JComponent Componente de control Butoane Clasa AbstractButton Butoane obişnuite (cu o singură stare stabilă) Butoane cu două stări stabile Casete de validare Butoane radio Liste Liste ascunse Câmpuri de text Rigle cu cursor Meniuri Meniuri cu bară Meniuri volante Componente de afişare Componente de afişare needitabile Componente de afişare editabile Întrebări. 240 241 244 244 244 245 245 246 246 249 250 253 256 258 258 260 262 262 263 265

Componentele din JFC/Swing
Pentru a realiza interfaţa utilizator grafică a unei aplicaţii folosind JFC/Swing, este necesar ca la fereastra aplicaţiei (din clasa JFrame) să se adauge diferite obiecte grafice numite componente. Deosebim: 1. Componente de control: butoane, butoane radio, casete de validare, liste, liste ascunse, meniuri, rigle ajustabile, câmpuri de text. 2. Componente de afişare needitabile: etichete, indicatoare, etc. 3. Componente de afişare editabile: zone de text, tabele editabile, selectoare de culori, selectoare de fişiere, arbori. 4. Containere: panouri, panouri glisante, etc.

O prezentare a acestor componente însoţită de ilustraţii sugestive şi de descrierea detaliată a utilizării fiecăreia se găseşte în lecţia Using Swing Components din sectiunea Creating a GUI

240

Programarea orientata pe obiecte în limbajul Java with JFC/Swing din Java Tutorial. Noi nu vom face aici, din lipsă de timp, decât o scurta prezentare a unor componente mai larg folosite şi a modului lor de utilizare. Majoritatea claselor de componente din JFC/Swing sunt derivate din clasa JComponent, moştenind caracteristicile acestei clase.

Clasa JComponent
Clasa abstracta javax.swing.JComponent extinde clasa java.awt.Container. In consecinţă, orice componenta Swing are şi caracteristici de container. Iată, ca exemplu, câteva facilităţi pe care le ofera orice JComponentă: - posibilitatea de a-i ataşa componentei o "inscripţie volanta" (engleză: ToolTip), adică o inscripţie explicativă care apare când punem cursorul de mouse pe componenta respectivă, fără să apăsăm butonul. O astfel de inscripţie se ataşează componentei prin metoda public void setToolTipText(String text); - posibilitatea de a-i pune componentei o bordură, folosind metoda
public void setBorder(Border border);

- posibilitatea de a modifica culoarea de fond şi culoarea de primplan ale componentei folosind metodele:
public void setBackground(Color color); public void setForeground(Color color); Mai multe metode sunt indicate în Indexul de clase.

O specificaţie mai detaliată a clasei JComponent este dată în indexul de clase. Specificaţia completă a clasei JComponent este dată in documentaţia Java API, iar o descriere amplă a facilităţilor şi modului de utilizare se găseşte în capitolul The JComponent class din tutorialul Java.

Caracteristicile componentelor JFC/Swing
Fiecare componentă a interfeţei utilizator grafice se caracterizează prin aspect, stare şi comportament. Aspectul este dat de figura prin care este reprezentată pe ecran componenta respectivă. Fiecare clasă de componente oferă un aspect tipic al instanţelor sale. În JFC/Swing programatorului i se pun însa la dispoziţie metode şi clase prin care poate modifica aspectul componentelor. Starea componentei este dată, ca la orice obiect, de valorile câmpurilor sale. Starea se poate modifica fie datorită acţiunilor utilizatorului, fie datorită invocării prin program a unor metode ale acesteia. În general, la modificarea stării componenta generează un eveniment, iar uneori îşi schimbă şi aspectul. Comportamentul componentei este modul în care aceasta reacţionează atunci cînd se acţionează asupra ei de către utilizator sau când este invocată o metodă şi constă în modificarea stării şi a aspectului şi în generarea de evenimente.

241

Severin Bumbaru Componentele din JFC/Swing oferă programatorului unele facilităţi importante privind stabilirea aspectului şi comportamentului acestora. Iată unele ditre ele: - posibilitatea de a introduce în componentă un text, o imagine, sau ambele; - posibilitatea de a adăuga la fiecare componentă a unui text explicativ volant (engleză: Tool Tip), care conţine indicaţii care apar când se pune cursorul mouse-ului pe componenta respectivă, fără a apăsa butonul de mouse. - posibilitatea de a pune în jurul componentei una sau mai multe borduri (engleză: Border), cu diferite aspecte: chenar simplu, chenar cu titlu, bordură care dă aspect de supraînălţare, bordură care dă aspect de scufundare; - posibilitatea de a alege aspectul general al tuturor componentelor interfeţei grafice (engleză: Look and Feel).

Pentru a ataşa componentei un text volant se foloseşte metoda clasei JComponent
public void setToolTipText(String text);

Pentru a pune o bordură se foloseşte clasa javax.swing.BorderFactory care oferă metode pentru crearea unor borduri standard. Clasele de borduri şi interfaţa Border se gasesc în pachetul javax.swing.border. Adăugarea bordurii la componentă se face prin metoda clasei JComponent
public void setBorder(Border border);

Iată unele din metodele clasei BorderFactory:
public static Border createLineBorder(Color color)

- creează o bordură (un -

chenar) dintr-o singură linie;
public static Border createLineBorder(Color color, int thickness)

creează o bordură formată dintr-o linie de grosime (thickness) dată; public static Border createRaisedBevelBorder() - creeaza o bordură cu aspect de înălţare a componentei; public static Border createLoweredBevelBorder() - creeaza o bordura cu aspect de scufundare a componentei;
public static Border createBevelBorder(int type, Color highlight, Color shadow) - creeaza o bordură pentru care se impun tipul şi culorile, unde: type - tipul bordurii, care poate fi BevelBorder.RAISED sau BevelBorder.LOWERED; highlight - culoarea marginii care creează aspectul de înălţare sau coborâre; shadow - culoarea umbrei. public static TitledBorder createTitledBorder(String title) - creeaza o

bordură cu titlu.
public static CompoundBorder createCompoundBorder(Border outsideBorder, Border insideBorder) - creeaza o bordură compusă, indicându-se

bordura exterioară (outsideBorder) şi cea interioara (insideBorder).

Exemplu În fişierul TestComp.java este dat un exemplu de testare a posibilităţii de a stabili aspectul

242

Programarea orientata pe obiecte în limbajul Java

componentelor şi a le ataşa texte volante. Drept componente s-au folosit, deocamdată, instanţe ale clasei JButton, care a mai fost deja utilizată în capitolul precedent. Acelaşi mod de lucru poate fi însă folosit pentru orice alte clase care sunt derivate din JComponent.
import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.border.*;

class TestComp { static AF af=new AF(); static IUG iug=new IUG("Butoane cu diferite aspecte"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { //JLabel lab1, lab2, lab3, lab4; //JPanel panel; JButton b1,b2,b3,b4,b5,b6,b7; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(300, 400); setLocation(200, 150); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra //panel=new JPanel(); cp.setLayout(new GridLayout(7,1)); //panel.setBackground(Color.red); b1=new JButton("Un buton obisnuit"); //b1.setBackground(Color.blue); b2=new JButton("Un buton cu bordura liniara"); b2.setBorder(BorderFactory.createLineBorder(Color.red, 4)); b2.setToolTipText("Bordura este rosie cu grosimea 4"); b3=new JButton("Un buton cu bordura cu titlu"); b3.setBorder(BorderFactory.createTitledBorder("Un titlu")); b4=new JButton("Un buton ridicat"); b4.setBorder(BorderFactory.createRaisedBevelBorder()); b5=new JButton("Un buton coborat"); b5.setBorder(BorderFactory.createLoweredBevelBorder()); b6=new JButton(new ImageIcon("buton_go.gif")); b6.setBorder(BorderFactory.createTitledBorder( "Un buton cu titlu, care contine o imagine")); b7=new JButton("Un buton cu bordura compusa"); Border bord1, bord2, bord3; bord1=BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.green, Color.blue); bord2=BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.cyan, Color.yellow); bord3=BorderFactory.createCompoundBorder(bord1, bord2); b7.setBorder(bord3); cp.add(b1); cp.add(b2); cp.add(b3); cp.add(b4); cp.add(b5); cp.add(b6); cp.add(b7); setVisible(true);

243

Severin Bumbaru

} } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Remarcăm următoarele: - pentru a lucra cu borduri, a fost necesar să se importe pachetul javax.swing.border; - s-au folosit bordurile standard din clasa BorderFactory; - butonului al doilea (b2, cel cu bordura roşie groasă) i s-a ataşat un text explicativ volant; dacă punem cursorul de mouse deasupra acestui buton fără să apăsăm şi aşteptăm câteva secunde, apare textul volant; - în penultimul buton (b6) s-a introdus imaginea care se găseşte în fişierul buton_go.gif. Dacă doriţi sa testati programul, această imagine trebuie să existe în acelaşi director cu clasa TestComp (deci în directorul curent al dvs). Pentru a realiza acest lucru, puneţi cursorul mouse-ului pe această imagine, apasaţi butonul din dreapta şi va apare un meniu din care selectaţi opţiunea "Save image as ..." sau "Save picture as ...", apoi salvaţi imaginea în acelaşi director cu fişierul TestComp.java.

Componente de control
Componentele de control sunt dispozitive de intrare virtuale, uşor de utilizat, prin intermediul cărora utilizatorul poate introduce anumite comenzi. Din această categorie fac parte butoanele, riglele ajustabile, listele, listele ascunse, meniurile şi câmpurile de text.

Butoane
Butoanele sunt obiecte grafice, al căror aspect este similar celor al butoanelor folosite în aparatura electronică şi electrică. Acţionarea butonului se face punând deasupra lui cursorul mouse-ului şi apasând un buton al acestuia. În JFC/Swing există mai multe tipuri de butoane: butonul simplu, caseta de validare, butonul radio şi articolul de meniu. Toate acestea sunt subclase ale clasei abstracte javax.swing.AbstractButton.

Clasa AbstractButton
Clasa javax.swing.AbstractButton defineşte caracteristicile comune ale diferitelor tipuri de butoane din JFC/Swing. Iată câteva dintre acestea:

244

Programarea orientata pe obiecte în limbajul Java - orice buton poate avea un text, care poate fi pus prin metoda void setText(String text) şi poate fi aflat prin metoda String getText(); - orice buton poate avea cel puţin o pictogramă, care poate fi pusă prin metoda void setIcon(Icon defaultIcon) şi obţinută prin metoda Icon getIcon(); - orice buton poate avea o mnemonică, adică i se poate asocia o tastă, a cărei apasare are acelaşi efect cu apăsarea cu mouse-ul a butonului respectiv. Mnemonica se pune cu metoda void setMnemonic(int mnemonic), al cărei argument este codul tastei care trebuie acţionată; - butonul generează trei tipuri de evenimente: . ActionEvent, când se acţioneaza asupra lui; . ChangeEvent, când îşi modifică starea; . ItemEvent, când butonul este selectat sau deselectat. Există metode de adăugare a ascultătoarelor pentru aceste trei tipuri de evenimente:
void addActionListener(ActionListener a), void addChangeListener(ChangeListener c), void addItemListener(ItemListener i);

- butonul are un nume al acţiunii de comandă pe care o exercită, sub forma unui şir de caractere; implicit, acest nume este identic cu textul butonului, dar el poate fi modificat prin metoda void setActionCommand(String command) şi poate fi aflat prin metoda String getActionCommand(); O prezentare mai amplă a clasei AbstractButton este dată în Indexul de clase. În secţiunile următoare vom prezenta modul de utilizare a diferitelor tipuri de butoane, folosind clasele corespunzătoare ale pachetului javax.swing.

Butonul obişnuit (cu o singură stare stabilă)
Butoanele obişnuite se realizează folosind clasa javax.swing.JButton, derivată din clasa javax.swing.AbstractButton, aşa cum am arătat în exemplele date în capitolul precedent. Butonul obişnuit are o singură stare stabilă, în care ajunge când este eliberat. Cât timp se ţine butonul de mouse apăsat deasupra lui, butonul se află în starea "apăsat" ("acţionat"). De fiecare dată când este acţionat, butonul generează un AcţionEvent şi un ChangeEvent. Când este eliberat, generează numai un ChangeEvent. Butonul generează de asemenea, ca orice altă componentă, evenimente de componentă, de mouse şi de tastă. Totuşi, în cazul butoanelor obişnuite, cel mai frecvent se folosesc evenimentele de acţiune (ActionEvent), care sunt ascultate de un ascultător de acţiune (ActionListener). Clasele Button şi JButton sunt specificate mai detaliat în Indexul de clase.

Butonul cu două stări stabile
Clasa javax.swing.JToggleButton este derivata din javax.swing.AbstractButton şi reprezintă butoane cu două stări stabile. Trecerea de la o stare la alta este impusă de utilizator prin click de mouse sau este comandată prin program. În practică, cel mai frecvent se folosesc caseta de validare şi butonul radio, realizate prin subclase ale clasei ToggleButton şi prezentate în continuare. O specificare mai detaliată a clasei JToggleButton este dată în indexul de clase.

245

Severin Bumbaru

Caseta de validare
Caseta de validare este un obiect de control cu două stări stabile. La fiecare click de mouse pe suprafata casetei de validare, aceasta trece dintr-o stare în cealaltă. În mod obişnuit, caseta de validare este reprezentată grafic printr-un patrat, în care apare un simbol de bifare (de validare), cum ar fi simbolul V sau X, când caseta se găseşte în starea "selectat". În starea opusă (deselectat), caseta este vidă. Imaginea casetei este insoţită şi de un text explicativ. În unele sisteme de interfaţă grafică, în locul casetei de validare se foloseste un comutator ("switch"). Acesta are aspectul unui buton, la care ambele stări ("apăsat" şi "eliberat") sunt stabile, trecerea de la o stare la alta făcându-se la fiecare click de mouse pe suprafaţa butonului. Din punct de vedere funcţional, astfel de comutatoare se comporta identic cu casetele de validare, diferenţa fiind numai în aspectul grafic. În JFC/Swing casetele de validare se realizează folosind clasa javax.swing.JCheckBox, care este derivată din clasa javax.swing.JToggleButton.

Dintre evenimentele generate de caseta de validare, cel mai important este java.awt.event.ItemEvent, care este ascultat de clase cu interfaţa java.awt.event.ItemListener. Astfel de evenimente sunt generate în momentele în care caseta a fost selectată sau deselectată. Metoda prin care se captează astfel de evenimente întrun ItemListener este
public void itemStateChanged(ItemEvent e)

Pentru a afla în ce stare se gaseşte caseta la un moment dat, se foloseşte metoda
public boolean isSelected()

din superclasa javax.swing.AbstractButton.

Butonul radio
Butonul radio este un buton cu două stări stabile, care face parte dintr-un grup de butoane, astfel că la un moment dat numai unul dintre ele poate fi "selectat". În momentul în care este selectat ("apăsat") un buton al grupului, se deselectează automat cel care era selectat anterior. În JFC/Swing, butoanele radio sunt realizate ca instanţe ale clasei javax.swing.JRadioButton, care este derivată din clasa javax.swing.JToggleButton. Gruparea butoanelor se face folosind clasa javax.swing.ButtonGroup.

Exemplu În fişierul TestButoane.java se dă un exemplu de aplicaţie, în care se testează butoanul simplu, caseta de validare şi butonul radio.
import java.awt.*; import java.awt.event.*;

246

Programarea orientata pe obiecte în limbajul Java

import javax.swing.*;

class TestButoane { static AF af=new AF(); static Ascultator ascult=new Ascultator(); static IUG iug=new IUG("Diferite tipuri de butoane"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JButton b1,b2; JCheckBox cb1, cb2; JRadioButton rb1, rb2, rb3; ButtonGroup grup; Box box1, box2, box3, box4; JLabel label; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(300, 150); setLocation(200, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra /* Se creeaza doua butoane, b1 si b2, care se pun in caseta box1 */ b1=new JButton("B1 (F1)"); b1.addActionListener(ascult); b1.setMnemonic(KeyEvent.VK_F1); b2=new JButton("B2 (F2)"); b2.addActionListener(ascult); b2.setMnemonic(KeyEvent.VK_F2); box1=Box.createVerticalBox(); box1.add(new JLabel("Butoane simple")); box1.add(b1); box1.add(b2); /* Se creeaza doua casete de validare, care se pun in box2 */ cb1=new JCheckBox("Caseta 1 (A)"); cb1.addItemListener(ascult); cb1.setMnemonic(KeyEvent.VK_A); cb2=new JCheckBox("Caseta 2 (S)"); cb2.addItemListener(ascult); cb2.setMnemonic(KeyEvent.VK_S); box2=Box.createVerticalBox(); box2.add(new JLabel("Casete validare")); box2.add(cb1); box2.add(cb2); /* Se creeaza trei butoane radio, care se grupeaza si se pun in box3 */ rb1=new JRadioButton("BR 1 (F5)"); rb1.setMnemonic(KeyEvent.VK_F5); rb1.addActionListener(ascult); rb2=new JRadioButton("BR 2 (F6)"); rb2.setMnemonic(KeyEvent.VK_F6); rb2.addActionListener(ascult); rb3=new JRadioButton("BR 3 (F7)"); rb3.setMnemonic(KeyEvent.VK_F7); rb3.addActionListener(ascult); box3=Box.createVerticalBox(); box3.add(new JLabel("Butoane radio"));

247

Severin Bumbaru

box3.add(rb1); box3.add(rb2); box3.add(rb3); grup=new ButtonGroup(); grup.add(rb1); grup.add(rb2); grup.add(rb3); /* box1, box2 si box3 se pun in box4 , iar aceasta se pune in controlPane */ box4=Box.createHorizontalBox(); box4.add(Box.createHorizontalGlue()); box4.add(box1); box4.add(Box.createHorizontalStrut(15)); box4.add(box2); box4.add(Box.createHorizontalStrut(15)); box4.add(box3); box4.add(Box.createHorizontalGlue()); cp.add(box4, BorderLayout.CENTER); label=new JLabel("Actionati butoanele si urmariti mesajul"); cp.add(label, BorderLayout.SOUTH); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa ascultatoare de actiuni si de selectari */ static class Ascultator implements ActionListener, ItemListener { public void actionPerformed(ActionEvent e) { iug.label.setText("A fost apasat butonul "+e.getActionCommand()); } public void itemStateChanged(ItemEvent e) { JCheckBox cb=(JCheckBox)e.getItem(); String selectie; if(cb.isSelected()) selectie="A fost selectata "; else selectie="A fost deselectata "; iug.label.setText(selectie+cb.getText()); } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

În fereastra principală a aplicaţiei apar: - două butoane simple din clasa JButton, cu textele B1 şi B2; butoanele sunt referite prin b1 şi b2 şi sunt puse în caseta verticală box1; - două casete de validare din clasa JCheckBox, cu textele Caseta 1 şi Caseta 2, referite prin cb1 şi cb2 şi puse în caseta verticală box2; - trei butoane radio din clasa JRadioButton, cu textele BR1, BR2 şi BR3, referite prin br1,

248

Programarea orientata pe obiecte în limbajul Java br2 şi br3 si puse în caseta verticală box3; - cele trei casete verticale, box1, box2 şi box3, sunt plasate în caseta orizontală box4, iar aceasta este pusă în zona CENTER a panoului controlPane; - în zona SOUTH a controlPane este pusă o etichetă din clasa JLabel, folosită pentru a afişa evenimentele. Ascultarea evenimentelor de acţiune (ActionEvent) şi de selecţie (ItemEvent) se face cu clasa Ascultare, care implementează interfeţele ActionListener şi ItemListener. Detectarea butonului care a fost apăsat, fie că este vorba de un buton simplu sau unul radio, se face în metoda actionPerformed(ActionEvent action) folosind metoda String getActionCommand() din clasa ActionEvent. Pentru a constata care caseta de validare care a fost selectată sau deselectată, în metoda itemStateChanged(ItemEvent item) se procedează astfel: se determină mai întâi obiectul selectabil (în cazul nostru caseta de validare) care a generat evenimentul, folosind metoda Object getItem() din clasa ItemEvent, după care se aplică metoda boolean isSelected() a acestui obiect. Fiecărui buton (sau casetă de validare) i-a fost ataşată o mnemonică. Pentru comoditatea utilizatorului, tasta corespunzătoare a fost menţionată între paranteze, în textul fiecărui buton. Dacă se dă comanda Alt-<Tastă mnemonică> se obţine acelaşi efect ca atunci când se face click de mouse pe butonul respectiv. De exemplu, în loc de a apăsa cu mouse-ul butonul B2, se apasă simultan tastele Alt şi F2. Simbolurile tastelor se găsesc în clasa java.awt.event.KeyEvent. Pentru aprofundarea folosirii diferitelor tipuri de butoane, recomandăm capitolul How to use Buttons, Check Boxes and Radio Buttons din Java Tutorial.

Liste
În cazul interfeţelor utilizator grafice, se numeşte listă o componentă care conţine articole selectabile. Selectarea lor se face de către utilizator, prin click de mouse pe articolul respectiv. Lista poate fi cu selecţie simplă sau cu selecţie multiplă, după cum pot fi selectate simultan unul sau mai multe articole. În JFC/Swing, listele se realizează ca instanţe ale clasei javax.swing.JList. Articolele din listă pot fi, în principiu, orice obiecte afişabile. La crearea listei, constructorului i se dă ca argument tabloul obiectelor pe care le va conţine lista. Ulterior este posibil să se adauge articole la listă, sau să se elimine. Daca lista este mai lunga decât spaţiul de afişare disponibil, ea poate fi pusă pe un panou glisant, din clasa javax.swing.JScrollPane.

Selectarea simplă a unui articol se face prin click de mouse pe articolul respectiv. Selectarea multiplă se face în două moduri: - se face click de mouse pe primul articol din zona care trebuie selectată, apoi se apasă tasta

249

Severin Bumbaru Shift şi se face click pe ultimul articol din această zonă. În acest fel se selectează toate articolele din zona respectivă; - se face click de mouse pe primul articol din zonă, apoi se apasă tasta Shift şi se acţionează tastele cu săgeţi sus/jos până când sunt selectate toate articolele din zona respectivă. De câte ori se face o selecţie de articole din listă, se generează un eveniment din clasa javax.swing.event.ListSelectionEvent, care este ascultat cu ajutorul unei clase care implementează interfaţa javax.swing.event.ListSelectionListener. Punerea articolelor în listă poate fi făcută astfel: - la crearea listei, folosind unul din constructorii public JList(Object[] listData) - primeşte ca argument un tablou de obiecte, care sunt articolele din listă public JList(Vector listData)- primeşte ca argument o instanţă a clasei java.util.Vector, care are ca elemente articolele listei; - în timpul utilizării listei, invocându-se una din metodele
public void setListData(Object[] listData) public void setListData(Vector listData)

Pentru a afla articolele selectate dintr-o listă se pot folosi metodele: public int[] getSelectedIndices() - întoarce tabloul indicilor articolelor selectate public Object[] getSelectedValues() - întoarce tabloul articolelor selectate. Clasa are numeroase alte metode. Pentru aprofundare, recomandăm capitolul How to Use Lists din Java Tutorial.

Liste ascunse
Lista ascunsă este o listă din care se vede un singur articol (cel care este selectat), deci ocupa pe ecran spaţiu mult mai putin decât una obişnuită. Celelalte articole ale listei sunt "ascunse" şi devin vizibile numai dacă se face click pe articolul vizibil. Dacă acum facem click de mouse pe un alt articol din lista devenită vizibilă, acest nou articol este selectat (rămâne vizibil), iar celelalte dispar. Într-o listă ascunsă nu este posibilă selecţia multiplă. În JFC/Swing, pentru realizarea listelor ascunse se foloseşte clasa javax.swing.JComboBox.

Când este selectat un nou articol din lista ascunsă, aceasta generează un eveniment de articol din clasa java.awt.event.ItemEvent, care este ascultat cu un java.awt.event.ItemListener. La fiecare acţionare asupra listei ascunse, se generează, de asemenea, un java.awt.ActionEvent. În mod implicit, instanţele clasei JComboBox nu sunt editabile. Totuşi, este posibil ca acestea sa fie făcute editabile, în care caz ele se comportă ca o combinaţie între listă şi câmpul de text: utilizatorul poate să introducă textul manual, sau poate să aleagă unul din listă. Punerea articolelor în listă se poate face astfel: - la crearea listei ascunse, folosind unul din constructorii

250

Programarea orientata pe obiecte în limbajul Java
public JComboBox(Object[] items) public JComboBox(Vector items)

- în timpul utilizării listei, folosind metodele
public void addItem(Object anObject) - adaugă un articol la sfârşitul listei; public void insertItemAt(Object anObject, int index) - înserează un articol pe poziţia index.

Eliminarea unui articol din listă se face cu una din metodele public void removeItem(Object anObject) - elimină articolul anObject; public void removeItemAt(int anIndex) - elimină articolul de pe poziţia anIndex. Pentru a afla articolul selectat se foloseşte metoda
public Object getSelectedItem()

iar pentru a afla indicele articolului selectat se foloşeste metoda
public int getSelectedIndex()

Pentru ca lista ascunsă sa devină editabilă, se foloseşte metoda public void setEditable(boolean aFlag)- în care parametrul boolean aFlag indică dacă lista va fi sau nu editabilă. Pentru aprofundarea folosirii listelor ascunse recomandăm capitolul How to Use Combo Boxes din Java Tutorial.

Exemplu În fişierul Liste.java este dat un exemplu de aplicaţie în care se folosesc instanţe ale claselor JList şi JComboBox.

import import import import

java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*;

class Liste { static AF af=new AF(); static AAL ascult=new AAL(); static AEA ascult1=new AEA(); static IUG iug=new IUG("Diferite tipuri de liste"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JList lista1, lista2; JComboBox combo; JLabel label; IUG(String titlu) { // constructorul clasei IUG super(titlu);

251

Severin Bumbaru

setSize(350, 240); setLocation(200, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra /* Se creeaza o lista cu zilele saptamanii. Listei nu i se pune un model de selectie, deci se pot face implicit selectii multiple intr-o singura zona continua */ String zile[]={"Luni", "Marti", "Miercuri", "Joi", "Vineri", "Sambata", "Duminica"}; lista1=new JList(zile); lista1.setBorder(BorderFactory.createTitledBorder( "Lista")); lista1.addListSelectionListener(ascult); // ascultarea listei cp.add(lista1, BorderLayout.WEST); /* Se creeaza o lista cu lunile anului, pusa intr-un panou glisant. Listei i se pune un model de selectie care permite numai selectarea unui singur articol */ String luni[]={"Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"}; lista2=new JList(luni); lista2.setBorder(BorderFactory.createTitledBorder( "Lista glisanta")); lista2.setSelectionModel(new DefaultListSelectionModel()); lista2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); lista2.addListSelectionListener(ascult); JScrollPane scroll=new JScrollPane(lista2); // panoul glisant // care contine lista nou creata, lista2 cp.add(scroll, BorderLayout.CENTER); /* Se creeaza o lista ascunsa (din clasa JComboBox) */ String ordinea[]={"Primul ", "Al doilea", "Al treilea", "Al patrulea"}; combo=new JComboBox(ordinea); combo.setBorder(BorderFactory.createTitledBorder( "Lista ascunsa")); combo.addItemListener(ascult1); JPanel panel=new JPanel(); panel.add(combo); cp.add(panel, BorderLayout.EAST); /* Se creeaza o "eticheta" pentru afisarea mesajelor */ label=new JLabel("Selectati articolele si urmariti efectul"); label.setBorder(BorderFactory.createTitledBorder( "Afisarea mesajelor privind articolele selectate")); cp.add(label, BorderLayout.SOUTH); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) {

252

Programarea orientata pe obiecte în limbajul Java

System.exit(0); // incheierea executarii aplicatiei } } /* Clasa ascultatoare de selectari ale articolelor de lista folosita pentru JList. La aparitia unui astfel de eveniment, se afiseaza toate articolele selectate din lista sursa */ static class AAL implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { JList sursa=(JList)e.getSource(); Object selectie[]=sursa.getSelectedValues(); StringBuffer buff=new StringBuffer(); for(int i=0; i<selectie.length; i++) buff.append(selectie[i].toString()+" "); iug.label.setText(buff.toString()); } } /* Clasa ascultatoare de evenimente de articol folosita pentru JComboBox. La aparitia unui astfel de eveniment, se afiseaza articolul care l-a produs */ static class AEA implements ItemListener { public void itemStateChanged(ItemEvent e) { iug.label.setText(e.getItem().toString()); } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Câmpul de text
Câmpul de text este principalul instrument al interfeţei grafice prin care utilizatorul introduce date de la tastatură. El se prezintă sub forma unei ferestre dreptunghiulare editabile, în care se poate introduce o singură linie de text. De obicei, prin intermediul câmpului de text se introduce o singură valoare numerică sau un şir de caractere. În JFC/Swing, câmpurile de text se realizează ca instanţe ale clasei javax.swing.JTextField. Când câmpul de text este activ şi se apasă tasta Enter, este generat un eveniment de acţiune din clasa java.awt.ActionEvent.

Textul conţinut în câmpul de text poate fi obţinut în două moduri: - prin metoda public String getText() a clasei JTextField; - prin metoda public String getActionCommand() a clasei ActionEvent. Dacă în câmpul de text se introduce o valoare numerică, ea trebuie verificata sintactic şi

253

Severin Bumbaru convertită din forma externă (de şir de caractere) în forma internă. În acest scop, se folosesc metodele de analiză şi conversie din clasele acoperitoare. De exemplu, pentru un numar de tip double se va folosi metoda
public double parseDouble(String str)

din clasa acoperitoare java.lang.Double, unde str este şirul care trebuie analizat. Întrucât aceste metode pot să genereze excepţii de format incorect (NumberFormatException), ele trebuie utilizate în secvenţe try .. catch; Pentru aprofundarea utilizării câmpurilor de text, recomandăm capitolul How to Use Text Fields din Java Tutorial. Menţionăm că, în afara clasei JTextField, există şi clasa javax.swing.JPasswordField, care se foloseşte pentru introducerea parolelor.

Exemplu În fişierul CâmpuriText.java este dat un exemplu de aplicaţie în care se utilizează trei câmpuri de text, pentru a introduce, respectiv, un şir de caractere, un număr întreg şi un număr în virgulă mobilă. În clasa imbricată Actiuni, prin care este realizat ascultătorul de evenimente de acţiune, se poate observa cum se determină care câmp de text a generat evenimentul de acţiune şi cum se preia şi analizează textul din câmpul respectiv, în funcţie de tipul valorii pe care acest câmp trebuie să o conţină.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class CampuriText { static AF af=new AF(); static Actiuni act=new Actiuni(); static IUG iug=new IUG("Campuri de text"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JPanel panel; JTextField tf1, tf2, tf3; JLabel label; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(450, 150); setLocation(200, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra /* Crearea panoului pentru campurile de text */ panel=new JPanel(); panel.setLayout(new GridLayout(3,2)); /* Adaugarea la panou a etichetelor si campurilor de text */ panel.add(new JLabel("Un sir de caractere: "));

254

Programarea orientata pe obiecte în limbajul Java

tf1=new JTextField(); tf1.addActionListener(act); panel.add(tf1); panel.add(new JLabel("Un numar intreg")); tf2=new JTextField(); tf2.addActionListener(act); panel.add(tf2); panel.add(new JLabel("Un numar real: ")); tf3=new JTextField(); tf3.addActionListener(act); panel.add(tf3); /* Adaugarea la contentPane a panoului cu campuri de text */ cp.add(panel, BorderLayout.CENTER); /* Se creeaza o "eticheta" pentru afisarea mesajelor */ label=new JLabel("Introduceti date in campuri si urmariti efectul"); label.setBorder(BorderFactory.createTitledBorder( "Afisarea mesajelor privind campurile de text")); cp.add(label, BorderLayout.SOUTH); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa ascultatoare a actiunilor asupra campurilor de text */ static class Actiuni implements ActionListener { public void actionPerformed(ActionEvent e) { JTextField sursa=(JTextField)e.getSource(); String text=e.getActionCommand(), text1="Ati introdus in campul "; if(sursa==iug.tf1) iug.label.setText(text1+"1 textul: "+text); else if(sursa==iug.tf2) try { int n=Integer.parseInt(text); iug.label.setText(text1+" 2 numarul intreg "+n); } catch(Exception e1) { iug.label.setText("In campul 2 nu este un numar intreg!"); } else if(sursa==iug.tf3) try { double d=Double.parseDouble(text); iug.label.setText(text1+"3 numarul real "+d); } catch(Exception e2) { iug.label.setText("In campul 3 nu este un numar real!"); } } } /* Metoda principala a aplicatiei */

255

Severin Bumbaru

public static void main(String args[]) { } }

Rigla cu cursor
Clasa javax.swing.JSlider oferă o componentă care are aspectul unei rigle prevăzute cu cursor. Prin deplasarea cursorului cu ajutorul mouse-ului se modifică valoarea indicată de riglă. Valoarea minimă şi cea maximă de pe scala riglei şi valoarea unei diviziuni se indică prin program. Rolul riglei poate fi asemănat cu cel al unui dispozitiv de ajustare continuă, cum este potenţiometrul din aparatura electronică. La deplasarea cursorului, rigla generează evenimente din clasa javax.swing.event.ChangeEvent, care sunt ascultate cu o clasă care implementează interfaţa javax.swing.event.ChangeListener.

Crearea riglei se face folosind unul dintre constructorii ei, dintre care cel mai complet este
public JSlider(int orientation, int min, int max, int value)

în care
orientation - orientarea riglei, care poate fi JSlider.HORIZONTAL sau JSlider.VERTICAL; min, max - valorile minimă şi maximă de pe scala riglei; value - valoarea indicată iniţial de cursorul riglei (trebuie să fie în intervalul

[min,max]).

Pentru a stabili aspectul riglei se folosesc metodele
public public public public void void void void setMajorTickSpacing(int n) - stabilirea valorii diviziunilor mari setMinorTickSpacing(int n) - stabilirea valorii diviziunilor mici setPaintTicks(boolean b) - determină dacă diviziunile vor fi vizibile setPaintLabels(boolean b) - determină dacă vor fi afişate valorile

numerice ale diviziunilor. Pentru aprofundarea utilizării riglelor cu cursor, recomandăm consultarea capitolului How to Use Sliders din Java Tutorial. Exemplu În fişierul TestRigla.java este dat un exemplu de aplicaţie în care se testează o riglă cu cursor realizată ca instanţă a clasei JSlider. La partea superioară a ferestrei apare o riglă în poziţie orizontală. Când se modifică cu mouse-ul poziţia cursorului, în eticheta de la partea inferioară a ferestrei aplicaţiei se afişeaza noua valoare indicată de riglă.

import java.awt.*; import java.awt.event.*; import javax.swing.*;

256

Programarea orientata pe obiecte în limbajul Java

import javax.swing.event.*;

class TestRigla { static AF af=new AF(); static AscultRigla ascult=new AscultRigla(); static IUG iug=new IUG("O rigla ajustabila"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JSlider rigla; JLabel label;

IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(450, 150); setLocation(200, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra /* Se creeaza o "eticheta" pentru afisarea mesajelor */ label=new JLabel("Deplasati cursorul riglei si urmariti efectul"); label.setBorder(BorderFactory.createTitledBorder( "Afisarea mesajelor privind valoarea ajustata")); cp.add(label, BorderLayout.SOUTH); rigla=new JSlider(JSlider.HORIZONTAL, -20, 80, 35); rigla.setBorder(BorderFactory.createTitledBorder( "Ajustarea valorii")); rigla.setMajorTickSpacing(10); rigla.setMinorTickSpacing(1); rigla.setPaintTicks(true); rigla.setPaintLabels(true); rigla.addChangeListener(ascult); cp.add(rigla, BorderLayout.CENTER); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa ascultatoare a evenimentelor de ajustare a riglei */ static class AscultRigla implements ChangeListener { public void stateChanged(ChangeEvent e) { iug.label.setText("Noua valoare: "+iug.rigla.getValue()); } } /* Metoda principala a aplicatiei */

257

Severin Bumbaru

public static void main(String args[]) { } }

Meniuri
La fel ca listele sau butoanele radio, meniurile permit utilizatorului să aleagă una din mai multe opţiuni posibile. În JFC/Swing se disting două categorii de meniuri: - meniuri cu bară, care pornesc dintr-o bară situată la partea superioară a ferestrei aplicaţiei; - meniuri derulante (pop-up), care apar în poziţia în care se găseşte cursorul de mouse.

Meniuri cu bară
Bara de meniu se poate plasa, daca este necesara, numai la partea superioara a ferstrei aplicatiei si se realizeaza ca o instanta a clasei javax.swing.JMenuBar. Bara de meniu poate sa conţină unul sau mai multe meniuri, care se realizează ca instanţe ale clasei javax.swing.JMenu. La rândul său, fiecare meniu poate conţine unul sau mai multe articole, care sunt instanţe ale claselor javax.swing.JMenuItem sau javax.swing.JCheckBoxMenuItem. Este însă posibil ca un articol de meniu să fie el însuşi un meniu (din clasa JMenu). Dacă utilizatorul alege cu mouse-ul un aricol de meniu, sunt posibile deci două situaţii: - acesta este un articol propriu-zis (un JMenuItem), în care caz opţiunea respectivă este selectată; - articolul ales este el însuşi un meniu (un JMenu), în care caz noul (sub)meniu se desfăşoară şi căutarea continuă.

Barele de meniu nu pot fi adăugate la un container ca nişte componente obişnuite. Pentru a pune bara de meniu la partea superioară a ferestrei aplicaţiei se foloseşte metoda clasei JFrame
public void setJMenuBar(JMenuBar menubar)

Adăugarea de meniuri la bara de meniu se face prin metoda clasei JMenuBar
public JMenu add(JMenu c)

iar adăugarea de articole sau submeniuri la un meniu se face prin metodele clasei JMenu
public public public public JMenuItem Component JMenuItem JMenuItem add(JMenuItem menuItem) add(Component c) add(String s) add(Action a)

Pentru a introduce în meniu o linie orizontală de separare a două articole se foloseşte metoda
public void addSeparator()

Atunci când este selectat un articol de meniu simplu (din clasa JMenuItem), acesta acţionează ca un buton obişnuit, adică generează un eveniment de acţiune (ActionEvent). Dacă se selectează un articol de meniu sub forma de casetă de validare (din clasa JCheckBoxMenuItem), acesta se comporta ca o caseta de validare, adică generează un

258

Programarea orientata pe obiecte în limbajul Java eveniment de articol (ItemEvent). Ascultarea acestor evenimente se face cu clase care implementează interfeţele ActionListener, respectiv ItemListener. Posibilităţile oferite de meniuri sunt mult mai bogate decât s-a arătat aici. Pentru aprofundare recomandăm consultarea capitolului How to Use Menus din Java Tutorial. Exemplu În fişierul Meniuri.java este dat un exemplu de aplicaţie cu bară de meniu.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class Meniuri { static AF af=new AF(); static AA aa=new AA(); static IUG iug=new IUG("O fereastra cu bara de menu"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JMenuBar menuBar; JMenu menu1, menu2; JLabel label; JMenuItem mi1, mi2, mi3, mi4, mi5; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(350, 150); setLocation(200, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra menuBar=new JMenuBar(); // se creeaza bara de menu setJMenuBar(menuBar); // bara de menu se adauga la fereastra /* Se creeaza meniurile si se adauga la bara de meniu */ menu1=new JMenu("Un menu"); menuBar.add(menu1); menu2=new JMenu("Alt menu"); menuBar.add(menu2); /* Se creaza articole de menu si se adauga la meniuri */ mi1=new JMenuItem("A"); mi1.addActionListener(aa); menu1.add(mi1); mi2=new JMenuItem("B"); mi2.addActionListener(aa); menu1.add(mi2); mi3=new JMenuItem("C"); mi3.addActionListener(aa); menu2.add(mi3); /* La menu2 se aduga un submeniu cu doua articole */ JMenu menu3=new JMenu("Un submeniu"); mi4=new JMenuItem("Alpha"); mi4.addActionListener(aa); menu3.add(mi4); mi5=new JMenuItem("Beta"); mi5.addActionListener(aa); menu3.add(mi5); menu2.add(menu3);

259

Severin Bumbaru

/* Se creeaza o "eticheta" pentru afisarea mesajelor */ label=new JLabel("Selectati articole de meniu si urmariti efectul"); label.setBorder(BorderFactory.createTitledBorder( "Afisarea mesajelor privind selectarea articolelor de meniu")); cp.add(label, BorderLayout.SOUTH); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Ascultator de actiuni pentru articolele de menu */ static class AA implements ActionListener { public void actionPerformed(ActionEvent e) { iug.label.setText("A fost selectata optiunea: "+ e.getActionCommand()); } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Meniuri volante (pop-up)
Spre deosebire de meniurile cu bară, meniurile volante sunt tratate ca nişte componente obişnuite, care pot să fie afişate prin program, atunci când este necesar. În JFC/Swing, meniurile volante se realizează ca instanţe ale clasei javax.swing.JPopupMenu. În rest, meniul pop-up se comportă la fel ca un meniu cu bară. Afişarea meniului pop-up trebuie programată, folosindu-se în acest scop metoda din clasa JPopupMenu
public void show(Component invoker, int x, int y)

în care invoker este referinţa la componenta in spaţiul căreia trebuie să apară meniul, iar x şi y sunt coordonatele punctului în care acesta va fi afişat. De obicei, se doreste ca afişarea meniului pop-up să se facă atunci când se efectuează o anumită acţiune a mouse-lui asupra componentei respective. În acest scop, se creează o clasă ascultătoare de mouse (care implementează interfaţa MouseListener sau extinde clasa MouseAdapter) şi - în corpul metodei corespunzatoare situaţiei în care trebuie să se afişeze meniul (mousePressed, mouseReleased sau mouseClicked) se invocă metoda show menţionată mai sus, dacă a fost acţionat butonul de mouse care ne interesează. Se demonstrează acest procedeu în exemplul de mai jos.

260

Programarea orientata pe obiecte în limbajul Java Exemplu În fişierul Popup.java se dă un exemplu de aplicaţie în care se testează utilizarea unui menu pop-up. Drept componentă pe suprafaţa căreia trebuie să apară meniul s-a ales eticheta de la partea inferioară a ferestrei aplicaţiei. Evenimentul care declanşează apariţia meniului este apăsarea pe butonul din dreapta al mouse-ului, atunci când acesta se găseşte în spaţiul ferestrei. Acest eveniment este urmărit de instanţa clasei AfisPopup, care este ataşata etichetei label în calitate de ascultător de mouse.

import java.awt.*; import java.awt.event.*; import javax.swing.*; class Popup { static AF af=new AF(); static AA aa=new AA(); static AfisPopup afisare=new AfisPopup(); static IUG iug=new IUG("Exemplu de menu pop-up"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JPopupMenu popup; JLabel label; JMenuItem mi1, mi2, mi3; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(350, 150); setLocation(200, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra /* Se creeaza meniul pop-up */ popup=new JPopupMenu(); /* Se creaza articole de menu si se adauga la meniuri */ mi1=new JMenuItem("A"); mi1.addActionListener(aa); popup.add(mi1); mi2=new JMenuItem("B"); mi2.addActionListener(aa); popup.add(mi2); mi3=new JMenuItem("C"); mi3.addActionListener(aa); popup.add(mi3); /* Se creeaza o "eticheta" pentru afisarea mesajelor */ label=new JLabel(" Apasati pe aceasta eticheta butonul drept al mouse-ului"); label.setBorder(BorderFactory.createTitledBorder( "Afisarea mesajelor privind selectarea articolelor de meniu")); cp.add(label, BorderLayout.SOUTH); /* Se adauga la eticheta label ascultatorul de mouse pentru afisarea meniului popup

261

Severin Bumbaru

*/ label.addMouseListener(afisare); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa ascultatoare de mouse pentru afisarea meniului pop-up */ static class AfisPopup extends MouseAdapter { public void mousePressed(MouseEvent e) { if((e.getModifiers()&InputEvent.BUTTON3_MASK)!=0) iug.popup.show(e.getComponent(), e.getX(), e.getY()); } } /* Ascultator de actiuni pentru articolele de menu */ static class AA implements ActionListener { public void actionPerformed(ActionEvent e) { iug.label.setText("A fost selectata optiunea: "+ e.getActionCommand()); } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Componente de afişare
Componentele de afişare sunt cele care servesc pentru a afişa anumite texte sau imagini şi pot fi needitabile sau editabile.

Componente de afişare needitabile
Componentele de afişare needitabile sunt realizate ca instanţe ale următoarelor clase: - javax.swing.JLabel - pentru realizarea unor "etichete" în care pot fi afişate texte sau imagini, fără a putea fi editate de utilizator; - javax.swing.JTooltip - pentru afişarea de "inscripţii volante", când se pune cursorul de mouse deasupra unei alte componente; - javax.swing.JProgressBar - pentru realizarea unor "bare de progres" adică a unor bare de lungime variabilă, care arată cum evoluează realizarea unei anumite activităţi (de la 0% la

262

Programarea orientata pe obiecte în limbajul Java 100%).

Utilizarea etichetelor şi a inscripţiilor volante a fost deja prezentată. Pentru aprofundarea acestora şi pentru a studia folosirea barelor de progres recomandăm capitolele How to Use Labels, How to Use Tool Tips şi How to Monitor Progress din Java Tutorial.

Componente de afişare editabile
Componentele de afişare editabile permit utilizatorului operaţii de editare în timpul executării programului. În JFC/Swing există un număr important de clase pentru astfel de componente. Astfel, pentru editare de texte există clasele javax.swing.JTextArea, javax.swing.JTextPane şi javax.swing.JEditorPane. În aceeaşi categorie pot fi încadrate şi clasele javax.swing.JTextField şi javax.swing.JPasswordField, care au fost prezentate anterior. Tot în categoria componentelor editabile intră şi clasele javax.swing.JTable (pentru realizarea de tabele editabile), javax.swing.JFileChooser (pentru realizarea de selectoare de fişiere), javax.swing.JTree (pentru realizarea de arbori) şi javax.swing.JColorChooser (pentru realizarea de selectoare de culori). Vom prezenta aici numai clasa JTextArea, iar pentru celelalte recomandăm folosirea documentaţiei indicate.

Pentru aprofundare recomandăm folosirea capitolelor Using Text Components, How to Use Tables, How to Use File Choosers, How to Use Trees, si How to Use Color Choosers din Java Tutorial.

Clasa JtextArea
Instanţele clasei javax.swing.JTextArea (arie de text) sunt suprafeţe de afişare a unor texte editabile cu mai multe linii. Componenta are comportamentul unui editor de text simplu: permite să se introducă text de la tastatură, să se şteargă textul în întregime sau pe porţiuni, să se adauge sau să se însereze text. Prin metode speciale se poate seta componenta astfel, încât să se realizeze automat trecerea de la un rând la altul. Se oferă metode prin care se poate adăuga text prin program, sau se poate obţine sub forma de String textul existent. Iată unele dintre metodele oferite de clasa JTextArea: public int getRows() - întoarce numărul maxim de linii din aria de text; public void setRows(int rows) - setează numărul maxim de linii; public int getColumns() - întoarce numărul maxim de coloane din aria de text; public void setColumns(int columns) - setează numărul maxim de coloane;

263

Severin Bumbaru
public void setFont(Font f) - setează fontul; public void setLineWrap(boolean wrap) - indică dacă se

face trecere automată la

linie nouă;
public void setWrapStyleWord(boolean word) - indică dacă trecerea automată la linie nouă se face la sfârşit de cuvânt; public int getLineCount() - întoarce numărul de linii de text conţinute efectiv; public void insert(String str, int pos) - înserează şirul str începând de la poziţia pos; public void append(String str) - adaugă la sfârşit textul str; public void replaceRange(String str, int start, int end) - înlocuieşte prin şirul str caracterele cuprinse între poziţiile start şi end; public String getText() - întoarce sub forma de String textul conţinut; public String getSelectedText() - întoarce textul selectat. Ultimele două metode sunt moştenite de la superclasa javax.swing.text.JTextComponent.

Exemplu În fişierul ZonaText.java se dă un exemplu simplu de creare a unei instanţe a clasei JTextArea, setând trecerea automată la linie nouă la sfârşit de cuvant (astfel încât un cuvânt să nu se împartă pe două linii). Punând în execuţie această aplicaţie, constatăm cum aria de text se comportă ca un mic editor de text: putem să introducem de la tastatură text nou sau să modificăm textul existent. Dacă modificăm cu mouse-ul dimensiunile ferestrei, observăm cum se rearanjează automat liniile de text.
import java.awt.*; import java.awt.event.*; import javax.swing.*; class ZonaText { static AF af=new AF(); // static AA aa=new AA(); //static AfisPopup afisare=new AfisPopup(); static IUG iug=new IUG("O zona de text"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JTextArea ta; JLabel label; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(450, 150); setLocation(200, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra ta=new JTextArea (); ta.setLineWrap(true); ta.setWrapStyleWord(true); cp.add(ta, BorderLayout.CENTER); setVisible(true); } } /* Clasa ascultatoare de fereastra */

264

Programarea orientata pe obiecte în limbajul Java

static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } }

/* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Întrebări
Nivel 1
1. Ce categorii de componente grafice cunoaşteţi? 2. Care este rădăcina ierarhiei claselor de componente? 3. Care sunt caracteristicile componentelor din JFC/Swing? 4. Cum se stabileşte aspectul componentelor din JFC/Swing? 5. Prin ce se caracterizează starea componentei? 6. Prin ce se caracterizeaza comportamentul componentei? 7. Care este deosebirea dintre componentele atomice şi containere? 8. Ce poate conţine o componentă atomică din JFC/Swing? 9. Care sunt componentele de control şi ce rol au? 10. Ce reprezintă clasa AbstractButton? 11. Ce reprezintă clasa JToggleButton? 12. Ce este o casetă de validare şi prin ce clasă se realizează? 13. Ce este un buton radio şi prin ce clasă se realizează? 14. La ce serveşte clasa ButtonGroup? 15. Ce este o listă în JFC/Swing şi prin ce clasă se realizează? 16. Ce este o listă ascunsă şi prin ce clasă se realizează? 17. Prin ce clasă se realizează câmpul de text şi ce conţine el? 18. Care este principala utilizare a câmpului de text? 19. Ce evenimente generează un câmp de text şi când le generează? 20. Ce este o riglă ajustabilă şi prin ce clasă se realizează? 21. Care este dispozitivul electronic pe care îl simulează o riglă ajustabilă? 22. Ce este un meniu? 23. Ce categorii de meniuri există în JFC/Swing? 24. Unde poate fi plasat un meniu cu bara? 25. Prin ce clasă se realizează bara de meniu? 26. Ce conţine bara de meniu? 27. Prin ce clasă se realizează un meniu utilizabil într-o bară de meniu? 28. Ce conţine un meniu? 29. Prin ce clase se realizează articolele de meniu? 30. Ce este un meniu derulant şi prin ce clasă se realizează?

265

Severin Bumbaru 31. Ce sunt componentele de afişare needitabile? 32. Ce componente de afişare needitabile cunoaşteţi? 33. Ce sunt componentele de afişare editabile? 34. Ce componente de afişare editabile cunoaşteţi? 35. Ce sunt instanţele clasei JTextArea şi cum se comportă ele?

Nivel 2
1. Care este superclasa clasei JComponent şi ce decurge din aceasta? 2. Ce este un text volant şi cum este el ataşat unei componente? 3. Cum se adaugă bordura unei componente? 4. Care este clasa prin care se realizează bordurile? 5. Cum se poate pune un text într-o componentă? 6. Cum se poate pune o imagine într-o componentă? 7. Cum se adaugă componentă la container? 8. Care sunt caracteristicile unui AbstractButton? 9. Ce este o mnemonică? 10. Ce evenimente generează un AbstractButton? 11. Cum se poate detecta momentul apăsării unui buton? 12. Cum se poate afla care buton a fost apăsat? 13. Cum se poate determina momentul schimbării stării unei casete de validare? 14. Cum se poate afla în ce stare se găseşte la un moment dat o casetă de validare? 15. Cum se face selectarea unui articol dintr-o listă? 16. Ce deosebire este între selecţia simplă şi selecţia multiplă? 17. Cum pot fi selectate mai multe articole ale listei? 18. Cum poate fi determinat momentul selectării unui articol din listă? 19. Cum se poate determina ce articole de listă sunt selectate la un moment dat? 20. Sunt oare editabile instanţele clasei JComboBox? 21. Cum se poate afla textul conţinut într-un câmp de text? 22. Cum se poate converti textul preluat din câmpul de text într-o valoare numerică? 23. Ce orientare poate avea o riglă ajustabilă? 24. Cum se pot trasa diviziunile riglei ajustabile? 25. Cum se stabileşte intervalul de valori al riglei ajustabile? 26. Cum se poate obţine valoarea indicată de riglă? 27. Prin ce metodă se pune bara de meniu în fereastra aplicaţiei şi cărei clase îi aparţine această metodă? 28. Cum se adaugă un articol la un meniu? 29. Ce evenimente generează un articol de meniu? 30. Cum se programează afişarea pe ecran a unui meniu pop-up? 31. Ce reprezintă instanţele clasei JTable? 32. Care este clasa de componente folosite la alegerea culorii? 33. Ce clase de componente pot fi folosite pentru a realiza un selector de fişier? 34. Ce metode ale clasei JTextArea cunoasteţi? 35. Ce clase pot fi folosite la realizarea de componente cu text editabil?

266

Programarea orientata pe obiecte în limbajul Java

Utilizarea ferestrelor de dialog. Desenarea
In aceasta sectiune vom studia utilizarea ferestrelor de dialog si a unor clase necesare pentru reprezentari grafice. Utilizarea ferestrelor de dialog Alegerea culorii Clasa Color Alegerea culorii folosind clasa JColorChooser Elemente de grafică Contextul grafic Metodele paint şi repaint Întrebări. 267 270 272 278 280 280 283 289

Utilizarea ferestrelor de dialog
Ferestrele de dialog sunt ferestre care se afişează pe ecran numai temporar, pentru a transmite anumite mesaje sau a permite introducerea unor date. Principalul avantaj al folosirii ferestrelor de dialog este că apar pe ecran numai atunci când sunt necesare şi nu încarcă fereastra principală a aplicaţiei cu elemente care nu sunt necesare în permanenţă. În JFC/Swing, ferestrele de dialog se realizează folosind clasa javax.swing.JDialog. Instanţele acestei clase se comportă asemanator cu cele ale clasei JFrame, permiţând adăugarea oricăror componente. La fel ca la JFrame, aceste componente nu se adaugă direct la fereastră, ci la panoul contentPane conţinut în aceasta şi obţinut prin metoda public Container getContentPane(). Fereastra de dialog poate avea ca "proprietar" (owner) o instanţă a claselor java.awt.Frame, javax.swing.JFrame, java.awt.Dialog sau javax.swing.JDialog. Proprietarul se transmite ca argument al constructorului ferestrei de dialog. Se permite ca, în loc de proprietar, să se pună argumentul null, în care caz se atribuie un proprietar implicit. Pentru afişarea pe ecran a ferestrei de dialog se invocă metoda public void show(), iar pentru ascunderea ei se invocă metoda public void hide(). În fine, pentru a elimina fereastra de dialog, eliberând toate resursele pe care le foloseşte, se invocă metoda public void dispose(). Aceste trei metode sunt moştenite de JDialog de la superclasa java.awt.Dialog. Când fereastra de dialog este deschisă, ea trece în prim plan sau în plan secund împreună cu proprietarul ei. Ferestrele de dialog pot fi modale sau nemodale. Când este afişată pe ecran o fereastră modală, toate celelalte ferestre existente pe ecran sunt blocate (nu se poate acţiona asupra lor), până când se încheie lucrul cu fereastra de dialog modală respectivă.

267

Severin Bumbaru Clasele Dialog şi JDialog sunt prezentate ceva mai detaliat în indexul de clase. Clasa JDialog este folosită pentru realizarea unor ferestre de dialog configurate de programator după dorinţa sa. În multe situaţii, este însă suficient să se folosească ferestre de dialog standard, preconfigurate, ceeace uşurează programarea. În acest scop, în JFC/Swing a fost creată clasa javax.swing.JOptionPane care este prezentată în indexul de clase.

Clasa JOptionPane oferă metode statice pentru a creea patru tipuri de ferestre de dialog: - pentru dialog de mesaj (showMessageDialog) - fereastra conţine o informaţie şi un buton OK care se apasă pentru a închide fereastra; - pentru dialog de confirmare (showConfirmDialog) - în fereastra se afişează un mesaj (o întrebare) şi trei butoane de confirmare: Yes/No/Cancel; la apăsarea unuia dintre ele, metoda întoarce valoarea corespunzatoare şi fereastra de dialog dispare; - pentru dialog de intrare (showInputDialog) - fereastra conţine un mesaj, un câmp de text şi două butoane: OK şi Cancel. Utilizatorul introduce în câmpul de text o valoare (un şir de caractere sau un număr) şi apasa tasta <Enter> sau actioneaza cu mouse-ul butonul OK. În ambele cazuri fereastra de dialog dispare, iar metoda întoarce şirul introdus în câmpul de text. Dacă se apasă butonul Cancel, fereastra de dialog dispare, iar metoda întoarce null. - pentru dialog de opţiune (showOptionDialog), care întruneşte caracteristicile celor trei tipuri de mai sus. Pentru fiecare din aceste patru tipuri de ferestre de dialog există mai multe metode cu numele corespunzător, care diferă între ele prin lista de argumente. Pentru detalii trimitem la documentaţia Java API.

Exemplu În fişierul Dialog1.java este dat un exemplu de aplicaţie, în care se testează diferitele tipuri de ferestre de dialog create cu metodele statice ale clasei javax.swing.JOptionPane. Fereastra principală a aplicaţiei conţine un buton cu inscripţia "Introducere text" şi o arie de text (JTextArea). Când este acţionat butonul, apare o fereastră de dialog de intrare, cu mesajul "Introduceţi un text". Dacă se acţioneaza butonul OK fără să fi introdus nimic în câmpul de text, apare o noua fereastră de dialog, care conţine mesajul de avertisment "Nu se permite un şir vid". Dacă se introduce un şir în câmpul de text şi se acţioneaza butonul OK sau tasta Enter, apare o nouă fereastră de dialog, care cere confirmarea că acest text trebuie introdus în aria de text. Dacă se răspunde prin acţionarea butonului Yes, se face adăugarea, iar dacă se răspunde prin apăsarea butonului No sau Cancel, fereastra de dialog dispare fără să se facă adăugarea textului. Întrucât întregul dialog este declanşat, în cazul de faţă, prin acţionarea butonului "Introducere text", programul prin care se realizează dialogul a fost plasat în metoda actionPerformed a clasei IntroducereText care ascultă evenimentele de acţiune generate de butonul menţionat.

/* Alegerea culorilor folosind obiectele de culoare predefinite din clasa Color

268

Programarea orientata pe obiecte în limbajul Java

*/ import java.awt.*; import java.awt.event.*; import javax.swing.*; //import javax.swing.event.*;

class Dialog1 { static AF af=new AF(); static IntroducereText it=new IntroducereText(); static IUG iug=new IUG("Utilizarea unei ferestre de dialog"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JTextArea arieText; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(250, 200); setLocation(100, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra JButton buton1=new JButton("Introducere text"); buton1.addActionListener(it); cp.add(buton1, BorderLayout.NORTH); arieText=new JTextArea(); arieText.setLineWrap(true); arieText.setWrapStyleWord(true); cp.add(arieText, BorderLayout.CENTER); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa pentru introducerea textului prin fereastra de dialog */ static class IntroducereText implements ActionListener { public void actionPerformed(ActionEvent e) { String sirIntrodus; boolean terminat=false; while(!terminat) { sirIntrodus=JOptionPane.showInputDialog("Introduceti un text"); if(sirIntrodus!=null) { if(sirIntrodus.trim().equals("")) JOptionPane.showMessageDialog(iug, "Nu se permite un sir vid!", "Avertisment", JOptionPane.WARNING_MESSAGE); else { int raspuns=JOptionPane.showConfirmDialog(iug, "Doriti adaugarea textului: "+sirIntrodus+"?"); if(raspuns==JOptionPane.YES_OPTION) iug.arieText.append(sirIntrodus+"\n");

269

Severin Bumbaru

terminat=true; } } else terminat=true; } } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Cu privire la metoda actionPerformed din clasa IntroducereText remarcăm următoarele: 1) Instrucţiunea
sirIntrodus=JOptionPane.showInputDialog("Introduceti un text");

are ca efect afişarea pe ecran a unei ferestre de dialog de intrare. Şirul introdus în câmpul de text al acestei ferestre este intors şi atribuit variabilei sirIntrodus. Dacă s-a acţionat butonul Cancel, metoda întoarce null. 2) Instrucţiunea
JOptionPane.showMessageDialog(null, "Nu se permite un sir vid!","Avertisment", JOptionPane.WARNING_MESSAGE);

are ca efect afişarea unei ferestre de dialog care conţine un mesaj de avertisment. 3) Instrucţiunea
int raspuns=JOptionPane.showConfirmDialog(iug, "Doriti adaugarea textului: "+sirIntrodus+"?");

are ca efect afişarea unei ferestre de dialog de confirmare, care întoarce o valoare de tip int, care poate fi egală cu una din cosnstantele YES_OPTION, NO_OPTION sau CANCEL_OPTION care sunt definite în clasa JOptionPane. În rest, considerăm că procedura de dialog rezultă clar din textul metodei. Pentru aprofundarea utilizării ferestrelor de dialog, recomandăm consultarea capitolului How to Make Dialogs din Java Tutorial.

Alegerea culorii
În multe aplicaţii, la proiectarea interfeţei utilizator grafice este necesar să se impună culorile de fond şi de primplan ale diferitelor componente, folosind metodele clasei Component public void setBackground(Color color) - pentru culoarea de fond; public void setForeground(Color color) - pentru culoarea de prim-plan (a textului). Pentru reprezentarea şi manipularea informaţiei despre culoare se foloseşte clasa java.awt.Color. Cea mai simplă utilizare a acestei clase este utilizarea culorilor predefinite, pe care le conţine sub forma de câmpuri statice. De exemplu, culoarea albastru este reprezentată prin câmpul static Color.blue, iar culoarea galben prin Color.yellow. În consecinţă, dacă dorim să punem componentei comp culoarea

270

Programarea orientata pe obiecte în limbajul Java de fond albastru şi culoarea de primplan galben, vom folosi instrucţiunile:
comp.setBackground(Color.blue); comp.setForeground(Color.yellow);

Pentru a afla toate culorile predefinite şi celelalte specificaţii privind clasa Color trimitem cititorii la documentaţia Java API. Culorile predefinite sunt folosite şi în exemplul care urmează.

Exemplu În fişierul Culori.java se dă un exemplu de aplicaţie, în care se demonstrează utilizarea culorilor predefinite din clasa java.awt.Color. Fereastra aplicaţiei conţine o listă cu denumirile culorilor predefinite din clasa Color şi un panou pe care se afişează culorile respective, prin setarea corespunzătoare a culorii de fond a panoului. Când se selectează o culoare din listă, se observa imediat modificarea corespunzătoare a culorii panoului. În acest scop, în program au fost folosite două tablouri: numeCulori - este un tablou de şiruri (String[]) care conţin numele culorilor; tablouCulori - este un tablou de instanţe ale clasei Color, care conţine toate culorile predefinite din aceasta clasă. Lista de culori listaCulori din clasa JList este construită pe baza tabloului numeCulori şi deci conţine ca articole numele culorilor (denumirile puteau fi date în limba română, dar am preferat ca cititorii să se familiarizeze cu cele existente în clasa Color). Când se selectează un articol din listă, se generează un ListSelectionEvent. Pentru ascultarea lui s-a creeat clasa AscultaLista. În metoda valueChanged a acestei clase, se determină indicele articolului care a fost selectat şi se setează drept culoare de fond a panoului culoarea cu acelaşi indice din tablouCulori.

/* Alegerea culorilor folosind obiectele de culoare predefinite din clasa Color */ import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*;

class Culori { static AF af=new AF(); static IUG iug=new IUG("Alegerea culorii din lista"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JPanel panouCuloare; // panoul pe care se prezinta culoarea String[] numeCulori={"black", "darkGray", "gray", "lightGray", "white", "blue", "cyan", "green", "orange", "yellow", "red", "pink", "magenta"}; Color[] tablouCulori={Color.black, Color.darkGray, Color.gray, Color.lightGray, Color.white, Color.blue, Color.cyan, Color.green, Color.orange, Color.yellow, Color.red, Color.pink, Color.magenta};

271

Severin Bumbaru

JList listaCulori; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(250, 200); setLocation(100, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra panouCuloare=new JPanel(); // crearea panoului pentru culoare cp.add(panouCuloare, BorderLayout.CENTER); Color[] tablouCulori={Color.black, Color.darkGray, Color.white}; listaCulori=new JList(numeCulori); listaCulori.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); listaCulori.addListSelectionListener(new AscultLista()); JScrollPane panouCulori=new JScrollPane(listaCulori); cp.add(panouCulori, BorderLayout.WEST); setVisible(true); } }

/* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Clasa ascultatoare a listei de culori */ static class AscultLista implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { int indiceCuloare=iug.listaCulori.getSelectedIndex(); iug.panouCuloare.setBackground(iug.tablouCulori[indiceCuloare]); } } /* Metoda principala a aplicatiei */ public static void main(String args[]) { } }

Clasa Color
Clasa Color încapsulează informaţiile despre culoare. În Java AWT se foloseşte modelul de culori RGB (engleză: red, green, blue) în care fiecare culoare este considerată ca fiind compusă din trei culori fundamentale: roşu, verde şi albastru. În consecinţă, culoarea este dată prin trei numere cuprinse în intervalul [0, 255], reprezentând ponderile celor trei culori fundamentale. În figura de mai jos este reprezentată schema de combinare a acestor trei culori fundamentale.

272

Programarea orientata pe obiecte în limbajul Java

Prin modificarea ponderilor celor trei culori se pot obţine, în principiu, toate culorile posibile, de la negru [r=0,g=0,b=0] la alb [r=255,g=255,b=255]. Dacă ponderile celor trei culori sunt egale, se obţin diferite nuanţe de gri. Culoarea galbenă are ponderile [r=255,g=255,b=0], culoarea cyan (azuriu) are ponderile [r=0,g=255,b=255] iar culoarea magenta are ponderile [r=255,g=0,b=255]. Alte culori: orange: [r=255,g=200,b=0], pink (roz): [r=255,g=175,b=175]. Clasa Color conţine şi un al patrulea câmp, numit alpha, care poate avea, de asemenea, valori întregi în intervalul [0..255] şi exprima opacitatea culorii respective. Daca alpha=255, culoarea este complet opaca. Aceasta înseamnă că, dacă o figură cu această culoare se suprapune peste alta, o maschează complet. Dacă alpha=0, culoarea este complet transparentă, deci practic invizibilă. Între aceste valori extreme putem avea diferite opacităţi intermediare, cuprinse intre 0 si 255. Culoarea în sistemul RGB poate fi exprimată nu numai printr-o instanţă a clasei Color, ci şi printr-un număr întreg (de tip int), în care cei patru octeţi au, respectiv, valorile componentelor alpha, r, g, b exprimate în intervalul [0..255]. Componentele sunt plasate astfel: alpha în biţii 24..31, r în biţii 16..23, g în biţii 8..15 şi b în biţii 0..7. Clasa Color conţine, de asemenea, constructori şi metode care suportă sistemele de culori sRBG şi HSB. Standardul sRGB defineşte o variantă standardizată a sistemului RGB, destinată monitoarelor cu tub catodic. În acest sistem, parametrii r, g, b şi alpha se exprimă prin numere reale în intervalul [0.0...1.0].

Sistemul de culori HSB (engleză: Hue, Saturation and Brightness) ofera o descriere a culorilor independentă de sistemul de afişare, folosind următorii trei parametri: Nuanţa (engleză: Hue) care se exprimă în grade, în intervalul [0..360], unde 0 este rosu, 60 este galben, 120 este verde etc. Saturaţie (engleză: Saturation) este puritatea culorii, exprimată în procente, în intervalul [0..100]. La valoarea 0, nuanţa (Hue) nu are semnificatie, iar la valoarea 100 culoarea este pură. Strălucire (engleză: Brightness) este exprimată de asemenea în procente, în intervalul [0..100]. Valoarea 0 înseamnă negru (iluminare 0%), deci parametrii Hue şi Saturation nu au

273

Severin Bumbaru sens. Valoarea 100 reprezintă strălucirea maximă. Diagrama de mai jos dă o reprezentare a culorilor exprimate în sistemul HSB.

În clasa Color, parametrii Hue, Saturation şi Brightness se exprimă prin numere reale (tipul float) în intervalul [0.0 .. 1.0]. Dacă valorile sunt în afara acestui interval, se ia în consideraţie numai partea fracţionara: de exemplu valoarea 2.73 se consideră echivalentă cu 0.73. În aceste condiţii, dacă se consideră saturaţia şi strălucirea egale cu 1.0 (100%), valorile parametrului Hue (nuanţă) pentru principalele culori sunt: red: h=0.0; yellow: h=0.6666 (600); green: h=0.3333 (1200); cyan: h=0.5 (1800); blue: h=0.6666 (2400); magenta: h=0.8333 (3000) Culoarea neagra (black) se obtine punând strălucirea Brightness=0, iar culoarea albă (white) se obţine punand saturaţia Saturation=0 şi strălucirea Brightness=1. Dacă se menţine saturaţia la valoarea 0 şi se dau strălucirii diferite valori între 0 şi 1 se obţin nuanţele de gri.

Variabile: Clasa Color conţine variabile statice finale (constante) care sunt instanţieri ale clasei pentru diferite culori tipice:
black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white yellow.

Acestea sunt, respectiv, culorile negru, albastru, azuriu, gri inchis, gri, verde, gri deschis, mov, roz, rosu, alb şi galben. De exemplu, culoarea verde va fi reprezentata prin Color.green. Toate aceste culori sunt opace, deci au componenta alpha cu valoarea 255.

274

Programarea orientata pe obiecte în limbajul Java

Constructori: Clasa Color are mai mulţi constructori, dintre care menţionăm aici pe cei mai frecvent folosiţi: public Color(int r, int g, int b) - în care argumentele r,g,b sunt numere întregi în intervalul [0..255], reprezentând ponderile culorilor red, green, blue, iar componenta alpha are implicit valoarea 255; public Color(int r, int g, int b, int alpha) - similar cu cel precedent, indicându-se în plus gradul de opacitate alpha, de asemenea în intervalul [0..255]; public Color(int rgb) - creează o culoare opacă cu parametrii r,g,b daţi ultimii trei octeţi ai argumentului rgb; public Color(int rgba, boolean hasalpha) - creează o culoare pornind de la parametrii alpha,r,g,b împacetaţi în cei patru octeţi ai argumentului rgba. Daca al doilea argument este true, parametrul alpha (bitii 24..31 ai argumentului) este luat în consideraţie; public Color(float r, float g, float b) - creează o culoare opacă în sistemul sRGB, în care r,g,b sunt ponderile culorilor fundamentale în intervalul [0.0...1.0], iar alpha are valoarea implicită 1.0; public Color(float r, float g, float b, float alpha) - creează o culoare în sistemul sRGB, fiind daţi toti cei patru parametri. Metode: Menţionăm aici metodele cel mai frecvent utilizate.
public public public public public public public int getRed() - întoarce ponderea culorii red (rosu); int getGreen() - întoarce ponderea culorii green(verde); int getBlue() - întoarce ponderea culorii blue(albastru); int getAlpha() - întoarce componenta alpha(opacitatea culorii); Color brighter() - creează o versiune mai luminoasă a acestei culori; Color darker() - creează o versiune mai întunecată a acestei culori; static Color getHSBColor(float h, float s, float b) - creează o

instanţă a clasei Color (deci în sistemul RGB) pornind de la parametrii h, s şi b ai culorii respective, exprimaţi is sistemul HSB, în intervalul [0.0 ... 1.0]; public static int HSBtoRGB(float h, float s, float b) - transformă parametrii h,s,b ai culorii exprimate în sistemul HSB prin numere reale în intervalul [0.0 ... 1.0] într-un număr întreg (int), în care cei patru octeţi sunt parametrii r,g,b,alpha ai aceleeaşi culori exprimate în sistemul RGB; O prezentare mai completă a câmpurilor, constructorilor şi metodelor clasei Color este dată în Indexul de clase. Exemplul 1: În fişierul TestRGB.java este dat un exemplu de aplicaţie, în care se testează compunerea diferitelor culori în sistemul RGB. În partea stângă a ferestrei sunt trei rigle de ajustare (JSlider) pentru cele trei culori fundamentale (roşu, verde, albastru). În dreptul fiecărei rigle există un câmp de text în care se afişează valoarea indicată, în întervalul [0, 255]. În partea dreaptă a ferestrei există un panou (JPanel) a cărui culoare de fond corespunde combinaţiei de culori date de cele trei rigle.

275

Severin Bumbaru Puteţi ajusta culorile şi urmări efectul produs. Exemplul 2: În fişierul TestHSB.java este dat un exemplu de aplicaţie în care se testează alegerea culorilor în sistemul HSB. Fereastra se aseamană cu cea din exemplul precedent, dar cele trei rigle servesc pentru ajustarea componentelor HSB: nuanţa, saturaţie şi strălucire. Valorile corespunzătoare sunt indicate în câmpurile de text alăturate, ca numere reale în intervalul [0.0, 1.0]. La partea inferioară a ferestrei sunt afişate componentele culorii în sistemul RGB.

/* Alegerea culorilor in sistemul HSB (Hue, Saturation, Brightness) */ import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*;

class TestHSB { static AF af=new AF(); static IUG iug=new IUG("Alegerea culorii in sistemul HSB"+ " (Hue, Saturation, Brightness)"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { AjustCuloare hue, satur, bright; // rigle de ajustare a culorilor Box box1; // caseta riglelor de ajustare JPanel panouCuloare; // panoul pe care se prezinta culoarea Color culoare; // culoarea panoului panouCuloare JLabel culoriRGB; // pentru afisarea culorilor in sistem RGB IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(500, 200); setLocation(100, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra /* Se creeaza caseta cu dispozitive de ajustare */ box1=Box.createVerticalBox(); hue=new AjustCuloare("Nuanta "); // ajustarea nuantei box1.add(hue); satur=new AjustCuloare("Saturatie "); // ajustarea saturatiei box1.add(satur); bright=new AjustCuloare("Stralucire "); // ajustarea stralucirii bright.ajustare.setPaintLabels(true); box1.add(bright); cp.add(box1, BorderLayout.WEST); panouCuloare=new JPanel(); // crearea panoului pentru culoare cp.add(panouCuloare, BorderLayout.CENTER); culoriRGB=new JLabel(); // crearea etichetei cu valori RGB cp.add(culoriRGB, BorderLayout.SOUTH); puneCuloarea(); // se pune culoarea initiala setVisible(true); }

276

Programarea orientata pe obiecte în limbajul Java

/* Metoda de determinare a culorii RGB */ void puneCuloarea() { culoare=new Color(Color.HSBtoRGB(hue.fval, satur.fval, bright.fval)); // conversie din HSB in RGB panouCuloare.setBackground(culoare); culoriRGB.setText("Culori RGB -> rosu: "+ culoare.getRed()+" verde: "+culoare.getGreen()+ " albastru: "+culoare.getBlue()); } } /* Un "dispozitiv" de ajustare a valorii unei culori */ static class AjustCuloare extends Box implements ChangeListener { JTextField valoare; // camp pentru afisarea valorii HSB JSlider ajustare; // rigla de ajustare a valorii float fval=1.0f; // valoarea HSB (in intervalul 0,..,1). AjustCuloare(String culoare) { super(BoxLayout.X_AXIS); add(new JLabel(culoare)); add(Box.createHorizontalGlue()); ajustare=new JSlider(JSlider.HORIZONTAL, 0, 100,100); ajustare.setMajorTickSpacing(20); ajustare.setMinorTickSpacing(10); ajustare.setPaintTicks(true); ajustare.addChangeListener(this); add(ajustare); add(Box.createHorizontalStrut(5)); valoare=new JTextField("1.00",4); valoare.setHorizontalAlignment(JTextField.RIGHT); valoare.setEditable(false); valoare.setBackground(Color.white); valoare.setMaximumSize(valoare.getMinimumSize()); add(valoare); add(Box.createHorizontalStrut(5)); } /* metoda de ascultare a deplasarii cursorului riglei */ public void stateChanged(ChangeEvent e) { fval=ajustare.getValue()/100.0f; // determinarea valorii reale // in intwervalul 0,..1 valoare.setText((fval+" ").substring(0,4)); // afisarea valorii iug.puneCuloarea(); // modificarea culorii panoului } }

/* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } }

/* Metoda principala a aplicatiei */ public static void main(String args[]) {

277

Severin Bumbaru

} }

Ajustând valorile prin intermediul riglelor, constatăm următoarele: - Dacă strălucirea este 0.0, culoarea obţinută este negru, indiferent de valorile celorlalte două componente. Cu cât valoarea strălucirii este mai mare, culoarea obţinută este mai deschisă. - Dacă saturaţia este 0.0, valoarea nuanţei nu are efect: în acest caz, modificând strălucirea de la 0.0 la 1.0 se obţin diferite nuanţe de gri, de la negru la alb. - Dacă saturaţia şi strălucirea au valori diferite de 0.0, culoarea se poate ajusta prin modificarea nuanţei. Cu cât saturaţia este mai mare, nuanţa respectivă este mai pronunţată, iar cu cât strălucirea este mai mare, culoarea este mai deschisă.

Alegerea culorii folosind clasa JColorChooser
Pentru a alege culoarea în mod interactiv, în JFC/Swing este prevazută clasa javax.swing.JColorChooser. Instanţele acestei clase sunt panouri de selectare a culorii, care pot fi folosite atât prin încorporarea lor într-o fereastră obişnuită, cât şi sub forma unei ferestre de dialog. Pentru a creea o fereastră de dialog, care conţine în ea un JColorChooser şi întoarce culoarea selectată, în clasa JColorChooser există metoda
public static JDialog createDialog(Component c,String title, boolean modal, JColorChooser chooserPane, ActionListener okListener, ActionListener cancelListener)

în care: - c este componenta beneficiară (owner) a ferestrei de dialog create; - title este titlul ferestrei de dialog create; - argumentul boolean modal indică dacă fereastra este modală; - chooserPane este instanţa clasei JColorChooser care se întroduce în fereastra de dialog; - okListener si cancelListener sunt ascultătoarele de acţiune care reacţioneaza la apăsarea butonului OK, respectiv Cancel al selectorului de culoare. Folosind un JColorChooser, alegerea culorii se poate face în trei moduri: - folosind un tablou de culori, în care fiecare celulă este colorată în altă culoare; - în sistemul RGB, folosind trei rigle de ajustare a culorilor fundamentale; - în sistemul HSB, selectând culoarea prin alegerea cu mouse-ul a punctului de pe panou care are culoarea potrivita (trei butoane alăturate, notate H, S, B, arată care din cele trei componente se menţine constantă, celelalte două fiind date de coordonatele punctului ales pe panou). Exemplu În fişierul SelectCulori.java se dă un exemplu de aplicaţie în care, pentru alegerea culorii unui panou, se foloseşte o fereastră de dialog, în care se găseşte un JColorChooser.

/* Alegerea culorilor folosind un JColorChooser */ import java.awt.*; import java.awt.event.*;

278

Programarea orientata pe obiecte în limbajul Java

import javax.swing.*; class SelectCulori { static AF af=new AF(); static AscultDialog ascultDialog=new AscultDialog(); static AscultButon ascultButon=new AscultButon(); static IUG iug=new IUG("Alegerea culorii folosind JColorChooser"); /* clasa imbricata pentru interfata utilizator grafica */ static class IUG extends JFrame { JPanel panouCuloare; // panoul pe care se prezinta culoarea JColorChooser selectorCuloare; JDialog dialogCuloare; IUG(String titlu) { // constructorul clasei IUG super(titlu); setSize(350, 200); setLocation(100, 50); Container cp=getContentPane(); addWindowListener(af); // adaugarea ascultatorului de fereastra panouCuloare=new JPanel(); // crearea panoului pentru culoare cp.add(panouCuloare, BorderLayout.CENTER); selectorCuloare=new JColorChooser(); JButton butonCuloare=new JButton("Schimbarea culorii"); butonCuloare.addActionListener(ascultButon); butonCuloare.setToolTipText("Selectarea culorii panoului"); cp.add(butonCuloare, BorderLayout.SOUTH); dialogCuloare=JColorChooser.createDialog(butonCuloare, "Alegerea culorii", true, selectorCuloare, ascultDialog, ascultDialog); setVisible(true); } } /* Clasa ascultatoare de fereastra */ static class AF extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); // incheierea executarii aplicatiei } } /* Ascultator actiuni fereastra dialog */ static class AscultDialog implements ActionListener { public void actionPerformed(ActionEvent e) { if(e.getActionCommand().equals("OK")) iug.panouCuloare.setBackground( iug.selectorCuloare.getColor()); } } /* Ascultarea butonului de comanda */ static class AscultButon implements ActionListener { public void actionPerformed(ActionEvent e) { iug.dialogCuloare.show(); } } /* Metoda principala a aplicatiei */

279

Severin Bumbaru

public static void main(String args[]) { } }

Elemente de grafică
Java API ofera programatorilor posibilităţi ample privind realizarea de desene, text stilizat şi alte construcţii grafice. Ne vom rezuma aici numai la câteva noţiuni elementare, utile în realizarea unor aplicaţii grafice simple. Pentru aprofundare recomandăm lectia Working with Graphics din Java Tutorial. Studiul poate fi apoi continuat cu secţiunea 2D Graphics a aceluiaşi tutorial. In principiu, desenarea se poate face pe orice componentă grafică. Există însă clasa java.awt.Canvas, ale cărei instanţe sunt simple panouri destinate desenării (în engleză Canvas este pânza pictorului). Pentru desenare se mai folosesc frecvent şi clasele JPanel şi JLabel. Sistemul de coordonate folosit pentru grafică are originea în colţul din stânga sus al componentei, axa Ox este orientată catre dreapta, iar axa Oy este orientată în jos. Coordonatele se exprimă în numere întregi (int), având ca unitate de măsură pixelul (punctul de pe ecran).

Contextul grafic
Modul de realizare a imaginilor şi desenelor este strâns dependent atât de echipament (hardware) cât şi de sistemul de operare. Pentru a se asigura independenţa de platformă, în Java API a fost introdusă clasa abstractă java.awt.Graphics. Aceasta este clasa de bază a tuturor contextelor grafice, care permit trasarea de desene pe suprafaţa componentelor grafice realizate pe diverse dispozitive fizice. Pe fiecare platformă, în mediul de execuţie Java, trebuie să existe o implementare a contextului grafic, adică o extindere a clasei Graphics, care conţine toate câmpurile şi metodele acestei clase, dar este specifică platformei respective. Un obiect din clasa Graphics încapsulează informaţia de stare a contextului grafic la care se referă şi anume: - referinţa la obiectul din clasa Component (sau dintr-o subclasa a acesteia) pe care se desenează; - o translaţie a originii sistemului de coordonate; toate coordonatele din desen sunt raportate la această origine; - decupajul curent (dreptunghiul în interiorul căruia se trasează desenul); - culoarea curentă; - fontul curent; - operaţia logică pe pixeli curentă (XOR sau paint); - alternarea curentă de culori pentru operaţia pe pixeli XOR.

280

Programarea orientata pe obiecte în limbajul Java

Originea sistemului de axe (punctul de coordonate 0,0) se găseşte în colţul din stânga-sus al dreptunghiului de desenare. Axa 0x este îndreptată spre dreapta, iar axa 0y - in jos. Practic, clasa abstractă Graphics conţine acele metode, care trebuie să existe în orice context grafic. Conţinutul concret al acestor metode, deci modul efectiv în care se realizează funcţiile respective, depinde de contextul grafic real, deci de dispozitivul fizic pe care se face desenarea şi de sistemul de operare folosit. Pe programatorul de aplicaţii sau miniaplicaţii în Java nu îl interesează însă acest lucru, deoarece el foloseşte în programele sale metodele clasei abstracte Graphics, fără să se preocupe de modul în care acestea vor fi executate.

Principalele metode ale clasei Graphics sunt următoarele:
public abstract Graphics create() - creează şi întoarce un nou obiect din clasa Graphics, care este o copie a obiectului pentru care se aplică această metodă; public abstract Graphics create(int x, int y, int width, int height) creează şi întoarce o copie a obiectului din clasa Graphics căruia i se aplică, însă cu o noua translaţie a originii (x,y) şi cu valori noi valori ale lăţimii şi înălţimii dreptungiului de desenare (suprafeţei de decupare); public abstract void translate(int x, int y) - translatează originea sistemului de coordonate în punctul (x,y) al sistemului de coordonate curent; public abstract Color getColor() - întoarce culoarea de desenare curentă; public abstract void setColor(Color c) - setează culoarea de desenare curentă; public abstract Font getFont() - întoarce fontul curent; public abstract void setFont(Font f) - setează fontul curent; public abstract FontMetrics getFontMetrics() - întoarce metrica fontului curent; public abstract void setFontMetrics(FontMetrics fm) - setează metrica fontului curent; public abstract Rectangle getClipBounds() - întoarce dreptunghiul de decupare curent; public abstract void setClip(int x, int y, int width, int height) setează dreptunghiul de decupare curent; public abstract void copyArea(int x, int y, int width, int height, int dx, int dy) - copiaza suprafaţa dreptunghiulara cu originea (x,y), lăţimea width şi

înălţimea height într-o noua zonă de aceleaşi dimensiuni, având originea (x+dx, y+dy).. public abstract void setPaintMode() - setează operaţia logică pe pixeli curentă la modul paint, adică desenarea se face peste fondul existent, folosinduse culoarea de desenare curentă, fără a lua în consideraţie culoarea fondului. public abstract void setXORMode(color c1) - setează operaţiile pe pixeli la modul XOR, adică se alternează pixelii de culoare curentă cu cei din culoarea c1; aceasta înseamnă că, dacă un pixel nou plasat are aceeaşi culoare curentă cu cea existentă anterior în acelaşi loc, ea va fi înlocuita cu c1; invers, dacă pixelul existent anterior avea culoarea c1, ea va fi înlocuită cu culoarea curentă. public abstract void drawLine(int x1, int y1, int x2, int y2) - se traseaza o linie dreaptă din punctul de coordonate (x1,y1) până în punctul (x2,y2); public abstract void drawRect(int x, int y, int width, int height) - se trasează un dreptunghi cu colţul din stânga sus în punctul (x,y), având lăţimea width şi

281

Severin Bumbaru înălţimea height;
public abstract void fillRect(int x, int y, int width, int height) umple cu culoarea curentă interiorul dreptunghiului cu colţul din dreapta sus în punctul (x,y) şi cu dimensiunile (width, height); public abstract void clearRect(int x, int y, int width, int height) şterge conţinutul dreptunghiului de coordonate şi dimensiuni specificate, umplându-l cu culoarea de fond; public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) - trasează un dreptunghi cu colţurile rotunjite, unde

arcWidth si arcHeight sunt respectiv diametrul orizontal şi diametrul vertical al arcelor de rotunjire;
public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) - umple cu culoarea curentă interiorul unui dreptunghi cu

colţurile rotunjite;
public abstract void draw3DRect(int x, int y, int width, int height, boolean raised) - desenează un dreptunghi astfel luminat, încât el apare că iese din

suprafaţa de baza, dacă raised=true, sau este scufundat în aceasta suprafaţă, dacă raised=false;
public abstract void fill3dRect(int x, int y, int width, int height, boolean raised) - umple cu culoarea curentă interiorul unui dreptunghi tridimensional,

luând în consideraţie şi iluminarea;
public abstract void drawOval(int x, int y, int width, int height)

- se

desenează ovalul înscris în dreptunghiul cu colţul din stânga sus în punctul (x,y) şi cu dimensiunile (width, height); - se umple cu culoarea curentă conţinutul ovalului înscris în dreptunghiul cu colţul stânga sus în punctul (x,y) şi de dimensiuni (width, height);
public abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) - se desenează un arc circular sau eliptic, care se înscrie în public abstract void fillOval(int x, int y, int width, int height)

dreptunghiul specificat. Putem să ne imaginăm că din elipsa înscrisă în acest dreptunghi şi având centrul în centrul dreptunghiului, se trasează efectiv numai arcul care începe de la unghiul startAngle şi se extinde pe un unghi egal cu arcAngle. Unghiurile sunt măsurate în grade. Unghiul 0 este corespunzator poziţiei de la ora 3 a acului de ceasornic, iar sensul pozitiv al unghiurilor este cel contra acelor de ceasornic;
public abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) - umple cu culoarea curentă sectorul mărginit de arcul

specificat prin parametri şi de razele de la capete;
public abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) - trasează o linie frântă, care trece prin punctele ale căror coordonate (x,y) sunt

date în tabelele xPoints şi yPoints; numărul de puncte este nPoints;
public abstract void drawPoligon(int[] xPoints, int[] yPoints, int nPoints) - trasează un poligon cu nPoints vârfuri, situate în punctele ale căror coordonate

(x,y) sunt date în tabelele xPoints şi yPoints;
public abstract void drawPoligon(Poligon p)

- trasează poligonul specificat ca

argument;
public abstract void fillPoligon(int[] xPoints, int[] yPoints, int nPoints) - umple cu culoarea curenta un poligon cu nPoints vârfuri, situate în punctele

ale căror coordonate (x,y) sunt date în tabelele xPoints şi yPoints; public abstract void fillPoligon(Poligon p) - umple cu culoarea curentă poligonul specificat ca argument; public abstract void drawString(String str, int x, int y) - trasează şirul

282

Programarea orientata pe obiecte în limbajul Java de caractere str, folosind fontul şi culoarea curente; baza primului caracter al şirului este în punctul de coordonate (x,y);
public abstract void drawString(AttributedCharacterIterator iterator, int x, int y) - trasează un şir de caractere conţinute în obiectul iterator, care specifică nu

numai caracterele propriuzise ci şi fontul fiecărui caracter; baza primului caracter este în punctul de coordonate (x,y);
public abstract void drawChars( char[] data, int offset, int length, int x, int y) - trasează length caractere din tabloul data, începând cu caracterul cu

indicele offset; baza primului caracter se găseşte în punctul de coordonate (x,y); fontul şi culoarea sunt cele curente;

Metodele paint si repaint
Pentru desenarea pe suprafaţa unei componente, în clasa java.awt.Component (rădăcina ierarhiei de clase a componentelor) există metoda
public void paint(Graphics g)

Această metodă traseaza efectiv desenul, folosind în acest scop contextul grafic g primit ca argument. Contextul grafic nu este instanţiat de către programator, ci este transmis acestei metode de către maşina virtuală Java. În schimb, metoda paint trebuie redefinită în program pentru orice componentă pe care dorim să se traseze un desen. În aceast scop se folosesc în metoda paint metodele de desenare ale clasei Graphics. Metoda paint nu este invocată explicit în program. Ea este invocată implicit (de către maşina virtuală Java) atunci când componenta respectivă este afişată pe ecran sau îşi modifică dimensiunile şi/sau poziţia. Dacă, totuşi, programatorul doreşte să solicite explicit desenarea, foloseşte metoda
public void repaint()

Aceasta metodă care există, de asemenea, în clasa java.awt.Component, nu trebuie redefinită, singurul ei rol fiind de a apela metoda paint.

Exemplul 1: În fişierul Desen.java este dat un exemplu de aplicaţie simplă, în care se testează diferite metode ale clasei Graphics. În acest scop, s-a creat clasa PanouDesen ca o extensie a clasei Canvas. În această clasă, a fost redefinită metoda paint(), astfel încât să se traseze diferite desene: un dreptunghi gol, un dreptunghi plin, un dreptunghi gol cu colţurile rotunjite, un dreptunghi plin cu colţurile rotunjite, un oval gol, un oval plin, o linie frântă şi un poligon. Sau testat în acest fel metodele clasei Graphics. În fereastra aplicaţiei s-a introdus o instanţă a clasei PanouDesen.

/* Testarea clasei Graphics In fereastra aplicatiei iug se introduce suprafata de desenare panouDesen din clasa PanouDesen, care este derivata din clasa Canvas In clasa PanouDesen, metoda paint() este redefinita, astfel incat sa traseze mai multe desene, testandu-se metodele clasei Graphics */

283

Severin Bumbaru

import java.awt.*; import java.awt.event.*; import javax.swing.*; class Desen { static Sfarsit sfarsit=new Sfarsit(); static IUG iug=new IUG("Exemplu de desenare"); static class IUG extends JFrame { PanouDesen panouDesen; IUG(String titlu) { super(titlu); addWindowListener(sfarsit); panouDesen=new PanouDesen(); setLocation(200,150); getContentPane().add(panouDesen); setSize(220,200); setVisible(true); } }

static class Sfarsit extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } static class PanouDesen extends Canvas { public void paint(Graphics g) { setBackground(Color.white); g.setColor(Color.black); g.drawRect(5,5,40,25); g.drawRect(50,5,40,25); g.setColor(Color.blue); g.fillRect(50,5,40,25); g.setColor(Color.red); g.drawRoundRect(100,5,40,25,10,10); g.drawRoundRect(150,5,40,25,10,10); g.setColor(Color.green); g.fillRoundRect(150,5,40,25,10,10); g.drawOval(5,40,40,25); g.drawOval(50,40,40,25); g.setColor(Color.magenta); g.fillOval(50,40,40,25); g.setColor(Color.black); g.drawArc(100,40,40,25,-5,130); g.drawArc(150,40,40,25,-5,130); g.setColor(Color.green); g.fillArc(150,40,40,25,-5,130); g.setColor(Color.black); int[] xlf={5,30,55,80,100,125}, ylf={100,70,85,75,75,80}; g.drawPolyline(xlf,ylf,6); int[] xp={15,50,75,65,55}, yp={100,90,95,120,150}; g.drawPolygon(xp,yp,5);

284

Programarea orientata pe obiecte în limbajul Java

g.setColor(Color.yellow); g.fillPolygon(xp,yp,5); } } public static void main(String args[]) { } }

Exemplul 2: În fişierul GraficeFunctii.java este dat un exemplu de aplicaţie pentru trasarea graficelor unor funcţii. În fereastra aplicaţiei s-au pus: - panoul fct, care conţine o listă de selectie a funcţiei de reprezentat şi trei câmpuri de text, în care se introduc de la tastatură marginile inferioară şi superioară a intervalului în care se trasează funcţia (sub forma de numere reale) şi numărul de subintervale în care se împarte acesta; - suprafaţa de afişare gr, pe care se trasează graficul; - eticheta mesaj, în care se afişează eventualele mesaje de eroare. Pentru panoul fct a fost creată clasa Functii, derivată din clasa JPanel şi s-a folosit gestionarul de poziţionare GridLayout. Pentru suprafaţa de afişare s-a creat clasa Grafic, derivată din clasa Canvas. Clasa Actiuni interceptează evenimentele generate de fereastra principală, câmpurile de text şi lista de selecţie a funcţiilor. În acest scop, ea este derivată din clasa WindowAdapter şi implementează interfeţele ActionListener şi ItemListener. Trasarea graficului se face sub forma unei linii frânte, folosind metoda drawPoliline(int x[],int y[],int nrPuncte), în care vectorii x şi y conţin coordonatele punctelor prin care trece curba. Scările de reprezentare pe cele două axe se aleg automat, astfel încât graficul să ocupe întreaga suprafaţă de desenare. În acest scop, se calculează mai întâi valorile reale ale funcţiei de reprezentat, completându-se cu ele vectorul real valy. Se determină apoi ymax şi ymin, iar dimensiunile suprafeţei de desenare se determină prin metodele getWidth() şi getHeight(), iar xmin şi xmax sunt date. Folosind aceste date se calculează scările pe x şi y, după care se calculează coordonatele pe desen ale punctelor, completându-se astfel vectorii x şi y. Aceste calcule se fac în metoda calcul() din clasa Actiuni, iar trasarea graficului se face în metoda paint() din clasa Grafic.

/* Aplicatie de trasare a graficelor unor functii. Se selecteaza functia de trasat si se introduc valorile marginilor intervalului de reprezentare. Trasarea functiei se face fie cand se selecteaza o noua functie, fie cand este selectat unul din campurile de text si se apasa tasta <Enter>. Daca se introduc date gresite, nu se traseaza functia, ci apare un mesaj de eroare */

285

Severin Bumbaru

import java.awt.*; import java.awt.event.*; import javax.swing.*; class GraficeFunctii { static Actiuni act=new Actiuni(); static Functii fct=new Functii(); static Grafic gr=new Grafic(); static Label mesaj=new Label(); static boolean trasare=false; static int nrPasi=200; // numarul de subintervale static int nrNoduri=nrPasi+1; // numarul de puncte ale graficului static int indFunc; // indicele functiei care se reprezinta grafic // Tabele de coordonate ale punctelor graficului functiei static int x[]=new int[nrNoduri], y[]=new int[nrNoduri]; // inaltimea si latimea suprafetei de desenare si pozitiile axelor static int inaltime, latime, xord, yabsc;

public static void main(String args[]) { JFrame fp=new JFrame("Grafice de functii"); Container cp=fp.getContentPane(); cp.add(fct,"North"); cp.add(gr,"Center"); gr.setBackground(Color.cyan); cp.add(mesaj,"South"); mesaj.setBackground(Color.green); fp.addWindowListener(act); fp.setSize(500,500); fp.setVisible(true); } // Introducerea datelor si calcularea coordonatelor static class Functii extends JPanel { // Componenta de selectare a functiei JLabel lab=new JLabel("Alegeti functia de reprezentat"); Choice f=new Choice(); // Campurile de text pentru introducerea marginilor intervalului // si numarului de subintervale JTextField inf=new JTextField("-6.28"); JTextField sup=new JTextField("6.28"); JTextField pasi=new JTextField("200"); // Constructorul clasei Functii Functii() { // Adaugarea de functii la lista de optiuni f.add("x^2"); f.add("x^3"); f.add("log(1+x^2)-1"); f.add("sin(x)"); f.add("cos(x)"); f.add("exp(-abs(0.1*x))*cos(x)"); f.add("cos(x)/sqrt(1+x*x)"); // Setarea gestionarului de pozitionare setLayout(new GridLayout(4,2)); // Adaugarea componentelor

286

Programarea orientata pe obiecte în limbajul Java

add(new Label("Alegeti functia de reprezentat: ")); add(f); add(new Label("Marginea inferioara a intervalului: ")); add(inf); add(new Label("Marginea superioara a intervalului: ")); add(sup); add(new Label("Numarul de subintervale pentru trasare: ")); add(pasi); // Adaugarea la componente a interceptorului de evenimente inf.addActionListener(act); sup.addActionListener(act); pasi.addActionListener(act); f.addItemListener(act); } // Calcularea valorii functiei in punctul de abscisa x double func(double x) { switch(indFunc) { case 0: return x*x; case 1: return x*x*x; case 2: return Math.log(1+x*x)-1; case 3: return Math.sin(x); case 4: return Math.cos(x); case 5: return Math.exp(-Math.abs(0.1*x))*Math.cos(x); case 6: return Math.cos(x)/Math.sqrt(1.0+x*x); } return 0.0; }

} // Captarea si tratarea evenimentelor static class Actiuni extends WindowAdapter implements ActionListener, ItemListener { public void windowClosing(WindowEvent e) { System.exit(0); } public void actionPerformed(ActionEvent e) { reprezinta(); } public void itemStateChanged(ItemEvent e) { reprezinta(); } public void reprezinta() { try { calcul();//Se calculeaza coordonatele punctelor graficului gr.repaint();//Se traseaza graficul functiei } catch(Exception e) { mesaj.setText("eroare: "+e); } } // Calcularea coordonatelor tuturor punctelor graficului void calcul() throws Exception { double xmin,xmax,ymin,ymax,pas,scarax,scaray; // Initializari mesaj.setText("");

287

Severin Bumbaru

trasare=false; gr.repaint(); // Preluarea datelor introduse in campurile de text int nrP=Integer.parseInt(fct.pasi.getText()); if(nrP<1) throw new Exception("Numar de intervale incorect"); if(nrP != nrPasi) { nrPasi=nrP; nrNoduri=nrPasi+1; x=new int[nrNoduri]; y=new int[nrNoduri]; } xmin=Double.parseDouble(fct.inf.getText()); // marginea din stanga xmax=Double.parseDouble(fct.sup.getText()); // marginea din dreapta if(xmin==xmax) throw new Exception("xmin==xmax"); pas=(xmax-xmin)/nrPasi; // Lungimea subintervalului double valy[]=new double[nrNoduri];// tabloul ordonatelor // Preluarea indicelui functiei selectate indFunc=fct.f.getSelectedIndex(); // se afla indicele functiei // Determinarea dimensiunilor suprafetei de desenare inaltime=gr.getHeight(); // inaltimea suprafetei de desenare latime=gr.getWidth(); // latimea suprafetei de desenare // Calcularea ordonatelor punctelor graficului for (int i=0; i<nrNoduri; i++) valy[i]=fct.func(xmin+i*pas); // Determinarea valorilor minima si maxima ale lui y ymin=valy[0]; ymax=valy[0]; for (int i=1; i<nrNoduri; i++) { if(valy[i]>ymax) ymax=valy[i]; if(valy[i]<ymin) ymin=valy[i]; } // Determinarea scarilor de reprezentare pe cele doua directii scarax=latime/(xmax-xmin); scaray=inaltime/(ymax-ymin); // Calcularea coordonatelor de pe desen ale punctelor graficului for(int i=0; i<nrNoduri; i++) { y[i]=inaltime-(int)Math.round((valy[i]-ymin)*scaray); x[i]=(int)Math.round(i*pas*scarax); } // Determinarea pozitiilor pe desen ale axelor de coordonate yabsc=inaltime+(int)Math.round(ymin*scaray); xord=(int)Math.round(-xmin*scarax); trasare=true; } } // Componenta pe care se traseaza desenul static class Grafic extends Canvas { // Metoda de trasare a graficului public void paint(Graphics g) { if(!trasare) return; // Trasarea axelor de coordonate g.setColor(Color.blue); if(yabsc>=0 && yabsc<=inaltime) g.drawLine(0,yabsc,latime,yabsc); if(xord>=0 && xord<=latime) g.drawLine(xord,0,xord,inaltime); // Trasarea curbei (sub forma de linie franta) g.setColor(Color.red); g.drawPolyline(x,y,nrNoduri); }

288

Programarea orientata pe obiecte în limbajul Java

} }

Întrebări
Nivel 1
1. 2. 3. 4. 5. 6. 7. 8. 9. Ce sunt ferestrele de dialog? Ce deosebire există între ferestrele de dialog modale şi cele nemodale? Ce sunt ferestrele de dialog standard? Cum se creeaza ferestrele de dialog standard? Cum se stabileşte culoarea de fond a unei componente? Ce este culoarea de prim plan a unei componente şi prin ce metodă se modifică? Ce sunt instanţele clasei Color? Care este principalul model de culori folosit în Java? Prin ce tip de date se exprimă culorile în sistemul RGB şi care este intervalul în care acestea iau valori? 10. Care este culoarea pentru care cele trei culori fundamentale RGB au valoarea 0? 11. Care este culoarea pentru care toate cele trei culori fundamentale au valoarea 255? 12. La ce foloseşte clasa JColorChooser? 13. Pe ce fel de componente se poate face desenarea? 14. Care este sistemul de coordonate pentru reprezentări grafice? 15. Care este unitatea de măsură folosită pe axele de coordonate pentru reprezentări grafice? 16. Ce este contextul grafic? 17. Prin ce clasă abstractă este reprezentat contextul grafic în Java API? 18. În ce clasă este declarată metoda paint şi ce rol are ea? 19. Cum este invocată metoda paint? 20. În ce scop este redefinită metoda paint? 21. Ce trebuie să conţină corpul metodei paint pentru a se trasa un desen? 22. La ce foloseşte metoda repaint? 23. Se redefineste în program metoda repaint?

Nivel 2
1. Prin ce clasă se realizează ferestrele de dialog în JFC/Swing? 2. Ce relaţie există între fereastra de dialog şi proprietarul ei? 3. Prin ce metodă se afişează pe ecran o fereastră de dialog? 4. Prin ce metode se închide o fereastră de dialog? 5. Care tipuri de ferestre pot fi create folosind metodele clasei JOptionPane? 6. Ce conţine o fereastră de dialog de mesaj? 7. Ce conţine o fereastră de dialog de confirmare? 8. Ce conţine o fereastră de dialog de intrare şi cum este ea utilizată? 9. Ce este modelul HSB? 10. Prin ce tip de date se exprimă componentele modelului HSB şi în ce interval de valori?

289

Severin Bumbaru 11. Pentru ce fel de culori HSB saturaţia are valoarea 0.0? 12. Cum se creează o instanţă a clasei JColorChooser? 13. Cum poate fi afişat un JColorChooser sub formă de fereastră de dialog? 14. Ce modele de culori se folosesc în JColorChooser? 15. Ce câmpuri conţine un obiect al clasei Graphics? 16. Ce fel de metode conţine clasa Graphics? 17. Unde se plasează în program redefinirea metodei paint?

290

Programarea orientata pe obiecte în limbajul Java

Introducere în HTML. Crearea şi utilizarea appleturilor
Introducere în HTML Ce este un hipertext Ce este WWW Ce este HTML Ctructura documentului HTML Carcaje de formatare a textului folosite frecvent Legaturi catre alte pagini Marcajul APPLET Utilizarea marcajelor HTML în textele introduse în componentele JFC/Swing Applet-uri Clasa Applet întrebări. 291

Introducere în HTML
Deşi prezentarea limbajului HTML nu constituie un obiectiv al acestui manual, vom da aici unele noţiuni introductive asupra acestui limbaj, fiind utile pentru o mai bună înţelegere a utilizării appleturilor.

Ce este un hipertext
Hipertextul (engleză: hypertext) este o colecţie de documente, numite şi noduri sau pagini, unite între ele prin legăturica în Figura 1.

291

Severin Bumbaru

- Figura 1 Este necesar, desigur, să se adopte o convenţie privind modul în care se însereaza în pagina de hipertext legăturile către alte pagini. Pentru utilizatorul care vede pe ecran pagina respectivă, această legătură apare, de regulă, sub forma unui cuvânt sau unui grup de cuvinte puse în evidenţă prin subliniere, colorare sau prin ambele procedee. Dacă se face click cu mouse-ul pe o astfel de "legătura", pagina curentă se schimbă cu pagina către care indică legătura respectivă. Citirea unui asemenea hipertext nu se face cu un editor de texte obişnuit, ci cu un program special numit navigator sau browser. Paginile (nodurile) hipertextului sunt stocate sub forma de fişiere, situate pe un singur calculator, sau pe mai multe calculatoare legate în reţea.

Ce este WWW
World-Wide Web (WWW) este un sistem de regăsire a informaţiei distribuite pe Internet. În limba engleză, web înseamnă "plasa", "reţea", "pânză de păianjen", ceeace sugerează legaturile existente între noduri într-un hipertext. Asadar, WWW este o astfel de "pânză de păianjen" care acopera întreaga lume şi ale cărei noduri sunt documente. Documentele sunt stocate sub formă de fişiere pe diferite calculatoare, care acţioneaza ca servere de web. Utilizatorul poate naviga pe această reţea, folosind un program numit browser de web (navigator). Cele mai răspândite browsere de web sunt, în prezent, Netscape Navigator şi Internet Explorer. Putem deci considera că WWW este perceput de utilizator ca un hipertext, ale cărui noduri sunt raspândite în întreaga lume. Transmiterea prin Internet a paginilor WWW se face folosind un protocol special, numit HTTP (Hypertext Transfer Protocol), iar pentru marcarea paginilor de WWW, cu scopul de a însera în ele legăturile necesare, se foloseşte un limbaj numit HTML.

Ce este HTML
HyperText Markup Language (HTML) este un limbaj de marcare a hipertextelor. În estenţă, o pagină HTML este un fişier de text ASCII, care conţine nişte simboluri speciale, numite marcaje sau taguri. Prin aceste marcaje se indică legăturile către alte documente şi către

292

Programarea orientata pe obiecte în limbajul Java appleturi, imagini sau secvenţe audio incluse în document, dându-se, de asemenea, informaţii privind formatarea documentului şi altele. Fiecare marcaj (tag) este de forma <tag> ... </tag>, deci începe printr-un nume de marcaj (tag) cuprins între paranteze unghiulare şi se termină prin acelaşi marcaj, având în faţa numelui simbolul / (slash). Astfel, fiecare document HTML începe cu marcajul <html> şi se termină cu </html>. În limbajul HTML nu se face distincţie în marcaje între literele mari şi cele mici, deci tagul <HTML> ... </HTML> este echivalent cu tagul <html> ... </html>. Aceste marcaje pot fi cuprinse unul în altul, ca în Figura 2.a, dar nu este permis ca domeniile lor să se intersecteze, ca în Figura 2.b.

- Figura 2 Pentru crearea de documente HTML se poate folosi un editor de text simplu, în mod ASCII, dar - în acest caz - cel care creează documentul trebuie să cunoască bine sintaxa HTML şi toate tagurile acestuia. Mult mai comoda este folosirea unor editoare speciale de pagini Web, cum sunt Netscape Composer sau Front Page, care prezintă interfaţă utilizator grafică, iar pe ecran se vede documentul sub forma în care acesta apare şi in browser.

Structura documentului HTML
Un document HTML are urmatoarea structură: <HTML> <HEAD> Antetul documentului </HEAD> <BODY> Corpul documentului </BODY> </HTML> Modul de punere în pagină nu are importanţă, deoarece întreaga informaţie despre structură şi formatare este conţinuta în marcaje (taguri). Acelaşi document poate fi pus în pagina, de exemplu, astfel: <HTML><HEAD>Antetul documentului </HEAD><BODY>Corpul documentului </BODY></HTML> Antetul documentului conţine, opţional, titlul acestuia şi informaţii despre autor, tipul de document etc. O componentă importantă a antetului este titlul documentului, care are forma:

293

Severin Bumbaru <TITLE>Titlul documentului</TITLE> Titlul este un text, care apare în bara de titlu a browserului care vizualizează documentul respectiv. Corpul documentului este documentul propriu-zis, care se afişează pe ecran, inclusiv legăturile conţinute în documentul respectiv.

Marcaje de formatare a textului folosite frecvent
Marcarea paragrafelor
Fiecare paragraf este cuprins între marcajele <P> .. </P>. Totusi, folosirea marcajului de sfârşit de paragraf </P> nu este obligatorie. Atunci când documentul este vizualizat în browser, fiecare paragraf începe de la capăt de linie. În interiorul paragrafului, trecerea la linie nouă se face automat, când se ajunge la marginea din dreapta a paginii. Se poate, totuşi, forţa trecerea la linie nouă prin marcajul <br> (prescurtare de la break). În marcajul <P> se pot introduce şi indicaţii privind culoarea de fond, culoarea caracterelor şi forma caracterelor din paragraful respectiv. Aceste indicaţii se dau prin parametri de forma nume=valoare. Culoarea caracterelor se indică prin parametrul color=culoare, unde culoare poate fi: black (negru), gray (gri), silver (argintiu), white (alb), red (rosu), green (verde), blue (albastru), yellow (galben), lime (verde deschis), aqua (albastru deschis), fuchsia (roz), purple (purpuriu), maroon (maron), olive (oliv), navy (bleu marin), teal (verde inchis). Remarcăm că sunt alte nume de culori decât cele din clasa java.awt.Color. Culoarea fondului se indică prin parametrul bgcolor=culoare, unde numele culorilor sunt aceleaşi ca pentru caractere. Forma caracterelor cu care este scris paragraful este indicată prin parametrul face=forma. În funcţie de platformă, se pot folosi diferite forme de caractere. Există, totuşi, trei forme care sunt disponibile pe orice platformă: Serif, sansSerif si Monospace. Primele două au caractere de lăţime variabilă (de exemplu, m este mai lat decat i), iar ultimul are toate caracterele de aceeasi lăţime (exemplu: în cuvantul lăţime, scris cu astfel de caractere, m şi i au aceeaşi lăţime). Deosebirea între Serif şi SansSerif este prezentă, respectiv absenţa serifurilor. Acestea sunt liniuţele mici care delimitează unele linii ale literelor, cum se poate observa comparând caracterele din cuvintele de mai jos:

Serif SansSerif
Indicarea fontului
Se numeşte font tipul de literă folosit într-un text. Termenul este preluat din tipografie şi se referă la reprezentarea formei şi mărimii unui caracter. S-a menţionat mai sus cum pot fi indicate forma şi culoarea caracterelor dintr-un paragraf ca parametri în marcajul <P>. Exista însă şi situaţii în care un anumit font se foloseşte numai într-o porţiune de paragraf, sau se extinde pe mai multe paragrafe. În acest scop, se foloseşte marcajul <font parametri> text</font>. Parametrii din marcajul <font > sunt face=formă, color=culoare şi size=mărime. Forma şi culoarea se indică în acelaşi mod ca în marcajul <P>. Mărimea este un număr întreg cu sau 294

Programarea orientata pe obiecte în limbajul Java fără semn. Dacă se foloseşte un număr întreg fără semn, acesta indică mărimea absolută a fontului. Mărimea indicată ca numar intreg cu semn în intervalul [-3, +3] arată mărimea relativă, faţă de cea pentru care este setat browserul utilizat. Aşa dar, de exemplu, parametrii size=3 si size=+3 indică fonturi de mărimi diferite. Exemplu Textul HTML următor:
<font face=serif color=purple size=+1> proba de text </font>

va apare scris pe ecran sub forma

proba de text
Nu este obligatoriu să se indice toţi cei trei parametri. Se folosesc numai cei prin care noul font diferă de cel anterior.

Indicarea stilului
În afară de forma, marimea şi culoarea fontului, textul se caracterizează şi prin stil: păstrând forma fontului, textul poate să apară îngroşat, cursiv, subliniat etc. În HTML, stilul textului se indică prin marcaje speciale: <B> ... </B> pentru text aldin, îngrosat (engleză: bold); <I> ... </I> pentru text cursiv (engleză: italic); <U> ... </U> pentru text subliniat (engleză: underline); <strike> ... </strike> pentru text tăiat cu o linie (engleză: strikethrough); <sub> ... </sub> text scris mai mic şi mai jos decât textul de bază (engleză: subscript); <sup> ... </sup> text scris mai mic şi mai sus decât cel de bază (engleză: superscript). Aceste stiluri pot fi şi combinate. De exemplu, textul HTML
<B><I><U>text aldin, cursiv şi subliniat</U></I></B>

apare pe ecran sub forma: text aldin, cursiv şi subliniat

Marcarea titlurilor şi subtitlurilor
Titlurile diferitelor secţiuni ale textului (capitole, subcapitole etc) se pun în evidenţă prin faptul că apar scrise cu caractere diferite de restul textului (de regulă mai mari) şi sunt distanţate prin câte o linie atât de textul de deasupra, cât şi prin cel de dedesubt. Pentru marcarea titlurilor se foloseşte marcajul <Hn>titlu</Hn> în care n este un număr întreg cuprins în intervalul [1, 6]. Cu cât cifra n este mai mare, titlul respectiv se afişează scris cu caractere mai mici. De exemplu, textul HTML
<H1>Primul titlu></H1><H2>Al doilea titlu</H2><H3>Al treilea titlu</H3>

apare pe ecran sub forma

Primul titlu
Al doilea titlu
Al treilea titlu
Se poate astfel continua până la marcajul <H6>.

295

Severin Bumbaru

Text preformatat
S-a arătat mai sus că aşezarea textului în pagină se face de către browser, respectând marcajele de formatare din textul HTML. În consecinţă, dacă se modifică dimensiunile ferestrei, se modifica şi aşezarea în pagină a textului. Spaţiile şi caracterele speciale din textul HTML, cum sunt caracterul de trecere la linie nouă sau de întoarcere a carului sunt ignorate de browser şi nu au efect la afişarea pe ecran. Există, totuşi un marcaj care impune ca textul sa fie aşezat pe ecran aşa cum este el în pagina HTML. Acest marcaj este <PRE> text preformatat </PRE>. De exemplu, textul
<PRE> un exemplu de text preformatat scris pe trei linii </PRE>

va apare pe ecran sub forma
un exemplu de text preformatat scris pe trei linii

Legături către alte pagini
Legăturile cu alte documente sunt marcate prin ancore, care sunt taguri de forma: <A HREF="referinţă la document">text de legătură</A> Parametrul HREF (Hypertext Reference) reprezintă indicarea locului în care poate fi găsit documentul către care se face trimiterea (referinţa la document). Această referinţă nu este vizibilă în fereastra de browser în care este afişat documentul. În schimb, este vizibil textul de legătură, sub forma unui text subliniat şi având o culoare diferită de a restului documentului. Referinţa la document depinde de locul în care acesta se găseşte. a/ Documentul ţintă se găseşte pe acelasi calculator. În acest caz, referinţa poate fi: a1/ Referinţa absolută, de forma: cale/document unde cale (engleza: path) este succesiunea de directoare parcurse de la rădăcina arborelui de directoare, până la directorul în care se găseşte fişierul care conţine documentul; pentru separarea directoarelor se foloseşte bara (slash, /), conform convenţiei din sistemul de operare Unix. De exemplu:
"C:/d1/d2/d3/docum.html"

este o referinţă la documentul docum.html, care se găseşte pe discul C, urmând calea d1/d2/d3/ unde d1, d2 si d3 sunt directoare. Dacă documentul ţintă se găseşte pe acelaşi disc cu documentul în care apare această referinţă, discul poate fi omis, punând referinţa sub forma
"/d1/d2/d3/docum.html"

dar având grijă să înceapă cu "/", ceeace înseamnă că se porneşte de la rădăcină, deci este o referinţă absolută. a2/ Referinţa relativă, în care calea se trasează pornind de la directorul în care se găseşte documentul sursă.La indicarea căii, simbolul .. (doua puncte succesive) înseamnă deplasare cu un director înapoi, iar / înseamna deplasare cu un director înainte. De exemplu, referinţa:
"../../d2/d3/docum.html"

are semnificaţia următoare: pornind de la directorul în care se găseşte documentul sursă, ne deplasăm două directoare "înapoi" către rădacină, după care parcurgem "înainte" calea d2/d3 şi ajungem la documentul docum.html. b/ Documentul ţintă se găseşte pe un alt calculator. În acest caz, referinţa la document este

296

Programarea orientata pe obiecte în limbajul Java un URL (engleză: Uniform Resource Locator), care este modul de adresare caracteristic pentru WWW. Iată exemple de URL-uri:
"http://www.w3.org/default.html" "http://java.sun.com/docs/books/jls/html/index.html" "http://lib.cs.ugal.ro/~sbumbaru/CursJava/Sapt01/poo.html"

Prima parte a URL-ului (în cazul de faţă http:) reprezintă protocolul de transmisie folosit. Cele două bare (//) indică modul de acces (în cazul de faţă - adresa pe Internet a unui calculator, de exemplu java.sun.com). Urmeaza calea absolută parcursă pe calculatorul respectiv (de exemplu /docs/books/jls/html/) şi numele documentului (index.html).

Modul de adresare pe Internet este, în principiu, următorul: Internetul este împărţit în domenii, care pot fi domenii naţionale (de exemplu: ro - Romania, fr - Franta, uk - Marea Britanie, de - Germania etc) sau domenii profesionale (com - comercial, edu - educaţional, org - organizaţii, net - retele). Fiecare domeniu este imparţit în subdomenii (de ex.: în domeniul ro există subdomeniul ugal - Universitatea din Galati, iar în domeniul com există subdomeniul sun - firma Sun Microsystems). Subdomeniile pot fi împărţite în subsubdomenii s.a.m.d până se ajunge la calculatoare individuale. De exemplu, locaţia atlas.stud.ugal.ro înseamnă calculatorul atlas din subreţeaua stud, din subdomeniul ugal al domeniului ro.

Marcajul APPLET
Pentru a introduce într-un document HTML o referinţă la un applet, se foloseşte marcajul (tagul) APPLET, care are forma următoare: <APPLET CODE=fişier_class [CODEBASE=localizare] WIDTH=lăţime HIGHT=înălţime[ALIGN=aliniere]> [<PARAM NAME=nume VALUE=valoare>]* [text_de_înlocuire] </APPLET> în care: fişier_class - fişierul cu extensia .class, în care se află bytecode-ul appletului (indicarea extensiei nu este obligatorie), de exemplu: CODE=TestFlowAp.class sau CODE=TestFlowAp localizare - referinţa la directorul în care se găseşte bytecode-ul appletului; această referinţă poate fi absolută, relativă sau sub forma de URL, la fel ca în cazul legăturilor către alte documente, cu observaţia că nu se mai termină prin numele documentului ţintă, acesta fiind cel din parametrul CODE; dacă fişierul bytecode al appletului se găseşte în acelaşi director cu fişierul HTML în care acesta este invocat, parametrul CODEBASE lipseşte; lăţime si înălţime - numere întregi, reprezentând lăţimea şi înălţimea appletului; aliniere - alinierea appletului în pagina de browser, poate fi una din următoarele: left | right | top | texttop | middle | absmiddle | baseline | bottom | absbottom unde left şi right indică aliniere la stânga sau la dreapta, top - aliniere cu elementul cel mai înalt din linie (fie el text, imagine sau alt applet), texttop - aliniere cu cel mai înalt element de text din linia respectivă, middle - mijlocul appletului se aliniază cu mijlocul liniei de bază a textului, absmiddle - mijlocul appletului se aliniază cu mijlocul elementului cel mai mare din linie, baseline sau bottom - baza appletului se aliniază cu linia de bază a textului, absbottom - baza appletului va fi elementul cel mai de jos din linie. text_de_înlocuire- un text care va apare în locul appletului, dacă browserul folosit pentru vizualizarea documentului HTML nu este capabil sa utilizeze appleturi.

297

Severin Bumbaru Dacă este necesar, marcajul APPLET poate să conţină unul sau mai mulţi parametri, care se vor transmite appletului la lansarea acestuia. Aceşti parametri apar în subtagul PARAM, pentru fiecare din ei indicându-se un nume şi o valoare.

Exemplul 1: marcajul <APPLET CODE="CBGroupAp" WIDTH=180 HEIGHT=80> Testarea clasei CheckboxGroup </APPLET> se foloseşte pentru a include în documentul HTML a unui applet, al cărui fişier de bytecode este CBGroupAp.class şi se găseşte în acelasi director cu documentul HTML în care există acest tag. Appletul va avea lăţimea 180 şi înălţimea 80. Dacă browserul folosit nu suportă appleturi java, în locul rezervat appletului va apare textul de înlocuire "Testarea clasei CheckboxGroup". Exemplul 2: marcajul <APPLET CODE=PrimApplet CODEBASE=../Sapt01/surse WIDTH=200 HEIGHT=100>Primul applet</APPLET> se foloseşte pentru a include în documentul HTML appletul al cărui fişier bytecode este PrimApplet.class şi care se găseşte în alt director decât fişierul HTML, dar pe acelaşi disc. În CODEBASE s-a folosit forma relativa a căii. Exemplul 3: marcajul <APPLET CODE=PrimApplet CODEBASE=/CursJava/Sapt11/surse WIDTH=200 HEIGHT=100> Primul applet </APPLET> are aceeaşi semnificaţie ca cel din exemplul anterior, dar în CODEBASE s-a folosit forma absolută a căii. Exemplul 4: marcajul <APPLET CODE=PrimApplet CODEBASE=http://lib.cs.ugal.ro/~sbumbaru/CursJava/Sapt11/surse WIDTH=200 HEIGHT=100> Primul applet </APPLET> are aceeaşi semnificaţie ca cel din exemplul anterior, dar în CODEBASE se foloseşte un URL la care poate fi găsit appletul respectiv.

Folosirea de parametri în appleturi
În marcajul APPLET pot fi introduşi şi unul sau mai mulţi parametri, folosind în acest scop sub-marcajul [<PARAM NAME=nume VALUE=valoare>]* Atât numele, cât şi valoarea sunt şiruri. Preluarea în applet a fiecăruia din aceşti parametri se face invocând metoda
public String getParameter(String name)

care primeşte ca argument numele parametrului şi întoarce valoarea acestuia sub forma de şir. Avantajul este că valorile parametrilor pot fi modificate direct în fişierul HTML, fără să mai

298

Programarea orientata pe obiecte în limbajul Java fie necesar să se modifice şi să se recompileze programul appletului. Exemplu: În fişierul DouaTexte.java este dat un exemplu de applet în care sunt preluaţi doi parametri, cu numele textul1 si textul2. Aceşti parametri sunt şiruri de caractere, care se afişează alternativ într-o etichetă. Schimbarea textului se face la apăsarea pe buton. Acest applet este inclus în pagina HTML din fişierul DouaTexte.html prin următorul marcaj: <APPLET CODE=DouaTexte WIDTH=250 HEIGHT=100 ALIGN=middle> <PARAM NAME=textul1 VALUE="Acesta este primul text"> <PARAM NAME=textul2 VALUE="Acesta este al doilea text"> </APPLET> Pentru a se afişa alte texte, este suficient să se modifice în mod corespunzător valorile parametrilor din acest marcaj, fără a se face modificări în programul appletului.

Utilizarea marcajelor HTML în textele pentru componentele JFC/Swing
În mod obişnuit, textul introdus în componentele AWT sau JFC/Swing prin metoda void setText(String text) apare pe ecran pe suprafaţa componentei respective sub forma unui text scris pe o singura linie, folosind fontul SansSherif, culoarea neagra şi o mărime implicită. Începând cu platforma Java 2 (SDK 1.2) în textele introduse în componentele JFC/Swing (cum ar fi cele din clasele JButton, JLabel, JPanel etc.) pot fi folosite marcaje HTML. Dacă textul introdus prin metoda setText() începe cu marcajul <HTML>, el este interpretat ca un text HTML şi tratat în mod corespunzător. Aceasta înseamnă că textul poate să apară pe suprafaţa componentei respective pe mai multe linii, să aibă diferite fonturi, culori şi stiluri, conform cu marcajele HTML utilizate. Dacă, însă, sintaxa HTML nu este respectată, metoda generează o excepţie.

Exemplu În fişierul TextHTML.java este dat un exemplu de aplicaţie, în care se experimentează folosirea textelor HTML în componentele interfeţei grafice. În fereastra aplicaţiei sunt plasate următoarele componente: - o arie de text (JTextArea), în care se afişează un text iniţial, care poate fi apoi modificat de utilizator; - o etichetă (JLabel) pe suprafaţa căreia se afişează textul existent în aria de text din partea stângă; - un buton cu inscripţia Vizualizare text, a cărui acţionare are ca efect afişarea pe suprafaţa etichetei a textului din aria de text; - un al doilea buton, cu inscripţia Reset, a cărui acţionare are ca efect revenirea la textul

299

Severin Bumbaru afişat iniţial în aria de text. Înscripţiile de pe cele două butoane au fost formatate folosind marcaje HTML. În aria de text este introdus iniţial un text HTML, care poate fi vizualizat apăsând pe butonul Vizualizare text. Acest text poate fi însă modificat în fereastra din stânga şi vizualizat în eticheta din dreapta, apăsând pe acelaşi buton. Dacă se doreşte, apoi, revenirea la textul iniţial, se apasă butonul Reset. Se pot experimenta, astfel, diferite modificări ale textului iniţial sau se poate introduce oricând un text obişnuit sau un hipertext nou.
/* Experimentarea folosirii marcajelor HTML in textele pentru componente JFC/Swing */ import java.awt.*; import java.awt.event.*; import javax.swing.*; class TextHTML extends JFrame { JTextArea ta; JLabel label; Sfarsit sfarsit; /* Textul introdus initial in aria de text */ String textInitial="<html>\n<h2>Proba HTML</h2>Text normal<br>\n"+ "<i>Text italic <b>Text italic aldin</b></i><br>\n"+ " <font face=serif color=blue size=4>\n"+ " Text albastru cu font serif\n </font>\n<p bgcolor=aqua>"+ "Revenire la text normal<br>cu fond aqua\n</html>"; /* Constructorul clasei TextHTML construieste interfata grafica */ TextHTML() { super("Incercare de texte HTML"); Container cp=getContentPane(); sfarsit=new Sfarsit(); // incheierea executarii aplicatiei addWindowListener(sfarsit); ta=new JTextArea(15,10); // aria in care se introduce textul HTML ta.setLineWrap(true); ta.setWrapStyleWord(true); ta.append(textInitial); ta.setBorder(BorderFactory.createTitledBorder( "Introduceti aici un text HTML")); Box vb=Box.createVerticalBox(); Box hb=Box.createHorizontalBox(); vb.add(ta); String textButon="<html><font color=red size=4>"+ "<b><i>Vizualizare text</i></b></font>"; JButton buton=new JButton(textButon); // butonul de comanda a // vizualizarii textului buton.addActionListener(new Vizualizare()); buton.setBorder(BorderFactory.createRaisedBevelBorder()); hb.add(buton); JButton reset=new JButton("<html><font color=blue size=4>"+ "<b>Reset</b></font>"); // butonul de revenire la textul initial reset.setBorder(BorderFactory.createRaisedBevelBorder()); reset.addActionListener(new Reset()); hb.add(reset); vb.add(hb);

300

Programarea orientata pe obiecte în limbajul Java

cp.add(vb,BorderLayout.WEST); label=new JLabel(); // eticheta pe care se vizualizeaza textul HTML label.setBorder(BorderFactory.createTitledBorder( "Vizualizarea textului HTML")); label.setOpaque(true); label.setBackground(Color.white); cp.add(label,BorderLayout.CENTER); setLocation(50,50); setSize(450,300); setVisible(true); } /* Clasa de ascultare a inchiderii ferestrei principale*/ class Sfarsit extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } /* clasa de ascultare a butonului de vizualizare a textului */ class Vizualizare implements ActionListener { public void actionPerformed(ActionEvent e) { try { /* afisarea pe componenta label a textului extras din aria de text ta */ label.setText(ta.getText()); } catch(Exception ex) { // s-a constatat o eroare in textul HTML label.setText("Eroare de sintaxa HTML"); } } } /* Clasa de ascultare a butonului de Reset */ class Reset implements ActionListener { public void actionPerformed(ActionEvent e) { ta.setText(textInitial); label.setText(""); } } public static void main(String args[]) { TextHTML text=new TextHTML(); // instantierea aplicatiei } }

Remarcăm că, în această aplicaţie, s-au introdus următoarele abordări noi faţă de aplicaţiile anterioare: - s-a considerat că interfaţa grafică este însăşi clasa principala TextHTML, derivata din clasa JFrame. În consecinţă, adăugarea componentelor la fereastra principală se face în constructorul clasei TextHTML; - clasele interioare şi câmpurile clasei TextHTML nu au mai fost declarate statice, în schimb, în metoda main() s-a creat o instanţă a acestei clase. Desigur că acest mod de construire a aplicaţiei nu are legătura cu tema acesteia, fiind doar o ilustrare a unui alt mod de lucru posibil în Java.

301

Severin Bumbaru

Applet-uri
Miniaplicaţia (engleză: applet) este un mic program cu interfaţă utilizator grafică, care nu poate rula în mod independent, ci este înglobat într-o altă aplicaţie, numită context de applet. Pentru executarea unui applet trebuie să existe deci două entităţi: applet-ul propriu-zis şi contextul în care acesta se execută (engleză: applet context). În mod normal, contextul de executare a unui applet este un navigator de Web (engleză: web browser), cum sunt Netscape Navigator, Internet Explorer sau HotJava. Pentru testare, appletul poate fi executat, de asemenea, folosind drept context un program de vizualizare special, existent in SDK, numit appletviewer. Orice applet este realizat ca o clasă, derivată din clasa Applet, existentă în pachetul java.applet, sau derivată din clasa JApplet, care extinde clasa Applet şi există în pachetul javax.swing. Întrucât utilizarea clasei JApplet este mai complicată, ne vom rezuma aici la descrierea şi exemplificarea utilizării clasei Applet.

Clasa Applet
Clasa Applet se găseşte în pachetul java.applet şi este o subclasă a clasei Panel din pachetul java.awt. În consecinţă, applet-ul este, de fapt, un caz special de container. Clasa Applet este superclasa tuturor miniaplicaţiilor care sunt încorporate în pagini Web sau pot fi vizualizate cu un Java Applet Viewer. Clasa Applet moşteneşte metodele superclaselor sale Component, Container şi Panel, oferind şi metode specifice. Dintre acestea, cele mai importante sunt : init(), start(), stop() şi destroy(). Aceste metode sunt apelate de către browser în momentele importante ale ciclului de viaţă al unui applet, respectiv în momentul încărcării acestuia în memorie, în momentul începerii sau întreruperii execuţiei şi înainte ca appletul să fie distrus. Aşa cum sunt ele oferite de clasa Applet, aceste metode nu fac nimic. Ele pot fi însă redefinite de programatori în subclasele clasei Applet, astfel încât să execute anumite acţiuni specifice momentelor în care sunt invocate. Metoda init() este utilizată pentru a crea partea "statică" a applet-ului: adăugarea de componente la applet, înregistrarea ascultătorilor de evenimente etc. Ea este redefinită în marea majoritate a applet-urilor. Metodele start() şi stop() se folosesc numai pentru lansarea şi oprirea proceselor dinamice (de exemplu animaţie şi/sau sunete) care nu trebuie să continuie când appletul nu este vizibil pe ecran. În fine, metoda destroy() se foloseşte în special pentru a distruge firele de execuţie paralele care au fost create de către applet, sau a elibera anumite resurse ocupate de acesta.

Principalele metode ale clasei Applet: Vom prezenta aici metodele mai frecvent utilizate. Pentru celelalte recomandăm consultarea documentaţiei originale.
public void init()

- metoda este invocată de către browser sau appletviewer atunci

302

Programarea orientata pe obiecte în limbajul Java când appletul este încărcat în memorie, deci înainte de prima invocare a metodei start(); public void start() - metoda este invocată de către browser atunci când appletul trebuie să îşi înceapă execuţia, adică atunci când este vizitată sau revizitată pagina Web în care acesta este inclus; public void stop() - metoda este invocată de către browser atunci când pagina de Web, în care se găseşte appletul, este înlocuită de alta, fără a fi însă eliminată din memorie. Întrucât appletul nu mai este vizibil pe ecran, acesta poate sa îşi înceteze temporar execuţia; metoda este invocată, de asemenea, înaintea invocării metodei destroy(); public void destroy() - metoda este invocată de către browser înainte de distrugerea appletului; public boolean isActive() - determină dacă appletul este activ; public String getAppletInfo() - dacă este redefinită în subclase, metoda poate intoarce informaţii despre applet: autorul, versiunea, drepturi de autor etc. În clasa Applet metoda întoarce null; public AppletContext getAppletContext() - întoarce o referinţă către contextul appletului; public URL getCodeBase() - întoarce URL-ul (locaţia pe Internet) la care se găseşte bytecode-ul appletului; public URL getDocumentBase() - întoarce URL-ul la care se găseşte documentul care conţine appletul; public String getParameter(String name) - întoarce valoarea parametrului cu numele dat ca argument, conţinut în tagul APPLET al documentului HTML.

Dăm în continuare trei exemple de applet-uri, împreună cu fişierele HTML în care se afişeaza acestea. Pentru compatibilitate, s-au utilizat numai componente din pachetele java.applet şi java.awt.

Exemplul 1: În introducere a fost deja dat un exemplu de applet simplu, în care nu se redefineşte nici o metodă a clasei Applet, folosindu-se numai o invocare de metodă prin care se scrie un text pe suprafaţa appletului. Programul acestui applet se găseşte în fişierul PrimApplet.java, iar fişierul HTML în care este folosit acest applet este PrimApplet.html. Exemplul 2: În fişierul CBGroupAp.java este dat un applet de testare a unui grup de casete de validare (butoane radio) din fişierul TestCheckboxGroup.java. Remarcăm următoarele deosebiri faţă de o aplicaţie similară: - nu mai există o fereastră principală sub formă de cadru (Frame sau JFrame) ca în cazul aplicaţiei, acest rol fiind îndeplinit chiar de către applet; - în consecinţă, nu mai este necesară o clasă care sa intercepteze evenimentele generate de fereastra principală; - adăugarea componentelor la applet, setarea culorilor şi înregistrarea interceptorului de evenimente, care în cazul aplicaţiei se fac în metoda main() sau în constructorul interfeţei utilizator grafice, acum se fac în metoda init(); - având în vedere că, pentru clasa Applet, gestionarul de poziţionare implicit este 303

Severin Bumbaru FlowLayout, pentru a utiliza gestionarul BorderLayout acesta a trebuit adăugat în mod explicit. Pentru vizualizarea appletului se foloseste fişierul CBGroupAp.html. Exemplul 3: În fişierul TestFlowAp.java se prezintă un applet de testare a gestionarului de poziţionare FlowLayout. Vizualizarea din browser a acestui applet se face folosind fişierul TestFlow.html. Pe suprafaţa appletului apar două butoane de comandă ("Pune" şi "Elimina") pentru adaugarea sau eliminarea de etichete şi trei butoane radio, prin care se selectează modul de aliniere a etichetelor pe suprafaţa containerului ("Stânga", "Centru", "Dreapta"). Acţionand aceste butoane se poate testa modul de aranjare al etichetelor pentru diferite tipuri de aliniere. Oricare din aceste applet-uri poate fi vizionat şi într-un appletviewer, dacă (din fereastra Xwindow sau MS-DOS) se dă comanda:
appletviewer fisierHTML

De exemplu, pentru a vizualiza applet-ul TestFlowAp, se dă comanda
appletviewer TestFlow.html

unde TestFlow.html este fişierul HTML în care este invocat acest applet.

Întrebări
Nivel 1
1. Ce este un hipertext? 2. Ce sunt nodurile hipertextului? 3. Ce este WWW? 4. Ce este un browser de Web? 5. Ce este HTTP? 6. Ce este HTML? 7. Ce formă au marcajele HTML? 8. Care este marcajul cu care începe şi se termină un document HTML? 9. Care este structura unui document HTML? 10. În ce zonă a documentului HTML se specifică titlul acestuia? 11. Cum se specifică titlul unui document HTML? 12. Prin ce se marchează, într-un document HTML, legăturile către alte pagini ale hipertextului? 13. La ce serveşte marcajul APPLET şi ce conţine el? 14. Cum sunt folosite marcajele HTML în componentele JFC/Swing? 15. Ce este un applet? 16. Ce clase se folosesc pentru realizarea applet-urilor? 17. Ce este un context de applet? 18. Ce contexte de applet cunoaşteţi? 19. Din ce clasă este derivată clasa Applet? dar clasa JApplet? 20. Care sunt principalele metode ale unui applet şi de către ce program sunt invocate ele? 21. La ce serveşte metoda init() în cazul unui applet? 22. Când sunt invocate metodele start() şi stop() ale unui applet? 23. În ce scop se foloseşte metoda destroy() a unui applet?

304

Programarea orientata pe obiecte în limbajul Java 24. Cum este utilizat un appletviewer?

Nivel 2
1. Cum se marchează paragrafele într-un document HTML? 2. Este absolut necesar ca un paragraf să se încheie cu marcajul </P>? 3. Cum se marchează într-un document HTML trecerea forţată la linie nouă? 4. Cum se specifică, într-un document HTML, culoarea fondului unui paragraf? 5. Cum se specifică culoarea caracterelor într-un document HTML? 6. Cum se specifică forma caracterelor într-un document HTML? 7. Care sunt principalele tipuri (forme) de caractere folosite în documentele HTML? 8. Ce conţine marcajul <font> şi ce semnificaţie are? 9. Ce este stilul textului? 10. Cum se marchează stilul textului într-un document HTML? 11. Cum se marcheaza titlurile şi subtitlurile de capitole într-un document HTML? 12. Ce este un text preformatat şi cum se marchează el într-un document HTML? 13. Cum se reprezinta, într-o legatură HTML, o referinţă absoluta la altă pagină de pe acelaşi calculator? 14. Cum se reprezintă, într-o legatură HTML, o referinţă relativă la altă pagina de pe acelaşi calculator? 15. Cum se reprezintă, într-o legatură HTML, o referinţă la o pagină situată pe alt calculator de pe Internet? 16. Cum pot fi introduşi parametri în marcajul APPLET şi la ce folosesc? 17. Ce componente ale interfeţei grafice permit folosirea textelor HTML? 18. Prin ce metodă se introduce un text HTML într-o componentă JFC/Swing? 19. Ce se întamplă dacă, într-un text care se pune într-o componentă JFC/Swing, există o eroare de sintaxa HTML? 20. Ce efect au metodele init(), start() şi stop() ale unui applet, aşa cum sunt ele conţinute în clasa Applet? 21. Este obligatoriu ca, la crearea unei noi clase de applet, să fie redefinite toate cele patru metode principale init(), start(), stop() şi destroy()?

305

Severin Bumbaru

Fluxuri de intrare/ieşire şi fişiere
Fluxuri de intrare/ieşire Introducere Pachetul java.io Clasele de bază ale ierarhiilor de fluxuri de intrare/ieşire Clasa Reader Clasa Writer Clasa InputStream Clasa OutputStream Consideraţii generale privind utilizarea fluxurilor Clasele PrintStream şi PrintWriter Fişiere Clasa File Citirea fişierelor Citirea fişierelor de octeţi: clasa FileIntputStream Citirea fişierelor de caractere: clasa FileReader Scrierea în fişiere Scrierea în fişiere de octeţi: clasa FileOutputStream Scrierea în fişiere de caractere: clasa FileWriter Fişiere cu acces direct Fluxuri de prelucrare Fluxuri de date Fluxuri de obiecte Întrebări. 306 306 307 310 310 311 312 312 313 313 315 316 319 319 310 322 322 323 324 327 327 329 331

Fluxuri de intrare/ieşire şi fişiere Introducere
În majoritatea aplicaţiilor, este necesar să se transmită date între diferite componente cum sunt: memoria internă, tastatura, ecranul, fişierele de pe disc, reţeaua de calculatoare etc. Poate fi necesar, de asemenea, să se transmită date între două aplicaţii sau între două fire de execuţie ale aceleeaşi aplicaţii. În limbajul Java, fluxul (engleză: stream) este o cale de comunicaţie între o sursă de date şi o destinaţie (Figura 1).

306

Programarea orientata pe obiecte în limbajul Java - Figura 1 Fluxul este un concept situat pe un nivel înalt de abstractizare, fiind privit ca o simplă succesiune de octeţi sau de caractere care se transmite între sursă şi destinaţie. Nu prezintă importanţă nici natura sursei sau a destinaţiei, nici modul în care trebuie interpretată secvenţa de octeti sau de caractere respectivă. De exemplu, un grup de 32 de octeţi transmişi între sursă şi destinaţie poate să reprezinte 32 de caractere ASCII sau 16 caractere Unicode sau 8 numere de tip int sau 4 numere de tip double etc. Această abstractizare permite să se trateze în mod unitar toate tipurile de transmisii de date. Se disting doua feluri de fluxuri: de ieşire şi de intrare. Pentru un proces dat, toate fluxurile transmise de acesta către exterior se numesc fluxuri de ieşire, iar cele primite din exterior se numesc fluxuri de intrare. În consecinţă, acelaşi flux este de ieşire în raport cu sursa şi de intrare în raport cu destinaţia. Principalele operaţii care au loc asupra unui flux sunt:

La sursă (flux de ieşire) - Deschiderea fluxului - cât timp (există date de transmis) scriere în flux -Închiderea fluxului

La destinaţie (flux de intrare) -Deschiderea fluxului - cât timp (există date de citit) citire din flux -Închiderea fluxului

Programatorului procesului sursă îi revine obligaţia de a pregăti, sub forma unei succesiuni de caractere sau de octeţi, datele care urmează a fi transmise pe fluxul de ieşire. Interpretarea şi tratarea datelor conţinute în flux se face de către procesul de destinaţie, deci întreaga responsabilitate îi revine celui care programează acest proces. Desigur că este necesar ca interpretarea datelor din flux la destinaţie să corespundă cu cea de la sursă. Programarea operaţiilor de intrare/ieşire poate fi destul de complicată, dar în limbajul Java ea este totuşi uşurată de existenţa unui număr destul de mare de clase cu această destinaţie, grupate în pachetul java.io.

Pachetul java.io
În Java 2 SDK se consideră că fluxurile pot fi de caractere saude octeţi. În primul caz, de la sursă la destinaţie se transmite o succesiune de caractere Unicode (de câte 16 biţi), iar în al doilea caz - o succesiune de octeţi (de 8 biţi). În mod corespunzător, pentru fiecare din cele două categorii de fluxuri există câte o ierarhie de clase de fluxuri de intrare şi o ierarhie de clase de fluxuri de iesire. Pentru fluxurile de caractere, rădăcinile ierarhiilor de clase sunt clasele abstracte Reader şi Writer.Pentru fluxurile de octeţi, rădăcinile acestor ierarhii sunt clasele abstracte InputStream şi OutputStream. Aceste ierarhii de clase sunt reprezentate în figurile 2, 3, 4 şi 5. În afară de cele patru ierarhii menţionate, în pachetul java.io există şi clase auxiliare şi interfete. Distingem trei tipuri de clase, care sunt reprezentate în aceste figuri prin culori diferite: - clase abstracte (culoare albastră); - clase care efectuează operaţiile de intrare sau de ieşire propriu-zise (culoare verde) şi modelează sursele sau destinaţiile fluxurilor (engleză: Data Sink Streams);

307

Severin Bumbaru - clase care efectuează unele operaţii de transformare a datelor de pe flux (culoare violet) şi reprezinta "fluxuri de prelucrare" (engleză: Processing Streams).

- Figura 2 - Ierarhia claselor pentru fluxuri de intrare de caractere

- Figura 3 - Ierarhia claselor pentru fluxuri de ieşire de caractere

308

Programarea orientata pe obiecte în limbajul Java

- Figura 4 - Ierarhia claselor pentru fluxuri de intrare de octeţi

- Figura 5 - Ierarhia claselor pentru fluxuri de ieşire de octeti Se observă corespondenţa între clasele de fluxuri de intrare şi de ieşire în cadrul aceleeaşi categorii de fluxuri. De exemplu, în cazul fluxurilor de caractere, unui BufferedReader îi corespunde un BufferedWriter, unui CharArrayReader îi corespunde un CharArrayWriter etc. În mod similar, în cazul fluxurilor de octeţi, unui FileInputStream îi corespunde un FileOutputStream, unui PipedInputStream îi corespunde un PipedOutputStream etc. Aceasta reflectă "simetria" operaţiilor de intrare şi de ieşire. Există totuşi şi clase de intrare fără corespondent la ieşire (de exemplu StringBufferInputStream sau PushbackInputStream) sau clase de ieşire fără corespondent la intrare (de exemplu PrintStream).

În JDK 1.0 existau numai clasele de fluxuri de octeţi. Fluxurile de caractere au fost introduse începand cu JDK 1.1. În prezent se folosesc ambele categorii de fluxuri. Pentru transmiterea textelor sau a datelor reprezentate în format extern şi codificate în Unicode este preferabil, evident, să se folosească fluxuri de caractere. Pentru transmiterea datelor codificate binar sau a textelor în care caracterele sunt codificate pe un octet (de exemplu în codul ASCII) este preferabilă folosirea fluxurilor de octeţi.

309

Severin Bumbaru Fluxurile pot fi înlănţuite, astfel încât ieşirea unui flux poate fi intrare a altui flux. Astfel, în Figura 6 este dată schema unei înlănţuiri de fluxuri de intrare: ieşirea fluxului InputStream (sub forma de fluxului de octeţi flux1) este dată la intrarea fluxului InputStreamReader, care o converteşte în fluxul de caracrtere flux2, iar acesta - la rândul lui - este preluat fe fluxul de intrare de caractere cu zonă tampon BufferedReader.

- Figura 6- Înlănţuire de fluxuri de intrare În Figura 7 este reprezentată o înlănţuire similară de fluxuri de ieşire: ieşirea fluxului de caractere BufferedWriter este aplicată la intrarea fluxului OutputStreamWriter, iar acesta face conversia într-un flux de octeţi, pe care îl transmite la un OutputStream (de exemplu la un PrintStream sau la un FileOutputStream).

- Figura 7 - Înlănţuire de fluxuri de ieşire Având în vedere complexitatea programării operaţiilor de intrare/ieşire, în cele ce urmează vom căuta să facem o prezentare graduală, de la simplu la complex. Vom începe însă cu prezentarea comparativă a claselor abstracte care constituie rădăcinile celor patru ierarhii de clase de fluxuri. Aceasta ne va permite să ne formăm o vedere de ansamblu asupra principalelor metode folosite în lucrul cu fluxuri.

Clasele de bază ale ierarhiilor de fluxuri de intrare/ieşire
Clasa Reader
Clasa abstractă java.io.Reader este rădăcina ierarhiei de clase de fluxuri de intrare de caractere. Metode: citeşte din fluxul de intrare un singur caracter; întoarce caracterul citit (în domeniul 0 .. 16383) sau -1 dacă s-a ajuns la sfârşit de fişier; metoda produce blocarea procesului în care este invocată, până când apare un caracter în fluxul de intrare; public int read(char[] cbuf) throws IOException - citeşte din flux o secvenţă de caractere şi le depune într-o zonă tampon (buffer) constituită din tabloul de caractere cbuf; întoarce numărul de caractere citite sau -1 dacă s-a atins sfârşitul de fişier; metoda produce blocarea procesului până când apar caractere în fluxul de intrare, sau se ajunge la sfârşit de fişier;
public int read() throws IOException -

310

Programarea orientata pe obiecte în limbajul Java
public abstract int read(char[] cbuf, int off, int len) throws IOException - acţionează asemănător cu metoda precedentă, dar depunerea caracterelor

citite în zona tampon de destinaţie se face începând de la poziţia off (offset), iar numărul maxim de caractere citite este len; public long skip(long n) throws IOException - se sare peste n caractere din fluxul de intrare, care nu vor fi citite; procesul apelant este blocat pană când apare cel puţin un caracter în fluxul de intrare; dacă se întâlneşte sfârşitul de fişier, se generează o excepţie de intrare/ieşire; întoarce numărul de caractere sărite efectiv; public boolean ready() - întoarce true dacă fluxul de intrare este gata pentru a putea fi citit; public void mark(int readAheadLimit) throws IOException - marchează poziţia curentă în fluxul de intrare, pentru a se putea reveni la ea ulterior; argumentul readAheadLimit indică numărul de caractere care vor putea fi ulterior citite din flux,fără ca acest marcaj să se piardă; excepţia de intrare/ieşire apare dacă fluxul nu suportă marcarea sau dacă se produce altă eroare de intrare/ieşire; public boolean markSupported() - indică dacă fluxul suportă marcarea; public void reset() throws IOException - dacă fluxul a fost marcat, este readus la poziţia corespunzătoare ultimului marcaj; dacă fluxul nu a fost marcat sau nu suportă resetarea, se generează o excepţie de intrare/ieşire; public abstract void close() throws IOException - închide fluxul; din acest moment, invocarea metodelor read(), ready(), mark() sau reset pentru acest flux va genera o excepţie de intrare/ieşire.

Clasa Writer
Clasa abstractă java.io.Writer este rădăcina ierarhiei de clase pentru fluxuri de ieşire de caractere. Metode:
public void write(int c) throws IOException -

scrie în fluxul de ieşire scrie în fluxul de ieşire

caracterul c;
public void write(char[] cbuf) throws IOException -

caracterele conţinute în tabloul cbuf;
public abstract void(char[] cbuf, int off, int len) throws IOException

scrie în fluxul de ieşire len caractere din tabloul cbuf, începând de la poziţia off (offset); public void write(String str) throws IOException - scrie în flux caracterele existente în şirul str;
public void write(String str, int off, int len) throws IOException -

scrie în flux len caractere din şirul str, începând de la poziţia off (offset); public abstract void flush() throws IOException - "descarcă" fluxul de ieşire; dacă fluxul a salvat într-o zonă tampon anumite caractere scrise cu metodele write(), aceste caractere sunt scrise efectiv în fluxul de destinaţie; dacă această destinaţie este tot un flux, invocă şi metoda flush() a acestuia, astfel că se "descarcă" întregul lanţ de fluxuri; public abstract void close() throws IOException - se închide fluxul de ieşire; invocarea ulterioară a metodelor write() sau flush() pentru acest flux va produce o excepţie de intrare/ieşire.

311

Severin Bumbaru

Clasa InputStream
Clasa java.io.InputStream este rădăcina ierarhiei de clase pentru fluxuri de intrare organizate pe octeţi. Metode: citeşte din fluxul de intrare un singur octet; întoarce octetul citit (in domeniul 0 .. 255) sau -1 dacă s-a ajuns la sfârşit de fişier; metoda produce blocarea procesului în care este invocată, până când apare un octet în fluxul de intrare; public int read(byte[] buf) throws IOException - citeşte din flux o secvenţă de octeţi şi îi depune într-o zonă tampon (buffer) constituită din tabloul de octeţi buf; întoarce numărul de octeţi citiţi sau -1 dacă s-a atins sfârşitul de fişier; metoda produce blocarea procesului până când apar octeţi în fluxul de intrare, sau se ajunge la sfârşit de fişier;
public int read() throws IOException public abstract int read(byte[] buf, int off, int len) throws IOException - acţionează asemănător cu metoda precedentă, dar depunerea octeţilor citiţi în tabloul de destinaţie byte[] se face începând de la poziţia off (offset), iar numărul maxim

de octeţi citiţi este len;
public long skip(long n) throws IOException - se sare peste n octeţi din fluxul de intrare, care nu vor fi citiţi; procesul apelant este blocat până când apare cel puţin un octet în fluxul de intrare; dacă se întâlneşte sfârşitul de fişier se generează o eroare de intrare/ieşire; întoarce numărul de octeţi săriţi efectiv; public int available() throws IOException - întoarce numărul de octeţi disponibili pentru citire în fluxul de intrare; public void mark(int readAheadLimit) throws IOException - marchează poziţia curenta în fluxul de intrare, pentru a se putea reveni la ea ulterior; argumentul readAheadLimit indică numărul de octeţi care vor putea fi ulterior citiţi din flux, fără ca acest marcaj să se piardă; excepţia de intrare/ieşire apare dacă fluxul nu suportă marcarea sau dacă se produce altă eroare de intrare/ieşire; public boolean markSupported() - indică dacă fluxul suportă marcarea; public void reset() throws IOException - dacă fluxul a fost marcat, este readus la poziţia corespunzătoare ultimului marcaj; dacă fluxul nu a fost marcat sau nu suporta resetarea, se generează o excepţie de intrare/ieşire; public abstract void close() throws IOException - închide fluxul; din acest moment, invocarea metodelor read(), ready(), mark() sau reset() pentru acest flux va genera o excepţie de intrare/ieşire.

Clasa OutputStream
Clasa java.io.OutputStream este rădăcina ierarhiei de clase pentru fluxuri de iesire de octeţi. Metode:
public void write(int c) throws IOException -

scrie în fluxul de ieşire ultimul scrie în fluxul de ieşire

octet al numărului c;
public void write(byte[] buf) throws IOException -

octeţii conţinuti în tabloul buf;
public abstract void(byte[] buf, int off, int len) throws IOException -

scrie în fluxul de ieşire len octeţi din tabloul buf, începând de la poziţia off (offset); public abstract void flush() throws IOException - "descarcă" fluxul de

312

Programarea orientata pe obiecte în limbajul Java ieşire; dacă fluxul a salvat într-o zonă tampon anumiţi octeţi scrişi cu metodele write(), aceşti octeţi sunt scrişi efectiv în fluxul de destinaţie; dacă această destinaţie este tot un flux, invocă şi metoda flush() a acestuia, astfel că se "descarcă" întregul lanţ de fluxuri; public abstract void close() throws IOException - se închide fluxul de ieşire; invocarea ulterioară a metodelor write() sau flush() pentru acest flux va produce o excepţie de intrare/ieşire.

Consideraţii privind utilizarea fluxurilor
Se remarcă cu uşurinţă faptul că metodele claselor Reader şi InputStream sunt similare atât ca seigatură (şi deci ca mod de invocare), cât şi ca funcţionalitate. Metodele read(), skip(), mark(), markSupported(), reset() şi close() există în ambele clase. Deosebirea este ca în clasa Reader metoda read() citeşte din fluxul de intrare un caracter, iar în clasa InputStream citeşte un octet. Metodele read() pentru citirea de secvenţe de caractere, respectiv de octeţi, se deosebesc numai prin aceea ca în clasa Reader se folosesc ca destinaţie tablouri de caractere, iar în clasa InputStream se folosesc tablouri de octeţi. Metodele claselor Writer şi OutputStream sunt, de asemenea, similare ca signatură şi funcţionalitate. Metodele write(), flush() şi close() există în ambele clase, însă în clasa Writer metoda write() scrie în fluxul de ieşire un caracter, iar în clasa OutputStream scrie un octet. În cazul că în metodele write() se scrie în flux conţinutul unui tablou, în clasa Writer acesta este un tablou de caractere, iar in clasa OutputStream este un tablou de octeţi. Remarcăm că ierarhiile de clase Reader şi Writer, introduse începând cu JDK 1.1, nu înlocuiesc pe cele anterioare, având ca rădăcini clasele InputStream şi OutputStream, ci le completează. Se vor putea deci folosi, după necesităţi, atât clasele de fluxuri de caractere, cât şi cele de fluxuri de octeţi.

Clasele PrintStream şi PrintWriter
Clasele java.io.PrintStream şi java.io.PrintWriter se folosesc pentru a transmite către un flux se ieşire date formatate pentru tipărire (afişare). Se ştie că forma internă a datelor diferă de forma externă. De exemplu, numerele întregi sunt reprezentate intern sub formă binară, în timp ce pe ecranul calculatorului sau la imprimantă apar sub forma unor şiruri de cifre zecimale, precedate eventual de semn. Metodele claselor PrintStream şi PrintWriter fac această conversie din forma internă în cea externă a diferitelor tipuri de date, generând reprezentările datelor respective sub forma de şiruri de octeţi (caractere în codul ASCII) sau, respectiv, de caractere Unicode. Aceste clase nu se folosesc în mod independent, ci adaugă altui flux de ieşire (de octeţi sau, respectiv, de caractere) capacitatea de formatare a datelor în vederea tipăririi.

Clasa PrintStream
Clasa PrintStream conţine doua feluri de metode de scriere a datelor: metodele cu numele write() scriu întotdeauna octeţi (fără formatare), în timp ce cele cu numele print() sau println() formatează datele, respectând convenţia de codificare (pe octeti sau pe caractere) specifică platformei pe care ruleaza aplicaţia respectivă. Totuşi, se recomandă ca pentru a obţine fluxuri de caractere să se folosească clasa PrintWriter. Deosebirea dintre print() şi println() este că metodele cu numele println() adaugă, la sfârşitul şirului de octeţi generat, un caracter de sfârşit de linie ('\n'). În consecinţă, dacă se 313

Severin Bumbaru foloseşte metoda print(), afişarea se face fără a se trece la o linie nouă, în timp ce dacă se foloseşte metoda println(), după afişare se trece la linie nouă. Dacă fluxul este cu descărcare automată, metoda println() provoacă, de asemenea, descărcarea acestuia (flush). Constructori:
public PrintStream(OutputStream out) - creează un nou flux de formatare pentru tipărire, conectat la fluxul de ieşire out; acest flux nu se descarcă automat; public PrintStream(OutputStream out, boolean autoFlush) - creeaza un nou flux de formatare a datelor pentru tipărire, conectat la fluxul de ieşire out; al doilea argument indică dacă fluxul se descarcă automat atunci când se întâlneste în flux caracterul '\n' (linie nouă) sau se execută metoda println() sau se tipăreşte un tablou de octeţi.

Metode:
public void flush() -

descarcă fluxul (goleşte zonele tampon, transmiţând de ieşire un singur octet (ultimul octet al

conţinutul lor la ieşire);
public void close() - închide fluxul; public void write(int b) - scrie în fluxul

argumentului); se scriu în fluxul de ieşire len octeţi din tabloul de octeti buf (buffer), începând de la poziţia off (offset); public void print(boolean b) - transmite în fluxul de ieşire forma externă a valorii variabilei booleene b, respectiv cuvântul true sau false; public void print(char c) - se afişează caracterul c (pe unul sau doi octeţi, depinzând de platformă); public void print(int i) - se afişează numărul întreg i; public void print(long l) - se afişează numărul întreg lung l; public void print(float f) - se afişează numărul real în simplă precizie f; public void print(double d) - se afişează numărul real în dublă precizie d; public void print(char[] s) - se afişează conţinutul tabloului de caractere s; public void print(String s) - se afişează şirul de caractere s; public void print(Object obj) - se afişeaza obiectul obj convertit în şir de caractere prin aplicarea metodei String.valueOf(obj) care, la rândul ei, apeleaza metoda obj.toString() din clasa căreia îi aparţine obiectul; public void print() - introduce în fluxul de ieşire codul caracterului '\n' (linie nouă); public void println(boolean b) - transmit în fluxul de ieşire forma externă a valorii variabilei booleene b, respectiv cuvântul true sau false; public void println(char c) - se afişează caracterul c (pe unul sau doi octeţi, depinzând de platformă); public void println(int i) - se afişează numărul întreg i; public void println(long l) - se afişează numărul întreg lung l; public void println(float f) - se afişează numărul real în simplă precizie f; public void println(double d) - se afişează numărul real în dublă precizie d; public void println(char[] s) - se afişează conţinutul tabloului de caractere s; public void println(String s) - se afişează şirul de caractere s; public void println(Object obj) - se afişează obiectul obj convertit în şir de caractere prin aplicarea metodei String.valueOf(obj);
public void write(byte[] buf, int off, int len) -

314

Programarea orientata pe obiecte în limbajul Java

Clasa PrintWriter
Constructori: - creează un nou flux de formatare pentru afişare, fără descărcare automată, conectându-l la fluxul de ieşire pe caractere out; public PrintWriter(Writer out, boolean autoFlush) - la fel ca şi constructorul precedent, dar al doilea argument specifică dacă are loc descărcarea automată a fluxului; public PrintWriter(OutputStream out) - creează un nou flux de formatare pe caractere, fără descărcare automată, conectându-l la fluxul de ieşire pe octeţi out; el creează şi un OutputStreamWriter intermediar, care face conversia caracterelor pe unul sau doi octeţi, dependent de platformă; public PrintWriter(OutputStream out, boolean autoFlush) - la fel ca şi constructorul precedent, dar al doilea argument indică dacă se face descărcare automată a fluxului.
public PrintWriter(Writer out)

Metode: Această clasa implementează aceleaşi metode ca şi clasa PrintStream, cu excepţia celor care scriu octeţi bruti (write()).

Fişiere
În memoria externă a calculatorului, datele se păstrează sub forma de fişiere. Fişierul (engleză: File) este o colecţie de înregistrări situată, de regulă, pe un suport extern şi identificată printr-un nume. Fiecare înregistrare (engleză: Record) este o grupare de informaţii sau de date care poate fi tratată în mod unitar. Fişierele pot fi clasificate după diferite criterii. După formatul înregistrărilor, distingem fişiere de text şi fişiere de date. În fişierele de text, fiecare înregistrare este o linie de text, adică un şir de caractere care se termină cu un marcaj de trecere la linie nouă. Acest marcaj depinde de platformă. De exemplu, în fişierele MS-DOS marcajul de sfârşit de linie este format din secvenţa de caractere "\r\n", adică din caracterul de întoarcere la cap de linie (Carriage Return, '\r' ) şi caracterul de linie nouă (New Line, '\n'), care au codurile ASCII 0x0D şi respectiv 0x0A. Pe platforme Unix, marcajul de sfârşit de linie este constituit numai din caracterul '\n' (New Line, 0x0A). Liniile textului pot avea lungimi diferite. Codificarea caracterelor în fişier depinde, de asemenea, de platformă. În prezent, pentru fişierele de text se foloseşte cel mai frecvent codul ASCII, dar pot exista şi platforme pe care se foloseşte Unicode sau o altă convenţie de codificare. În fişierele de date, înregistrările au, de regulă, lungime predefinită, iar fiecare înregistrare este constituită din mai multe câmpuri de date. În principiu, toate înregistrările unui fişier de date au acelaşi format. Prin formatul înregistrării se înţelege descrierea structurii acesteia, care constă din specificarea câmpurilor de date pe care le conţine, a lungimii fiecărui câmp, a tipului de date conţinut în fiecare câmp şi a modului de reprezentare a datelor. Datele din câmpuri pot fi reprezentate fie sub forma lor externă, fie sub formă binară, iar formatul înregistrărilor diferă de la un fişier la altul.

315

Severin Bumbaru După modul de exploatare, fişierele pot fi de intrare, de ieşire sau de intrare/ieşire (de manevră). În cazul fişierelor de intrare, din momentul deschiderii fişierului şi până în momentul închiderii acestuia se pot efectua numai operaţii de citire. În cazul fişierelor de ieşire, între momentele de deschidere şi de închidere a fişierului respectiv se pot face numai operaţii de scrieire. Desigur însă că, după ce s-a încheiat scrierea într-un anumit fişier de ieşire şi acesta a fost închis, el poate fi deschis ca fişier de intrare. Acelaşi fişier poate fi citit de mai multe ori. Fişierele de intrare/ieşire permit ca, după ce au fost deschise, să se efectueze atât operaţii de scriere, cât şi de citire. După modul de acces fişierele pot fi cu acces secvenţial sau cu acces direct. Fişierele cu acces secvenţial se caracterizează prin faptul că înregistrările lor pot fi parcurse într-un singur sens, în ordinea în care acestea sunt plasate în fişier. În cazul fişierelor cu acces direct, numite şi fişiere cu acces aleator (engleză: random access file) ordinea de parcurgere a înregistrărilor din fişier este arbitrară, în sensul că la fiecare operaţie de intrare/ieşire făcută asupra fisierului respectiv se poate indica adresa sau numărul de ordine al înregistrării care va fi citită sau scrisă. În limbajul Java, fişierul este privit ca sursa sau destinaţia unui flux. În cazul citirii din fişier, datele se transmit de la acesta către memoria internă sub forma unui flux de intrare. În cazul operaţiei de scriere, datele se transmit de la memoria internă la fişier sub forma unui flux de ieşire. În principiu, comunicarea între memoria internă şi un fişier de text se face sub forma unui flux de caractere. Totuşi, pe platformele pe care reprezentarea caracterelor se face pe un octet (de exemplu în cod ASCII), acesta poate fi tratat şi ca un flux de octeţi. În cazul fişierelor de date, comunicarea dintre memoria internă şi fişier se poate face prin fluxuri de caractere numai dacă datele din fişier sunt reprezentate exclusiv în format extern (deci sub forma de şiruri de caractere). Dacă însă există şi câmpuri de date în format binar, legatura dintre memoria internă şi fişierul de date se face, de regula, prin fluxuri de octeţi. Operaţiile cu fişierul se fac în ordinea următoare: 1/ se deschide fişierul, în care scop trebuie să se comunice sistemului de operare numele fişierului, locul în care se găseşte (de exemplu unitatea de disc şi calea către directorul care îl conţine) şi modul în care va fi utilizat (pentru citire, pentru scriere sau în ambele moduri); 2/ se exploatează fişierul, efectuând o succesiune de operaţii de citire/scriere; 3/ se închide fişierul. În limbajul Java, se consideră că, în operaţiile de intrare/ieşire, între fişier şi memorie se transmit numai fluxuri de caractere sau de octeţi, fară să se ia în consideraţie informaţia pe care o conţin aceste fluxuri. Întreaga responsabilitate privind interpretareaacestor fluxuri revine programelor care le prelucrează.

Clasa File
Instanţele clasei java.io.File conţin informaţii privind numele fişierului şi calea pe care se găseste acesta (engleza: Path). Clasa File oferă, de asemenea, metode prin care se pot face unele operaţii legate de prezenţa fişierului respectiv: se poate afla dacă fişierul există, dacă el poate fi citit sau scris, se poate crea un fişier nou, se poate şterge un fişier existent etc.

316

Programarea orientata pe obiecte în limbajul Java Calea indică modul în care poate fi localizat fişierul de pe disc. Calea poate fi absolută sau relativă. Calea absolută constă în indicarea unităţii de disc şi a succesiunii de directoare prin care se ajunge de la rădacină la fişierul care ne interesează. Calea relativă, arată cum se poate ajunge de la directorul curent la fişierul căutat. Se ştie că, pe diferite platforme, calea se reprezintă în moduri diferite. De exemplu, în sistemul Unix, calea relativa "../../alpha/beta/fisier1.txt" indică faptul că, pornind de la directorul curent, se face o deplasare către rădăcină cu doua directoare, după care se merge înainte (către frunzele arborelui director) trecând la subdirectorul alpha şi de aici la subdirectorul beta, în care se găseşte fişierul căutat cu numele fişier1.txt. Remarcăm că separarea directoarelor se face prin caracterul '/' (slash). Pe platformele firmei Microsoft (MS-DOS, Windows), ca separator se foloseşte caracterul '\' (backslash), care în limbajele C şi Java se reprezintă sub forma '\\'. În consecinţă, aceeaşi cale relativă se va scrie pe o astfel de platforma sub forma "..\\..\\alpha\\beta\\fisier1.txt". În clasa File, reprezentarea căii se face sub o formă independentă de platformă. În acest scop, în instanţele clasei File, calea se păstrează sub forma unui tablou de şiruri de caractere, în care se memoreaza numele fiecărui director conţinut în cale. Separatorul se păstrează într-un câmp separat şi este setat automat în funcţie de sistemul de operare al calculatorului pe care se execută programul. În acest fel, portabilitatea programului creşte, întrucât nu trebuie modificate căile fişierelor folosite în program atunci când se trece de pe o platformă pe alta. ATENŢIE: pentru asigurarea portabilităţii programelor sursă, este recomandabil ca, în căile date ca argumente ale metodelor clasei File, să se folosească numai varianta Unix de separatori. Compilatorul javac de pe platformele Microsoft înlocuieşte automat separatorul '/' prin ' \\' in timp ce pe platformele Unix (Linux) nu se face înlocuirea inversă. Remarcăm că numele clasei File poate să ne inducă în eroare, lăsându-ne să credem că instanţele acestei clase sunt fişiere. În realitate, instanţele ei conţin căile şi numele fişierelor.

317

Severin Bumbaru

Clasa File prezentată în detaliu
Câmpuri: conţine caracterul prin care se separă directoarele dintr-o cale; acest carecter este dependent de platforma, fiind '/' (slash) pe platforme Unix şi '\' (backslash) pe platforme Microsoft; public static final String separator - conţine caracterul de separare (separatorChar) sub forma de String; public static final char pathSeparatorChar - conţine caracterul de separare a căilor; acest caracter este dependent de platformă, fiind ':' (două puncte) sub Unix şi ';' (punct şi virgulă) pe platforme Microsoft; public static final String pathSeparator - caracterul de separare a căilor (pathSeparatorChar) sub formă de String. NOTĂ: aceste câmpuri se completează automat, în funcţie de platforma pe care rulează programul.
public static final char separatorChar -

Constructori:
public File(String pathname) -

creează o instanţă a clasei File, care conţine calea

dată ca argument;
public File(String parent, String child) - creează o instanţă a clasei File, în care calea este compusă dintr-o cale "părinte" (formată numai din directoare) şi o cale "copil" (care poate fi director sau fişier); public File(File parent, String child) - se deosebeşte de constructorul precedent numai prin faptul că "părintele" este o instanţă a clasei File.

Metode: Specificăm aici principalele metode ale clasei File. public String getName() - întoarce numele fişierului (ultimul nume din cale); dacă această cale este vidă, întoarce un şir vid; public String getParent() - întoarce calea părinte; public File getParentFile() - întoarce calea părinte sub forma de instanţă a clasei File; public String getPath() - întoarce calea conţinută în această instanţă; calea va conţine separatorii impliciţi de pe platforma curentă; public boolean isAbsolute() - intoarce true dacă această cale este absolută (dacă este prefixată cu '/' pe platformele Unix sau cu '\\' pe platformele Microsoft); public String getAbsolutePath() - întoarce calea absolută corespunzătoare căii conţinute în această instanţă; public File getAbsoluteFile() - întoarce o instanţă a clasei File care conţine calea absolută corespunzatoare celei din instanţa curentă; public URL toURL() - întoarce un URL (Uniform Resource Locator) corespunzător căii din această instanţă; forma acestui URL este dependentă de sistem (Nota: URL-ul se foloseşte pentru a localiza un fişier cu ajutorul unui browser de Web); public boolean canRead() - testează dacă aplicaţia poate citi fişierul indicat de această instanţă a clasei File; public boolean canWrite() - testează dacă aplicaţia poate scrie în fişierul indicat; public boolean exists() - testează dacă fişierul indicat există; public boolean isDirectory() - testează dacă ultimul nume din această cale este al unui director;

318

Programarea orientata pe obiecte în limbajul Java
public boolean isFile() -

testează dacă ultimul nume din această cale este al unui

fişier; timpul la care s-a făcut ultima modificare în fişierul indicat de această cale (exprimat în milisecunde de la 1 ianuarie 1970 ora 00:00:00 GMT); public long length() - întoarce lungimea fişierului indicat de această instanţă (în octeţi); public boolean createNewFile() throws IOException - dacă fişierul indicat în această cale nu există, creează un fişier nou vid; public boolean delete() - şterge fişierul; întoarce truedacă ştergerea a avut loc; public void deleteOnExit() - cere ca fişierul să fie şters când se încheie funcţionarea maşinii virtuale Java (are efect numai dacă funcţionarea se încheie normal); public String[] list() - întoarce un tablou de şiruri, care conţine numele de directoare şi de fişier din această cale; public File[] listFile() - dacă această cale nu se încheie cu un nume de director, întoarce null; altfel întoarce un tablou de instanţe ale clasei File, câte una pentru fiecare nume de fişier din directorul indicat de această cale.
public long lastModified() - întoarce

Exemplu: În fişierul TestFile.java este dat un exemplu de aplicaţie, în care se testează utilizarea metodelor clasei File. Metodele se aplică pentru un fişier situat în directorul curent, pentru un director şi pentru un fişier situat în alt director.

Citirea fişierelor
Citirea fluxurilor de octeţi: clasa FileInputStream
Clasa java.io.FileInputStream permite citirea datelor din fişiere sub forma de fluxuri de octeţi. Orice instanţă a acestei clase este un flux de intrare, care are ca sursă un fişier. La crearea acestei instanţe se caută şi se deschide fişierul indicat ca argument al constructorului. Dacă fişierul nu există, sau nu poate fi deschis pentru citire, se generează o excepţie.

319

Severin Bumbaru

Constructorii şi metodele clasei FileInputStream
Metodele acestei clase permit să se citească din fişierul de intrare octeţi sau secvenţe de octeţi, fără a le da nici o interpretare. Constructori:
public FileInputStream(String name) throws FileNotFoundException - se creează un flux de intrare de octeţi având ca sursă fişierul name; (name este numele fişierului; dacă acesta nu se află în directorul curent, atunci şirul name conţine şi calea); public FileInputStream(File file) throws FileNotFoundException - se creează un flux de intrare de octeţi având ca sursă fişierul indicat de instanţa file a clasei File; public FileInputStream(FileDescriptor fdObj) - se creează un nou flux de intrare de octeţi, care este conectat la fluxul de intrare fdObj deja existent.

Metode:
public int read() throws IOException - citeşte din fişier un singur octet; dacă octetul nu este înca disponibil în fluxul de intrare, programul intră în aşteptare; public int read(byte[] b) throws IOException - se citesc din fişier cel mult b.length octeţi, care se pun în tabloul b; întoarce numărul de octeţi citit efectiv, sau zero dacă s-a intâlnit sfârşitul de fisier fără să se citească nici un octet; dacă în fluxul de intrare nu mai sunt octeţi de citit, dar nu s-a întâlnit sfârşitul de fişier, programul intră în aşteptare; public int read(byte[] b, int off, int len) throws IOException - se citesc din fluxul de intrare cel mult len octeţi, care se pun în tabloul b începând de la poziţia off (offset); întoarce numărul de octeţi citit efectiv; dacă s-a întâlnit sfârşitul de fişier fără să se citească nici un octet, intoarce -1; dacă nu s-a întâlnit sfârşitul de fişier şi nu sunt octeţi disponibili pentru citire în fluxul de intrare, programul intră în aşteptare; public long skip(long n) throws IOException - se sare peste n octeţi din fişierul de intrare; se întoarce numărul de octeţi săriţi efectiv; public int available() throws IOException - întoarce numărul de octeţi disponibili pentru citire din fluxul de intrare; public void close() throws IOException - închide fişierul; public final FileDescriptor getFD() throws IOException - întoarce un descriptor al acestui fişier.

NOTA: descriptorii de fişiere sunt obiecte care aparţin clasei java.io.FileDescriptor şi pot fi folosiţi ca argument al constructorului pentru a crea un nou flux conectat la un fişier deja deschis. Clasa FileDescriptor conţine, de asemenea, câmpurile statice in, out şi err, care sunt descriptori ai fluxurilor de intrare/ieşire standard. În consecinţă, dacă se foloseşte ca argument al constructorului clasei FileInputStream obiectul FileDescriptor.in, se creaza un flux care citeşte datele de la tastatura. Exemplu: În fişierul TestFileInput.java este un exemplu de aplicaţie, în care se testeaza metodele clasei FileInputStream. În aplicaţie se deschide fişierul f1, prin care se citesc octeţi dintr-un fişier de text, al cărui nume se dă ca parametru în linia de comandă, după care octeţii respectivi se afişează. Întrucât se ştie că f1 este un fişier de text, octeţii sunt afişati convertiţi în caractere. Se deschide apoi un al doilea flux, f2, care este echivalent cu f1, deoarece au acelaşi descriptor. În consecinţă, în continuare fluxurile f1 şi f2 citesc date din acelaşi fişier. În final, se creează un nou flux (cu referinţa f1), care citeşte datele de la tastatura, deoarece la creare s-

320

Programarea orientata pe obiecte în limbajul Java a folosit descriptorul FileDescriptor.in. Acesta este câmpul in din clasa java.io.FileDescriptor şi este o referinţă la fluxul de intrare standard, deci tratează fluxul de la tastatură ca pe un fişier folosit pentru citire.

Citirea din fişiere de caractere: Clasa FileReader
Citirea unui fişier de text se poate face nu numai folosind o instanţă a clasei FileInputStream, ci şi o instanţă a clasei FileReader. Deosebirea este că această ultimă clasă creează un flux de caractere, în loc de un flux de octeţi. Chiar dacă fişierul nu este codificat în Unicode, ci în alt cod de caractere (de cele mai multe ori ASCII), se face automat conversia în Unicode. Clasa java.io.FileReader este derivată din clasa java.io.InputStreamReader şi foloseşte metodele acesteia. Constructori:
public FileReader(String fileName) throws FileNotFoundException -

deschide ca flux de caractere de intrare fişierul cu numele fileName (eventual acest şir cuprinde şi calea); public FileReader(File file) - throws FileNotFoundException - deschide ca flux de intrare de caractere fişierul cu calea file; public FileReader(FileDescriptor fd) - deschide un nou flux de intrare de caractere şi îl conectează la fluxul deja existent, al cărui descriptor este fd. Metode: Clasa FileReader nu are metode proprii, dar moşteneşte următoarele metode ale clasei InputStreamReader:
public int read() throws IOException -

citeşte din fluxul de intrare un singur

caracter şi-l întoarce convertit în int;
public int read(char[] buf, int off, int len) throws IOException -

citeşte din fluxul de intrare cel mult len caractere, pe care le pune în tabloul buf începând de la poziţia off (offset); public boolean ready() throws IOException - verifică dacă fluxul de intrare este gata pentru citire (dacă conţine caractere care pot fi citite); public void close() throws IOException - închide fluxul de intrare; public String getEncoding() - întoarce numele canonic al codului folosit pentru caractere. Exemplu: În fişierul TestFileReader.java este dat un exemplu de aplicaţie în care se testează metodele clasei FileReader. Fişierul care se citeşte poate fi acelaşi ca şi în cazul citirii cu un flux de octeţi (din clasa FileInputStream). În această aplicaţie, citirea fişierului se face de trei ori, folosind diverse metode.

321

Severin Bumbaru

Scrierea în fişiere
Clasa FileOutputStream
Fiecare instanţă a clasei java.io.FileOutputStream este un flux de octeţi de ieşire conectat la un fişier, în care se sriu octeţii primiţi din flux. Fluxul se poate conecta şi la un flux de octeti de ieşire deja existent.

Constructorii şi metodele clasei FileOutputStream
Constructori:
public FileOutputStream(String name) throws FileNotFoundException -

Deschide pentru scriere fişierul cu numele (şi, eventual, calea) name şi creează un flux de octeţi către acest fişier; dacă fişierul nu există, se va crea pe disc un fişier nou, cu numele dat ca argument; excepţia se generează dacă fişierul nu există şi nici nu poate fi creat; scrierea în fişier se va face de la începutul acestuia ("peste" ceeace, eventual, exista deja scris);
public FileOutputStream(String name, boolean append) throws FileNotFoundException - acţionează la fel ca în cazul constructorului precedent,

dar dacă al doilea parametru este true, scrierea se va face în coada fişierului deja existent (scrierea începe după ultima înregistrare deja existentă); public FileOutputStream(File file) throws IOException - se deschide pentru scriere fişierul indicat de calea file şi se creează un flux de octeţi către acesta; dacă fişierul nu există, se creează unul nou; scrierea se face de la începutul fişierului; public FileOutputStream(FileDescriptor fd) - creeaza un nou flux de octeţi de ieşire, care se conectează la fluxul deja existent cu descriptorul fd. Metode:
public void write(int b) throws IOException -

scrie în fişier un singur octet, scrie în fişier toti octeţii scrie

conţinut în argumentul b;
public void write(byte[] b) throws IOException -

conţinuti în tabloul b;
public void write(byte[] b, int off, int len) throws IOException -

în fişier len octeţi din tabloul b începând de la poziţia off (offset); public void close() throws IOException - închide fişierul;
public final FileDescriptor getFD() throws IOException - întoarce un

descriptor al acestui fişier;

Exemplu: În fişierul TestFileOutput.java se dă un exemplu de aplicaţie, în care se testează metodele clasei FileOutputStream. Se deschide un fişier cu numele "ProbaScriere.txt" (dacă nu există, se creează unul nou). În fişier se scrie textul dat în linia de comandă. În acest scop, lansarea în execuţie se face sub forma java TestFileOutput text_de_scris_în_fişier După ce s-a scris textul, fişierul este închis, apoi este redeschis pentru citire şi conţinutul este afişat pe ecran. Se închide şi se redeschide iarăşi, dar de data aceasta pentru scriere "în coada" conţinutului existent. Se scriu cuvintele "Text adăugat", după care se închide, se redeschide pentru citire şi se afişează. Metoda de scriere în fişier folosită de fiecare dată este cea în care

322

Programarea orientata pe obiecte în limbajul Java se scrie un tablou de octeţi. Pentru afişare pe ecran, a doua oară s-a folosit tot un flux creat ca instanţă a clasei FileOutputStream, dar care a fost conectat la fluxul standard de iesire java.ioFileDescriptor.out, fiind astfel tratat acest flux ca un fişier.

Clasa FileWriter
Scrierea într-un fişier de text se poate face, de asemenea, folosind clasa FileWriter. Instanţele acestei clase sunt fluxuri de ieşire de caractere, prin care se face scrierea într-un fişier. Clasa FileWriter este derivată din java.io.OutputStreamWriter şi foloseşte metodele acesteia. Constructori:
public FileWriter(String fileName) throws IOException - deschide fişierul fileName pentru scriere şi creează un flux de caractere de ieşire conectat la acest fişier; dacă fişierul nu există, îl creează; public FileWriter(String fileName, boolean append) throws IOException

la fel ca în cazul constructorului precedent, dar, dacă al doilea parametru este true, scrierea în fişier se va face în coada celui deja existent; public FileWriter(File file) throws IOException - deschide pentru scriere fişierul indicat prin calea file şi creează un flux de caractere de ieşire conectat la acest fişier; public FileWriter(FileDescriptor fd) - creează un flux de caractere de ieşire şi îl conectează la fluxul deja existent, cu descriptorul fd;
-

Metode: Metodele clasei FileWriter sunt moştenite de la clasa OutputStreamWriter.
public void write(int c) throws IOException -

scrie în fişier un singur

caracter;
public void write(char[] cbuf, int off, int len) throws IOException -

scrie în fişierul de ieşire len caractere din tabloul de caractere cbuf, începând de la poziţia off (offset);
public void write(String str, int off, int len) throws IOException -

scrie în fişier len caractere din şirul str începând de la poziţia off; public void flush() throws IOException - goleşte conţinutul zonei tampon, descărcându-l în fişierul de ieşire; public void close() throws IOException - închide fişierul de ieşire; public String getEncoding() - întoarce numele canonic al codului de caractere utilizat de acest flux. Exemplu: În fişierul TestFileWriter.java este dat un exemplu similar cu cel din fişierul TestFileOutput.java, în care în locul claselor FileInputStream şi FileOutputStream s-au folosit clasele FileReader şi, respectiv, FileWriter.

323

Severin Bumbaru

Fişiere cu acces direct
Fişierele cu acces direct, numite şi fisiere cu acces aleator (engl.: Random Access File), sunt fişiere la care programatorul poate indica prin program locul (adresa) din fişier de la care începe operaţia de citire sau de scriere. De regulă, astfel de fişiere pot fi utilizate atât pentru citire, cât şi pentru scriere. Pentru ca înregistrările sale să poată fi parcurse într-o ordine arbitrară, fişierul cu acces direct trebuie să fie stocat pe un suport adresabil, cum ar fi discul magnetic, discul optic, memoria RAM sau ROM. În JDK, fişierele cu acces direct sunt instanţe ale clasei RandomAccessFile.

Clasa RandomAccessFile
Clasa java.io.RandomAccessFile este derivată direct din clasa Object, deci nu face parte din niciuna din cele patru ierarhii de fluxuri de intrare/ieşire prezentate anterior, deşi face parte, ca şi acestea, din pachetul java.io. Fişierul cu acces direct este privit aici ca un tablou de octeţi memorat într-un sistem de fişiere (de regulă în memoria externă). Există un cursor al fişierului (numit în engleză File Pointer), care se comportă ca un indice al acestui tablou. Valoarea acestui indice (poziţia cursorului de fişier relativ la începutul acestuia) poate fi "citită" cu metoda getFilePointer() şi poate fi modificată cu metoda seek(). Orice citire sau scriere se face începând de la poziţia pe care se găseşte acest cursor. La sfârşitul operaţiei, cursorul se deplasează pe o distanţă corespunzatoare cu numarul de octeţi care au fost citiţi sau scrişi efectiv. Fişierul cu acces direct poate fi deschis în modul read (numai pentru citire), write (numai pentru scriere) sau read/write. În ultimul caz, este posibil să se efectueze atât citiri , cât şi scrieri.

Scrierea în fişierele cu acces direct se poate face atât în mod text (succesiunea de octeţi fiind privită ca o succesiune de caractere), cât şi în mod binar, la fel ca în fişierele de date, în care scrierea s-a făcut folosind un DataOutputStream. În consecinţă, clasa RandomAccessFile oferă atât metode de scriere şi de citire de octeţi, cât şi metode de scriere şi de citire pentru toate tipurile de date primitive şi pentru şiruri de caractere, la fel ca în cazul claselor DataOutputStream şi DataInputStream. Este evident că, la citirea din fişier, trebuie să se respecte modul în care acesta a fost scris. Dacă la citire se întâlneşte sfârşitul de fişier, se generează o excepţie de sfârşit de fişier din clasa EOFException (End of File Exception), care este derivată din IOException. În toate celelalte cazuri când nu se poate efectua o operaţie de citire sau de scriere se genereaza o IOException (Input-Output Exception).

Constructori
public RandomAccessFile(String file, String mode) throws FileNotFoundException - deschide fişierul cu acces direct cu numele file în

modul de exploatare mode. Modul poate fi "r" (numai citire) sau "rw" (citire si scriere); dacă este "rw"

324

Programarea orientata pe obiecte în limbajul Java şi fişierul nu există, se creează un fişier nou;
public RandomAccessFile(File file, String mode) throws IOException -

acţionează asemănător cu constructorul precedent, dar calea şi numele fişierului care se deschide sunt date de instanţa file a clasei File. Metode
public final FileDescriptor getFD() throws IOException - întoarce

descriptorul de fişier; citeşte din fişier un singur octet (sau întoarce -1 dacă s-a atins sfârşitul fişierului); dacă în fluxul de intrare nu este disponibil nici un octet, se intră în aşteptare; public int read(byte[] b) throws IOException - citeşte maximum b.length octeţi pe care îi pune în tabloul b; întoarce numărul de octeţi citit efectiv;
public int read() throws IOException public int read(byte[] b, int offset, int length)throws IOException -

citeşte cel mult length octeţi pe care îi pune în tabloul b începând de la poziţia offset; întoarce numărul de octeţi cititi efectiv sau -1; public final void readFully(byte[] b) throws IOException - citeşte exact b.length octeţi, pe care îi pune în tabloul b; se blochează dacă nu are sufucienţi octeţi, iar la întâlnirea sfârşitului de fişier generează o EOFException;
public final void readFully(byte[] b, int offset, int length) throws IOException - citeşte exact length octeţi, pe care îi depune în tabloul b începând de la

poziţia offset;
public int skipBytes(int n) - throws IOException - încearca

să sară peste n

octeţi; întoarce numărul de octeţi săriţi efectiv;
public void write(int n) throws IOException - scrie în fişier un singur octet; public void write(byte[] b) throws IOException - scrie în fişier conţinutul

tabloului b;
public void write(byte[] b, int offset, int length)throws IOException -

scrie length octeţi din tabloul b începând cu cel de pe poziţia offset;
public long getFilePointer() throws IOException - întoarce poziţia

cursorului de fişier;
public void seek(long pos) throws IOException -

deplasează cursorul de fişier

pe poziţia pos;
public long length() throws IOException - întoarce lungimea fişierului

(exprimată în octeti);
public void setLength(long newLength) throws IOException -

setează noua

lungime a fişierului;
public void close() throws IOException - închide fişierul; public final boolean readBoolean() throws IOException -

citeşte o valoare

booleană (un octet);
public final byte readByte() throws IOException -

citeşte o valoare primitivă

de tip byte; citeşte un singur octet, pe care îl interpretează ca număr întreg fără semn (în intervalul 0 .. 255); public final short readShort() throws IOException - citeşte un întreg scurt (pe doi octeţi); public final int readUnsignedShort() throws IOException - citeşte doi octeţi, pe care îi interpretează ca un numar întreg scurt fără semn; public final char readChar() throws IOException - citeşte doi octeţi, pe care îi interpretează ca un caracter Unicode;
public final int readUnsignedByte() throws IOException -

325

Severin Bumbaru
public final int readInt() throws IOException -

citeşte un int (pe patru citeşte un long (pe opt citeşte un float (pe citeşte un double (pe

octeţi);
public final long readLong() throws IOException -

octeţi);
public final float readFloat() throws IOException -

patru octeţi);
public final double readDouble()throws IOException -

opt octeţi); citeşte din fişier o linie de text, adică un şir de caractere încheiat prin marcajul de sfârşit de linie ('\n' pe platforme Unix sau '\r' '\n' pe platforme Microsoft); public final String readUTF() throws IOException - citeşte un şir de caractere în format UTF-8; public final void writeBoolean(boolean b) throws IOException - scrie o valoare booleană (pe un octet); public final void writeByte(byte v) throws IOException - scrie o valoare de tip byte; public final void writeShort(short v) throws IOException - scrie un short (pe doi octeţi); public final void writeChar(char v) throws IOException - scrie un caracter Unicode (pe doi octeţi); public final void writeInt(int v) throws IOException - scrie un int (pe patru octeţi); public final void writeLong(long v) throws IOException - scrie un long (pe opt octeţi); public final void writeFloat(float v) throws IOException - scrie un float (pe patru octeţi); public final void writeDouble(double v) throws IOException - scrie un double (pe opt octeţi); public final void writeBytes(String s) throws IOException - scrie un şir de caractere convertit în secvenţă de octeţi (fiecare caracter este reprezentat pe un octet, eliminându-se octetul superior al codului caracterului); public final void writeChars(String s) throws IOException - scrie un şir de caractere Unicode; public final void writeUTF(String s) throws IOException - scrie un şir de caractere în format UTF-8.
public final String readLine() throws IOException -

Exemplu În fişierul AccesDirect.java este dat un exemplu de aplicaţie, în care se creează şi se utilizează un fişier cu acces direct, cu numele "Proba.fad". Prima dată fişierul este deschis în mod "rw" (read/write). Se fac două serii de scrieri de date, care apoi sunt citite şi afişate. Înainte de fiecare citire a unei serii de date se poziţionează cursorul de fişier la începutul seriei respective, iar datele se citesc respectând modul în care acestea au fost scrise. Fişierul este apoi închis şi redeschis în mod "r" ("read"), după care se poziţionează cursorul la începutul celei de a doua serii de date, care este citită şi afişată.

326

Programarea orientata pe obiecte în limbajul Java

Fluxuri de prelucrare
Fluxurile de prelucrare se conectează la alte fluxuri de intrare/ieşire pentru a face anumite transformări asupra datelor din fluxul respectiv. Clasele de fluxuri de prelucrare din Java API au fost prezentate pe scurt la descrierea pachetului java.io. În această secţiune ne vom ocupa de două categorii de fluxuri de prelucrare: fluxurile de date şi fluxurile de obiecte.

Fluxuri de date
În unele aplicaţii se doreşte să se transmită într-un flux de ieşire sau să se recepţioneze dintrun flux de intrare date primitive reprezentate binar (de tip boolean, char, byte, short, int, long, float sau double) şi şiruri de caractere. În acest scop, pot fi folosite clasele java.io.DataOutputStream şi java.io.DataInputStream.Se pot citi cu un DataInputStream numai date care au fost scrise cu un DataOutputStream. Instanţele acestor clase nu sunt fluxuri de intrare/ieşire propriu-zise, ci fluxuri de prelucrare. În consecinţă, ele nu se conectează direct la sursă sau la destinaţie, ci la alte fluxuri. Astfel, ieşirea unui DataOutputStream se poate conecta la intrarea unui FileOutputStream, ByteArrayOutputStream sau PipedOutputStream. Se poate conecta, de asemenea la intrarea unui BufferedOutputStream care, la rândul lui, este conectat la oricare din cele trei menţionate anterior. În mod similar, intrarea unui DataInputStream poate fi conectată la ieşirea unui FileInputStream, ByteArrayInputStream sau PipedInputStream.

Clasa DataOutputStream
Clasa java.io.DataOutputStream este derivată din clasa java.io.FilterOutputStream şi implementează interfaţa java.io.DataOutput. Ea permite să se transmită date primitive către un flux de ieşire într-o formă portabilă (independentă de platformă), cu condiţia ca aceste date să fie citite ulterior printr-un DataInputStream. Constructor: - creează un nou flux de ieşire de date, conectat la ieşire la un OutputStream (adică la o instanţă a unei clase derivate din clasa OutputStream).
public DataOutputStream(OutputStream out)

Metode: Aceasta clasă conţine metodele clasei java.io.OutputStream, la care se adaugă următoarele metode specifice, destinate scrierii în fluxul de ieşire a datelor primitive sau a unor şiruri de caractere:
public final void writeBoolean(boolean v) throws IOException

- scrie o

valoare booleană;
public final void writeChar(char v) throws IOException

- scrie o valoare de - scrie o valoare de - scrie o valoare

tip char, în format Unicode;
public final void writeByte(byte v) throws IOException

tip byte pe un octet;
public final void writeShort(short v) throws IOException

de tip short pe doi octeţi;
public final void writeInt(int v) throws IOException

- scrie o valoare de tip

327

Severin Bumbaru int pe 4 octeţi;
public final void writeLong(long v) throws IOException

- scrie o valoare de - scrie o valoare - scrie o

tip long pe 8 octeţi;
public final void writeFloat(float v) throws IOException

de tip float pe 4 octeţi;
public final void writeDouble(double v) throws IOException

valoare de tip double pe opt octeţi;
public final void writeBytes(String v) throws IOException - scrie un şir de caractere, convertit într-un şir de octeţi prin suprimarea octetului superior al fiecărui caracter; public final void writeChars(String v) throws IOException - scrie în flux un şir de caractere (în Unicode); public final void writeUTF(String v) throws IOException - scrie un şir de caractere codificat în formatul UTF-8 într-o formă independentă de maşină (se scrie mai întâi lungimea şirului pe doi octeţi, apoi fiecare caracter din şir pe câte un singur octet); public final int size() - întoarce numărul de octeţi scrişi în acest flux de ieşire.

Clasa DataInputStream
Un DataInputStream este un flux de intrare de octeţi care este conectat la intrare la un alt InputStream şi citeste din acesta date primitive într-o formă independentă de platformă. Se pot citi cu DataInputStream numai date care au fost scrise cu DataOutputStream. Clasa java.io.DataInputStream este derivată din clasa java.io.FilterInputStream şi implementează interfaţa java.io.DataInput.

Constructor:
public DataInputStream(InputStream in) - creează un citeşte date din fluxul de intrare in, primit ca argument.

flux de date de intrare, care

Metode: Aceasta clasă conţine toate metodele clasei java.io.InputStream, la care se adaugă următoarele metode specifice, prin care se implementează interfaţa java.io.DataInput: - citeşte din fluxul de intrare un număr de octeţi egal cu lungimea tabloului b, primit ca argument, şi îi pune în acest tablou. Dacă în fluxul de intrare nu sunt înca suficienţi octeţi, dar nu s-a atins sfârşitul de fişier, procesul este pus în aşteptare până apar noi octeţi. Dacă se întâlneşte sfârşitul de fişier (EOF - End of File), se generează o EOFException;
public final void readFully(byte[] b) throws IOException public final void readFully(byte[] b, int off, int len) throws IOException - acţionează similar cu metoda precedentă, dar se citesc len octeţi, care sunt

depuşi în tabloul b, începand de la poziţia off;
public final void skipBytes(int n) throws IOException - sare peste n octeţi din fluxul de intrare; întoarce numărul de octeţi săriţi efectiv (este posibil să nu reuşească să sară n octeti, de exemplu dacă a întalnit sfârşitul fluxului); public final boolean readBoolean() throws IOException - citeşte un octet din fluxul de intrare şi îl converteşte în valoare booleană; public final byte readByte() throws IOException - citeşte din fluxul de intrare

328

Programarea orientata pe obiecte în limbajul Java un singur octet, pe care îl tratează ca un număr întreg cu semn în intervalul [-128, 127]; se consideră că a fost scris cu writeByte(byte v); public final int readUnsignedByte() throws IOException - citeşte din fluxul de intrare un singur octet, pe care îl tratează ca număr întreg fără semn în intervalul [0, 255] şi îl întoarce sub formă de int; public final short readShort() throws IOException - citeşte doi octeţi din fluxul de intrare şi îi interpretează ca un număr de tip short, scris anterior cu writeShort(short v); public final int readUnsignedShort() throws IOException - citeşte din fluxul de intrare doi octeţi, pe care îi interpretează ca număr întreg fără semn şi-l întoarce sub formă de int; public final char readChar() throws IOException - citeşte din fluxul de intrare un char (doi octeţi, format Unicode); public final int readInt() throws IOException - citeşte patru octeţi si îi interpretează ca un număr de tip int scris cu writeInt(int v); public final long readLong() throws IOException - citeste opt octeţi si îi interpretează ca un număr de tip long, scris cu writeLong(long v); public final float readFloat() throws IOException - citeşte patru octeţi si îi interpretează ca un număr de tip float, scris cu writeFloat(float v); public final double readDouble() throws IOException - citeşte opt octeţi si îi interpretează ca un număr de tip double, scris cu writeDouble(double v); public final String readUTF() throws IOException - citeşte un şir de caractere în format UTF (care a fost scris cu writeUTF(String str)); Exemplu În fişierul FisierDate.java se dă un exemplu de aplicaţie, în care se creează un fişier de date şi se scriu date în acest fişier folosind un DataOutputStream legat la un FileOutpusStream. După ce au fost scrise datele, se închide fişierul pentru scriere şi se redeschide pentru citire, după care se citesc datele în aceeaşi ordine în care au fost scrise. Datele scrise în fişier şi cele citite sunt afişate pe ecran pe perechi, pentru comparaţie.

Fluxuri de obiecte
Fluxurile de obiecte sunt fluxuri de prelucrare care permit să se transmită obiecte. În acest scop, obiectele transmise trebuie să fie serializate, înainte de a fi transmise, adică să fie puse sub forma unei serii de octeti, într-un format care să permită la destinaţie reconstituirea în memorie a obiectului respectiv. Astfel de obiecte se numesc serializabile. Pe platforma Java 2, fluxurile de obiecte se realizează prin clasele java.io.ObjectOutputStream şi java.io.ObjectInputStream. Obiectele transmise de aceste fluxuri trebuie sa implementeze interfaţa java.io.Serializable.

Interfaţa Serializable
Orice obiect care se transmite pe un flux trebuie să aparţină unei clase care implementează interfaţa java.io.Serializable. Aceasta inseamnă că trebuie îndeplinite următoarele condiţii:

329

Severin Bumbaru - la declararea clasei respective, se pune clauza implements Serializable; - clasa trebuie să conţină un constructor fără argumente; - toate câmpurile obiectului trebuie să fie serializabile, adică fie să aparţina unor tipuri de date, primitive, fie unor clase care implementează interfaţa Serializable. Interfaţa Serializable nu conţine câmpuri sau metode. Clauza implements Serializable constituie doar o indicaţie pentru compilatorul Java să o pună sub forma serializabilă.

Dacă, la transmiterea obiectelor pe flux, se constată că unul din acestea nu este serializabil, se generează excepţia NotSerializableException. Clasele care necesită o manipulare specială la serializare sau deserializare trebuie să conţina metode cu signaturile următoare:
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

O prezentare mai amplă a acestei interfeţe se găseşte în documentaţia Java API.

Clasa ObjectOutputStream
Clasa java.io.ObjectOutputStream este clasa fluxurilor de prelucrare pentru obiecte, având rolul de a serializa obiectele care urmează să fie puse într-un flux de ieşire. Fluxurile de obiecte se conectează la ieşire la un alt flux, de preferinţă la un FileOutputStream, ByteArrayOutputStream sau PipedOutputStream, iar utilizarea lor este asemănătoare cu cea a fluxurilor de date (DataOutputStream). Într-un flux de obiecte se pot scrie atât obiecte serializate, cât şi date primitive sau şiruri. Clasa ObjectOutputStream conţine toate metodele de scriere a datelor primitive şi a şirurilor, care există şi în clasele DataOutputStream şi RandomAccessFile. În plus, ea conţine metode necesare pentru scrierea în flux a obiectelor, cea mai frecvent folosită fiind următoarea: public final void writeObject(Object obj)throws IOException - scrie în fluxul de ieşire obiectul obj sub formă serializată; Informaţii mult mai ample despre această clasă pot fi găsite în documentaţia Java API.

Clasa ObjectInputStream
Clasa java.io.ObjectInputStream serveşte pentru citirea (deserializarea) fluxurilor de obiecte care au fost scrise folosind clasa java.io.ObjectOutputStream. Intrarea unui ObjectInputStream este conectată la ieşirea unui alt flux de intrare, de preferinţă FileInputStream, ByteArrayInputStream sau PipedInputStream. Clasa ObjectInputStream conţine toate metodele de citire a datelor primitive şi a şirurilor existente în clasa DataInputStream. În plus, ea conţine metode necesare pentru citirea

330

Programarea orientata pe obiecte în limbajul Java obiectelor serializate, dintre care cea mai frecvent folosită este
public final Object readObject() throws OptionalDataException, ClassNotFoundException, IOException

Informaţii mai ample despre această clasă se găsesc în documentaţia Java API. Exemplu În fişierul FisierObiecte.java este dat un exemplu de aplicaţie, în care se testează clasele ObjectOutputStream şi ObjectInputStream. În prima parte a metodei main(), se creează un fişier (Proba1.dat) în care se scriu atât obiecte (din clasele Persoana şi Student), cât şi date primitive, şiruri şi tablouri. Pentru scrierea obiectelor şi a tablourilor se foloseşte metoda void writeObject(Object obj), iar pentru datele primitive şi pentru şiruri se folosesc metodele specifice pentru scrierea tipurilor respective. După ce s-a încheiat scrierea datelor, se închide fişierul Proba1.dat. În partea a doua a metodei main() se deschide fişierul Proba1.dat pentru citire şi se citesc din el toate datele scrise anterior, în ordinea în care au fost scrise. Întrucât metoda Object readObject() întoarce un Object, acesta este convertit prin cast într-un obiect din clasa căruia îi aparţine în realitate. În ultima parte a metodei main() se afişează pe ecran datele citite, putându-se astfel verifica faptul că serializarea şi deserializarea s-au făcut corect. Remarcăm că, în afară de date primitive şi obiecte, au fost scrise în fişier (prin metoda writeObject) şi tablouri unidimensionale şi bidimensionale, care au fost apoi citite corect (prin metoda readObject). Din acest exemplu se poate constata că clasele ObjectOutputStream şi ObjectInputStream constituie un instrument foarte puternic de salvare şi restaurare a datelor din memorie la nivelul obiectelor (al structurilor de date), care îl scutesc pe programator de munca laborioasă şi dificilă se a programa aceste operaţii la nivelul inferior al fluxurilor de octeţi sau la nivelul fluxurilor de date primitive.

Întrebări
Nivel 1
1. Ce este un stream? 2. Ce deosebire este între fluxurile de ieşire şi cele de intrare? 3. Care sunt etapele de utilizare a unui flux de iesire? 4. Care sunt etapele de utilizare a unui flux de intrare? 5. Ce conţine pachetul java.io? 6. Ce deosebire este între fluxurile de caractere şi cele de octeţi? 7. Care sunt rădăcinile ierarhiilor de clase pentru fluxuri de caractere? 8. Care sunt rădăcinile ierarhiilor de clase pentru fluxuri de octeţi? 9. Ce sunt fluxurile de prelucrare? 10. Ce este un fişier? 11. Care sunt clasele folosite pentru citirea fişierelor? 12. Care sunt clasele folosite pentru scrierea fişierelor? 13. Cum se deschide un fişier? 14. Cum se închide un fişier? 15. Ce clase se folosesc pentru a scrie date într-un fişier de octeţi şi pentru a citi date

331

Severin Bumbaru dintr-un astfel de fişier? 16. Ce sunt fişierele cu acces direct? 17. Cărei clase îi aparţin fişierele cu acces direct? 18. Ce fel de operaţii se pot face asupra unui fişier cu acces direct? 19. Ce este cursorul (pointerul) fişierului cu acces direct? 20. Care sunt modurile în care poate fi deschis un fişier cu acces direct? 21. Ce sunt fluxurile de prelucrare? 22. Ce sunt fluxurile de date? 23. Prin ce clase se realizează fluxurile de date? 24. Ce este un flux de obiecte? 25. Prin ce clase se realizează fluxurile de obiecte? 26. Ce proprietate trebuie sa aibă obiectele pentru a putea fi transmise într-un flux de obiecte? 27. Ce reprezintă interfaţa Serializable? 28. Cum este folosită interfaţa Serializable? 29. Ce condiţii trebuie să îndeplinească o clasă pentru a fi serializabilă? 30. Ce metode conţine interfaţa Serializable?

Nivel 2
1. Dece fluxul este un obiect abstract? 2. Clasele Reader şi Writer sunt abstracte sau instanţiabile? 3. Ce se înţelege prin marcarea unui flux şi prin ce metodă se realizează? 4. Cum se poate reveni într-un flux la marcajul făcut anterior? 5. Clasele InputStream şi OutputStream sunt sau nu instanţiabile? 6. În ce situaţii este obligatorie folosirea fluxurilor de octeţi? 7. Ce clasă se foloseşte pentru a scrie date într-un fişier de caractere? 8. Ce clasă se foloseşte pentru a citi date dintr-un fişier de caractere? 9. Prin ce metode se scriu datele primitive într-un fişier cu acces direct? 10. Prin ce metode se citesc datele primitive dintr-un fişier cu acces direct? 11. Prin ce metode se scriu şirurile de caractere într-un fişier cu acces direct? 12. Ce este UTF? 13. Pot fi fluxurile de date conectate direct la un fişier? 14. La ce fluxuri se conectează fluxurile de date? 15. Ce se întamplă dacă se încearcă introducerea într-un flux de obiecte a unui obiect neserializabil? Când se constată anomalia: la compilare sau la execuţie? 16. Un flux de obiecte poate conţine şi date primitive? dece? 17. Pot fi puse tablourile într-un flux de obiecte?

332

Programarea orientata pe obiecte în limbajul Java

Fire de execuţie
Conceptul de proces. Procese paralele şi concurente Fire de execuţie Clasa Thread Interfaţa Runnable Sincronizarea firelor de execuţie Întrebări. 333 335 335 346 348 354

Conceptul de proces. Procese paralele şi concurente
În general, se numeşte proces o succesiune de transformări sau de operaţii care conduce la realizarea unui anumit rezultat. Esenţial pentru un proces este caracterul său temporal: fiecare transformare elementară necesită un anumit timp, iar procesul în ansamblu este o succesiune de astfel de transformări elementare. Termenul este folosit în domenii diferite: procese fizice, procese chimice, procese tehnologice, procese biologice, procese economice, procese sociale, procese juridice etc. În informatică, procesul este un program în execuţie. În consecinţă, fiecărui proces i se asociază un program, şi un ansamblu de resurse necesare executării acestui program: o anumită zonă din memoria internă în care se păstrează datele programului, acces la procesorul sistemului, acces la anumite dispozitive de intrare/ieşire etc. Procesul constă tocmai în efectuarea în timp a succesiunii de operaţii prevăzută în program, folosind în acest scop resursele alocate. În mod tradiţional, programul este executat de către un singur procesor (dispozitiv de prelucrare), care poate efectua, la un moment de timp dat, numai o singură operaţie. În consecinţă, operaţiile prevăzute în program trebuie executate secvenţial, astfel încât operaţia următoare nu poate începe decât după încheierea celei precedente. Toate programele prezentate de noi în capitolele precedente se încadrează în această categorie.

Este posibil ca mai multe procese să se desfăşoare simultan, adică intervalele de timp corespunzătoare acestor procese să se suprapună total sau parţial. Astfel de procese pot fi paralele sau concurente.

333

Severin Bumbaru

Dacă procesele de calcul care au loc simultan folosesc resurse diferite (sunt executate de procesoare diferite, folosesc zone de memorie internă şi externă diferite etc.), ele se numesc procese paralele. Dacă procesele care se desfăşoară în acelaşi interval de timp folosesc anumite resurse comune, ele se numesc procese concurente. Cel mai frecvent se întâlneşte situaţia, în care mai multe procese folosesc în comun acelaşi procesor. Întrucât, la un moment dat, procesorul nu poate efectua decât o singură operaţie, înseamnă că este necesar să se adopte o anumită strategie, conform căreia timpul de funcţionare al procesorului este partajat între diferitele procese concurente. În principiu, aceste strategii se împart în două categorii: 1. Se acordă fiecăruia dintre procesele concurente un anumit nivel de prioritate. Dacă mai multe procese solicită simultan aceeaşi resursă, ea este alocată procesului cu prioritate superioară.

Conform acestei strategii, dacă, în timpul derularii unui proces A, procesorul este solicitat de un alt proces B de prioritate superioară, procesul A este întrerupt, trecându-se la executarea procesului B. După încheierea executării procesului prioritar B, se revine la procesul A, care este continuat din starea în care a fost întrerupt. Este însa posibil ca, în timp ce se deruleaza procesul B, procesorul sa fie solicitat de un alt proces C, de prioritate şi mai înalta. În acest caz, şi procesul B va fi întrerupt, trecându-se la executarea procesului C şi aşa mai departe. 2. Timpul total de funcţionare a procesorului este porţionatsau divizat (împărţit în intervale de durate egale sau diferite), acordându-se succesiv câte un astfel de înterval de timp fiecăruia dintre procesele concurente. La expirarea intervalului de timp alocat, numit şi felie sau tranşă de timp (engleză: time slice), procesul în curs este întrerupt şi se trece la executarea procesului următor, prin rotaţie. O astfel de utilizare a procesorului se numeşte "partajare a timpului" (engleză: time sharing sau time slicing). Cele două strategii menţionate aici pot fi aplicate în diferite variante şi pot fi combinate în diferite moduri. Cel mai frecvent, partajarea timpului este aplicată numai pentru procesele cu acelaşi nivel de prioritate. Majoritatea sistemelor de operare ale calculatoarelor moderne permit executarea în acelaşi timp a mai multor programe, deci permit concurenţa proceselor la nivel de program (de aplicaţie). De exemplu, în timp ce edităm un text, putem tipări alt text la imprimantă, putem aştepta sosirea de pe reţea a unei pagini de Web etc. Studierea acestui mod de funcţionare a calculatorului, cunoscut sub numele de multitasking, se face la cursul de "Sisteme de operare". Pentru a pune în evidenţă distincţia între program şi proces, putem menţiona că acelaşi program poate da naştere la procese diferite: dacă programul conţine ramificaţii şi/sau cicluri, parcurgerea efectivă a acestora (ce ramuri vor fi executate efectiv şi de câte ori se vor parcurge ciclurile) depinde de datele de intrare. În cadrul multitaskingului, este chiar posibil ca doi utilizatori să utilizeze simultan acelaşi program folosind date diferite, astfel încât se generează procese diferite.

334

Programarea orientata pe obiecte în limbajul Java

Fire de execuţie
Firul de execuţie (în engleză: Thread) este, în esenţă, un subproces strict secvenţial. Menţinând definiţia procesului ca un program în curs de execuţie, putem considera acum că procesul este format din mai multe fire de execuţie care se derulează în paralel, concurând la utilizarea resurselor alocate procesului respectiv. În limbajul Java, există posibilitatea de a se crea programe care conţin mai multe fire de execuţie. Aceasta înseamnă că, la executarea lor, se vor crea mai multe subprocese care se vor desfăşura simultan, folosind acelaşi procesor şi aceeaşi zonă de memorie. Acest mod de funcţionare se numeşte în engleză multithreading.

Chiar şi în cazul programelor Java în care nu sunt prevăzute explicit mai multe fire de execuţie, în timpul executării lor coexistă cel puţin două astfel de fire: cel al aplicaţiei propriu-zise şi cel al colectorului de reziduuri (garbage collector). Colectorul de reziduuri este un fir de execuţie cu nivel de prioritate coborât, care funcţionează atunci când apar întreruperi în desfăşurarea aplicaţiei propriu-zise (de exemplu, când se aşteaptă efectuarea unor operaţii de intrare/ieşire). În consecinţă, maşina virtuală Java are intrinsec o funcţionare de tip multithreading. Există două moduri de a programa un fir de execuţie: - prin extinderea clasei Thread; - prin implementarea interfeţei Runnable.

Clasa Thread
Clasa java.lang.Thread realizează un fir de execuţie. Acesta poate fi un fir de execuţie obişnuit, sau un demon. Demonul (engleză: daemon thread) este un fir de execuţie de prioritate coborâtă, care nu este invocat în mod explicit. El stă "adormit" şi intră automat în execuţie atunci când sunt îndeplinite anumite condiţii. Un exemplu tipic de demon este colectorul de reziduuri. După cum ştim deja, acesta este un fir de execuţie de prioritate coborâtă, care intră în funcţiune atunci când în memorie apar obiecte către care nu mai există referinţe. Crearea unui fir de execuţie se face invocând unul dintre constructorii clasei Thread. Dintre aceştia, îi menţionâm aici pe următorii:

- creează un nou fir de execuţie cu numele implicit Thread-n, unde n este un număr de ordine; public Thread(String name) - creează un nou fir de execuţie cu numele name; public Thread(Runnable target) - creează un nou fir de execuţie, care conţine obiectul target cu interfaţa Runnable, iar numele firului este cel implicit;
public Thread()

335

Severin Bumbaru
public Thread(Runnable target, String name) - creează un conţine obiectul target cu interfaţa Runnable şi are numele name.

nou fir de execuţie,

care

Cele mai frecvent utilizate metode ale clasei Thread sunt run(), start() şi sleep(). Iată o listă a principalelor metode ale acestei clase:

public void run() - conţine programul care trebuie executat de firul respectiv; Aşa cum este ea în clasa Thread, metoda nu face nimic (are corpul vid). Această metodă trebuie redefinită fie prin crearea unei subclase a clasei Thread, fie prin crearea unei obiect cu interfaţa Runnable, care să fie înglobat în acest Thread (folosind constructorul Thread(Runnable target)); public void start() - pune firul nou creat în starea "gata pentru execuţie"; public static void sleep(long millis) throws InterruptedException opreşte temporar execuţia acestui fir, pentru o durata de millis milisecunde; public static void yield() - opreşte temporar execuţia acestui fir, permiţând monitorului să dea controlul altui fir de aceeaşi prioritate; public static Thread currentThread() - întoarce o referinţă la firul care este executat în momentul curent; public final void setPriority(int newPriority) - seteaza prioritatea firului de execuţie; prioritatea trebuie sa fie cuprinsă în intervalul [Thread.MIN_PRIORITY, Thread.MAX_PRIORITY] (valorile lor fiind, respectiv, 1 şi 10). Nerespectarea acestui interval genereaza o IllegalArgumentException. Daca nu este stabilită explicit, prioritatea este Thread.NORM_PRIORITY (are valoarea 5). public final int getPriority() - întoarce prioritatea curentă a firului de execuţie. public final void setName(String name) - pune firului de execuţie un nou nume; public final String getName() - întoarce numele firului de execuţie; public final void setDaemon(boolean on) - dă firului de execuţie calitatea de demon (dacă argumentul este true) sau de fir obişnuit (dacă argumentul este false); Metoda poate fi invocată numai dacă firul nu este activ, altfel se generează o IllegalThreadStateException. public final boolean isDaemon() - testează dacă firul de execuţie este demon.

La programarea unui fir de execuţie, principala atenţie se acordă metodei run(), deoarece ea conţine programul propriu-zis, care trebuie executat de acest fir. Totuşi, metoda run()nu este invocată explicit. Ea este invocată de maşina virtuală Java, atunci când firul respectiv este pus în mod efectiv în execuţie. Pentru redefinirea metodei Run este necesar sa creem o subclasă a clasei Thread sau sa creem o clasă cu interfaţa Runnable, şi să dăm o instanţă a acestei clase ca argument constructorului clasei Thread. După ce firul de execuţie a fost creat (de exemplu prin expresia new Thread()), el există în memorie, dar înca nu poate fi executat. Se găseşte, deci, în starea "nou creat" (engleza: born). Pentru a-l face gata de execuţie, pentru firul respectiv este invocată metoda start().

După invocarea metodei start(), firul este executabil, dar aceasta nu înseamnă că el este

336

Programarea orientata pe obiecte în limbajul Java pus în execuţie imediat: noul fir devine concurent cu cele deja existente (în primul rând cu cel care l-a creat), şi va fi pus în execuţie în mod efectiv (i se va acorda acces la procesor) atunci când îi vine rândul, conform cu strategia de alocare a resurselor adoptată. Un fir în curs de execuţie poate fi oprit temporar ("adormit") invocând metoda statică sleep(long millis). Argumentul acestei metode este intervalul de timp cât procesul se va găsi în stare de "somn", exprimat în milisecunde.

Referitor la utilizarea acestei metode, remarcăm două aspecte: - întrucât ea poate "arunca" o InterruptedException, este necesar să fie prinsă într-un bloc try..catch; - întrucat metoda este statică şi nu are ca argument un fir de execuţie, ea nu poate fi invocată decât în metodele firului de execuţie căruia i se aplică. Fiecărui fir de execuţie i se asociază o anumită prioritate. Aceasta este un numar întreg, cuprins între valorile Thread.MIN_PRIORITY şi Thread.MAX_PRIORITY (având valorile 1 şi respectiv 10). Dacă metoda setPriority(int newPriority) nu este invocată explicit, firului i se dă prioritatea implicita Thread.NORM_PRIORITY, respectiv 5. Daca firul de execuţie solicită o operaţie de intrare/ieşire, el rămîne blocat pe întreaga durată a executării operaţiei respective. În timpul în care firul de execuţie "doarme" (ca urmare a invocării metodei sleep()) sau este blocat (deoarece asteapta terminarea unei operatii de intrare/iesire), se pot executa alte fire, de prioritate egală sau inferioară. În schimb, firele de prioritate superioară pot întrerupe în orice moment executarea celor de prioritate inferioară. Un proces poate fi pus, de asemenea, în aşteptare prin invocarea metodei wait(). În acest caz, firul aşteaptă confirmarea efectuării unei anumite acţiuni, prin invocarea metodei notify() sau notifyAll(). Aceste trei metode există în clasa Object şi servesc la sincronizarea firelor de execuţie.

Având în vedere cele de mai sus, punem în evidenţă urmatoarele stări în care se poate găsi un fir de execuţie pe durata ciclului său de viaţă: - nou creat (engleză: born) - este starea în care se găseşte imediat ce a fost creat prin operatorul new; în această stare, firul nu poate fi executat; - gata de execuţie (engleză: ready) - este starea în care firul poate fi pus în execuţie; punerea efectivă în execuţie se face de către maşina virtuala Java atunci când procesorul este liber şi nu sunt gata de execuţie fire de prioritate superioară; - în execuţie (engleză: running) - este starea în care procesul se execută efectiv (ocupă procesorul); - adormit (engleză: sleeping) - este starea de oprire temporară, ca urmare a invocării metodei sleep(); - blocat (engleză: blocked) - este starea în care aşteaptă incheierea unei operaţii de intrare/ieşire; - în aşteptare (engleză: waiting) - este starea în care firul se găseşte din momentul în care

337

Severin Bumbaru se invocă metoda wait(), până când primeşte o confirmare dată prin invocarea metodei notify(); - mort (engleză: dead) - este starea în care intră firul de execuţie dupa ce s-a încheiat executarea metodei run(). Relaţiile dintre aceste stări sunt reprezentate grafic în figura de mai jos.

Denumirile stărilor firului de execuţie au fost date în limba engleză, pentru a putea fi urmarită mai uşor documentaţia originală din Java API. Trecerea de la starea Born la starea Ready (gata) se face prin invocarea metodei start(). Trecerea de la starea Ready la starea Running (în execuţie) se face de către maşina virtuală Java (de către dispecerul firelor) atunci când sunt create condiţiile necesare: procesorul este liber şi nici un fir de execuţie de prioritate superioară nu se găseşte în starea Ready. Trecerea de la starea Running la starea Ready se face la executarea metodei yield(), sau atunci când a expirat tranşa de timp alocată procesului (în cazul sistemelor cu diviziune a timpului). Trecerea de la starea Running la starea Sleeping se face la executarea metodei sleep(). Trecerea de la starea Sleeping la starea Ready se face când a expirat intervalul de timp de

338

Programarea orientata pe obiecte în limbajul Java "adormire" (intervalul dat ca argument în metoda sleep(long millis)). Trecerea de la starea Running la starea Blocked are loc atunci când firul de execuţie respectiv solicită efectuarea unei operaţii de intrare ieşire. Trecerea de la starea Blocked la starea Ready are loc când s-a încheiat operaţia de intrare/ieşire solicitată de acest fir. Trecerea de la starea Running la starea Waiting se face la invocarea metodei wait(). Trecerea de la starea Waiting la starea Ready se face când se primeşte o confirmare prin invocarea metodei notify() sau notifyAll(). Trecerea de la starea Running la starea Dead are loc atunci când se încheie executarea metodei run() a firului de execuţie respectiv. Se observă că orice fir de execuţie îşi începe ciclul de viaţă în starea Born şi îl încheie în starea Dead. Punerea firului în execuţie efectivă (trecerea de la Ready la Running) se face numai de către dispecerul firelor. În toate cazurile în care se încheie o stare de oprire temporară a execuţiei (Sleeping, Waiting sau Blocked), firul de execuţie respectiv trece în starea Ready, aşteptând ca dispecerul să-l pună efectiv în execuţie (să-l treacă în starea Running). În timpul cât firul de execuţie se găseşte în orice altă stare decât Running, procesorul calculatorului este liber, putând fi utilizat de alte fire de execuţie sau de alte procese.

Exemplul 1 Fişierul DouaFireA.java conţine un exemplu de aplicaţie, în care se creează fire de execuţie folosind o clasă care extinde clasa Thread.
/* Crearea si utilizarea unei clase de fire de executie care extinde clasa Thread dar nu foloseste metodele sleep sau yield. Se creeaza doua fire de aceeasi prioritate (Thread.NORM_PRIORITY). */ class DouaFireA { /* Clasa firelor de executie */ static class Fir extends Thread { Fir(String numeFir) { super(numeFir); System.out.println("S-a creat firul "+getName()); } /* Redefinirea metodei run() din clasa Thread */ public void run() { System.out.println("Incepe executarea firului "+getName()); for(int i=0; i<6; i++) {

339

Severin Bumbaru

System.out.println("Firul "+getName()+" ciclul i="+i); } } } /* incheierea clasei Fir */ public static void main(String args[]) throws Exception { Fir fir1, fir2; System.out.println("Se creeaza firul Alpha"); fir1=new Fir("Alpha"); System.out.println("Se creeaza firul Beta"); fir2=new Fir("Beta"); System.out.println("Se pune in executie firul Alpha"); fir1.start(); System.out.println("Se pune in executie firul Beta"); fir2.start(); System.out.println("Sfarsitul metodei main()"); } /* Sfarsitul metodei main() */ }

Clasa Fir din această aplicaţie extinde clasa Thread, deci instanţele ei sunt fire de execuţie. În clasa Fir s-a redefinit metoda run(), astfel încât să conţină programul firului respectiv. În cazul nostru, firul execută un ciclu, în care afişează la terminal un mesaj, conţinând numele firului şi indicele ciclului executat. În metoda main() a aplicaţiei se creează două instanţe ale clasei Fir, dându-le, respectiv, numele Alpha şi Beta, apoi se lansează în execuţie aceste fire. Iată un exemplu de rezultat al executării acestei aplicaţii:

Se creeaza firul Alpha S-a creat firul Alpha Se creeaza firul Beta S-a creat firul Beta Sfarsitul metodei main() Incepe executarea firului Alpha Firul Alpha ciclul i=0 Firul Alpha ciclul i=1 Firul Alpha ciclul i=2 Firul Alpha ciclul i=3 Firul Alpha ciclul i=4 Firul Alpha ciclul i=5 Incepe executarea firului Beta Firul Beta ciclul i=0 Firul Beta ciclul i=1 Firul Beta ciclul i=2 Firul Beta ciclul i=3 Firul Beta ciclul i=4 Firul Beta ciclul i=5

Remarcăm cele ce urmează. - În realitate, în afară de firele de execuţie Alpha şi Beta create explicit de către noi, mai exista incă două fire de execuţie: cel al aplicaţiei propriu-zise (executarea metodei main()) şi cel al colectorului de reziduuri. Întrucât nu s-au setat explicit priorităţile, firele Alpha, Beta şi main() au toate prioritatea Thread.NORM_PRIORITY.

340

Programarea orientata pe obiecte în limbajul Java - Întrucât toate firele au aceeaşi prioritate, durata executării fiecărui fir este mică şi nu s-au folosit metode de suspendare a execuţiei (yield(), sleep(), wait()), în momentul în care începe executarea unui fir acesta se execută până la capăt, după care procesorul sistemului este preluat de firul următor. În consecinţă s-a executat mai întâi metoda main() până la incheierea ei (confirmată prin mesajul "Sfârşitul metodei main()"), după care se execută complet firul Alpha şi apoi firul Beta (în ordinea lansării). - Aplicaţia se încheie în momentul în care s-a încheiat ultimul fir de execuţie.

Exemplul 2 În fişierul DouaFireA1.java se reia aplicaţia din exemplul precedent, dar la sfârşitul fiecărui ciclu de indice i se invocă metoda yield() care suspendă firul în curs şi permite astfel să se transmită controlul următorului fir de aceeaşi prioritate.
/* Crearea si utilizarea unei clase de fire de executie care extinde clasa Thread. Se foloseste metoda yield() pentru a ceda controlul altui fir de aceeasi prioritate. Se creeaza doua fire de aceeasi prioritate (Thread.NORM_PRIORITY). */ class DouaFireA1 { /* Clasa firelor de executie */ static class Fir extends Thread { Fir(String numeFir) { super(numeFir); System.out.println("S-a creat firul "+getName()); } /* Redefinirea metodei run() din clasa Thread */ public void run() { System.out.println("Incepe executarea firului "+getName()); for(int i=0; i<6; i++) { System.out.println("Firul "+getName()+" ciclul i="+i); yield(); // cedarea controlului procesorului } } } /* incheierea clasei Fir */ public static void main(String args[]) throws Exception { Fir fir1, fir2; System.out.println("Se creeaza firul Alpha"); fir1=new Fir("Alpha"); System.out.println("Se creeaza firul Beta"); fir2=new Fir("Beta"); System.out.println("Se pune in executie firul Alpha"); fir1.start(); System.out.println("Se pune in executie firul Beta"); fir2.start(); System.out.println("Sfarsitul metodei main()");

341

Severin Bumbaru

} /* Sfarsitul metodei main() */ }

Rezultatul executarii acestei aplicatii este urmatorul:

Se creeaza firul Alpha S-a creat firul Alpha Se creeaza firul Beta S-a creat firul Beta Sfarsitul metodei main() Incepe executarea firului Alpha Firul Alpha ciclul i=0 Incepe executarea firului Beta Firul Beta ciclul i=0 Firul Alpha ciclul i=1 Firul Beta ciclul i=1 Firul Alpha ciclul i=2 Firul Beta ciclul i=2 Firul Alpha ciclul i=3 Firul Beta ciclul i=3 Firul Alpha ciclul i=4 Firul Beta ciclul i=4 Firul Alpha ciclul i=5 Firul Beta ciclul i=5

Se observă cu uşurinţă că, în acest caz, executarea celor două fire, Alpha şi Beta, are loc intercalat, deoarece, după fiecare parcurgere a unui ciclu într-un fir, se invocă metoda yield() , cedându-se procesorul sistemului în favoarea celuilalt fir. Aceasta nu s-ar fi întâmplat, dacă cele doua fire ar fi avut priorităţi diferite.

Exemplul 3 În fişierul DouaFireB.java s-a reluat aplicaţia din Exemplul 1, aducându-i-se următoarele modificări: - în ciclul cu contorul i din metoda run() a clasei Fir s-a mai introdus un ciclu interior (cu contorul j) care se parcurge de 100000000 ori, pentru a mări în mod artificial durata de execuţie; în locul acestuia se putea pune, evident, orice alta secvenţa de instrucţiuni cu durata de execuţie comparabilă; - cele două cicluri (pe i şi pe j) au fost introduse şi în metoda main(), pentru a putea urmări executarea acesteia după lansarea firelor Alpha şi Beta. Fragmentele de program nou adăugate sunt puse în evidenţă în textul de mai jos cu caractere aldine (îngroşate).
/* Crearea si utilizarea unei clase de fire de executie. In metoda run a firului nu se folosesc metodele sleep() sau yield(), dar se parcurge un ciclu de contorizare (pe j) de 100000000 ori pentru a mari durata de executie a ciclului exterior (pe indicele i). Se creeaza doua fire de aceeasi prioritate (Thread.NORM_PRIORITY). */

342

Programarea orientata pe obiecte în limbajul Java

class DouaFireB { /* Clasa firelor de executie */ static class Fir extends Thread { Fir(String numeFir) { super(numeFir); System.out.println("S-a creat firul "+getName()); } /* Redefinirea metodei run() din clasa Thread */ public void run() { System.out.println("Incepe executarea firului "+getName()); for(int i=0; i<5; i++) { for(int j=0; j<100000000; j++); System.out.println("Firul "+getName()+" ciclul i="+i); } } } /* incheierea clasei Fir */ public static void main(String args[]) throws Exception { Fir fir1, fir2, fir3; System.out.println("Se creeaza firele Alpha si Beta"); fir1=new Fir("Alpha"); fir2=new Fir("Beta"); System.out.println("Se pun in executie cele doua fire"); fir1.start(); fir2.start(); for (int i=0; i<5; i++) { for(int j=0; j<100000000; j++); System.out.println("main() ciclul i="+i); } System.out.println("Sfarsitul metodei main()"); } /* Sfarsitul metodei main() */ }

Iată un exemplu de rezultat obţinut prin executarea acestei aplicaţii:

Se creeaza firele Alpha si Beta S-a creat firul Alpha S-a creat firul Beta Incepe executarea firului Alpha Incepe executarea firului Beta Firul Alpha ciclul i=0 Firul Beta ciclul i=0 main() ciclul i=0 main() ciclul i=1 Firul Alpha ciclul i=1 Firul Beta ciclul i=1 main() ciclul i=2 Firul Alpha ciclul i=2 Firul Beta ciclul i=2 main() ciclul i=3 Firul Alpha ciclul i=3 Firul Beta ciclul i=3

343

Severin Bumbaru

main() ciclul i=4 Sfarsitul metodei main() Firul Alpha ciclul i=4 Firul Beta ciclul i=4

Constatăm că, întrucat durata de execuţie a fiecărui fir a devenit mult mai mare decât în exemplul precedent şi toate cele trei fire (Alpha, Beta si main()) au aceeaşi prioritate (Thread.NORM_PRIORITY), se aplică o strategie de partajare a timpului, astfel încât timpul de funcţionare al procesorului este alocat sub forma de tranşe succesive fiecăruia dintre procese. În consecinţă, se creeaza impresia că cele trei procese se desfăşoară în paralel, simultan în timp.

Exemplul 4 În fişierul DouaFireC.java se reia aplicaţia din exemplul precedent, dar s-a renunţat la ciclurile interioare (cu contorul j), în schimb după parcurgerea fiecărui ciclu de indice i se pune firul respectiv in aşteptare timp de 1000 milisecunde prin invocarea metodei sleep. Această metodă aruncă excepţia InterruptedException care trebuie captată. În mod similar s-a procedat şi în ciclul din metoda main. Având însă în vedere că metoda main() nu se găseşte într-o clasa derivata din Thread, la invocarea metodei statice sleep a fost necesar să se indice clasa căreia îi aparţine, deci invocarea s-a făcut sub forma Thread.sleep(1000).

/* Crearea şi utilizarea unei clase de fire de execuţie. În metoda run a firului se foloseste metoda sleep(1000) pentru a pune firul in asteptare pe o durata de 1000 milisecunde. Similar se procedeaza in metoda main() */ class DouaFireC { /* Clasa firelor de executie */ static class Fir extends Thread { Fir(String numeFir) { super(numeFir); System.out.println("S-a creat firul "+getName()); } /* Redefinirea metodei run() din clasa Thread */ public void run() { System.out.println("Incepe executarea firului "+getName()); for(int i=0; i<5; i++) { System.out.println("Firul "+getName()+" ciclul i="+i); try { sleep(1000); } catch(InterruptedException e) {}

344

Programarea orientata pe obiecte în limbajul Java

} } } /* incheierea clasei Fir */ public static void main(String args[]) throws Exception { Fir fir1, fir2, fir3; System.out.println("Se creeaza firele Alpha si Beta"); fir1=new Fir("Alpha"); fir2=new Fir("Beta"); System.out.println("Se pun in executie cele doua fire"); fir1.start(); fir2.start(); for (int i=0; i<5; i++) { System.out.println("main() ciclul i="+i); try { Thread.sleep(1000); } catch(InterruptedException e) {} } System.out.println("Sfarsitul metodei main()"); } /* Sfarsitul metodei main() */ }

Iată rezultatul executării acestei aplicaţii:

Se creeaza firele Alpha si Beta S-a creat firul Alpha S-a creat firul Beta Se pun in executie cele doua fire main() ciclul i=0 Incepe executarea firului Alpha Firul Alpha ciclul i=0 Incepe executarea firului Beta Firul Beta ciclul i=0 main() ciclul i=1 Firul Alpha ciclul i=1 Firul Beta ciclul i=1 main() ciclul i=2 Firul Alpha ciclul i=2 Firul Beta ciclul i=2 main() ciclul i=3 Firul Alpha ciclul i=3 Firul Beta ciclul i=3 main() ciclul i=4 Firul Alpha ciclul i=4 Firul Beta ciclul i=4 Sfarsitul metodei main()

În timp ce un fir "doarme", se pot executa alte fire, chiar dacă ele sunt de prioritate inferioară!

345

Severin Bumbaru

Interfaţa Runnable
Interfaţa java.lang.Runnable trebuie implementată de orice clasă care nu este descendentă a clasei java.lang.Thread, dar ale cărei instanţe trebuie tratate ca nişte fire de execuţie. Singura metodă a acestei interfeţe este
public void run()

care are acelaşi rol cu cea din clasa Thread. Necesitatea implementării interfeţei Runnable apare atunci când dorim să creem o clasa de fire de execuţie care nu extinde clasa Thread. Motivul ar putea fi, de exemplu, cel că clasa nou creată, B, trebuie sa extindă o alta clasă, A, care nu este descendentă a clasei Thread Se ştie că în Java nu există moştenire multiplă, deci clasa B nu poate avea ca superclase atât clasa A, cât şi clasa Thread. În acest caz, vom crea o clasă B care extinde clasa A şi implementeaza interfaţa Runnable, care conţine metoda run(). Utilizarea instanţelor claselor cu interfaţa Runnable se face, punându-le ca argumente ale următorilor constructori ai clasei Thread: O se comportă ca şi cum ar fi instanţa unei extensii a clasei Thread care ar conţine metoda run() a obiectului target cu interfaţa Runnable.
public Thread(Runnable target) public Thread(Runnable target, String name) instanţă a clasei Thread creată cu un astfel de constructor,

Exemplul 1 În fişierul DouaFireAR.java se dă un exemplu de aplicaţie similară cu cea din fişierul DouaFireA1.java, cu deosebirea că clasa Fir nu extinde clasa Thread, ci implementează interfaţa Runnable. Fragmentele de program modificate sunt puse în evidenţa în textul sursă de mai jos prin caractere aldine (ingroşate).
/* Crearea si utilizarea unei clase cu interfata Runnable. Se foloseste metoda yield() pentru a ceda controlul altui fir de aceeasi prioritate. Se creeaza doua fire de aceeasi prioritate (Thread.NORM_PRIORITY), utilizand ca argument al constructorului instante ale clasei Fir cu interfata Runnable. */ class DouaFireAR { /* Clasa cu interfata Runnable */ static class Fir implements Runnable { /* Definirea metodei run() din interfata Runnable */ public void run() { System.out.println("Incepe executarea firului "+ Thread.currentThread().getName()); for(int i=0; i<6; i++) { System.out.println("Firul "+

346

Programarea orientata pe obiecte în limbajul Java

Thread.currentThread().getName()+" ciclul i="+i); Thread.yield(); // cedarea controlului procesorului } } } /* incheierea clasei Fir */ public static void main(String args[]) throws Exception { Thread fir1, fir2; System.out.println("Se creeaza firul Alpha"); fir1=new Thread(new Fir(), "Alpha"); System.out.println("Se creeaza firul Beta"); fir2=new Thread(new Fir(), "Beta"); System.out.println("Se pune in executie firul Alpha"); fir1.start(); System.out.println("Se pune in executie firul Beta"); fir2.start(); System.out.println("Sfarsitul metodei main()"); } /* Sfarsitul metodei main() */ }

Comparând cu Exemplul 2 din sectiunea Clasa Thread, constatăm cele ce urmează. - Clasa Fir nu mai extinde clasa Thread, ci implementeaza interfaţa Runnable. - În metoda run() a clasei Fir s-a avut în vedere că aceasta nu extinde clasa Thread şi deci nu moşteneşte metodele ecesteia. În consecinţă: . metoda statică yield() a clasei Thread a fost invocată sub forma Thread.yield(); . pentru a obţine o referinţă la instanţa curentă a clasei Thread, necesară invocării metodei getName(), s-a folosit metoda statică Thread.getCurrentThread(). - Crearea celor două fire de execuţie în metoda main() s-a făcut folosind un constructor al clasei Thread, căruia i s-au dat ca parametri o instanţă a clasei Fir şi numele firului respectiv, de exemplu:
fir1=new Thread(new Fir(), "Alpha");

Dacă se compilează şi se execută această aplicaţie, se obţin exact aceleaţi rezultate ca în cazul când s-a recurs la extinderea clasei Thread.

Exemplul 2 În fişierul Bondari.java este dat un exemplu de aplicaţie, în care se pot crea mai multe fire de execuţie, fiecare având propria sa interfaţă grafică. Fiecare fir de execuţie este o instanţa a clasei Bondar, care extinde clasa JFrame şi implementează interfaţa Runnable. Aplicaţia propriu-zisă (care conţine metoda main()) este clasa Bondari, care extinde şi ea clasa JFrame. Fereastra principală a aplicaţiei (interfaţa clasei Bondari) conţine numai un buton cu inscripţia "Creare nou bondar". De câte ori se apasă acest buton, se creeaza o noua instanţă a clasei Bondar. Clasa Bondar are propria ei interfaţă grafică, în care există două rigle pentru ajustarea perioadei şi amplitudinii şi o fereastră în care evoluează "bondarul". Acesta este un mic

347

Severin Bumbaru dreptunghi roşu, a cărui mişcare o simuleaza pe cea a unei insecte în zbor. Amplitudinea, cât şi perioada deplasărilor "bondaruliui" sunt ajustate cu cele două rigle. Dacă se acţioneaza butonul de închidere a ferestrei "bondarului" (cel marcat cu X din colţul dreapta-sus), se încheie ciclul de viaţă al firului de execuţie respectiv. Aceasta are loc, întrucat ciclul din metoda run() a firului continuă cât timp variabila booleana esteViu are valoarea true. Când se acţioneaza butonul de închidere a ferestrei "bondarului", evenimentul este captat de metoda windowClosing() a clasei SfarsitBondar, care pune variabila esteViu a "bondarului" respectiv la valoarea false. Dacă se acţionează butonul de închidere a ferestrei principale (cea care conţine butonul "Creare nou bondar"), evenimentul este captat de metoda windowClosing() a clasei Iesire, astfel că se execută metoda exit(0). Efectul este încheierea execuţiei tuturor firelor şi oprirea maşinii virtuale Java.

Sincronizarea firelor de execuţie
Pană în prezent, am considerat că fiecare fir (Thread) se execută independent, fără legătura cu celelalte fire ale aceleeaşi aplicaţii. Există însă situaţii, în care este necesar să se stabilească anumite interdependenţe între fire. Aceasta se întâmplă, în special, atunci când un fir trebuie să folosească datele produse de alt fir: este evident că nu le poate folosi înainte ca ele să fie produse. În limbajul Java, sincronizarea firelor de execuţie se face prin intermediul monitoarelor. Se numeşte monitor instanţa unei clase care conţine cel puţin o metodă sincronizată, sau o metodă care conţine un bloc sincronizat. Se numeşte metodă sincronizată orice metodă care conţine în antetul său modificatorul synchronized, deci este declarată sub forma
[modif]synchronized tip nume_metoda(declaratii_argumente) {corpul_metodei}

unde modif reprezinta alţi eventuali modificatori (public, static etc.). Când un fir începe executarea uni metode sincronizate a unui monitor, el devine "proprietarul" monitorului căruia îi aparţine această metodă (engleză: owner) şi deţine această calitate până la încheierea executării metodei sincronizate respective, sau până când se autosuspendă invocând metoda wait(), aşa cum vom explica ulterior. Pe toata durata cât un fir de execuţie este proprietarul unui monitor, nici un alt fir nu poate invoca o metodă sincronizată a monitorului respectiv. Aşa dar, orice fir care, în acest interval de timp, ar încerca să invoce o metodă sincronizată a unui monitor al cărui proprietar este alt fir, trebuie să aştepte până când monitorul respectiv este eliberat de proprietarul existent. În acest fel, se evită situaţiile în care un fir de execuţie ar interveni să facă modificări asupra unui obiect, în timp ce acesta se găseşte deja în curs de prelucrare de către un alt fir. De exemplu, dacă un fir de execuţie efectuează ordonarea datelor dintr-un tablou, nu este corect ca un alt fir, în acest timp, să modifice datele din acest tablou.

348

Programarea orientata pe obiecte în limbajul Java

Relaţia producător/consumator şi utilizarea metodelor wait(), notify() şi notifyAll()
În aplicaţiile limbajului Java, pentru sincronizarea firelor se foloseşte frecvent modelul producător/consumator. Considerăm că un fir de execuţie "produce" anumite date, pe care le "consumă" alt fir. Pentru efectuarea acestor operaţii folosim un monitor, care conţine atât datele transmise, cât şi o variabilă booleană, numită variabila de condiţie a monitorului, care indică dacă sunt disponibile date noi, puse de producator şi neutilizate de consumator. Atât punerea de date noi, cât şi preluarea acestora se fac numai folosind metode sincronizate ale monitorului. În corpul acestor metode sincronizate, se pot folosi metodele wait(), notify() şi notifyAll() ale clasei Object. Metoda
public final void wait() throws InterruptedException

şi variantele ei
public final void wait(long timeout) throws InterruptedException public final void wait(long timeout, int nanos) throws InterruptedException

au ca efect trecerea firului de execuţie activ din starea Running în starea Waiting (vezi schema). Metoda wait() fără argumente face această trecere pentru un timp nedefinit, în timp ce celelalte două metode primesc ca argument intervalul de timp maxim pentru care se face suspendarea execuţiei firului. Metodele
public final void notify() public final void notifyAll()

au ca efect trecerea firului de execuţie din starea Waiting în starea Ready, astfel încât el poate fi activat de către dispecer în momentul în care procesorul sistemului devine disponibil. Deosebirea dintre ele este ca metoda notify() trece în starea Readyun singur fir de execuţie dintre cele care se gasesc în momentul respectiv în starea Waiting provocată de acest monitor (care din ele depinde de implementare), în timp ce notifyAll() le trece în starea Ready pe toate. Clasa monitor concepută pentru asigurarea relaţiei producător/consumator are următoarea formă:
class NumeClasaMonitor { // declaraţii câmpuri de date ale monitorului boolean variabilaConditie=false; [public] synchronized void puneDate(declaraţii_argumente) { if(variabila_condiţie) { try { wait(); // se asteapta folosirea datelor puse anterior } catch(InterruptedException e) { /* instrucţiuni de executat dacă a apărut o excepţie de întrerupere */ } } /* punerea de date noi în câmpurile de date ale monitorului */ variabilaConditie=true; // s-au pus date noi

349

Severin Bumbaru
notify(); // se notifica punerea de noi date } // sfarsit puneDate [public] synchronized tip preiaDate(declaratii_argumente) { if(!variabilaConditie) { try { wait(); // se aşteaptă să se pună date noi } catch(InterruptedException e) { /* Instrucţiuni de executat dacă a apărut o excepţie de întrerupere */ } } /* instrucţiuni prin care se folosesc datele din monitor şi se formează valoarea_întoarsă */ variabilaConditie=false; // datele sunt deja folosite notify(); // se notifică folosirea datelor return valoarea_intoarsa; } // sfarsit preiaDate /* Alte metode ale monitorului (sincronizate sau nesincronizate) */ } // sfarsit clasa monitor

Iniţial, variabila variabilaConditie a monitorului are valoarea false, întrucat deocamdată - nu există date noi. Dacă firul de execuţie producător a ajuns în starea, în care trebuie să pună în monitor date noi, el invocă metoda sincronizată puneDate, devenind astfel proprietar (owner) al monitorului. În această metodă, se verifică, în primul rând, valoarea variabilei variabilaConditie. Dacă această variabilă are valoarea true, înseamnă că în monitor există deja date noi, înca nefolosite de consumator. În consecinţă, se invocă metoda wait() şi firul de execuţie monitor este suspendat, trecând în starea Waiting, astfel că el încetează să mai fie proprietar (owner) al monitorului. Dacă un alt fir (în cazul de faţă consumatorul) invocă o metodă sincronizată a aceluiaşi monitor care, la rândul ei, invocă metoda notify() sau notifyAll(), firul pus în aşteptare anterior trece din starea Waiting în starea Ready şi, deci, poate fi reactivat de către dispecer. Dacă variabila variabilaConditie are valoarea false, firul producător nu mai intră în aşteptare, ci execută instrucţiunile prin care modifică datele monitorului, după care pune variabilaConditie la valoarea true şi se invocă metoda notify() pentru a notifica consumatorul că exista date noi, după care se încheie executarea metodei puneDate. Funcţionarea firului de execuţie consumator este asemănătoare, dar acesta invocă metoda preiaDate. Executând această metodă, se verifică mai întâi dacă variabilaConditie are valoarea false, ceeace înseamnă că nu există date noi. În această situaţie, firul consumator intră în aşteptare, până va primi notificarea că s-au pus date noi. Dacă, însă, variabilaConditie are valoarea true, se folosesc datele monitorului, după care se pune variabilaConditie la valoarea false, pentru a permite producătorului să modifice datele şi se invocă metoda notify() pentru a scoate producătorul din starea de aşteptare. Este foarte important să avem în vedere că metoda puneDate este concepută pentru a fi invocată de firul producător, în timp ce metoda preiaDate este concepută pentru a fi invocată de către firul consumator. Numele folosite aici atât pentru clasa monitor, cât şi pentru metodele acesteia şi variabila de condiţie sunt convenţionale şi se aleg de către

350

Programarea orientata pe obiecte în limbajul Java programator, din care cauză au fost scrise în cele de mai sus cu roşu cursiv.

Exemplul 1 În fişierul Sincro.java se dă un exemplu de aplicaţie în care se sincronizeaza două fire de execuţie folosind modelul producător/consumator. Pentru a uşura citirea programului, clasele au fost denumite chiar Producator, Consumator şi Monitor, dar se puteau alege, evident, orice alte nume. Firul de execuţie prod, care este instanţa clasei Producator, parcurge un număr de cicluri impus (nrCicluri), iar la fiecare parcurgere genereaza un tablou de numere aleatoare, pe care îl depune în monitor. Firul de execuţie cons, care este instanţă a clasei Consumator, parcurge acelaşi număr de cicluri, în care foloseşte tablourile generate de firul prod. Transmiterea datelor se face prin intermediul monitorului monit, care este instanţă a clasei Monitor. Această clasă conţine tabloul de numere întregi tab şi variabila de condiţie valoriNoi, care are iniţial valoarea false, dar primeşte valoarea true atunci când producătorul a pus în monitor date noi şi primeşte valoarea false, atunci când aceste date au fost folosite de consumator. Pentru punerea de date noi în monitor, producătorul invocă metoda sincronizată puneTablou. Pentru a folosi datele din monitor, consumatorul invocă metoda sincronizată preiaTablou. Fiecare din aceste metode testează valoarea variabilei de condiţie valoriNoi şi pune firul de execuţie curent în aşteptare, dacă valoarea acestei variabile nu este corespunzătoare. După ce s-a efectuat operaţia de punere/utilizare a datelor, metoda sincronizată prin care s-a făcut această operaţie invocă metoda notify(). Dacă ar fi putut exista mai multe fire în starea Waiting, era preferabil sa se invoce metoda notifyAll().
class Sincro { Producator prod; Consumator cons; Monitor monit; int nrCicluri; /* Clasa Producator. Producatorul genereaza un tablou de date si il pune in monitor pentru a fi transmis consumatorului */ class Producator extends Thread { public void run() { System.out.println("Incepe executarea firului producator"); for (int i=0; i<nrCicluri; i++) { int n=((int)(5*Math.random()))+2; int tab[]=new int[n]; for(int j=0; j<n; j++) tab[j]=(int)(1000*Math.random()); monit.puneTablou(tab); System.out.print("Ciclul i="+i+" S-a pus tabloul: "); for(int j=0; j<n; j++) System.out.print(tab[j]+" "); System.out.println(); } System.out.println("Sfarsitul executiei firului Producator"); } } /* Sfarsitul clasei Producator */ /* Clasa Consumator. Consumatorul foloseste (in cazul de fata doar afiseaza) datele preluate din monitor

351

Severin Bumbaru

*/ class Consumator extends Thread { public void run() { System.out.println("Incepe executarea firului Consumator"); int tab[]; for(int i=0; i<nrCicluri; i++) { tab=monit.preiaTablou(); System.out.print("Ciclul i="+i+ " s-a preluat tabloul "); for(int j=0; j<tab.length; j++) System.out.print(tab[j]+" "); System.out.println(); } System.out.println("Sfarsitul executiei firului Consumator"); } } /* Sfarsitul clasei Consumator */ /* Clasa Monitor. Monitorul contine date si metode folosite in comun de Producator si Consumator. In acest scop, producatorul si consumatorul folosesc metodele sincronizate ale monitorului */ class Monitor { int tab[]; // tabloul de date care se transmit boolean valoriNoi=false; // variabila de conditie a monitorului /* Metoda prin care producatorul pune date in monitor */ public synchronized void puneTablou(int tab[]) { if(valoriNoi) { try { wait(); // se asteapta sa fie folosite datele puse anterior } catch(InterruptedException e) {} } this.tab=tab; // se modifica tabloul tab din monitor valoriNoi=true; // monitorul contine date noi notify(); // se notifica consumatorul ca s-au pus date noi } /* Metoda prin care consumatorul preia datele din monitor */ public synchronized int[] preiaTablou() { if(!valoriNoi) { try { wait(); // se asteapta sa se puna date noi } catch(InterruptedException e) {} } valoriNoi=false; // datele puse anterior au fost folosite notify(); // se notifica producatorul ca datele au fost preluate return tab; } } /* Sfarsitul clasei Monitor */ public Sincro(int numarCicluri) { nrCicluri=numarCicluri; prod=new Producator(); cons=new Consumator(); monit=new Monitor(); prod.start(); cons.start();

352

Programarea orientata pe obiecte în limbajul Java

} public static void main(String args[]) { int numarCicluri=8; System.out.println("Incepe executarea metodei main()"); Sincro sinc=new Sincro(numarCicluri); System.out.println("Sfarsitul metodei main()"); } }

Referitor la acest program, remarcăm urmatoarele: - instrucţiunile care constituie subiectul acestei lecţii au fost puse în evidenţă prin îngrosare; - metodele run() din clasele Producător şi Consumator sunt scrise ca şi când ele s-ar executa în mod independent, însă sincronizarea firelor se face prin invocarea metodelor sincronizate puneTablou şi preiaTablou ale clasei Monitor; în aceste metode, sincronizarea se face folosind variabila de condiţie booleană dateNoi şi invocând în mod corespunzător metodele wait() şi notify(). Iată un exemplu de executare a acestei aplicaţii:

Incepe executarea metodei main() Sfarsitul metodei main() Incepe executarea firului producator Ciclul i=0 S-a pus tabloul: 827 789 Incepe executarea firului Consumator Ciclul i=1 S-a pus tabloul: 464 312 Ciclul i=0 s-a preluat tabloul 827 789 Ciclul i=2 S-a pus tabloul: 455 995 271 40 583 Ciclul i=1 s-a preluat tabloul 464 312 Ciclul i=3 S-a pus tabloul: 581 193 9 635 Ciclul i=2 s-a preluat tabloul 455 995 271 40 583 Ciclul i=4 S-a pus tabloul: 621 164 215 Ciclul i=3 s-a preluat tabloul 581 193 9 635 Ciclul i=5 S-a pus tabloul: 554 626 791 444 Ciclul i=4 s-a preluat tabloul 621 164 215 Ciclul i=6 S-a pus tabloul: 204 961 Ciclul i=5 s-a preluat tabloul 554 626 791 444 Ciclul i=7 S-a pus tabloul: 476 692 Sfarsitul executiei firului producator Ciclul i=6 s-a preluat tabloul 204 961 Ciclul i=7 s-a preluat tabloul 476 692 Sfarsitul executiei firului Consumator

Se observă că, deşi cele două fire se derulează în mod autonom, exista între ele o sincronizare corectă, în sensul că datele puse de producător la un ciclu cu un anumit indice, sunt preluate de consumator la ciclul cu acelaşi indice (de exemplu datele puse de producator în ciclul 3 sunt luate de consumator tot în ciclul 3). Aşa dar, nu există date pierdute sau preluate de două ori.

353

Severin Bumbaru Exemplul 2 În fişierul Bondari1.java se dă un exemplu de aplicaţie cu interfaţă grafică, în care există trei fire de execuţie (în afară de cel al metodei main()): două fire din clasa Bondar, care calculeaza fiecare mişcările unui "bondar", şi un fir de executie din clasa Fereastră, care reprezintă grafic mişcările celor doi bondari. În acest caz, ambii "bondari" se mişca în aceeaşi fereastră. Pentru că fiecare din acestea extinde câte o clasa de interfaţă grafică (respectiv clasele JPanel şi Canvas), pentru realizarea firelor de execuţie s-a folosit interfaţa Runnable. Cele două fire "bondar" (respectiv fir1 şi fir2) au rolul de producător, iar firul care conţine fereastra de afişare (respectiv fir3) are rolul de consumator. Rolul monitorului este îndeplinit de instanţa clasei CutiePoştală, care nu conţine decât variabila de condiţie a monitorului valoareNoua şi două metode sincronizate amPus şi amLuat. Având în vedere că există posibilitatea ca, la un moment dat, să existe în starea Waiting mai multe fire de aşteptare care trebuie reactivate, în aceste metode s-a folosit invocarea notifyAll() în loc de notify(). Dacă se execută această aplicaţie se poate vedea cum cei doi "bondari" evoluează în mod independent în aceeaşi fereastră, putându-se ajusta în mod independent perioada şi amplitudinea fiecăruia.

Întrebări
Nivel 1
1. Ce este un proces (în general)? 2. Cum se defineşte procesul în informatică? 3. Ce se înţelege prin multitasking? 4. Ce sunt firele de execuţie? 5. Ce se înţelege prin multithreading? 6. În ce moduri se poate programa o clasă de fire de execuţie? 7. Ce este clasa Thread? 8. Care sunt principalele metode ale clasei Thread? 9. La ce serveşte metoda run() a clasei Thread? 10. În ce mod este invocată metoda run() a clasei Thread? 11. Ce este interfaţa Runnable şi la ce foloseşte? 12. În ce stare se găseşte firul de execuţie imediat după ce el a fost creat? 13. În ce clasă există metodele wait(), notify() şi notifyAll() şi la ce folosesc ele? 14. Ce metode conţine interfaţa Runnable? 15. Cum se creeaza un fir de execuţie folosind un obiect cu interfaţa Runnable?

Nivel 2
1. Ce sunt procesele paralele? 2. Ce sunt procesele concurente? 3. În ce situaţii şi cum se iau în consideraţie priorităţile proceselor?

354

Programarea orientata pe obiecte în limbajul Java 4. Ce se înţelege prin partajarea timpului? 5. Ce fire de execuţie există obligatoriu în timpul funcţionării maşinii virtuale Java? 6. Ce este un demon? 7. În ce stare trece firul de execuţie după invocarea metodei start()? 8. La ce serveşte metoda sleep() şi în ce stare trece firul de execuţie la invocarea ei? 9. Prin ce metodă se modifică prioritatea unui fir de execuţie? 10. Care este prioritatea implicită a unui fir de execuţie? 11. În ce stare trece firul de execuţie în timpul executării unei operaţii de intrare/ieşire? 12. În ce stare trece firul de execuţie după ce a invocat metoda wait()? 13. În ce stare trece firul de execuţie după ce a expirat intervalul de "somn" dat prin metoda sleep()? 14. În ce stare trece un fir de execuţie blocat după ce s-a încheiat operaţia de intrare/ieşire solicitată? 15. Când se încheie executarea unui fir de execuţie şi în ce stare trece el în acest caz? 16. Dece este necesară sincronizarea firelor de execuţie? 17. Cum se sincronizează firele de execuţie în Java? 18. Ce este un monitor? 19. Ce este o metodă sincronizată şi la ce foloseşte? 20. În ce situaţie firul de execuţie devine proprietarul unui monitor? 21. Ce se întamplă în timpul cât un fir de execuţie este proprietarul unui monitor? 22. Cum se realizează sincronizarea între un fir de execuţie producător şi unul consumator? 23. La ce serveşte variabila de condiţie a monitorului? 24. În ce fel de metode se pot invoca metodele wait(), notify() şi notifyAll()? 25. Care este efectul invocării metodei notify()? 26. Care este efectul invocării metodei notifyAll()?

355

Severin Bumbaru

Index de clase şi de interfeţe
În acest index sunt cuprinse rezumate ale descrierilor de clase şi interfete din Java API care sunt folosite în acest curs practic. În Java API există mult mai multe pachete, clase şi interfeţe decât se prezintă aici. Descrierile complete ale tuturor claselor şi interfetelor pot fi găsite în documentaţia Java API la următoarele adrese: java.sun.com/j2se/1.4/docs/api/ - la firma Sun Microsystems; http://lib.cs.ugal.ro/java/jdk140/api/index.html - pe intranetul Catedrei de Calculatoare si Informatica Aplicata al Universităţii "Dunărea de Jos" din Galaţi

Pachetul java.lang
Clase
Byte - clasă acoperitoare pentru tipul byte - v.cap.4 Boolean - clasă acoperitoare pentru tipul boolean – v. cap.4 Character - clasă acoperitoare pentru tipul char – v.cap.4 Class - clasa claselor – v.cap.4 Double - clasă acoperitoare pentru tipul double – v.cap.4 Float - clasă acoperitoare pentru tipul float – v. cap.4 Integer - clasă acoperitoare pentru tipul int – v. cap.4 Long - clasă acoperitoare pentru tipul long – v. cap.4 Math - clasă care oferă metode pentru calcularea funcţiilor matematice uzuale – v.cap.4 şi completarea din acest index, pag. 361 Number - clasă abstractă, rădăcina claselor acoperitoare numerice – v. cap.4 Object - rădăcina ierarhiei de clase Java (un obiect oarecare) – v.cap.4 şi completarea din acest index pag. 363 Short - clasa acoperitoare pentru tipul short – v.cap.4 String - clasa şirurilor de caractere nemodificabile - v.cap.4 şi completarea din acest index pag. 364 StringBuffer - clasa şirurilor de caractere modificabile (instanţa este o zonă tampon care conţine un şir modificabil) – v. cap.4 şi completarea din acest index pag. 369 System - clasa sistemului de execuţie – v. cap.4 Thread - clasa firelor de execuţie – v.cap.12 Void - clasă acoperitoare pentru tipul void – v.cap.4

Interfeţe
Cloneable - implementată de clasele pentru care se poate folosi metoda clone() din clasa Object. Nu contine metode. Comparable - implementată de clasele ale căror instanţe sunt comparabile între ele (formează o mulţime ordonată). – v. specificatia de la pag. 372 Runnable - implementată de clasele, ale căror instanţe pot fi rulate ca fire de execuţie. – v.cap.12.

Pachetul java.io
Conţine clase şi interfeţe necesare pentru operaţiile de intrare/ieşire (input/output - io).

356

Programarea orientata pe obiecte în limbajul Java

Clase
(ierarhiile de clase de intrare/ieşire sunt prezentate în cap.11, secţiunea pachetul java.io din acest manual) BufferedInputStream - clasa fluxurilor de intrare de octeţi, cu zonă tampon – v. cap.11 si completarea de la pag.373 BufferedOutputStream - clasa fluxurilor de ieşire de octeţi, cu zonă tampon – v.cap.11 şi completarea de la pag. 373. BufferedReader - clasa fluxurilor de intrare de caractere, cu zonă tampon – v.cap.11 şi completarea de la pag. 374. BufferedWriter - clasa fluxurilor de ieşire de caractere, cu zonă tampon – v.cap.11 şi completarea de la pag. 374. ByteArrayInputStream - clasa fluxurilor care citesc dintr-un tablou de octeţi situat în memoria internă + v.cap.11 şi completarea de la pag. 374. ByteArrayOutputStream - clasa fluxurilor care scriu într-un tablou de octeţi situat în memoria internă + v.cap.11 şi completarea de la pag. 374. CharArrayReader - clasa fluxurilor care citesc dintr-un tablou de caractere situat în memoria internă – v. cap.11 şi pag. 376. CharArrayWriter - clasa fluxurilor care scriu într-un tablou de caractere situat în memoria internă – v. cap.11 şi pag.376 DataInputStream - clasa fluxurilor de octeţi de intrare pentru date - v. cap.11 DataOutputStream - clasa fluxurilor de octeţi de ieşire de date – v. cap.11 File - clasă ale cărei instanţe conţin informaţii despre fişiere – v. cap.11 FileDescriptor - clasa descriptorilor de fişiere – v. cap.11 şi pag.377 FileInputStream - clasa fluxurilor de octeţi de intrare din fişiere (clasa fişierelor deschise pentru intrare) – v. cap.11 FileOutputStream - clasa fluxurilor de octeţi de ieşire în fişiere (clasa fişierelor deschise pentru ieşire) – v. cap.11. FileReader - clasa fluxurilor de caractere de citire din fişiere – v. cap.11 FileWriter - clasa fluxurilor de caractere de scriere în fişiere – v. cap.11 FilterInputStream - clasa fluxurilor de intrare de octeţi cu filtru – v. cap.11 şi pag. 377. FilterOutputStream - clasa fluxurilor de ieşire de octeţi cu filtru – v.cap.11 şi pag. 378. FilterReader - clasa flucurilor de intrare de caractere cu filtru – v. cap.11 şi pag. 378. FilterWriter - clasa flucurilor de ieşire de caractere cu filtru – v. cap.11 şi pag.378. InputStream - clasa fluxurilor de intrare de octeţi – v. cap.11 ObjectInputStream - clasa fluxurilor de intrare de obiecte – v. cap.11 ObjectOutputStream - clasa fluxurilor de ieşire de obiecte – v. cap.11 OutputStream - clasa fluxurilor de ieşire de octeţi – v. cap.11 PrintStream - clasa fluxurilor de imprimare de octeţi – v. cap.11 PrintWriter - clasa fluxurilor de imprimare de caractere – v. cap.11 RandomAccessFile - clasa fişierelor cu acces direct (acces aleator). – v. cap.11 Reader - clasa fluxurilor de intrare de caractere – v. cap.11 Writer - clasa fluxurilor de ieşire de caractere – v. cap.11

Interfeţe
Serializable - interfaţă pe care trebuie să o aibă obiectele serializabile (care pot fi scrise cu ObjectOutputStream şi citite cu ObjectInputStream) – v. cap.11

357

Severin Bumbaru

Pachetul java.util
Clase
EventObject - clasa obiectelor care conţin informaţii despre evenimente – v. cap. 7 şi pag. 379

Interfeţe
EventListener - interfaţă generică pentru ascultătoarele de evenimente. – v. cap.7 şi pag. 379

Pachetul java.awt
Clase
AWTEvent - superclasa ascultătoarelor de evenimente generate de componentele interfeţei grafice – v. cap.7 şi pag. 380. BorderLayout - gestionar de poziţionare – v. cap.7 şi pag. 381. Button - clasa butoanelor simple – v. cap.8 şi pag. 382. Canvas - clasă specială de componente pe care se trasează desene - v. cap.9 şi pag. 383 CardLayout - gestionar de poziţionare – v. cap. 7 şi pag. 383 Color - clasa culorilor – v. cap.9 şi pag. 384 Component - superclasa tuturor claselor de componente ale interfeţei grafice. – v. cap.7 şi pag. 387 Container - clasa containerelor (componente care conţin alte componente) – v. cap.7 şi pag. 393. Dialog - clasa ferestrelor de dialog + v. cap.9 şi pag. 394. Dimension - dimensiunile unei componente – v. cap.9 şi pag. 396. Event - clasa evenimentelor din JDK1.0 (înlocuită acum prin clasa AWTEvent) – v. cap.7 şi pag. 396. FlowLayout - gestionar de poziţionare – v. cap.7 şi pag. 397. Font - clasa fonturilor – v. cap.9 şi pag. 398. Frame - clasa ferestrelor principale ale aplicaţiilor. – v. cap.7 şi pag. 399. Graphics - clasa contextelor grafice simple – v. cap.9 Graphics2D - clasa contextelor grafice 2D – v. cap.9 şi pag. 401. GridBagLayout - gestionar de poziţionare – v. cap.7 şi pag. 401. GridLayout - gestionar de poziţionare – v. cap.7 şi pag. 401. Insets - clasa inserţiilor (marginilor libere ale containerelor) + v. pag. 402. Panel - clasa panourilor – v. cap.7 şi pag. 402. Point - clasa punctelor – v. pag. 403. Rectangle - clasa dreptunghiurilor – v. pag. 404. Window - clasa ferestrelor. + v. cap.7 şi pag. 405.

Interfeţe
LayoutManager - interfaţă pentru clsasele de gestionare a poziţionării – v. cap.7 şi pag. 406 LayoutManager2 - interfaţă pentru clasele de gestionare a poziţionării cu restricţii – v. cap.7 şi pag. 407.

358

Programarea orientata pe obiecte în limbajul Java

Pachetul java.awt.event
Clase
ActionEvent - eveniment de acţiune – v. cap.8 şi pag. 408. AdjustementEvent - eveniment de ajustare – v. cap.8 şi pag. 408. ComponentAdapter - adaptor pentru ascultătoarele de evenimente de componentă.- v.cap.8 şi pag. 409. ComponentEvent - eveniment de componentă – v.cap.8 şi pag. 409. InputEvent - clasă abstractă. Rădăcina ierarhiei claselor de evenimente de intrare – v.cap.7 şi pag.410. ItemEvent - eveniment de articol (selectare sau deselectare) – v.cap.8 şi pag.411. KeyAdapter - adaptor pentru ascultarea evenimentelor de tastă – v.cap.7 şi pag.411. KeyEvent - eveniment de tastă – v.cap.7 şi pag.412. MouseAdapter - adaptor pentru ascultarea evenimentelor de mouse – v.cap.7 şi pag.416. MouseEvent - eveniment de mouse – v.cap.7 şi pag.416. MouseMotionAdapter - adaptor pentru ascultarea evenimentelor de mişcare a mouse-ului – v.cap.7 şi pag.417. TextEvent - eveniment de text - v.cap.8 şi pag.417. WindowAdapter - adaptor pentru ascultarea evenimentelor de fereastră – v.cap.7 şi pag.418. WindowEvent - eveniment de fereastră - v. cap.7 şi pag.418.

Interfeţe
ActionListener - interfaţă pentru ascultătoarele de evenimente de acţiune – v.cap.8 şi pag.419 AdjustmentListener - interfaţă pentru ascultătoarele de evenimente de ajustare – v.cap.8 şi pag.420. ComponentListener - interfaţă pentru ascultătoarele de evenimente de componentă – v.cap.8 şi pag.420. ItemListener - interfaţă pentru ascultătoarele de evenimente de articol – v.cap.8 şi pag.421. KeyListener - interfaţă pentru ascultătoare de evenimente de tastă – v.cap.7 şi pag.421. MouseListener - interfaţă pentru ascultătoare de evenimente de mouse v.cap.7 şi pag.421. MouseMotionListener - interfaţă penteru ascultătoare de evenimente de mişcare a mouseului – v.cap.7 şi pag.422. TextListener - interfaţă pentru ascultătoare de evenimente de text – v.cap.8 şi pag.422. WindowListener - interfaţă pentru ascultătoare de evenimente de fereastră – v.cap.7 şi pag.423

Pachetul javax.swing
Clase
AbstractButton - clasă abstractă. Superclasa claselor de butoane – v.cap.8 şi pag. 423. BorderFactory - clasă care permite producerea de borduri pentru componentele Swing – v. cap.8. Box - container sub formă de casetă – v. cap.8. Box.Filler - clasa componentelor invizibile, folosite pentru distanţare în instanţele clasei Box + v. cap.8 şi pag.425. BoxLayout - gestionar de poziţionare în casetă – v. cap.7.

359

Severin Bumbaru ButtonGroup - clasa grupurilor de butoane – v.cap.8 şi pag.426. JApplet - clasa applet-urilor din Swing – v.cap.10 şi pag. 426. JButton - clasa butoanelor – cap.8 şi pag.427. JCheckBox - clasa casetelor de validare – v. cap.8. JCheckBoxMenuItem - clasa articolelor de meniu cu casetă de validare – v. cap.8 şi pag.427. JColorChooser - clasa selectorului de culori + v.cap.9. JComboBox - clasa listelor ascunse – v. cap.8. JComponent - rădăcina ierarhiei de clase de componente Swing – v.cap.8 şi pag.428. JDialog - clasa ferestrelor de dialog – v.cap.9 şi pag.430. JEditorPane - clasă pentru editoare de text formatat – v.cap.8 şi pag.432. JFileChooser - clasa selectoarelor de fişiere – v.cap.8 şi pag.432. JFrame - clasă folosită în special pentru ferestrele principale ale aplicaţiilor – v.cap.7 şi pag.433. JLabel - clasa etichetelor (componente de afişare needitabile) + v.cap.8 şi pag.435. JList - clasa listelor afişabile în interfaţa grafică – v. cap.8. JMenu - clasa meniurilor – v. cap.8 şi pag.436. JMenuBar - clasa barelor de menu – v. cap.8 şi pag.437. JMenuItem - clasa articolelor de meniu – v. cap.8 şi pag.438. JOptionPane - clasă cu metode pentru realizarea unor ferestre de dialog standard – v. cap.9 şi pag.439. JPanel - clasa panourilor (containere simple) – v.cap.7 şi pag.441. JPasswordField - clasa câmpurilor pentru introducerea parolei – v. cap.8 şi pag.441. JPopupMenu - clasa meniurilor volante (meniuri pop-up) – v. cap.8. JProgressBar - clasa barelor de progres – v. cap.8 şi pag.442. JRadioButton - clasa butoanelor radio – v. cap.8. JRadioButtonMenuItem - clasa articolelor de meniu cu buton radio – v. cap.8 şi pag.443. JScrollBar - clasa barelor de defilare – v. cap.8 şi pag. 444. JScrollPane - clasa panourilor cu bare de defilare – v. cap.7 şi pag.445. JSeparator - clasa separatoarelor de meniu v. cap.8 şi pag.446. JSlider - clasa riglelor cu cursor – v. cap.8. JSplitPane - clasa panourilor care pot fi scindate - v. cap.7 şi pag.447. JTabbedPane - clasa panourilor tabulate – v. cap.8 şi pag.448. JTable - clasa tabelelor – v. cap.8 şi pag. 450. JTextArea - clasa ariilor de text – v. cap.8. JTextField - clasa câmpurilor de text – v. cap.8. JTextPane - clasă pentru editoare de text stilizat – v. cap.8 şi pag.450. JToggleButton - superclasa butoanelor cu două stări stabile – v. cap.8 şi pag.451 JWindow - clasa ferestrelor – v. cap.7 şi pag. 451.

Pachetul javax.swing.event
Clase
ChangeEvent - eveniment de schimbare a stării sursei – v. cap.7 şi pag.452. ListDataEvent - eveniment de modificare a datelor dintr-o listă – v.cap.8 şi pag.452. ListSelectionEvent - clasa evenimentelor de selectare a articolelor de listă v.cap.8 şi pag.453 MenuEvent - clasa evenimentelor de meniu – v. cap.8 şi pag.453. MouseInputAdapter - adaptor pentru ascultătoarele de evenimente de mouse – v. cap.7 şi

360

Programarea orientata pe obiecte în limbajul Java pag.454. PopupMenuEvent - clasa evenimentelor generate de meniuri pop-up – v.cap.8 şi pag.454.

Interfeţe
ChangeListener - ascultător de evenimente de schimbare a stării – v.cap.7 şi pag.454. ListDataListener - ascultător de evenimente de modificare a conţinutului unei liste – v.cap.8 şi pag.455. ListSelectionListener - ascultător de evenimente de modificare a articolelor selectate dintr-o listă – v. cap.8 şi pag.455. MenuListener - ascultător al evenimentelor de meniu – v. cap.8 şi pag.455. MouseInputListener - ascultător de mouse - v. cap.7 şi pag.456. PopupMenuListener - ascultător al evenimentelor de meniu pop-up v. cap.8 şi pag.456.

361

Severin Bumbaru Completări la descrierea claselor şi interfeţelor

Clasa Math
public final class Math extends Object

Clasa Math contine metode statice pentru calcularea unor functii matematice uzuale, cum sunt functiile trigonometrice, radacina patrata si altele. Campuri:
public static final double E public static final double PI

- numarul e (baza logaritmilor naturali); - numarul pi (raportul dintre circumferinta cercului si

diametru). Metode:
public static double sin(double a)- intoarce sinusul

trigonometric sin a, unde unghiul

a este in radiani.
public static double cos(double a)- intoarce cosinusul

trigonometric cos a, unde

unghiul a este in radiani;
public static double tan(double a)- intoarce tangenta trigonometrica

tg a, unde

unghiul a este in radiani;
public static double asin(double a)- intoarce arcsin public static double acos(double a)- intoarce arccos public static double atan(double a)- intoarce arctg

a in intervalul [-pi/2, pi/2]; a in intervalul [0.0, pi];

a in intervalul [-pi/2, pi/2]; argumentul angDeg din

public static double toRadians(double angdeg)- converteste

grade in radiani;
public static double toDegrees(double angrad)- converteste

argumentul angrad din

radiani in grade;
public static double exp(double a)- calculeaza functia

exponentiala ea; natural ln a;

public static double log(double a)- calculeaza logaritmul public static double sqrt(double a)- calculeaza radacina

patrata a argumentului a.

Daca argumentul este negativ sau NaN, rezultatul este NaN.
public static double IEEEremainder(double f1, double f2)- calculeaza restul impartirii f1/f2 conform prescriptiilor standardului IEEE 754. Restul este egal matematic cu

362

Programarea orientata pe obiecte în limbajul Java valoarea expresiei f1-f2*n, unde n este numarul intreg cel mai apropiat de valoarea exacta a raportului f1/f2. Daca restul este zero, el are semnul primului argument.
public static double ceil(double a)- intoarce,

sub forma de valoare double, cel mai mic numar intreg care nu este mai mic decat argumentul (rotunjire prin adaos);
public static double floor(double a)- intoarce,

sub forma de valoare double, cel mai mare numar intreg care nu este mai mare decat argumentul (rotunjire prin micsorare);
public static double rint(double a)- intoarce,

sub forma de valoare double, numarul

intreg cel mai apropiat de argument;
public static double atan2(double a, double b)- calculeaza arctg(a/b) in

intervalul [-pi, pi] tinand seama de semnele argumentelor a si b (se considera ca se calculeaza argumentul theta la conversia din coordonate carteziene (b, a) in coordonate polare (r, theta)).
public static double pow(double a, double b)- calculeaza a . Daca (a==0 sau daca (a<=0 si b nu este un numar intreg) se genereaza o exceptie aritmetica. public static int round(float a)- se intoarce numarul
b

si b<=0)

intreg cel mai apropiat de argument. Daca acest intreg este mai mic decat marginea inferioara pentru tipul int, rezultatul este Integer.MIN_VALUE. Daca acest intreg este mai mare decat marginea superioara pentru tipul int, se obtine rezultatul Integer.MAX_VALUE.
public static long round(double a)- intoarce numarul

intreg cel mai apropiat de

argument (cu aceleasi observatii ca la metoda precedenta).
public static double random()- intoarce un

numar pseudoaleator in intervalul [0.0, 1.0], avand o lege de repartitie (aproximativ) uniforma in acest interval.
public static int abs(int a)- intoarce modului

argumentului a (valoarea absoluta); argumentului a (valoarea absoluta);

public static long abs(long a)- intoarce modului public static float abs(float a)- intoarce

modulul argumentului a (valoarea

absoluta);
public static double abs(double a)- intoarce modulul

argumentului a (valoarea

absoluta);
public static int max(int a, int b)- intoarce cel public static long max(long a, long b)- intoarce

mai mare din cele doua argumente; cel mai mare din cele doua

argumente;
public static float max(float a, float b)- intoarce

cel mai mare din cele doua

argumente;

363

Severin Bumbaru
public static double max(double a, double b)- intoarce

cel mai mare din cele doua

argumente;
public static int min(int a, int b)- intoarce cel

mai mic din cele doua argumente; mai mic din cele doua

public static float min(float a, float b)intoarce cel

argumente;
public static double min(double a, double b)intoarce cel

mai mic din cele doua

argumente;

Class Object
java.lang.Object -------------------------------------------------------------------------------public class Object

Clasa Object este radacina ierarhiei de clase Java. Orice alta clasa ore clasa Object ca superclasa. Toate celelalte clase, inclusiv tablourile, implementeaza metodele acestei clase ------------------------------------------------------------------------------Constructor:
public Object()

Metode:
protected Object clone()

Creaza si intoarce o copie a acestui obiect.
public boolean equals(Object obj)

Indica daca acest obiect este sau nu "egal cu" obiectul obj primit ca argument.
protected void finalize()

Este apelata de colectorul de reziduuri atunci cand acesta constata ca nu mai exista referinte la obiectul respectiv.
public Class getClass()

Intoarce clasa caruia ii apartine obiectul.
public int hashCode()

Intoarce codul de dispersie al obiectului
public void notify()

Notifica un singur fir de executie dintre cele care asteapta acest obiect.

364

Programarea orientata pe obiecte în limbajul Java
public void notifyAll()

Notifica toate firele de executie care asteapta acest obiect
public String toString()

Intoarce reprezentarea sub forma de String a acestui obiect.
public void wait()

Pune firul de executie curent in asteptare, pana cand alt fir de executie invoca metoda notify sau notifyAll() pentru acest obiect.
public void wait(long timeout)

Pune firul de executie curent in asteptare, pana cand alt fir de executie invoca metoda notify sau notifyAll() pentru acest obiect, sau pana cand exprira intervalul de timp dat timeout dat ca argument. Timpul este exprimat in milisecunde.
public void wait(long timeout, int nanos)

Pune firul de executie curent in asteptare, pana cand alt fir de executie invoca metoda notify sau notifyAll() pentru acest obiect, sau pana cand exprira intervalul de timp dat timeout dat ca argument, sau pana cand alt fir de asteptare il intrerupe pe cel curent. Timpul de asteptare, exprimat in nanosecunde, este in acest caz 1000000*timeout+nanos, unde nanos este un intreg in intervalul 0..999999.

Clasa String
public final class String extends Object implements Serializable, Comparable

Instantele clasei String sunt siruri de caractere. Orice literal sir in limbajul Java, de exemplu "abc", este un astfel de obiect. Sirurile sunt constante, deci continutul lor nu poate fi modificat. Pentru a obtine siruri modificabile se va folosi clasa StringBuffer. Nota: amintim ca in limbajul Java reprezentarea interna a caracterelor se face pe 16 biti, in Unicode. Campuri:
public static Comparator CASE_INSENSITIVE_ORDER Campul contine un Comparator care ordoneaza obiectele-sir

cand ele sunt comparate

cu metoda comparetoIgnoreCase(). Constructori:
public String()

Creeaza un sir vid.
public String(byte[] bytes)

Creeaza un obiect al clasei String care contine caracterele din tabloul de octeti primit ca argument. Se considera ca in acest tablou caracterele sunt reprezentate pe 8 biti, in codul local valabil pe calculatorul pe care se executa programul. Daca referinta la tablou este nula se genereaza o NullPointerException. 365

Severin Bumbaru
public String(byte[] bytes, int offset, int length)

Actioneaza similar cu constructorul precedent, cu observatia ca se preiau din tabloul bytes numai length octetii de pe pozitiile care incep de la indicele offset. Pot fi generate NullPointerException daca referinta la tablou este nula, sau o IndexOutOfBoundsException daca indicii sunt gresiti.
public String(byte[] bytes, int offset, int length, String enc)

Actioneaza similar cu constructorul precedent, dar la conversia din octet in Unicode se foloseste sistemul de codificare a caracterelor enc. Pot fi generate NullPointerException daca referinta la tablou este nula, sau o IndexOutOfBoundsException daca indicii sunt gresiti,sau UnsupportedEncodingException daca argumentul enc este gresit.
public String(byte[] bytes, String enc)

Construieste un sir care contine caracterele din tabloul de octeti bytes. Se considera ca in acest tablou caracterele sunt reprezentate in codul enc. Se genereaza NullPointerException daca referinta la tablou este nula, sau UnsupportedEncodingException daca argumentul enc este gresit..
public String(char[] value)

Construieste un sir care contine caracterele din tabloul de caractere value. Se genereaza NullPointerException daca referinta la tablou este nula.Pot fi generate NullPointerException daca referinta la tablou este nula, sau o IndexOutOfBoundsException daca indicii sunt gresiti.
public String(char[] value, int offset, int count)

Actioneaza similar cu constructorul precedent, dar preia din tabloul value numai count caractere, incepand cu cel de pe pozitia offset. Pot fi generate NullPointerException daca referinta la tablou este nula, sau o IndexOutOfBoundsException daca indicii sunt gresiti.
public String(String value)

Creeaza un nou sir, in care il copiaza pe cel dat ca argument.
public String(StringBuffer buffer)

Creeaza un nou sir, care contine aceleasi caractere ca cele din argumentul buffer, care apartine clasei StringBuffer (deci este o zona-tampon pentru siruri). Metode:
public char charAt(int index)

Intoarce caracterul situat in sir pe pozitia index. Se genereaza IndexOutOfBoundsException daca indicele este gresit.
public int compareTo(Object o) Compara acest sir cu obiectul o

primit ca argument. Daca acest obiect nu este un sir, se genereaza o exceptie de incompatibilitate de clase (ClassCastException). Daca o este un sir, atunci compararea se face la fel ca in metoda urmatoare.
public int compareTo(String anotherString) Compara acest sir cu sirul anotherString primit

ca argument. Daca cele doua siruri sunt identice, intoarce 0 (zero). Daca acest sir il precede pe o intoarce o valoare negativa, iar

366

Programarea orientata pe obiecte în limbajul Java daca ii succede lui o intoarce o valoare pozitiva. Compararea sirurilor se face in ordine lexicografica (in ordinea in care ar fi plasate intr-un dictionar).
public int compareToIgnoreCase(String str) Compara lexicografic acest sir cu sirul str primit

ca argument, ignorand deosebirea

dintre literele majuscule si cele minuscule.
public String concat(String str)

Concateneaza acest sir cu sirul str primit ca argument.
public static String copyValueOf(char[] data)

Intoarce un sir care contine aceleasi caractere cu cele din tabloul data. Se poate genera NullPointerException daca referinta la tablou este nula.
public static String copyValueOf(char[] data, int offset, int count) Similar cu metoda precedenta, dar se preiau din tabloul data numai count caractere incepand cu pozitia offset. Se poate genera NullPointerException daca referinta la tablou

este nula
public boolean endsWith(String suffix)

Testeaza daca acest sir se termina cu subsirul suffix.
public boolean equals(Object anObject)

Testeaza daca acest sir este "egal" cu obiectul anObject.
public boolean equalsIgnoreCase(String anotherString)

Testeaza daca acest sir contine aceleasi caractere ca argumentul anotherString, ignorand deosebirea dintre literele mici si cele mari.
public byte[] getBytes()

Converteste acest sir intr-un tablou de octeti, luind in consideratie codificarea locala de pe calculatorul pe care ruleaza programul.
public byte[] getBytes(String enc)

Similara cu metoda precedenta, dar conversia se face folosind codificarea enc. Se genereaza UnsupportedEncodingException daca argumentul enc este gresit.
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) Copiaza caracterele din acest sir in tabloul de caractere de destinatie char[]. Copierea incepe de la caracterul de pe pozitia srcBegin si se termina la caracterul de pe pozitia srcEnd-1. Plasarea in tabloul de destinatie incepe cu pozitia dstBegin. Se poate genera

NullPointerException daca referinta la tablou este nula sau IndexOutOfBoundsException daca indicii sunt gresiti..
public int hashCode()

Intoarce codul de dispersie pentru acest sir.
public int indexOf(int ch)

Intoarce indicele la care se gaseste in sir prima aparitie a caracterului ch. Daca acest caracter nu exista in sir, intoarce -1.

367

Severin Bumbaru
public int indexOf(int ch, int fromIndex)

Similar cu metoda precedenta, dar cautarea in sir incepe de la pozitia fromIndex.
public int indexOf(String str)

Intoarce indicele pozitiei de pe care in acest sir apare prima data subsirul str.
public int indexOf(String str, int fromIndex)

Similar cu metoda precedenta, dar cautarea in acest sir incepe de la pozitia fromIndex.
public String intern()

Intoarce o reprezentare canonica a acestui obiect-sir.
public int lastIndexOf(int ch)

Intoarce indicele ultimei pozitii pe care apare caracterul ch in acest sir.
public int lastIndexOf(int ch, int fromIndex)

Intoarce indicele ultimei pozitii pe care se gaseste caracterul ch daca se face cautarea inapoi incepand de la pozitia fromIndex.
public int lastIndexOf(String str)

Intoarce indicele ultimei aparitii in acest sir a subsirului str.
public int lastIndexOf(String str, int fromIndex)

Similar cu metoda precedenta, dar cautarea inapoi se face de la pozitia fromIndex.
publicint length()

Intoarce lungimea acestui sir (numarul de caractere continute).
public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) Testeaza daca o regiune din acest sir este egala cu o regiune din sirul other primit ca argument. Daca primul argument (ignoreCase) are valoarea true, comparatia se face ignorand deosebirea dintre caracterele mari si cele mici. Argumentele tooffset si ooffset

reprezinta indicii pozitiilor de la care incep regiunile comparate in acest sir si, respectiv, in sirul-argument other. Argumentul len este lungimea celor doua zone comparate.
public boolean regionMatches(int toffset, String other, int ooffset, int len)

Similar cu metoda precedenta, dar se ia in consideratie deosebirea intre literele mici si cele mari.
public String replace(char oldChar, char newChar)

Intoarce un nou sir, care are acelasi continut cu acest sir, dar in care toate aparitiile caracterului oldChar sunt inlocuite prin caracterul newChar.
public boolean startsWith(String prefix) Testeaza daca acest sir incepe cu subsirul prefix. public boolean startsWith(String prefix, int toffset) Testeaza daca subsirul prefix incepe in acest sir de pe pozitia index.

368

Programarea orientata pe obiecte în limbajul Java
public String substring(int beginIndex)

Intoarce un nou sir, care contine caracterele acestui sir incepand de la pozitia beginIndex, pana la sfarsit.
public String substring(int beginIndex, int endIndex) Intoarce subsirul situat intre pozitiile beginIndex si endIndex-1. public char[] toCharArray()

Converteste acest sir intr-un tablou de caractere.
public String toLowerCase()

Intoarce un nou sir, in care literele mari ale acestui sir sunt inlocuite cu litere mici.
public String toLowerCase(Locale locale)

Similar cu metoda precedenta, dar inlocuirea literelor mari cu cele mici se face respectand conventia locale.
public String toString()

Intoarce chiar acest sir.
public String toUpperCase()

Intoarce un nou sir, provenit din acest sir, in care toate literele mici au fost inlocuite cu litere mari.
public String toUpperCase(Locale locale)

Similar cu metoda precedenta, dar conversia se face respectand conventia locale.
public String trim()

Intoarce un nou sir, obtinut din acest sir prin eliminarea spatiilor de la inceput si de la sfarsit.
public static String valueOf(boolean b)

Intoarce reprezentarea sub forma de sir a unui argument de tip boolean.
public static String valueOf(char c)

Intoarce un sir care contine numai caracterul c.
public static String valueOf(char[] data)

Intoarce reprezentarea sub forma de sir a tabloului de caractere data. Se genereaza NullPointerException daca referinta la tablou este nula.
public static String valueOf(char[] data, int offset, int count) Intoarce reprezentarea sub forma de sir a subtabloului de lungime count cuprins tabloul data incepand de la indicele offset. Se genereaza NullPointerException daca

in

referinta la tablou este nula.
public static String valueOf(double d)

Intoarce reprezentarea sub forma de sir a unui argument de tip double.
public static String valueOf(float f)

Intoarce reprezentarea ca sir a unui argument de tip float.

369

Severin Bumbaru
public static String valueOf(int i)

Intoarce reprezentarea ca sir a unui argument de tip int.
public static String valueOf(long l)

Intoarce reprezentarea ca sir a unui argument de tip long.
public static String valueOf(Object obj) Intoarce reprezentarea ca sir a obiectului obj.

Metode mostenite de la clasa java.lang.Object:
clone, finalize, getClass, notify, notifyAll, wait

Clasa StringBuffer
public final class StringBuffer extends Object implements Serializable

Un StringBuffer implementeaza un sir de caractere care poate fi modificat. El reprezinta o zona tampon din memorie, in care se ppoate plasa un sir de caractere. Operatiile principale asupra unui StringBuffer sunt metodele append si insert. Fiecare din ele converteste o data intr-un String, pe care apoi il adauga la sirul din StringBuffer sau il insereaza in acesta pe o pozitie data. Fiecare StringBuffer are o capacitate si o lungime. Lungimea este numarul efectiv de caractere continute, iar capacitatea este numarul maxim de caractere care incap in zona tampon rezervata in memorie. Daca, printr-o noua adaugare de caractere, lungimea depaseste capacitatea, atunci capacitatea se mareste in mod automat.

Constructori:
public StringBuffer()

- construieste un StringBuffer vid cu capacitatea 16 caractere. lungime length.

public StringBuffer(int length)- construieste un StringBuffer vid de Daca length este negativ, se genereaza o NegativeArraySizeException. public StringBuffer(String str)- construieste iar capacitatea este lungimea lui str plus 16.

un StringBuffer care contine sirul str,

Metode:
public int length()-

intoarce lungimea sirului continut in StringBuffer (numarul de

caractere).
public int capacity()- intoarce

capacitatea curenta a zonei tampon. 370

Programarea orientata pe obiecte în limbajul Java
public void ensureCapacity(int minimumCapacity)- asigura capacitatea minima a zonei tampon. Daca capacitatea curenta este mai mica decat minimumCapacity, se aloca in

memorie o noua zona tampon si se transfera in ea sirul curent. Capacitatea noului StringBuffer este cea mai mare dintre valoarea argumentului minimumCapacity si dublul vechii capacitati.
public void setLength(int newLength)- seteaza noua lungime a acestui StringBuffer. Daca noua lungime newLength este mai mica decat cea existenta, sirul este trunchiat. Daca newLength este mai mare decat cea curenta, se adauga un numar suficient de caractere nule

(\0000) pentru a se obtine noua lungime. Daca argumentul newLength este negativ se genereaza o exceptie IndexOutOfBoundsException.
public char charAt(int index)- intoarce

caracterul de pe pozitia de indice index. Daca indicele nu se incadreaza in lungimea sirului curent, se genereaza o IndexOutOfBoundsException.
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

Caracterele situate in StringBuffer intre pozitiile srcBegin si srcEnd-1 sunt copiate in tabloul de caractere dst incepand de la pozitia dstBegin. Daca dst este null se genereaza o NullPointerException. Daca srcBegin, srcEnd sau dstBegin nu sunt corecte, se genereaza o IndexOutOfBondsException.
public void setCharAt(int index, char ch)- se pune caracterul ch Daca index este incorect, se genereaza o IndexOutOfBoundsException. public StringBuffer append(Object obj)- se adauga obiectul obj public StringBuffer append(String str)- se adauga sirul str. public StringBuffer append(char[] str)- se adauga toate caracterele tabloul str.

pe pozitia index.

convertit in sir.

continute in

public StringBuffer append(char[] str, int offset, int len)- se adauga la acest StringBuffer len caractere din tabloul str, incepand de la pozitia offset din acest tablou. public StringBuffer append(boolean b)- se

adauga valoarea booleana b convertita in

sir.
public StringBuffer append(char c)- se adauga

caracterul c. convertit in sir. intreg llung l convertit in sir. real f convertit in sir. real d convertit in sir.

public StringBuffer append(int i)- se adauga intregul i public StringBuffer append(long l)- se adauga numarul

public StringBuffer append(float f)- se adauga numarul

public StringBuffer append(double d)- se adauga numarul

371

Severin Bumbaru
public StringBuffer delete(int start, int end)- se elimina caracterele de pe pozitiile de la indicele start pana la end-1. Daca start este negativ, mai mare ca lungimea sau mai mare ca end se genereaza o StringIndexOutOfBoundsException. public StringBuffer deleteCharAt(int index)- se elimina caracterul index sau se genereaza o StringIndexOutOfBoundsException.

de pe pozitia

public StringBuffer replace(int start, int end, String str)- caracterele situate in StringBuffer pe pozitiile de la start la end-1 se inlocuiesc prin subsirul str. Daca indicii start sau end nu sunt corecti se genereaza o StringIndexOutOfBoundsException. public String substring(int start)- intoarce subsirul sau genereaza o StringIndexOutOfBoundsException.

care incepe de la pozitia start

public String substring(int start, int end)- intoarce subsirul pozitia end-1 sau genereaza o StringIndexOutOfBoundsException. public StringBuffer insert(int index, char[] str, int offset, int len)

de la pozitia start la

Incepand de la pozitia index insereaza in StringBuffer len caractere situate in tabloul str de la pozitia offset sau se genereaza o StringIndexOutOfBoundsException.
public StringBuffer insert(int offset, Object obj)- insereaza incepand

de la pozitia offset obiectul obj reprezentat ca sir. Daca offset nu este corect se genereaza o StringingIndexOutOfBoundsException.
public StringBuffer insert(int offset, String str)- se insereaza incepand pozitia offset sirul str. Daca offset este incorect se genereaza o StringIndexOutOfBoundsException.

de la

public StringBuffer insert(int offset, char[] str)- incepand de la pozitia offset se insereaza caracterele din tabloul str. Daca offset este incorect se genereaza o StringIndexOutOfBoundsException. public StringBuffer insert(int offset, boolean b)- se insereaza incepand pozitia offset valoarea booleana b convertita in sir, sau se genereaza o StringIndexOutOfBoundsException. public StringBuffer insert(int offset, char c)- pe pozitia offset caracterul c sau se genereaza o StringIndexOutOfBoundsException. public StringBuffer insert(int offset, int i)- incepand insereaza numarul intreg i convertit in sir sau se genereaza o StringIndexOutOfBoundsException.

de la

se insereaza

de la pozitia offset se

public StringBuffer insert(int offset, long l)- incepand

de la pozitia offset se

insereaza numarul intreg lung l convertit in sir, sau se genereaza o StringIndexOutOfBoundsException. 372

Programarea orientata pe obiecte în limbajul Java
public StringBuffer insert(int offset, float f)- incepand

de la pozitia offset se

insereaza numarul real f convertit in sir sau se genereaza o StringIndexOutOfBoundsException.
public StringBuffer insert(int offset, double d)- incepand

de la pozitia offset se

insereaza numarul real f convertit in sir sau se genereaza o StringIndexOutOfBoundsException.
public StringBuffer reverse()- se pun

caracterele din StringBuffer in ordine inversa

celei actuale.
public String toString()- intoarce un String StringBuffer.

care are acelasi continut cu acest

-------------------------------------------------------------------------------Metode mostenite de la clasa Object:
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait

Interfaţa Comparable
java.lang.Comparable

Interfaţa este implementată de clasele ale căror instanţe pot fi comparate între ele, deci fac parte dintr-o mulţime ordonată. Interfaţa conţine o singură metodă: - compară obiectul de care aparţine cu obiectul obj primit ca argument. Valoarea întoarsă este: 0 (zero) - dacă cele două obiecte sunt egale; valoare negativă, dacă obiectul propriu îl precede pe obj; valoare pozitivă, dacă obiectul propriu este succesor al lui obj.
public int compareTo(Object obj)

Fie a şi b două obiecte, aparţinând unei clase cu interfaţa Comparable. În acest caz: a.compareTo(b)<0 dacă a<b (a precede lui b); a.compareTo(b)==0 dacă a==b; a.compareTo(b)>0 dacă a>b (a succede lui b).

Clasa BufferedInputStream
Face parte din pachetul java.IO şi extinde clasa FilterInputStream. Instanţele acestei clase sunt fluxuri de intrare de octeţi, care conţin o zonă tampon (buffer). Octeţii sunt citiţi unul câte unul din această zonă tampon. Când zona tampon se goleşte, ea se

373

Severin Bumbaru umple din nou, în mod automat, prin citirea din fluxul de intrare căruia acest BufferedInputStream îi este ataşat.

Constructori
public BufferedInputStream(InputStream in) ataşat fluxului de intrare in.

- se creează un BufferedInputStream

public BufferedInputStream(InputStream in, int size) - se creează un BufferedInputStream ataşat fluxului de intrare in, lungimea zonei tampon fiind de size

octeţi.

Metode
Aceleaşi ca în clasa InputStream.

Clasa BufferedOutputStream
Face parte din pachetul java.io şi extinde clasa FilterOutputStream

Constructori
public BufferedOutputStream(OutputStream out) - creează o instanţa a clasei BufferedOutputStream conectată la ieşire la fluxul out şi conţinând o zonă tampon cu

lungimea implicită de 512 octeţi;
public BufferedOutputStream(OutputStream out, int size) - creează o instanţa a clasei BufferedOutputStream conectată la ieşire la fluxul out şi conţinând o zonă tampon cu lungimea de size octeţi;

Metode
Aceleaşi ca în clasa OutputStream.

Clasa BufferedReader
Face parte din pachetul java.io şi extinde clasa Reader.

Constructori
- creează un flux de intrare de caractere, cu zonă tampon de lungime implicită, conectat la ieşirea fluxului de intrare de caractere in.
public BufferedReader(Reader in)

374

Programarea orientata pe obiecte în limbajul Java
public BufferedReader(Reader in, cu zonă tampon de lungime sz, conectat

- creează un flux de intrare de caractere, la ieşirea fluxului de intrare de caractere in.
int sz)

Metode
Aceleaşi ca în clasa Reader.

Clasa BufferedWriter
Face parte din pachetul java.io şi extinde clasa Writer. Caracterele primite sunt acumulate într-o zonă tampon şi sunt transmise la ieşire numai când această zonă tampon s-a umplut, sau când se execută metoda flush().

Constructori
- creează un flux de ieşire de caractere, cu zonă tampon de lungime implicită, conectat la ieşire la fluxul de caractere out.
public BufferedWriter(Writer out) public BufferedWriter(Writer out, int sz) - creează un flux de ieşire de caractere, zonă tampon de lungime sz, conectat la ieşire la fluxul de caractere out.

cu

Metode
Aceleaşi ca în clasa Writer.

Clasa ByteArrayInputStream
Face parte din pachetul java.io şi extinde clasa InputStream. Acest flux conţine o zonă tampon (buffer) din care se citesc datele, situată în memoria internă.

Câmpuri
protected byte[] buf - tabloul de octeţi din care se face citirea, situat în memoria internă. protected int pos - indicele următorului octet care va fi citit din tabloul de octeţi de intrare buf[]. protected int mark - poziţia marcată din flux (este iniţializată implicit la zero şi poate fi modificată prin metoda mark()). protected int count - numărul de octeţi existenţi efectiv în tabloul buf (este cel puţin 0 şi

cel mult egal cu lungimea tabloului)

Constructori
public ByteArrayInputStream(byte[] buf) - creează un citeşte din tabloul buf[] situat în memoria internă.

flux de intrare de octeţi, care

375

Severin Bumbaru
public ByteArrayInputStream(byte[] buf, int offset, int length) - creează un flux de intrare de octeţi, care citeşte dintr-o zonă a tabloului de octeţi buf[] care începe de la poziţia de indice offset a acestui tablou şi are lungimea length.

Metode
Aceleaşi ca la clasa InputStream.

Clasa ByteArrayOutputStream
Face parte din pachetul java.io şi extinde clasa OutputStream. Flux de ieşire de octeţi, care scrie într-un tablou de octeţi situat în memoria internă. Zona tampon (tabloul) în care se scrie are capacitate variabilă: capacitatea lui se mareşte automat dacă prin o nouă scriere se depăşeşte capacitatea existentă. Referinţa la zona tampon pentru scriere se obţine prin metoda toByteArray().

Câmpuri
protected byte[] buf - tabloul de octeţi în care se face scrierea, situat în memoria protected int count - numărul de octeţi valizi existenţi în tabloul buf[].

internă.

Constructori
- creează un flux de ieşire de octeţi care scrie într-o zonă tampon (tablou) care are iniţial capacitatea de 32 octeţi, dar capacitatea creşte automat, dacă este necesar.
public ByteArrayOutputStream()

- creează un flux de ieşire de octeţi care scrie într-o zonă tampon (tablou) care are iniţial capacitatea de size octeţi, dar capacitatea creşte automat, dacă este necesar.
public ByteArrayOutputStream(int size)

Metode
Metodele sunt cele din clasa OutputStream, la care se adaugă:
public void writeTo(OutputStream out) throws IOException - scrie în fluxul ieşire out întregul conţinut al tabloului (yonei tampon) buf, ca şi când s-ar fi fi făcut invocarea de metodă out.write(buf, 0, cont). public byte[] toByteArray() - întoarce un conţinutul valid al yonei tampon buf.

de

nou tablou de octeţi, în care este copiat

public int size() - întoarce dimensiunea curentă a zonei tampon (valoarea câmpului count).

Clasa CharArrayReader

376

Programarea orientata pe obiecte în limbajul Java Face parte din pachetul java.io şi extinde clasa Reader. Instanţele acestei clase sunt fluxuri de intrare de carractere, care citesc dintr-un tablou de caractere situat in memoria internă.

Constructori
public CharArrayReader(char[] buf) - creează un flux de intrare de caractere, citeşte din tabloul de caractere buf[], situat ]n memoria internă.

care de

public CharArrayReader(char[] buf, int offset, int length) - creează un flux intrare de caractere, care citeşte din tabloul de caractere buf[], situat ]n memoria internă. Citirea începe de la poziţia de indice offset, iar yona de citire are lungimea length.

Metode
Aceleaşi ca în clasa Reader.

Clasa CharArrayWriter
Face parte din pachetul java.io şi extinde clasa Writer. Instanţele sunt fluxuri de ieşire de caractere, care scriu într-o zonă tampon sub formă de tablou de caractere extensibil, situat în memoria internă. Datele din zona tampon de ieşire pot fi obţinute prin metodele toCharArray() şi toString().

Constructori
- creează un flux de ieşire de caractere, care scrie într-o zonă tampon situată în memoria internă, a cărei lungime este dată implicit.
public CharArrayWriter()

- creează un flux de ieşire de caractere, care scrie într-o zonă tampon situată în memoria internă, a cărei lungime este initialSize.
public CharArrayWriter(int initialSize)

Metode
Aceleaşi ca în clasa Writer, la care se adaugă:
public void writeTo(Writer out) throws IOException în fluxul de ieşire de caractere out.

- scrie datele din zona tampon

public char[] toCharArray() - întoarce un tablou de caractere, având acelaşi conţinut cu zona tampon de ieşire.
public int size()

- întoarce lungimea curentă a zonei tampon. - întoarce un şir, care are acelaşi conţinut cu zona tampon.

public String toString()

Clasa FileDescriptor
377

Severin Bumbaru Face parte din pachetul java.io. Instanţele clasei FileDescriptor servesc ca manipulatori ai unor fişiere sau socluri deja deschise. Principala utilizare este la crearea unor fluxuri din clasele FileInputStream sau FileOutputStream, conectate la fişiere deja deschise.

Câmpuri
public static final FileDescriptor in sistemului, System.in.

- descriptorul fişierului de intrare standard a - descriptorul fişierului de ieşire standard a - descriptorul fişierului de ieşire pentru erori

public static final FileDescriptor out sistemului, System.out public static final FileDescriptor err a sistemului, System.err

Constructori
public FileDescriptor()

- creează o instanţă a clasei Filedescriptor, care nu este validă

(nu indică nici un fişier) Observaţie: pentru a obţine un descriptor de fişier valid se foloseşte metoda public final FileDescriptor getFD()throws IOException - existentă în clasele FileInputStream şi FileOutputStream. Această metodă întoarce descriptorul fişierului respectiv.

Metode
public boolean valid()

- indică dacă acest descriptor de fişier este valid (se referă la un - forţează sincroniăarea zonelor

fişier sau soclu deschis).
public void sync() throws SyncFailedException

tampon ale sistemului pentru dispozitivul corespunzător.

Clasa FilterInputStream
Face parte din pachetul java.io. Este superclasa claselor de fluxuri de intrare de octeţi, care realizează anumite operaţii de filtrare (prelucrare) a datelor de intrare. De fapt, metodele acestei clase sunt cele moştenite de la superclasa InputStream, deci nu fac nici o prelucrare. Aceste metode pot fi însă redefinite în subclase, pentru a realiza astfel de prelucrări. În pachetul java.io există subclasele DataInputStream, BufferedInputStream şi PushbackInputStream. Putem crea şi propriile noastre subclase.

Constructor
protected FilterInputStream(InputStream in) filtru, care preia fluxul de intrare in.

- creează un flux de intrare de octeţi cu

Metode
Aceleaşi ca în clasa InputStream.

378

Programarea orientata pe obiecte în limbajul Java

Clasa FilterOutputStream
Face parte din pachetul java.io. Este superclasa claselor de fluxuri de ieşire de octeţi, care realizează anumite operaţii de filtrare (prelucrare) a datelor de ieşire. De fapt, metodele acestei clase sunt cele moştenite de la superclasa OutputStream, deci nu fac nici o prelucrare. Aceste metode pot fi însă redefinite în subclase, pentru a realiza astfel de prelucrări. În pachetul java.io există subclasele DataOutputStream, BufferedOutputStream, PrintStream şi altele. Putem crea şi propriile noastre subclase.

Constructor
- creează un flux de ieşire de octeţi cu filtru, care transmite datele la fluxul de ieşire de octeţi out.
public FilterOutputStream(OutputStream out)

Metode
Metodele sunt aceleaşi ca în clasa OutputStream.

Clasa FilterReader
Face parte din pachetul java.io. Este superclasa claselor de fluxuri de intrare de caractere, care realizează anumite operaţii de filtrare (prelucrare) a datelor de intrare. De fapt, metodele acestei clase sunt cele moştenite de la superclasa Reader, deci nu fac nici o prelucrare. Aceste metode pot fi însă redefinite în subclase, pentru a realiza astfel de prelucrări. În pachetul java.io există subclasa PushbackReader. Putem crea şi propriile noastre subclase.

Constructor
protected FilterReader(Reader in) care preia fluxul de intrare in.

- creează un flux de intrare de caractere cu filtru,

Metode
metodele sunt la fel cele din superclasa Reader.

Clasa FilterWriter
Face parte din pachetul java.io. Este superclasa claselor de fluxuri de ieşire de caractere, care realizează anumite operaţii de filtrare (prelucrare) a datelor de ieşire. De fapt, metodele acestei clase sunt cele moştenite de la superclasa Writer, deci nu fac nici o prelucrare. Aceste metode pot fi însă redefinite în subclase, pentru a realiza astfel de prelucrări.

379

Severin Bumbaru

Constructor
protected FilterWriter(Writer out) - creează un flux de ieşire de caractere cu filtru, care transmite datele la fluxul de ieşire de caractere out.

Metode
Aceleaşi ca în superclasa Writer.

Clasa EventObject
public class EventObject extends Object implements Serializable Face parte din pachetul java.util. Este rădăcina ierarhiei de clase de evenimente. Fiecare eveniment este generat de o sursă (source). Fiecare instanţă a clasei EventObject conţine o referinţă la această sursă şi o metodă prin care ea este obţinută.

Constructor
public EventObject(Object source)

- se generează un eveniment, indicându-se sursa.

Metode
public Object getSource() public String toString()

- întoarce sursa evenimentului.

- întoarce reprezentarea sub formă de String a evenimentului.

Interfaţa EventListener
public interface EventListener Face parte din pachetul java.util. Este o interfaţă generică, pe care trebuie să o extindă direct sau indirect - orice interfaţă de ascultătoare de evenimente. Interfaţa nu conţine nici o metodă.

Clasa AWTEvent
public abstract class AWTEvent extends EventObject Face parte din pachetul java.awt. Este superclasa tuturor claselor de evenimente AWT. Orice eveniment AWT are un identificator, reprezentat prin câmpul id. Clasa conţine câmpuri

380

Programarea orientata pe obiecte în limbajul Java statice, care au ca valori măşti pentru selectarea după id a diferitelor tipuri de evenimente. Clasele de evenimente create în afara pachetelor din Java API trebuie să aibă valoarea câmpului id superioară valorii maxime din câmpul RESERVED_ID_MAX. Clasa AWTEvent înlocuieşte clasa Event din JDK 1.0, care a fost menţinută în pachet numai pentru compatibilitate. Subclasele directe ale clasei AWTEvent sunt: ActionEvent, AdjustmentEvent, AncestorEvent, ComponentEvent, HierarchyEvent, InputMethodEvent, InternalFrameEvent, InvocationEvent, ItemEvent, TextEvent. Ele se găsesc în pachetul java.awt.event, cu excepţia clasei InternalFrameEvent, care se găseşte în pachetul javax.swing.event.

Câmpuri
protected int id

- identificatorul tipului de eveniment. - masca pentru selectarea

public static final long COMPONENT_EVENT_MASK

evenimentelor de componentă.
public static final long CONTAINER_EVENT_MASK

- masca pentru selectarea

evenimentelor de container.
public static final long FOCUS_EVENT_MASK

- masca pentru selectarea evenimentelor

de focalizare
public static final long KEY_EVENT_MASK

- masca pentru selectarea evenimentelor de

tastă.
public static final long MOUSE_EVENT_MASK

- masca pentru selectarea evenimentelor

de mouse.
public static final long MOUSE_MOTION_EVENT_MASK

- masca pentru selectarea

evenimentelor de mişcare a mouse-ului.
public static final long WINDOW_EVENT_MASK

- masca pentru selectarea evenimentelor

de fereastră.
public static final long ACTION_EVENT_MASK

- masca pentru selectarea evenimentelor

de acţiune.
public static final long ADJUSTMENT_EVENT_MASK

- masca pentru selectarea

evenimentelor de ajustare.
public static final long ITEM_EVENT_MASK

- masca pentru selectarea evenimentelor de

articol.
public static final long TEXT_EVENT_MASK

- masca pentru selectarea evenimentelor de

text.
public static final long INPUT_METHOD_EVENT_MASK

- masca pentru selectarea

evenimentelor de metodă de intrare.

381

Severin Bumbaru
public static final long PAINT_EVENT_MASK

- masca pentru selectarea evenimentelor

de desenare.
public static final long INVOCATION_EVENT_MASK

- masca pentru selectarea

evenimentelor de invocare.
public static final long HIERARCHY_EVENT_MASK

- masca pentru selectarea

evenimentelor de ierarhie.
public static final long HIERARCHY_BOUNDS_EVENT_MASK

- masca pentru selectarea

evenimentelor de margini ale ierarhiei.
public static final int RESERVED_ID_MAX

- valoarea maximă rezervată pentru id.

Constructori
- construieşte un obiect din clasa AWTEvent, folosind paramerii unei instanţe a clasei Event.
public AWTEvent(Event event)

- construieşte o nouă instanţă a clasei AWTEvent, specificându-se sursa şi tipul de eveniment.
public AWTEvent(Object source, int id)

Metode principale
Metodele clasei EventObject, la care se adaugă:
public int getID()

- întoarce tipul de eveniment. - întoarce reprezentarea sub formă de String a evenimentului.

public String toString()

- întoarce un şir care conţine parametrii evenimentului. Se foloseşte în special la depanarea programului.
public String paramString()

Clasa BorderLayout
public class BorderLayout extends Object implements LayoutManager2, Serializable Suprafaţa containerului prevăzut cu acest gestionar de poziţionare se împarte în cinci zone: NORTH, SOUTH, WEST, EAST şi CENTER. În fiecare din aceste zone poate fi pusă câte o singură componentă. Dăm aici un exemplu de utilizare a unui gestionar de poziţionare din clasa BorderLayout:
p.setLayout(new BorderLayout()); p.add(new Button("OK"), BorderLayout.SOUTH);

În prima instrucţiune, pentru containerul p se setează gestionarul de poziţionare BorderLayout. În a doua instrucţiune, se pune butonul cu inscripţia "OK" în zona SOUTH a aceluiaşi container. Dacă se omite zona, se consideră implicit că adăugarea componentei se face în zona CENTER.

382

Programarea orientata pe obiecte în limbajul Java

Constructori
- se creează un gestionar de poziţionare BorderLayout, fără distanţare între componente.
public BorderLayout()

- se creează un gestionar de poziţionare BorderLayout, având distanţări între componente hgap pe orizontală şi vgap pe verticală.
public BorderLayout(int hgap, int vgap)

Metode
Metodele din interfeţele LayoutManager şi LayoutManager2, la care se adaugă următoarele:
public int getHgap()

- întoarce distanţarea pe orizontală între componente.

public void setHgap(int hgap) - setează distanţarea pe orizontală între componente.
public int getVgap()

- întoarce distanţarea pe verticală între componente. - setează distanţarea pe verticală între componente.

public void setVgap(int vgap)

Clasa Button
public class Button extends Component implements Accessible Instanţele acestei clase sunt butoane care conţin un text numit "etichetă" (engl: label). Când este apăsat, butonul generează un eveniment de acţiune, care poate fi ascultat cu un ActionListener.

Constructori
public Button()

- construieşte un buton care nu conţine text. - construieşte un buton care conţine eticheta specificată.

public Button(String label)

Metode
Metodele sunt cele ale clasei Component la care se adaugă următoarele:
public String getLabel()

- întoarce eticheta butonului. - setează eticheta butonului.

public void setLabel(String label)

- setează numele acţiunii de comandă generată de buton. Dacă nu se foloseşte această comandă, implicit acest nume este identic cu eticheta butonului.
public void setActionCommand(String command) public String getActionCommand()

- întoarce numele acţiunii de comandă generată de

buton.

383

Severin Bumbaru
public void addActionListener(ActionListener l)

- adaugă la buton un ascultător de - elimină ascultătorul de

evenimente de acţiune.
public void removeActionListener(ActionListener l)

evenimente de acţiune specificat.

Clasa Canvas
public class Canvas extends Component implements Accessible Instanţele acestei clase sunt suprafeţe rectangulare albe, pe care se pot trasa desene (în engleză, canvas este pânza pe care se pictează). Pentru a realiza un desen, se creează o subclasă a clasei Canvas, pentru care se redefineşte metoda paint.

Constructori
public Canvas()

- creează o nouă instanţă a clasei Canvas. - creează o nouă instanţă a clasei

public Canvas(GraphicsConfiguration config) Canvas, folosind configuraţia grafică config.

Medtode
Metodele clasei Component, dintre care cea mai utilizată este metoda:
public void paint(Graphics g) folosind contextul grafic g.

- desenează pe suprafaţa acestei componente (Canvas),

Clasa CardLayout
public class CardLayout extends Object implements LayoutManager2, Serializable Instanţele acestei clase sunt gestionari de poziţionare, care plasează componentele în container una peste alta, ca într-un pachet de cărţi de vizită.La un moment dat este vizibilă numai componenta de deasupra, însă componentele îşi pot schimba locurile între ele prin rotaţie.

Constructori
public CardLayout()

- construieşte un CardLayout fără spaţiu liber în jurul componentelor. cardLayout care are pe

public CardLayout(int hgap, int vgap) - construieşte un margini spaţiile libere hgap pe orizontală şi vgap pe verticală.

384

Programarea orientata pe obiecte în limbajul Java

Metode
Metodele interfeţelor LayoutManager şi LayoutManager2, la care se adaugă următoarele:
public int getHgap()

- întoarce spaţiul liber pe orizontală. - setează spaţiul liber pe orizontală.

public void setHgap(int hgap) public int getVgap()

- întoarce spaţiul liber pe verticală. - setează spaţiul liber pe verticală. - pune deasupra prima componentă din

public void setVgap(int vgap)

public void first(Container parent)

containerul părinte specificat.
public void next(Container parent)

- pune deasupra următoarea componentă a

containerului specificat. - pune deasupra componenta precedentă a containerului specificat.componenta cu numele name din containerul specificat.
public void previous(Container parent)

public void show(Container parent, String name) - pune deasupra
public void last(Container parent)

- pune deasupra ultima componentă din

containerul specificat.

Clasa Color
Clasa Color încapsulează culorile din spaţiul de culori implicit sRGB, sau culori dintrun spqaţiu de culori arbitrar, identificat prin ColorSpace. Fiecare culoare conţine, în afară de componentele de culoare fundamentale, şi o componentă numită alpha, care semnifică opacitatea şi are valori reale în intervalul 0.0 ... 1.0 sau valori întregi în intervalul 0 .. 255. Valoarea alpha=0 înseamnă opacitate nulă (culoare total transparentă), iar valoarea maximă a lui alpha înseamnă opacitate maximă (culoare total netransparentă). În mod implicit se admite opacitatea maximă. Culorile fundamentale din sistemul sRGB (standard RGB) sunt următoarele: red (roşu), green (verde) şi blue (albastru). Acestea pot fi date prin numere întregi în intervalul 0 .. 255 sau prin numere reale în intervalul 0.0 .. 1.0. Aceste valori reprezintă intensitatea culorii fundamentale respective în culoarea dată. Orice culoare se obţine prin combinarea acestor trei componente fundamentale, cu intensităţi diferite. Explicaţii suplimentare sunt date şi în secţiunea "Clasa Color" din acest manual. Valorile componentelor fundamentale ale culorii pot fi date şi împachetate într-un singur număr de tip int, în care fiecare octet conţine una din componente (în intervalul 0 .. 255) astfel: blue - biţii 0 ..7; green: biţii 8 .. 15; red: biţii 16 .. 23; alpha: biţii 24 .. 31. Clasa Color conţine şi câmpuri finale pentru culorile tipice.

385

Severin Bumbaru

Câmpuri
public static final Color white

- culoarea albă. - culoarea gri deschis.

public static final Color lightGray public static final Color gray

- culoarea gri. - culoarea gri închis.

public static final Color darkGray public static final Color black public static final Color red

- culoarea neagră.

- culoarea roşie. - culoarea roz. - culoarea portocalie (orange). - culoarea galbenă.

public static final Color pink

public static final Color orange public static final Color yellow public static final Color green

- culoarea verde. - culoarea violet.

public static final Color magenta public static final Color cyan public static final Color blue

- culoarea azuriu (albastru deschis) - culoarea albastră.

Constructori
- creează o instanţă a clasei Color pentru cu culoarea cu componentele RGB specificate prin numere întregi cuprinse între 0 şi 255.
public Color(int r, int g, int b)

- creează o instanţă a clasei Color pentru cu culoarea cu componentele RGB şi componenta a (alpha) specificate prin numere întregi cuprinse între 0 şi 255.
public Color(int r, int g, int b, int a)

- creează o instanţă a clasei Color, dându-i ca argument un număr de tip int (pe 4 octeţi), care în primii trei octeţi conţine cele trei componente fundamentale.
public Color(int rgb)

public Color(int rgba, boolean hasalpha) - creează o instanţă a clasei Color, dându-i ca argument un număr de tip int (pe 4 octeţi), care în primii trei octeţi conţine cele trei componente fundamentale, iar în ultimul octet componenta alpha. Al doilea argument indică dacă există sau nu componenta alpha. public Color(float r, float g, float b) - creează o instanţă a clasei Color pentru cu culoarea cu componentele RGB specificate prin numere reale cuprinse între 0.0 şi 1.0. public Color(float r, float g, float b, float a) - public Color(int r, int g, int b, int a) - creează o instanţă a clasei Color pentru cu culoarea cu componentele RGB şi componenta a (alpha) specificate prin numere reale cuprinse între 0.0 şi 1.0. 386

Programarea orientata pe obiecte în limbajul Java
public Color(ColorSpace cspace, float[] components, float alpha) - creează o instanţă a clasei Color, dându-se spaţiul de culori cspace (instanţă a clasei ColorSpace), tabloul de componente components şi opacitatea alpha.

Metode
public int getRed()

- întoarce componenta roşie. - întoarce componenta verde.

public int getGreen() public int getBlue()

- întoarce componenta albastră. - întoarce componenta alpha.

public int getAlpha() public int getRGB()

- întoarce un int, care conţine sub formă împachetată cele patru componente (r, g, b, alpha).
public Color brighter() public Color darker()

- întoarce o variantă mai deschisă a acestei culori.

- întoarce o variantă mai închisă a acestei culori.

public static Color decode(String nm) throws NumberFormatException

converteşte un şir de caractere, care reprezintă culoarea codificată în octal sau hexazecimal, şi întoarce instanţa corespunzătoare a clasei Color. - caută o culoare în proprietăţile sistemului: caută proprietatea cu numele nm şi decodifică valoarea acestei proprietăţi (care este interpretat ca un şir care conţine număr întreg), generând instanţa corespunzătoare a clasei Color.
public static Color getColor(String nm)

- similar cu metoda precedentă, dar se indică şi culoarea implicită v care va fi folosită dacă proprietatea de sistem cu numele nm.
public static Color getColor(String nm, Color v) public static Color getColor(String nm, int v)

- similar cu metoda precedentă, dar

culoarea implicită este dată printr-un număr întreg.
public static int HSBtoRGB(float hue, float saturation, float brightness)

converteşte culoarea din sistemul HSB (dată prin parametrii hue: nuanţă, saturation: saturaţie şi brightness: strălucire) într-o culoare din sistemul RGB, reprezentată sub forma unui număr de tip int.
public static float[] RGBtoHSB(int r, int g, int b, float[] hsbvals)

- face conversia culorii din RGB în HSB. Întoarce un tablou, care conţine cele trei componente fundamentale HSB, corespunzătoare componentelor de culoare RGB date ca parametri. Ultimul argument poate fi null, sau poate conţine un tablou în care se vor întoarce componwentele HSB rezultate.

387

Severin Bumbaru - întoarce o instanţă a clasei Color, care conţine culoarea pentru care s-au dat ca argumente valorile componentelor sale în sistemul HSB.
public static Color getHSBColor(float h, float s, float b) public float[] getRGBComponents(float[] compArray)

- întoarce un tablou de tip float care conţine cele trei componente RGB şi componenta alpha pentru această culoare. Parametrul compArray poate fi null, sau este un tablou cu cel puţin 4 componente de tip float, în care se vor pune valorile întoarse.
public float[] getRGBColorComponents(float[] compArray)

- similar cu metoda precedentă, dar întoarce un tablou cu trei componente, care conţine numai cele trei componente RGB (fără componenta alpha).
public ColorSpace getColorSpace()

- întoarce spaţiul culorilor pentru această culoare (o

instanţă a clasei ColorSpace).
public PaintContext createContext(ColorModel cm, Rectangle r, Rectangle2D r2d, AffineTransform xform, RenderingHints hints) - întoarce un context de desenare, care este necesar ca argument în metodele claselor cu interfaţa Paint. public int getTransparency()

- întoarce transparenţa acestei culori (necesară pentru

implementarea interfeţei Paint).

Clasa Component
public abstract class Component extends Object implements ImageObserver, MenuContainer, Serializable Face parte din pachetul java.awt. Este rădăcina ierarhiei tuturor claselor de componente ale interfeţelor grafice AWT şi JFC/Swing.

Metode
public String getName()

- întoarce numele componentei. - setează numele componentei.

public void setName(String name) public Container getParent()

- întoarce o referinţă către containerul care conţine

această componentă. - setează un obiect DropTarget pentru această componentă. Componenta va putea fi ţinta unei operaţii de "drag and drop" (tragere şi lăsare cu mausul), atunci când este activă.
public void setDropTarget(DropTarget dt) public DropTarget getDropTarget()

- întoarce obiectul DropTarget asociat acestei

componente.

388

Programarea orientata pe obiecte în limbajul Java - întoarce configuraţia grafică a acestei componente sau a containerului în care se găseşte, sau null.
public GraphicsConfiguration getGraphicsConfiguration() public final Object getTreeLock()

- întoarce obiectul de închidere al arborelui de componente AWT şi de gestionare a poziţionării (obiectul proprietar al monitorului de sincronizare a firului de execuţie).
public Toolkit getToolkit() public boolean isValid()

- întoarce Toolkit-ul pentru această componentă.

- indică dacă această componentă este validă (este corect dimensionată şi poziţionată în containerul său părinte). - indică dacă această componentă este afişabilă (este conectată la o resursă de ecran de afişare nativă).
public boolean isDisplayable()

- indică dacă această componentă este vizibilă. Toate componentele sunt iniţial vizibile, cu excepţia celor de cel mai înalt nivel, cum sunt cele din clasele Frame şi JFrame.
public boolean isVisible() public boolean isShowing() public boolean isEnabled()

- indică dacă această componentă apare efectiv pe ecran.

- arată dacă această componentă este activă (poate răspunde la intrările date de utilizator şi să genereze evenimente). - face ca această componentă să fie activă sau inactivă, după cum valoarea argumentului este true sau false.
public void setEnabled(boolean b) public void setVisible(boolean b) public Color getForeground()

- setează proprietatea de vizibilitate a componentei.

- întoarce culoarea de prim-plan a componentei (culoarea - setează culoarea de prim-plan a componentei.

textului).
public void setForeground(Color c) public Color getBackground()

- întoarce culoarea de fond a componentei. - setează culoarea de fond a componentei.

public void setBackground(Color c) public Font getFont()

- întoarce fontul componentei. - setează fontul componentei. - întoarce modelul de culori al componentei.

public void setFont(Font f)

public ColorModel getColorModel() public Point getLocation()

- întoarce locaţia componentei (coordonatele colţului din stânga sus), în sistemul de coordonate al containerului părinte. - întoarce locaţia componentei (coordonatele colţului din stânga sus) în sistemul de coordonate al ecranului.
public Point getLocationOnScreen()

389

Severin Bumbaru - setează locaţia componentei (coordonatele colţului din stânga sus) în sistemul de coordonate al containerului părinte.
public void setLocation(int x, int y)

- setează locaţia componentei (coordonatele colţului din stânga sus) în sistemul de coordonate al containerului părinte.
public void setLocation(Point p) public Dimension getSize()

- întoarce dimensiunile componentei. - setează dimensiunile componentei.

public void setSize(int width, int height) public void setSize(Dimension d) public Rectangle getBounds()

- setează dimensiunile componentei.

- întoarce o instanţă a clasei Rectangle (dreptunghi) care conţine lăţimea, înălţimea şi locaţia componentei.
public void setBounds(int x, int y, int width, int height)

- setează

coordonatele colţului din stânga sus, lăţimea şi înălţimea componentei.
public void setBounds(Rectangle r) public int getX()

- setează locaţia şi dimensiunile componentei.

- întoarce coordonata x a colţului din stânga-sus (în sistemul de coordonate al containerului părinte). - întoarce coordonata y a colţului din stânga-sus (în sistemul de coordonate al containerului părinte).
public int getY() public int getWidth()

- întoarce lăţimea componentei. - întoarce înălţimea componentei. - indică dacă această componentă este opacă (implicit, toate

public int getHeight()

public boolean isOpaque()

componentele sunt opace). - întoarce true dacă această componentă nu are un suport nativ. Toate componentele Swing şi unele din componentele AWT sunt "Lightweight", adică sunt desenate în Java şi deci nu folosesc componentele grafice de pe platforma locală.
public boolean isLightweight() public Dimension getPreferredSize()

- întoarce dimensiunile preferabile ale

componentei.
public Dimension getMinimumSize() public Dimension getMaximumSize() public void doLayout()

- întoarce dimensiunile minime ale componentei. - întoarce dimensiunile maxime ale componentei.

- transmite gestionarului de poziţionare indicaţia de a poziţiona - asigură că această componentă are un gestionar de poziţionare

componenta.
public void validate()

valid.

390

Programarea orientata pe obiecte în limbajul Java - invalidează componenta. Această componentă şi părinţii săi sunt marcaţi că necesită poziţionare.
public void invalidate()

- creează un context grafic pentru această componentă.Intoarce null dacă această componentă nu este afişabilă.
public Graphics getGraphics() public void setCursor(Cursor cursor) public Cursor getCursor()

- setează cursorul pentru această componentă.

- întoarce cursorul setet pentru această componentă sau

pentru părintele ei. public void paint(Graphics g) - desenează componenta. Această metodă este apelată automat când trebuie desenată componenta.
public void update(Graphics g) - actualizează desenul componentei, ca răspuns la invocarea metodelor repaintupdate sau repaint. Aceasta înseamnă că: (1) se şterge vechiul

conţinut, umplând componenta cu culoarea de fond; (2) se setează culoarea de primplan în contextul grafic pentru această componentă; (3) se apelează metoda paint pentru a desena complet componenta.
public void paintAll(Graphics g)

- desenează această componentă şi toate

subcomponentele sale. - redesenează componentă (face ca metoda update pentru această componentă să fie invocată cât mai rapid posibil).
public void repaint() public void repaint(long tm)

- apelează metoda update în tm milisecunde. - redesenează

public void repaint(int x, int y, int width, int height)

dreptunghiul specificat al componentei.
public void repaint(long tm, int x, int y, int width, int height) redesenează dreptunghiul specificat al componentei după tm milisecunde. public void print(Graphics g)

-

- tipăreşte componenta. - tipăreşte componenta şi toate subcomponentele ei.

public void printAll(Graphics g)

public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) - redesenează componenta când s-a modificat imaginea pe care o conţine. public Image createImage(ImageProducer producer)

- creează o imagine din

producătorul de imagine specificat. - verifică dacă această componentă conţine punctul de coordonate (x, y), în sistemul de coordonate propriu al componentei.
public boolean contains(int x, int y)

- verifică dacă această componentă conţine punctul p, în sistemul de coordonate propriu al componentei.
public boolean contains(Point p)

391

Severin Bumbaru
public Component getComponentAt(int x, int y)

- întoarce subcomponenta care

conţine punctul de coordonate (x, y).
public Component getComponentAt(Point p)

- întoarce subcomponenta care conţine - adaugă acestei - elimină ascultătorul

punctul p.
public void addComponentListener(ComponentListener l)

componente un ascultător de evenimente generate de către ea.
public void removeComponentListener(ComponentListener l)

de evenimente de componentă specificat.
public void addFocusListener(FocusListener l)

- adaugă un ascultător de evenimente - elimină ascultătorul de - adaugă un ascultător de - elimină ascultătorul

de focalizare.
public void removeFocusListener(FocusListener l)

evenimente de focalizare.
public void addHierarchyListener(HierarchyListener l)

evenimente de ierarhie.
public void removeHierarchyListener(HierarchyListener l)

de evenimente de ierarhie.
public void addKeyListener(KeyListener l)

- adaugă un ascultător de evenimente de - elimină ascultătorul de evenimente - adaugă un ascultător de evenimente - elimină ascultătorul de - adaugă un ascultător - elimină

tastă.
public void removeKeyListener(KeyListener l)

de tastă.
public void addMouseListener(MouseListener l)

de mouse.
public void removeMouseListener(MouseListener l)

evenimente de mouse.
public void addMouseMotionListener(MouseMotionListener l)

de evenimente de mişcare a mouse-ului.
public void removeMouseMotionListener(MouseMotionListener l)

ascultătorul de evenimente de mişcare a mouse-ului.
public void addInputMethodListener(InputMethodListener l)

- adaugă un ascultător - elimină

de evenimente de metodă de intrare.
public void removeInputMethodListener(InputMethodListener l)

ascultătorul de evenimente de metodă de intrare.

392

Programarea orientata pe obiecte în limbajul Java
public EventListener[] getListeners(Class listenerType)

- întoarce un tablou de ascultătoare de evenimente ataşate acestei componente şi având tipul dat ca argument. De exemplu, pentru a obţine tabloul ascultătoarelor de evenimente de mouse adăugate componentei c se pune instrucţiunea: MouseListener[] mls = (MouseListener[])(c.getListeners(MouseListener.class))
public void add(PopupMenu popup)

- adauga la componentă meniul popup specificat. - elimină meniul popup specificat.

public void remove(MenuComponent popup) protected String paramString()

- întoarce un şir de caractere care reprezintă starea componentei. Se foloseşte numai pentru depanarea programului.
public String toString() public void list()

- întoarce reprezentarea sub formă de şir a acestei componente.

- afişează listingul acestei componente pe System.out. - scrie listingul acestei componente în fluxul out. - scrie listingul acestei componente

public void list(PrintStream out)

public void list(PrintStream out, int indent) în fluxul out, indentat cu indent. public void list(PrintWriter out)

- scrie listingul acestei componente în fluxul out.

public void list(PrintWriter out, int indent) - scrie listingul acestei componente în fluxul out, indentat cu indent.
public void addPropertyChangeListener(PropertyChangeListener listener)

-

adaugă un ascultător de evenimente de schimbare a proprietăţilor.
public void removePropertyChangeListener(PropertyChangeListener listener)

-

elimină ascultătorul de evenimente de schimbare a proprietăţilor.
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) - adaugă un ascultător de evenimente

de schimbare a

proprietăţilor, pentru proprietatea specificată prin primul argument.
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) - elimină ascultătorul de evenimente

de schimbare a

proprietăţilor, pentru proprietatea specificată prin primul argument.
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) - suport pentru raportarea modificărilor de proprietăţi. Metoda poate fi

apelată când s-a modificat o proprietate şi ea va transmite un PropertyChangeEvent către toţi acsultătorii de evenimente de proprietate înregistraţi la această componentă.
public void setComponentOrientation(ComponentOrientation o)

- setează orientarea

componentei.

393

Severin Bumbaru
public ComponentOrientation getComponentOrientation()

- întoarce orientarea

componentei.
public AccessibleContext getAccessibleContext()

- întoarce contextul accesibil.

Clasa Container
public class Container extends Component Containerele sunt componente care pot conţine alte componente (inclusiv alte containere, deoarece şi acestea sunt sunt tot componente). Componentele adăugate în container sunt păstrate într-o listă. Ordinea din această listă este cea în care componentele sunt luate în consideraţie de către gestionarul de poziţionare.

Constructor
public Container()

- construieşte un nou container.

Metode
Metodele clasei Component, la care se adaugă următoarele:
public int getComponentCount()

- întoarce numărul de componente conţinute în acest

container.
public Component getComponent(int n)

- întoarce componenta care are indicele i în

lista de componente a containerului.
public Component[] getComponents()

- întoarce un tablou cu toate componentele

conţinute în container.
public Insets getInsets()

- întoarce lăţimea bordurii containerului (sub forma unui - adaugă componenta comp la sfârşitul listei de - se adaugă componenta comp pe

obiect din clasa Insets).
public Component add(Component comp)

componente a containerului.
public Component add(Component comp, int index) poziţia din lista de componente cu indicele index.

public void add(Component comp, Object constraints) - se adaugă componenta comp cu restricţiile date de obiectul constraints. Adăugarea se face la sfârşitul listei de

componente.
public void add(Component comp, Object constraints, int index) - se adaugă componenta comp cu restricţiile date de obiectul constraints. Adăugarea se face pe poziţia de indice index a listei de componente.

394

Programarea orientata pe obiecte în limbajul Java
public void remove(int index) public void removeAll()

- elimină componenta de indice index.

- elimină toate componentele conţinute în container. - întoarce gestionarul de poziţionare al containerului. - setează gestionarul de poziţionare al

public LayoutManager getLayout()

public void setLayout(LayoutManager mgr)

containerului.
public void doLayout()

- pune în acţiune gestionarul de poziţionare al containerului. - desenează fiecare componentă din acest - tipăreşte toate componentele din acest - adaugă un ascultător de - elimină ascultătorul

public void paintComponents(Graphics g)

container.
public void printComponents(Graphics g)

container.
public void addContainerListener(ContainerListener l)

evenimente de container.
public void removeContainerListener(ContainerListener l)

de evenimente de container specificat.
public Component findComponentAt(int x, int y)

- întoarce componenta care conţine

punctul de coordonate (x,y).
public Component findComponentAt(Point p)

- întoarce componenta care conţine

punctul p.

Clasa Dialog
public class Dialog extends Window Fereastra de dialog este o fereastră de cel mai înalt nivel (care poate fi plasată direct pe ecranul nativ). Ea are bordură şi este folosită în special pentru dialogul (schimbul de informaţii interactiv) cu utilizatorul. Managerul de poziţionare implicit este BorderLayout. Generează următoarele evenimente de fereastră: WindowOpened, WindowClosing, WindowClosed, WindowActivated, WindowDeactivated. Fereastra de dialog are ca proprietar o altă fereastră din clasa Frame sau Dialog. Când proprietarul este ascuns sau minimizat, fereastra de dialog este de asemenea ascunsă. Fereastra de dialog poate fi modală sau nemodală (implicit este nemodală). O fereastră modală blochează toate intrările către orice altă fereastră de cel mai înalt nuvel şi descendentele lor, cu excepţia celor care au ca proprietar chiar această fereastră de dialog.

395

Severin Bumbaru

Constructori
public Dialog(Frame owner) proprietar cadrul owner.

- construieşte o fereastră de dialog fără titlu, având ca

public Dialog(Frame owner, boolean modal) - construieşte o fereastră de dialog fără titlu, cu proprietarul owner, specificându-se dacă ea este sau nu modală. public Dialog(Frame owner, String title)

- construieşte o ferestră de dialog, - construieşte o fereastră

indicându-se proprietarul şi titlul ferestrei.
public Dialog(Frame owner, String title, boolean modal)

de dialog, indicându-se proprietarul, titlul şi dacă este sau nu modală.
public Dialog(Dialog owner)

- construieşte o fereastră de dialog fără titlu, având ca - construieşte o fereastră de dialog cu

proprietar altă fereastră de dialog.
public Dialog(Dialog owner, String title)

titlu, având ca proprietar altă fereastră de dialog. - construieşte o fereastră de dialog cu titlu, având ca proprietar altă fereastră de dialog şi indicându-se dacă este sau nu modală.
public Dialog(Dialog owner, String title, boolean modal)

Metode
Metodele clasei Window, la care se adaugă următoarele:
public boolean isModal()

- indică dacă fereastra de dialog este sau nu modală. - specifică dacă fereastra de dialog va fi sau nu

public void setModal(boolean b)

modală.
public String getTitle()

- întoarce titlul ferestrei de dialog (poate fi şi null). - setează titlul ferestrei de dialog.

public void setTitle(String title) public boolean isResizable()

- îndică dacă fereastra de dialog este sau nu

redimensionabilă. public void setResizable(boolean resizable) - specifică dacă această fereastră de dialog va fi sau nu redimensionabilă.

Clasa Dimension
public class Dimension extends Dimension2D implements Serializable

396

Programarea orientata pe obiecte în limbajul Java Instanţele acestei clase conţin dimensiunile orizontală şi vericală ale unei componente, exprimate ca numere întregi.

Câmpuri
public int width

- lăţimea. - înălţimea.

public int height

Constructori
public Dimension()

- construieşte o instanţă a clasei Dimension, conţinând dimensiuni - construieşte o copie a lui d. - construieşte o instanţă a clasei Dimension,

nule.
public Dimension(Dimension d)

public Dimension(int width, int height) conţinând lăţimea width şi înălţimea height.

Metode
public double getWidth()

- întoarce lăţimea. - întoarce înălţimea. - setează lăţimea şi înălţimea,

public double getHeight()

public void setSize(double width, double height)

argumentele fiind de tip double.
public void setSize(int width, int height)

- setează lăţimea şi înălţimea,

argumentele fiind de tip int. public boolean equals(Object obj) - redefineşte metoda equals din clasa Object.

Clasa Event
public class Event extends Object implements Serializable Clasa Event este clasa evenimentelor generate de interfaţa utilizator grafică utilizată în JDK 1.0 (în prima versiune a Java API) şi menţinută în continuare pentru compatibilitate, deşi începând cu JDK 1.1 ea a fost înlocuită prin clasa AWTEvent.

Clasa FlowLayout
397

Severin Bumbaru Gestionarul de poziţionare FlowLayout plasează componentele pe suprafaţa containerului pe linii succesive, de la stânga la dreapta şi de sus în jos, ca literele dintr-un text. Fiecare componentă are deimensiunile ei preferate. Alinierea componentelor poate fi făcută la stânga, la dreapta sau pe centru (alinierea implicită este pe centru). Dacă se modifică dimensiunile containerului, componentele se rearanjează, menţinându-şi ordinea şi dimensiunile, dar putând trece de pe o linie pe alta. Clasa conţine câmpuri statice finale, cu denumirile modurilor de aliniere a componentelor.

Câmpuri
public static final int LEFT

- aliniere la stânga. - aliniere la centru.

public static final int CENTER public static final int RIGHT

- aliniere la dreapta

- aliniere la muchia frontală pentru orientarea dată a containerului (adică aliniere la stânga, în cazul când ordinea de plasare este de la stânga la dreapta).
public static final int LEADING

- aliniere la muchia terminală, ţinând cont de orientarea dată a containerului (deci aliniere la dreapta, dacă ordinea de plasare a componentelor este de la stânga la dreapta).
public static final int TRAILING

Constructori
- construieşte un FlowLayout cu aliniere implicită a componentelor (la centru) şi cu spaţii libere între componente de 5 unităţi.
public FlowLayout()

- construieşte un nou FlowLayout, cu alinierea componentelor specificată. Parametrul align poate fi unul din câmpurile: FlowLayout.LEFT, FlowLayout.CENTER, FlowLayout.RIGHT.
public FlowLayout(int align)

- construieşte un nou FlowLayout, fiind specificate alinierea şi spaţiile libere între componente pe orizontală şi pe verticală.
public FlowLayout(int align, int hgap, int vgap)

Metode
metodele din interfaţa LayoutManager, la care se adaugă următoarele:
public int getAlignment()

- întoarce alinierea. unul

public void setAlignment(int align) - setează alinierea. Parametrul align poate fi din câmpurile: FlowLayout.LEFT, FlowLayout.CENTER, FlowLayout.RIGHT. public int getHgap()

- întoarce spaţiul liber între componente pe orizontală. - setează spaţiul liber între componente pe orizontală.

public void setHgap(int hgap)

398

Programarea orientata pe obiecte în limbajul Java
public int getVgap()

- întoarce spaţiul liber între componente pe verticală. - setează spaţiul liber între componente pe verticală.

public void setVgap(int vgap)

Clasa Font
public class Font extends Object implements Serializable Instanţele clasei Font reprezintă fonturi şi încorporează fiecare informaţii privind forma, mărimea şi stilul unui caracter. Caracterele sunt simbolurile tipografice folosite în texte. Stilurile sunt: plain (normal), bold, italic, bold+italic. Mărimea fontului (înălţimea literei majuscule) se exprimă în puncte tipografice. Cele mai răspândite fonturi folosite în programele Java sunt:

Serif - toate caracterele au serifuri, adică mici liniuţe prin care se termină liniile
principale care formează caracterul respectiv; lăţimea caracterelor este variabilă, de exemplu W este mai lat decât I).

SansSerif - caracterele nu au serifuri, dar au lăţimea variabilă. Monospaced - toate caracterele au aceeaşi lăţime,

ca

cele de la maşina de scris. Aceste fonturi există pe toate maşinile virtuale Java, dar pot fi instalate şi alte fonturi.

Câmpuri statice finale pentru stiluri
public static final int PLAIN public static final int BOLD

- stilul PLAIN (normal).

- stilul BOLD (caractere îngroşate, aldine). - stilul italic (cursiv).

public static final int ITALIC

Constructori
- se construieşte un nou font, fiind specificate numele fontului, stilul şi mărimea. Numele fontului poate fi "Serif", "SansSerif", "Monospaced" sau alt stil existent pe maşina virtuală Java respectivă. Stiluol poate fi Font.PLAIN, Font.BOLD, Font.ITALIC sau Font.BOLD|Font.ITALIC.
public Font(String name, int style, int size)

- construieşte un nou font, folosind atributele specificate prin argument. La alcătuirea mapării atributelor se folosesc numai cheile definite în clasa TextAttribute (pentru detalii se va consulta Java API).
public Font(Map attributes)

Metode principale
public static Font getFont(Map attributes)

- întoarce fontul care corespunde cel mai

bine setului de atribute specificat ca argument.
public AffineTransform getTransform()

- întoarce transformarea asociată acestui font.

399

Severin Bumbaru
public String getFamily()

- întoarce numele familiei de fonturi din care face parte acest

font.
public String getPSName()

- întoarce numele PostScript al acestui font. logic al acestui font. Se va folosi

public String getName() - întoarce numele getFontName() pentru a afla numele fontului. public String getFontName() public int getStyle() public int getSize()

- întoarce numele fontului.

- întoarce stilul fontului.

- întoarce mărimea fontului, exprimată în puncte tipografice. - indică dacă stilul caracterului este PLAIN (normal).

public boolean isPlain() public boolean isBold()

- indică dacă stilul caracterului este BOLD (aldin, îngroşat). - indică dacă stilul caracterului este ITALIC (cursiv).

public boolean isItalic()

- întoarce fontul din lista de proprietăţi a sistemului. Ca parametru se dă numele proprietăţii.
public static Font getFont(String nm) public static Font decode(String str) - întoarce fontul str. Dacă argumentul este null, se întoarce un font implicit. public Font deriveFont(int style, float size)

pe care îl descrie argumentul

- întoarce un nou font, care să aibă aceeaşi formă cu acesta, dar să aibă stilul şi mărimea specificate.
public Font deriveFont(float size)

- întoarce un nou font, similar acestuia, dar cu

mărimea specificată.
public Font deriveFont(int style)

- întoarce un nou font, similar acestuia, dar cu stilul

specificat.

Clasa Frame
public class Frame extends Window implements MenuContainer Face parte din pachetul java.awt. Instanţele acestei clase sau ale subclaselor ei sunt folosite ca ferestre pr4incipale ale aplicaţiilor. Un Frame este o fereastră care are chenar şi bară de titlu. Este o fereastră de cel mai înalt nivel (top-level window), deci poate fi plasată direct pe ecranul fizic. Generează evenimente de fereastră (din clasa WindowEvent), care pot fi ascultate cu un WindowListener sau WindowAdapter. La partea superioară i se poate pune o bară de meniu (MenuBar). Iniţial, cadrul este invizibil, dar poate fi făcut vizibil invocând setVisible(true), iar dimensiunile se stabilesc prin setSize(int, int), ambele fiind metode ale clasei Component.

400

Programarea orientata pe obiecte în limbajul Java

Constructori
public Frame()

- construieşte un cadru (Frame) fără titlu.

- construieşte un cadru (Frame) folosind configuraţia grafică gc (poate fi folosită pentru a afişa cadrul pe alt ecran decât cel principal, într-un sistem multiecran).
public Frame(GraphicsConfiguration gc) public Frame(String title)

- construieşte un cadru (Frame) cu titlul title. - construieşte un cadru

public Frame(String title, GraphicsConfiguration gc) (Frame) cu titlul title şi cu configuraţia grafică gc.

Metode
public String getTitle()

- întoarce titlul cadrului (cel din bara de titlu). - pune titlul title în bara de titlu.

public void setTitle(String title) public Image getIconImage()

- întoarce imaginea care trebuie afişată ca pictogramă la - setează imaginea care trebuie afişată ca

minimizarea acestui cadru.
public void setIconImage(Image image)

pictogramă la minimizarea acestui cadru.
public MenuBar getMenuBar()

- îmtoarce bara de meniu a acestui cadru, sau null, dacă ea - setează pentru acest cadru bara de meniu mb.

nu există.
public void setMenuBar(MenuBar mb) public boolean isResizable()

- indică dacă acest cadru poate fi redimensionat de către utilizator (prin tragere cu mouse-ul). Implicit, toate instanţele clasei Frame sunt redimensionabile.
public void setResizable(boolean resizable)

- setează dacă acest cadru poate sau nu

să fie redimensionat de către utilizator.
public void setState(int state) public int getState()

- setează starea acestui cadru.

- întoarce starea acestui cadru. - elimină bara de meniu specificată.

public void remove(MenuComponent m) public static Frame[] getFrames()

- întoarce tabloul tuturor cadrelor (Frame) deschise

de o aplicaţie sau accesibile unui applet. Clasa Frame moşteneşte şi toate metodele clasei Window, deci şi pe cele ale claselor Container şi Component.

401

Severin Bumbaru

Clasa Graphics2D
public abstract class Graphics2D extends Graphics Este noul context grafic, care extinde clasa Graphics, pentru a putea realiza desene mult mai complexe. Pentru utilizarea acestei clase şi, deci, a aplica în Java tehnicile de grafică 2D, recomandăm să se consulte documentaţia originală Java API şi capitolul 2D Graphics din Tutorialul Java.

Clasa GridBagLayout
public class GridBagLayout extends Object implements LayoutManager2, Serializable Este un gestionar de poziţionare flexibil, care permite să se alinieze orizontal şi vertical componente de dimensiuni diferite. Pentru folosirea acestei clase recomandăm să se consulte Tutorialul Java şi să se folosească documentaţia Java API originală.

Clasa GridLayout
public class GridLayout extends Object implements LayoutManager, Serializable Gestionarii de poziţionare din clasa GridLayout aranjează componentele în container sub forma unei grile rectangulare. Fiecare celulă a grilei conţine o singură componentă.

Constructori
public GridLayout()

- construieşte un GridLayout cu o singură linie de componente. linii şi

public GridLayout(int rows, int cols) - construieşte un GridLayout cu rows cols coloane. Unul din cele două argumente (dar nu ambele) poate fi 0 (zero). public GridLayout(int rows, int cols, int hgap, int vgap)

- construieşte un nou GridLayout, specificându-se numărul de linii şi de coloane şi spaţiile libere între componente pe orizontală şi pe verticală.

Metode
Metodele interfeţei LayoutManager, la care se adaugă următoarele:
public int getRows()

- întoarce numărul de linii al grilei. - setează numărul de linii al grilei.

public void setRows(int rows) public int getColumns()

- întoarce numărul de coloane al grilei.

402

Programarea orientata pe obiecte în limbajul Java
public void setColumns(int cols) public int getHgap()

- setează numărul de coloane al grilei.

- întoarce distanţarea pe orizontală a componentelor. - setează distanţarea pe orizontală a componentelor.

public void setHgap(int hgap) public int getVgap()

- întoarce distanţarea pe verticală a componentelor. - setează distanţarea pe verticală a componentelor.

public void setVgap(int vgap)

Clasa Insets
public class Insets extends Object implements Cloneable, Serializable Instanţele acestei clase conţin lăţimile marginilor libere ale containerelor (care nu conţin componente). Marginea respectivă poate fi complet liberă, sau poate conţine o bordură sau un titlu.

Câmpuri
public int top

- marginea de sus - marginea din stânga - marginea de jos

public int left

public int bottom public int right

- marginea din dreapta.

Constructor
- construieşte o instanţă a clasei Insetws, fiind specificate marginile de sus, stânga, jos şi dreapta.
public Insets(int top, int left, int bottom, int right)

Metode
Cele moştenite de la clasa Object.

Class Panel
public class Panel extends Container implements Accessible Panoul este cea mai simplă clasă de containere. Un panou este un dreptunghi fără bordură pe suprafaţa căruia se pot plasa diferite componente ale interfeţei grafice, inclusiv alte panouri.

403

Severin Bumbaru

Constructori
public Panel()

- construieşte un nou panou, având gestionarul de poziţionare implicit - construieşte un nou panou, cu gestionarul de

FlowLayout.
public Panel(LayoutManager layout)

poziţionare specificat.

Metode
Metodele clasei Container şi superclaselor acesteia.

Clasa Point
public class Point extends Point2D implements Serializable Fiecare instanţă a acestei clase conţine coordonatele (x, y) ale unui punct, exprimate prin numere întregi.

Constructori
public Point()

- construieşte un punct de coordonate (0, 0). - construieşte o copie a punctului p.

public Point(Point p)

public Point(int x, int y) - construieşte un punct de coordonate (x, y).

Metode principale
public double getX() public double getY()

- întoarce coordonata x. - întoarce coordonata y. - setează noile valori ale coordonatelor - efectuează o translaţie a acestui punct, astfel

public void setLocation(int x, int y)

punctului.
public void translate(int dx, int dy)

că noile coordonate vor fi x+dx, y+dy.

Clasa Rectangle
Instanţele acestei clase sunt suprafeţe de formă dreptunghiulară, cu laturile dispuse orizontal şi vertical, pentru care se dau următoarele elemente: x, y - coordonatele colţului din stânga sus; width - lăţimea dreptunghiului; height - înălţimea dreptunghiului. Valorile acestor elemente sunt exprimate prin numere întregi. 404

Programarea orientata pe obiecte în limbajul Java

Constructori
public Rectangle()

- construieşte un dreptunghi cu coordonatele (0, 0) şi dimensiunile - construieşte o copie a dreptunghiului r.

nule.
public Rectangle(Rectangle r)

- construieşte un dreptunghi, pentru care sunt specificate coordonatele originii (x, y), lăţimea şi înălţimea.
public Rectangle(int x, int y, int width, int height)

- construieşte un dreptunghi cu originea în punctul de coordonate (0 0) şi având specificate lăţimea şi înălţimea.
public Rectangle(int width, int height) public Rectangle(Point p, Dimension d)

- construieşte un dreptunghi cu originea în

punctul p şi dimensiunile d.
public Rectangle(Point p)

- construieşte un dreptunghi cu originea în punctul p şi - construieşte un dreptunghi cu originea în punctul (0, 0)

dimensiuni nule.
public Rectangle(Dimension d)

şi dimensiunile d.

Metode mai frecvent utilizate
public double getX() public double getY()

- întoarce coordonata x a colţului stânga-sus. - întoarce coordonata y a colţului stânga-sus. - întoarce lăţimea. - întoarce înălţimea. - setează toate

public double getWidth()

public double getHeight()

public void setBounds(int x, int y, int width, int height)

elementele date ca parametri.
public void setBounds(Rectangle r)

- setează elementele acestui dreptunghi la fel ca la

dreptunghiul r.
public Point getLocation()

- întoarce punctul de origine (colţul stânga-sus). - setează punctul de origine (colţul stânga-sus). - setează coordonatele punctului de origine - translatează dreptunghiul, astfel că noile

public void setLocation(Point p)

public void setLocation(int x, int y)

(colţul stânga-sus).
public void translate(int dx, int dy)

coordonate devin (x+dx, y+dy).
public Dimension getSize()

- întoarce dimensiunile dreptunghiului. - setează dimensiunile dreptunghiului. 405

public void setSize(Dimension d)

Severin Bumbaru
public void setSize(int width, int height) public boolean contains(Point p)

- setează dimensiunile dreptunghiului.

- indică dacă acest dreptunghi conţine punctul p. - indică dacă acest dreptunghi conţine punctul

public boolean contains(int x, int y)

de coordonate (x, y).
public boolean contains(Rectangle r)

- indică dacă acest dreptunghi conţine - indică dacă acest

dreptunghiul r.
public boolean contains(int x, int y, int width, int height)

dreptunghi conţine dreptunghiul ale cărui elemente sunt specificate.
public boolean intersects(Rectangle r)

- indică dacă acest dreptunghi intersectează - întoarce intersecţia acestui dreptunghi

dreptunghiul r.
public Rectangle intersection(Rectangle r)

cu dreptunghiul r. - întoarce dreptunghiul cel mai mic care conţine în întregime acest dreptunghi şi dreptunghiul r.
public Rectangle union(Rectangle r)

Clasa Window
public class Window extends Container implements Accessible Instanţele acestei clase sunt ferestre de cel mai înalt nivel (pot fi plasate direct pe eranul nativ), fără bordură şi fără bară de meniu. Trebuie să aibă drept proprietar un cadru, o fereastră de dialog sau o altă instanţă a clasei Window.

Constructori
public Window(Frame owner)

- se construieşte o nouă fereastră, având ca proprietar o - se construieşte o nouă fereastră, având ca proprietar altă

instanţă a clasei Frame.
public Window(Window owner)

fereastră din clasa Window. public Window(Window owner, GraphicsConfiguration gc) - se construieşte o nouă fereastă, având proprietarul owner şi configuraţia grafică gc (poate fi afişată şi pe un alt ecran fizic, în sistemele cu mai multe ecrane).

Metode
Metodele clasei Container, la care se adaugă următoarele:

406

Programarea orientata pe obiecte în limbajul Java - face ca fereastra să fie dimensionată la dimensiunea preferată şi componentele ei să fie aranjate de către gestionarul de poziţionare, după care fereastra este făcută afişabilă şi este validată.
public void pack() public void show() public void hide()

- face fereastra vizibilă pe ecran. - ascunde fereastra (o face invizibilă). - plasează fereastra deasupra celorlalte de pe ecran.

public void toFront() public void toBack()

- plasează fereastra în spatele celorlalte de pe ecran. - întoarce textul de avertizare, care însoţeşte

public final String getWarningString()

această fereastră dacă ea nu este sigură.
public Window getOwner()

- întoarce proprietarul ferestrei. - întoarce tabloul ferestrelor, pe care această - adaugă un ascultător de - elimină ascultătorul de

public Window[] getOwnedWindows()

fereastră le deţine ca proprietar.
public void addWindowListener(WindowListener l)

evenimente de fereastră.
public void removeWindowListener(WindowListener l)

evenimente de fereastră specificat.
public EventListener[] getListeners(Class listenerType)

- întoarce un tablou al

tuturor ascultătoarelor de evenimente adăugate la această fereastră.
public Component getFocusOwner()

- dacă fereastra este activă, întoarce fereastra copil

care este deţine focalizarea.

Interfaţa LayoutManager
Defineşte interfaţa pentru clasele care gestionează poziţionarea componentelor în container. Clase care implementează această interfaţă: GridLayout, FlowLayout, ViewportLayout, ScrollPaneLayout, BasicOptionPaneUI.ButtonAreaLayout, BasicTabbedPaneUI.TabbedPaneLayout, BasicInternalFrameTitlePane.TitlePaneLayout, BasicScrollBarUI, BasicInternalFrameUI.InternalFrameLayout, BasicComboBoxUI.ComboBoxLayoutManager, BasicSplitPaneDivider.DividerLayout

407

Severin Bumbaru

Metode
public void addLayoutComponent(String name, Component comp) container componenta comp cu numele name. public void removeLayoutComponent(Component comp)

- adaugă la

- elimină din container

componenta specificată.
public Dimension preferredLayoutSize(Container parent)

- întoarce dimensiunile preferate ţinând seama de componentele din containerul părinte specificat.
public Dimension minimumLayoutSize(Container parent)

- întoarce dimensiunile minime ţinând cont de componentele din containerul părinte specificat.
public void layoutContainer(Container parent)

- se face redimensionarea şi

poziţionarea componentelor din containerul specificat.

Interfaţa LayoutManager2
public interface LayoutManager2 extends LayoutManager Clase care implementează această interfaţă: CardLayout, BorderLayout, GridBagLayout, BoxLayout, OverlayLayout, JRootPane.RootLayout, BasicSplitPaneUI.BasicHorizontalLayoutManager Interfaţă pentru clasele de gestionare a poziţionării care folosesc un obiect de restricţii (care stabileşte restricţiile pe care trebuie să le satisfacă poziţionarea).

Metode
Are toate metodele interfeţei LayoutManager la care se adaugă următoarele metode:
public void addLayoutComponent(Component comp, Object constraints) componenta comp respectând restricţiile constraints. public Dimension maximumLayoutSize(Container target)

- adaugă

- întoarce dimensiunea

maximă a containerului. - întoarce un număr în intervalul 0.0 .. 1.0, care indică modul în care se face alinierea componentelor pe axa oX: 0.0 - la origine, 0.5 - la centru, 1.0 - la extremitatea dreaptă.
public float getLayoutAlignmentX(Container target)

- întoarce un număr în intervalul 0.0 .. 1.0, care indică modul în care se face alinierea componentelor pe axa oY: 0.0 - la origine, 0.5 - la centru, 1.0 - la extremitatea de jos.
public float getLayoutAlignmentY(Container target) public void invalidateLayout(Container target)

- se invalidează gestionarul de

poziţionare, descărcându-se informaţia pe care o conţine.

408

Programarea orientata pe obiecte în limbajul Java

Clasa ActionEvent
public class ActionEvent extends AWTEvent Eveniment semantic, care indică faptul că asupra unei componente a fost exercitată o acţiune specifică acelei componente (de exemplu, un buton a fost apăsat). Cu ajutorul măştilor date drept câmpuri statice finale se pot determina anumite caracteristici ale evenimentului produs. De exemplu, câmpul ALT_MASK permite sa se determine dacă, în momentul generării evenimentului, era apăsată tasta alt. Aceasta se obţine intersectănd masca cu valoarea întoarsă de metoda getModifiers().

Câmpuri
public static final int SHIFT_MASK

- mască pentru a detecta dacă tasta shift era

apăsată.
public static final int CTRL_MASK public static final int META_MASK public static final int ALT_MASK

- mască pentru a detecta dacă tasta ctrl era apăsată - mască pentru a detecta dacă tasta meta era apăsată

- mască pentru a detecta dacă tasta alt era apăsată.

Metode
Metodele clasei AWTEvent, la care se adaugă următoarele:
public String getActionCommand()

- întoarce un şir, care identifică acţiunea de comandă

indicată de acest eveniment.
public int getModifiers()

- întoarce modificatorii evenimentului (ce taste de control erau apasate când s-a produs evenimentul).
public String paramString()

- întoarce un şir, care conţine parametrii evenimentului şi

este util în special la depanare.

Clasa AdjustmentEvent
public class AdjustmentEvent extends AWTEvent Instanţele acestei clase sunt evenimente, generate atunci când este ajustată o mărime specifică sursei de eveniment respective.

Metode
public Adjustable getAdjustable()

- întoarce obiectul ajustabil care a generat acest

eveniment.
public int getValue()

- întoarce valoarea curentă a mărimii ajustate.

409

Severin Bumbaru
public int getAdjustmentType() - întoarce tipul de ajustare, care poate fi următoarele: UNIT_INCREMENT, UNIT_DECREMENT, BLOCK_INCREMENT, BLOCK_DECREMENT, TRACK. public String paramString()

unul din

- întoarce un şir, care conţine parametri necesari, în special,

la depanare.

Clasa ComponentAdapter
public abstract class ComponentAdapter extends Object implements ComponentListener Clasă abstractă, servind drept prototip pentru realizarea claselor ascultătoare de evenimente de componentă. Clasa conţine toate metodele interfeţei ComponentListener, dar aceste metode nu fac nimic (au corpul vid). Penteru a crea un ascultător de evenimente de componentă, se crează o subclasă a clasei ComponentAdapter, în care se redefinesc numai metodele efectiv necesare.

Constructor
public ComponentAdapter()

Metode
Toate metodele interfeţei ComponentListener.

Clasa ComponentEvent
public class ComponentEvent extends AWTEvent Clasa evenimentelor generate la modificarea stării unei componente: deplasare, modificare a stării, modificare a vizibilităţii. Este, de asemenea, superclasă pentru alte evenimente de componentă, cum sunt ContainerEvent, FocusEvent, InputEvent, PaintEvent, WindowEvent. Evenimentele de componentă sunt date numai pentru scopuri de notificare (în mod obişnuit nu sunt folosite explicit în programul de aplicaţie, ci numai în mod implicit, pentru redimensionarea şi repoziţionarea componentelor).

Metode
public Component getComponent() public String paramString()

- întoarce componenta care a generat evenimentul.

- întoarce un şir de parametri, utili pentru depanare.

410

Programarea orientata pe obiecte în limbajul Java

Clasa InputEvent
public abstract class InputEvent extends ComponentEvent Clasă abstractă, care constituie rădăcina ierarhiei de clase pentru evenimente de intrare. Are ca subclase directe KeyEvent şi MouseEvent. Conţine câmpuri statice finale, ale căror valori sunt măşti pentru recunoaşterea tipului de eveniment. Conţine, de asemenea, metode pentru recunoaşterea diferitelor situaţii existente la generarea evenimentelor de intrare.

Câmpuri statice finale
public static final int SHIFT_MASK public static final int CTRL_MASK public static final int META_MASK public static final int ALT_MASK

- mască pentru tasta SHIFT.

- mască pentru tasta CTRL. - mască pentru tasta META.

- mască pentru tasta ALT. - mască pentru tasta ALT_GRAPH.

public static final int ALT_GRAPH_MASK public static final int BUTTON1_MASK public static final int BUTTON2_MASK public static final int BUTTON3_MASK

- mască pentru butonul 1 de mouse. - mască pentru butonul 2 de mouse. - mască pentru butonul 3 de mouse.

Metode
public boolean isShiftDown()

- la generarea evenimentului, tasta SHIFT era apăsată. - la generarea evenimentului, tasta CTRL era apăsată.

public boolean isControlDown() public boolean isMetaDown() public boolean isAltDown()

- la generarea evenimentului, tasta META era apăsată.

- la generarea evenimentului, tasta ALT era apăsată. - la generarea evenimentului, tasta ALT_GRAPH era

public boolean isAltGraphDown()

apăsată.
public long getWhen()

- întoarce momentul de timp când a avut loc evenimentul (dat de

ceasul sistemului). - întoarce modificatorii (din care, folosind măştile) se pot obţine caracteristicile evenimentului.
public int getModifiers()

411

Severin Bumbaru

Clasa ItemEvent
public class ItemEvent extends AWTEvent Eveniment semantic, care arată că un articol a fost selectat sau deselectat. Este generat de către obiecte selectabile (de exemplu articolele de listă).

Metode
public ItemSelectable getItemSelectable()

- întoarce articolul care a generat acest

eveniment.
public Object getItem()

- întoarce articolul afectat de către acest eveniment. generarea

public int getStateChange() - întoarce schimbarea de stare care a provocat evenimentului (poate fi ItemEvent.SELECTED sau ItemEvent.DESELECTED) public String paramString()

- întoarce un şir, care conţine parametrii evenimentului

(poate fi util la depanare).

Clasa KeyAdapter
public abstract class KeyAdapter extends Object implements KeyListener Adaptor pentru ascultătoarele de evenimente de tastă. Clasa conţine toate metodele din interfaţa KeyListener, dar aceste metode nu fac nimic (au corpul vid). Pentru a obţine un ascultător de evenimente de tastă, se face o subclasă în care se redefinesc numai metodele necesare în aplicaţia respectivă.

Constructor
public KeyAdapter()

Metode
public void keyTyped(KeyEvent e)

- a avut loc apăsarea şi eliberarea imediată a unei - a fost apăsată o tastă. - a fost eliberată o tastă.

taste.
public void keyPressed(KeyEvent e)

public void keyReleased(KeyEvent e)

412

Programarea orientata pe obiecte în limbajul Java

Clasa KeyEvent
public class KeyEvent extends InputEvent Eveniment care indică faptul că a fost acţionată o tastă. Evenimentul este generat de componenta care este activă în momentul acţionării tastei. Clasa conţine câmpuri statice finale, care permit identificarea tastei care a fost apăsate şi a modificatorilor corespunzători (a tastelor auxiliare care erau apăsate în momentul când s-a acţionar tasta generatoare de eveniment).

Principalele câmpuri statice finale
public static final int KEY_TYPED

- tasta a fost apăsată şi eliberată imediat. - tasta a fost apăsată. - tasta a fost eliberată.

public static final int KEY_PRESSED

public static final int KEY_RELEASED public static final int VK_ENTER

- a fost acţionată tasta ENTER. - tasta BackSpace

public static final int VK_BACK_SPACE public static final int VK_TAB

- tasta Tab - tasta Shift - tasta Ctrl

public static final int VK_SHIFT

public static final int VK_CONTROL public static final int VK_ALT

- tasta Alt - tasta Pause - tasta CapsLock (blocare pe litere majuscule)

public static final int VK_PAUSE

public static final int VK_CAPS_LOCK public static final int VK_ESCAPE public static final int VK_SPACE

- tasta Esc (Escape)

- tasta de spaţiu liber - tasta PageUp (o pagină în sus) - tasta PageDown (o pagină în jos)

public static final int VK_PAGE_UP

public static final int VK_PAGE_DOWN public static final int VK_END

- tasta End - tasta Home - tasta cu săgeată spre stânga

public static final int VK_HOME public static final int VK_LEFT public static final int VK_UP

- tasta cu săgeata în sus

413

Severin Bumbaru
public static final int VK_RIGHT public static final int VK_DOWN

- tasta cu săgeata la dreapta

- tasta cu săgeata în jos - tasta cu virgula (caracterul ","). - tasta cu minus (caracterul "-"). - tasta cu punctul (caracterul ".").

public static final int VK_COMMA public static final int VK_MINUS

public static final int VK_PERIOD public static final int VK_SLASH public static final int VK_0 public static final int VK_1

- tasta cu linia de fracţie (caracterul "-").

- tasta cu cifra zero - tasta cu cifra 1

. . . . . . . . . . . . . similar VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9 respectiv
pentru cifrele 2, 3, 4, 5, 6, 7, 8, 9 public static final int VK_SEMICOLON - tasta punct şi virgulă (caracterul ";") public static final int VK_EQUALS - tasta egal (caracterul "="). public static final int VK_A public static final int VK_B

- tasta cu litera A - tasta cu litera B

. . . . . . . . . . . . . similar pentru literele C, D, E, F, G, K, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
public static final int VK_OPEN_BRACKET

- tasta paranteză dreaptă deschisă (caracterul

"[" )
public static final int VK_BACK_SLASH

- caracterul bară inversă (caracterul "\" ) - caracterul paranteză dreaptă închisă

public static final int VK_CLOSE_BRACKET

(caracterul "]" )
public static final int VK_NUMPAD0 public static final int VK_NUMPAD1

- tasta 0 (zero) de pe tastatura numerică. - tasta 1 de pe tastatura numerică.

. . . . . . . . . . . . similar pentru tastele 2, 3, 4, 5, 6, 7, 8, 9 de pe tastatura numerică
public static final int VK_MULTIPLY public static final int VK_ADD

- caracterul "*" de pe tastatura numerică

- caracterul "+" de pe tastatura numerică - caracterul separator

public static final int VK_SEPARATER

414

Programarea orientata pe obiecte în limbajul Java
public static final int VK_SUBTRACT public static final int VK_DIVIDE public static final int VK_DELETE

- caracterul scădere de pe tastatura numerică

- caracterul împărţire de pe tastatura numerică - tasta Delete (ştergere) - tasta NumLock (trecerea pe tastatura numerică) tasta ScrollLock (blocarea defilării)

public static final int VK_NUM_LOCK

public static final int VK_SCROLL_LOCK public static final int VK_F1

- tasta specială F1

. . . . . . . . . . . . . similar pentru tastele speciale F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24
public static final int VK_PRINTSCREEN public static final int VK_INSERT public static final int VK_HELP public static final int VK_META

- tasta PrintScreen (tipărirea ecranului)

- tasta Insert

- tasta Help - tasta Meta - tasta apostrof invers (caracterul ` )

public static final int VK_BACK_QUOTE public static final int VK_QUOTE public static final int VK_KP_UP

- tasta apostrof (caracterul ' ) - tasta funcţională săgeată în sus - tasta funcţională săgeată în jos - tasta funcţională săgeată spre stânga - tasta funcţională săgeată la dreapta - tasta ampersand (caracterul "&" )

public static final int VK_KP_DOWN public static final int VK_KP_LEFT

public static final int VK_KP_RIGHT

public static final int VK_AMPERSAND public static final int VK_ASTERISK public static final int VK_QUOTEDBL public static final int VK_LESS

- tasta asterisc (caracterul "*" ) - tasta ghilimele (caracterul " )

- tasta "mai mic" (caracterul "<" ) - tasta "mai mare" (caracterul ">" ) - tasta "acolada stanga" (caracterul "{" ) - tasta "acolada dreapta" (caracterul "}" )

public static final int VK_GREATER

public static final int VK_BRACELEFT

public static final int VK_BRACERIGHT

415

Severin Bumbaru
public static final int VK_AT

- tasta cu caracterul at (caracterul "@" ) - tasta "doua puncte" (caracterul ":" ) - tasta circumflex (caracterul "^" )

public static final int VK_COLON

public static final int VK_CIRCUMFLEX public static final int VK_DOLLAR

- tasta cu simbolul valutar dolar (caracterul "$" ) - tasta cu simbolul valutar Euro. - tasta cu semnul de exclamatie - paranteză stânga (caracterul "(" )

public static final int VK_EURO_SIGN

public static final int VK_EXCLAMATION_MARK

(caracterul "!" )
public static final int VK_LEFT_PARENTHESIS public static final int VK_NUMBER_SIGN public static final int VK_PLUS

- tasta cu simbolul numeric (caracterul "#" )

- tasta plus (caracterul "+" ) - tasta paranteza dreapta (caracterul

public static final int VK_RIGHT_PARENTHESIS

")" )
public static final int VK_UNDERSCORE public static final int VK_ALT_GRAPH

- tasta underscore (caracterul "_" )

- Tasta AltGraph

Metode principale
public int getKeyCode()

- întoarce codul tastei asociat cu acest eveniment de intrare (cel care poate fi comparat cu unul din câmpurile statice finale de mai sus)
public char getKeyChar()

- întoarce caracterul logic al tastei (caracterul generat la apăsarea tastei respective: de exemplu, dacă s-au apăsat tastele Shift A, se întoarce caracterul "A", în timp ce dacă s-a apăsat simplu tasta A, se întoarce caracterul "a") - întoarce un şir, care conţine textul de pe tasta al cărui cod este dat ca argument (un text cum ar fi "HOME", "F1" sau "A").
public static String getKeyText(int keyCode)

- întoarce un şir, care conţine modificatorii, adică tastele auxiliare apasate la generarea evenimentului respectiv, de exemplu: "Shift" sau "Ctrl+Shift". (Observaţie: parametrul modifiers poate fi obţinut aplicând metoda getModifiers() a superclasei InputEvent).
public static String getKeyModifiersText(int modifiers)

- indică dacă aceasta este o tastă de acţiune, adică o tastă prin care utilizatorul cere o acţiune, cum ar fi tastele PageUp, PageDown, F1, F2 etc.
public boolean isActionKey()

416

Programarea orientata pe obiecte în limbajul Java

Clasa MouseAdapter
public abstract class MouseAdapter extends Object implements MouseListener Clasă abstractă, care poate fi extinsă pentru realizarea de ascultătoare de evenimente care implementează interfaţa MouseListener. Clasa conţine toate metodele interfeţei MouseListener, dar acestea nu fac nimic (au corpul vid). Extinzând această clasă, programatorul poate redefini numai acele metode, de care are nevoie.

Constructor
public MouseAdapter()

Metode
Cele ale interfeţei MouseListener.

Clasa MouseEvent
public class MouseEvent extends InputEvent Clasa evenimentelor generate de o componentă, atunci când este acţionat mouse-ul, iar cursorul de mouse se găseşte pe componenta respectivă. Există două categorii de astfel de evenimente: - evenimente de mouse propriu-zise: apăsarea sau eliberarea unui buton de mouse, click de mouse, intrarea cursorului de mouse pe suprafaţa componentei sau ieşirea de pe aceasta. - evenimente de mişcare a mouse-ului: tragerea mouse-ului pe suprafaţa componentei (deplasarea cursorului acestuia în timp ce unul din butoane este ţinut apăsat), sau deplasarea cursorului de mouse pe suprafaţa componentei (fără să fie apăsat nici unul din butoanele de mouse). Evenimentele de mouse sunt ascultate folosind interfaţa MouseListener, iar cele de mişcare a mouse-ului sunt ascultate prin interfaţa MouseMotionListener. În pachetul javax.swing.event există, de asemenea, interfaţa MouseInputListener şi clasa MouseInputAdapter, care permit ascultarea ambelor categorii de evenimente. Pentru a se afla care buton a fost apăsat, se folosesc modificatorii obţinuţi prin metoda getModifiers() şi măştile corespunzătoare din superclasa InputEvent.

Metode
Metodele superclaselor AWTEvent şi InputEvent, la care se adaugă următoarele:

417

Severin Bumbaru
public int getX()

- întoarce coordonata x a cursorului de mouse din momentul producerii evenimentului, în sistemul de coordonate al componentei sursă.
public int getY()

- întoarce coordonata y a cursorului de mouse din momentul producerii evenimentului, în sistemul de coordonate al componentei sursă. - întoarce punctuil în care se găsea cursorul de mouse în momentul producerii evenimentului, în sistemul de coordonate al componentei sursă.
public Point getPoint()

- tranlatează punctul în care se găsea cursorul de mouse în momentul producerii evenimentului, adăugând la cele două coordonate valorile dx şi dy specificate.
public void translatePoint(int dx, int dy)

- întoarce numărul de clickuri (se are în vedere că este posibil să se facă succesiv, la intervale mici de timp, mai multe clickuri de mouse).
public int getClickCount() public boolean isPopupTrigger()

- indică dacă acest eveniment de mouse este un trigger

de menu pop-up pentru platforma dată.

Clasa MouseMotionAdapter
public abstract class MouseMotionAdapter extends Object implements MouseMotionListener Clasă abstractă pentru adaptoarele de ascultoare de evenimente de mişcare a mouse-ului. Această clasă conţine toate metodele interfeţei MouseMotionListener, dar aceste metode nu fac nimic (corpul lor este vid). Pentru a realiza un ascultător de evenimente de mişcare a mouse-ului, se extinde această clasă, redefinind numai metodele care sunt necesare efectiv.

Constructor
public MouseMotionAdapter()

Metode
Cele ale interfeţei MouseMotionListener.

Clasa TextEvent
public class TextEvent extends AWTEvent Eveniment semantic, care indică faptul că a avut loc o modificare a textului conţinut în componenta care a generat evenimentul.

418

Programarea orientata pe obiecte în limbajul Java

Metodă
public String paramString()

- întoarce un şir, care conţine parametrii evenimentului.

Metodă utilă pentru depanare.

Clasa WindowAdapter
public abstract class WindowAdapter extends Object implements WindowListener Clasă abstractă, care serveşte la crearea claselor ascultătoare de evenimente de fereastră. Conţine toate metodele interfeţei WindowListener, dar aceste metode nu fac nimic. Pentru a crea o clasă ascultătoare de evenimente de fereastră, se crează o subclasă a clasei WindowAdapter, redefinind numai metodele efectiv necesare în aplicaţia respectivă.

Constructor
public WindowAdapter()

Metode
Toate metodele interfeţei WindowListener.

Clasa WindowEvent
public class WindowEvent extends ComponentEvent Clasă de evenimente de nivel coborât, care indică faptul că a avut loc o modificare a stării unei ferestre: fereastra s-a deschis, urmează să se închidă, s-a închis, s-a activat, s-a dezactivat, s-a iconificat sau deiconificat.

Metode
Metodele superclasei ComponentEvent, la care se adaugă următoarea:
public Window getWindow()

- întoarce fereastra care a generat evenimentul.

Interfaţa ActionListener
public interface ActionListener extends EventListener

419

Severin Bumbaru Această interfaţă este implementată de clasele ascultătoare de evenimente de acţiune. Aceste ascultătoare pot fi adăugate la componentele care suportă metoda addActionListener(actionListener l). Când componenta respectivă generează un ActionEvent, este activată metoda actionPerformed a ascultătorului.
Metodă public void actionPerformed(ActionEvent e) eveniment din clasa ActionEvent.

- metodă invocată când se produce un

Interfaţa AdjustmentListener
public interface AdjustmentListener extends EventListener Interfaţă pentru ascultătoarele de evenimente de ajustare.

Metodă
public void adjustmentValueChanged(AdjustmentEvent e)

- este invocată când are loc

ajustarea obiectului care generează evenimentul.

Interfaţa ComponentListener
public interface ComponentListener extends EventListener Interfaţă pentru ascultătoarele de evenimente de componentă. Ascultarea acestor evenimente se face numai în scop de notificare, deoarece tratarea lor se face automat de către AWT. În loc de a crea o clasă care implementează direct această interfaţă, se poate crea o subclasă a clasei ComponentAdapter.

Metode
public void componentResized(ComponentEvent e)

- componenta şi-a modificat

dimensiunile.
public void componentMoved(ComponentEvent e) public void componentShown(ComponentEvent e)

- componenta s-a deplasat. - componenta a fost făcută vizibilă. - componenta a fost făcută

public void componentHidden(ComponentEvent e)

invizibilă.

420

Programarea orientata pe obiecte în limbajul Java

Interfaţa ItemListener
public interface ItemListener extends EventListener Interfaţa pentru ascultătoare de evenimente de articol.

Metodă
- este invocată atunci, când are loc o modificare a stării ogiectului care generează evenimente de articol (ItemEvent).
public void itemStateChanged(ItemEvent e)

Interfaţa KeyListener
public interface KeyListener extends EventListener Interfaţă pentru ascultătoarele de evenimente de tastă. În loc de creea un ascultător de evenimente care implementează direct această interfaţă, se poate extinde clasa KeyAdapter. Pentru a afla care tastă a provocat generarea evenimentelor, se folosesc metodele şi măştile din clasa KeyEvent.

Metode
public void keyTyped(KeyEvent e)

- a avut loc apăsarea şi eliberarea imediată a unei - a fost apăsată o tastă. - a fost eliberată o tastă.

taste.
public void keyPressed(KeyEvent e)

public void keyReleased(KeyEvent e)

Interfaţa MouseListener
public interface MouseListener extends EventListener Interfaţă pentru ascultătoarele de mouse care ascultă principalele evenimente generate de acesta, respectiv apăsarea sau eliberarea unui buton, click de mouse, intrarea cursorului de mouse pe suprafaţa unei componente sau ieşirea acestuia. Pentru ascultarea evenimentelor de mişcare a mouse-ului se foloseşte interfaţa MouseMotionListener. În loc de a se crea o clasă de ascultare care implementează direct această interfaţă, se poate crea o subclasă a clasei MouseAdapter.

421

Severin Bumbaru

Metode
public void mouseClicked(MouseEvent e)

- metodă invocată când s-a produs un click de - metodă invocată când este apăsat unul din - metodă invocată când este eliberat unul

mouse.
public void mousePressed(MouseEvent e)

butoanele de mouse.
public void mouseReleased(MouseEvent e)

din butoanele de mouse.
public void mouseEntered(MouseEvent e)

- metodă invocată când cursorul de mouse

intră pe suprafaţa unei componente.
public void mouseExited(MouseEvent e)

- metodă invocată când cursorul de mouse

iese de pe suprafaţa unei componente.

Interfaţa MouseMotionListener
public interface MouseMotionListener extends EventListener Interfaţă pentru ascultătoarele de evenimente care se produc la mişcarea mouse-ului. În loc de a crea o clasă care implementează direct această interfaţă, se poate crea o subclasă a clasei MouseMotionAdapter.

Metode
public void mouseDragged(MouseEvent e)

- mouse-ul a fost "tras" (adică a fost deplasat

menţinând unul din butoane apăsat).
public void mouseMoved(MouseEvent e)

- mouse-ul a fost mişcat (fără să fie apăsat nici

unul din butoane).

Interfaţa TextListener
public interface TextListener extends EventListener Interfaţă pentru clasele de ascultare a evenimentelor de text. Astfel de evenimente sunt generate la modificarea textului conţinut într-o componentă.

Metodă
public void textValueChanged(TextEvent e)

- metodă invocată când a avut loc o

modificare a textului.

422

Programarea orientata pe obiecte în limbajul Java

Interfaţa WindowListener
public interface WindowListener extends EventListener Interfaţă pentru ascultătoarele de evenimente de fereastră. În loc de a crea o clasă ascultătoare care implementează direct această interfaţă, se poate crea o subclasă a clasei WindowAdapter.

Metode
public void windowOpened(WindowEvent e)

- metodă invocată când fereastra a fost

deschisă. - metodă invocată când fereastra urmează să se închidă (a fost apăsat butonul de închidere din colţul dreapta-sus, sau a fost selectat articolul Close din meniul din colţul stânga-sus).
public void windowClosing(WindowEvent e) public void windowClosed(WindowEvent e)

- metodă invocată când fereastra a fost - metodă invocată când fereastra a fost - metodă invocată când fereastra a

închisă.
public void windowIconified(WindowEvent e)

iconificată.
public void windowDeiconified(WindowEvent e)

fost deiconificată. - metodă invocată când fereastra a fost activată (poate primi intrări de la tastatură şi poate genera evenimente de tastă)
public void windowActivated(WindowEvent e) public void windowDeactivated(WindowEvent e)

- metodă invocată când fereastra a

fost dezactivată (nu mai primeşte intrări de la tastatură)

Clasa AbstractButton
public abstract class AbstractButton extends JComponent implements ItemSelectable, SwingConstants Clasă abstractă. Superclasă a claselor de butoane şi de articoloe de meniu. Butonul poate conţine un text şi/sau o pictogramă. Când este apăsat (se pune pe buton cursorul de mouse şi se apasă unul din butoanele de la mouse), butonul generează un eveniment de acţiune. Numele acestui eveniment (ActionCommand) este implicit identic cu textul de pe buton, dar el poate fi modificat prin metoda setActionCommand şi poate fi aflat prin metoda getActionCommand.

423

Severin Bumbaru Butonului i se poate ataşa şi o mnemonică, folosind metoda setMnemonic. Aceasta este o tastă, a cărei acţionare are acelaşi efect ca acţionarea butonului respectiv. Clasa conţine foarte multe metode, pentru cunoaşterea cărora recomandăm consultarea documentaţiei Java API.

Constructor
public AbstractButton()

Metode principale
public String getText()

- întoarce textul butonului. - setează textul butonului.

public void setText(String text) public boolean isSelected()

- indică dacă acest buton sau articol de meniu este selectat. - setează pentru ascest buton sau articol de meniu

public void setSelected(boolean b)

starea selectat sau neselectat.
public void setMargin(Insets m) public Insets getMargin() public Icon getIcon()

- setează marginile dintre bordură şi text.

- întoarce marginile dintre bordură şi text.

- întoarce pictograma implicită conţinută în buton. - setează pictograma implicită conţinută în - se setează numele acţiunii de

public void setIcon(Icon defaultIcon)

buton.
public void setActionCommand(String actionCommand)

comandă generată la apăsarea acestui buton.
public String getActionCommand()

- întoarce numele acţiunii de comandă produsă de

acest buton.
public int getMnemonic()

- întoarce mnemonica ataşată acestui buton (sau null, dacă ea

nu există). - setează mnemonica ataşată acestui buton, specificând ca parametru codul tastei respective (codul conţinut în clasa KeyEvent, de exemplu KeyEvent.VK_A ).
public void setMnemonic(int mnemonic)

- setează mnemonica ataşată acestui buton, indicând caracterul desenat pe tasta respectivă (de exemplu 'A').
public void setMnemonic(char mnemonic) public void addChangeListener(ChangeListener l)

- adaugă un ascultător de - elimină ascultătorul de

evenimente de schimbare.
public void removeChangeListener(ChangeListener l)

evenimente de schimbare specificat.

424

Programarea orientata pe obiecte în limbajul Java
public void addActionListener(ActionListener l)

- adaugă un ascultător de - elimină ascultătorul de

evenimente de acţiune.
public void removeActionListener(ActionListener l)

evenimente de acţiune specificat.
public void addItemListener(ItemListener l)

- adaugă un ascultător de evenimente - elimină ascultătorul de

de articol.
public void removeItemListener(ItemListener l)

evenimente de acţiune specificat.
public void setEnabled(boolean b) public String getLabel()

- face ca butonul să fie sau nu valabil.

- întoarce textul de pe buton. - setează textul butonului.

public void setLabel(String label)

Clasa Box.Filler
public static class Box.Filler extends Component implements Accessible O componentă invizibilă, folosită în instanţele clasei Box pentru distanţarea altor componente. Este clasă imbricată în clasa Box.

Constructor
public Box.Filler(Dimension min, Dimension pref, Dimension max)

- se creează

un BoxFiller, fiind specificate dimensiunile minimă, preferată şi maximă.

Metode
public void changeShape(Dimension min, Dimension pref, Dimension max)

- se modifică dimensiunile, fiind specificate noile dimensiuni (minimă, preferată şi maximă).
public Dimension getMinimumSize()

- intoarce dimensiunea minimă. - întoarce dimensiunea preferată.

public Dimension getPreferredSize() public Dimension getMaximumSize()

- întoarce dimensiunea maximă. - întoarce contextul accesibil.

public AccessibleContext getAccessibleContext()

425

Severin Bumbaru

Clasa ButtonGroup
public class ButtonGroup extends Object implements Serializable Instanţele clasei ButtonGroup nu sunt componente grafice propriu-zise, rolul lor fiind de a creea grupuri de butoane. Toate butoanele conţinute într-un grup sunt legate logic între ele, astfel că numai un singur buton din grup se poate găsi la un moment dat în starea "apăsat", celelalte fiind în starea "eliberat". Când se apasă un buton, se eliberează automat butonul din acelaşi grup, care era apăsat anterior.Se foloseşte, de regulă, la gruparea butoanelor din clasele JRadioButton şi JRadioButtonMenuItem.

Constructor
public ButtonGroup()

Metode principale
public void add(AbstractButton b)

- se adaugă la grup butonul specificat. - se elimină din grup butonul specificat

public void remove(AbstractButton b) public Enumeration getElements() public int getButtonCount()

- întoarce o enumeraţie a elementelor grupului.

- întoarce numărul butoanelor din grup.

Clasa JApplet
public class JApplet extends Applet implements Accessible, RootPaneContainer Este o versiune extinsă a clasei Applet, introdusă în Swing. Principala deosebire faţă de clasa Applet este că, la fel ca la clasa JFrame, adăugarea de componente nu se face direct la applet. Fiecare instanţă a clasei JApplet conţine un container numit contentPane, la care se adaugă toqate celelalte componente. Referinţa la acest container se obţine prin metoda getContentPane. Tot ca la JFrame, un JApplet poate avea bară de meniu, pusă prin metoda SetJMenuBar(). Explicatii despre crearea şi utilizarea appleturilor se dau în secţiunea Applet-uri din acest curs. Explicaţii mai ample se gasesc în secţiunea Writing Applets din Tutorialul Java. Descrierea tuturor metodelor este dată în Java API.

Metode frecvent utilizate
Toate metodele superclasei Applet, la care se adaugă:
public void update(Graphics g)

- invocă metoda paint(g). - setează bara de meniu specificată.

public void setJMenuBar(JMenuBar menuBar)

426

Programarea orientata pe obiecte în limbajul Java
public JMenuBar getJMenuBar()

- întoarce bara de meniu. - întoarce containerul contentPane al acestui

public Container getContentPane()

JApplet.

Clasa JButton
public class JButton extends AbstractButton implements Accessible Orice instanţă a acestei clase este un buton cu o singură stare stabilă (starea normală). Dacă butonul este apăsat (punând pe el cursorul de mouse şi apăsând butonul mouse-ului), el îşi schimbă temporar starea şi aspectul, dar revine la starea normală imediat ce este eliberat. Butonul poate conţine un text şi/sau o imagine (o pictogramă). Butonului i se poate ataşa un text volant (ToolTip) şi o mnemonică (o tastă, a cărei apăsare are acelaşi efect cu apăsarea butonului respectiv). Consultaţi şi secţiunea Butonul obişnuit din acest manual.

Constructori
public JButton()

- creează un buton, care nu conţine text sau pictogramă. - creează un buton, care conţine pictograma specificată. - creează un buton, care conţine textul specificat.

public JButton(Icon icon)

public JButton(String text) public JButton(Action a)

- creează un buton, cu proprietăţile date de acţiunea specificată - creează un buton, care va conţine textul şui

ca parametru.
public JButton(String text, Icon icon)

acţiunea specificate.

Metode
Metodele principale sunt cele ale clasei AbstractButton. Pentru a cunoaşte toate metodele, se va consulta documentaţia Java API.

Clasa JCheckBoxMenuItem
public class JCheckBoxMenuItem extends JMenuItem implements SwingConstants, Accessible Este un articol de meniu care se comportă la fel ca o casetă de validare (JCheckBox). Are aspectul unui patrat în care apare un simbol de validare (V) dacă este selectat şi nu apare

427

Severin Bumbaru acest simbol dacă este deselectat. Caseta de validare poate fi însoţită de un text sau/şi o pictogramă.

Constructori
public JCheckBoxMenuItem()

- construieşte un JChecBoxMenuItem fără pictogramă şi - construieşte un JChecBoxMenuItem cu - construieşte un JChecBoxMenuItem cu textul - construieşte un

text.
public JCheckBoxMenuItem(Icon icon)

pictograma specificată.
public JCheckBoxMenuItem(String text)

specificat.
public JCheckBoxMenuItem(String text, Icon icon)

JChecBoxMenuItem cu textul şi pictograma specificate. - construieşte un JChecBoxMenuItem cu textul specificat, indicând dacă în starea iniţială este sau nu selectat.
public JCheckBoxMenuItem(String text, boolean b)

- construieşte un JChecBoxMenuItem cu textul şi pictograma specificate, indicând dacă în starea iniţială este sau nu selectat.
public JCheckBoxMenuItem(String text, Icon icon, boolean b)

Metode principale
public boolean getState()

- întoarce starea.

public void setState(boolean b) - setează starea.

Clasa JComponent
public abstract class JComponent extends Container implements Serializable Clasa de bază pentru toate componentele Swing, cu excepţia containerelor de cel mai înalt nivel (JFrame, JDialog, JApplet). Orice JComponentă poate avea o bordură. Oricărei JComponente i se poate ataşa un "text volant" ("ToolTip"), adică un text explicativ, care apare dacă se pune cursorul de mouse pe componenta respectivă. Clasa JComponent conţine numeroase câmpuri şi metode. Aici se dau numai metodele cele mai frecvent folosite. Pentru o documentaţie completă se va consulta Java API.

Constructor
public JComponent()

428

Programarea orientata pe obiecte în limbajul Java

Metode frecvent utilizate
public void update(Graphics g) public void paint(Graphics g)

- invocă metoda paint(g) pentru această componentă.

- desenează această componentă, folosind contextul

grafic g. - setează dimensiunea preferată a componentei. Dacă este null, dimensiunea preferată va fi stabilită implicit.
public void setPreferredSize(Dimension preferredSize) public Dimension getPreferredSize()

- întoarce dimensiunea preferată. - setează dimensiunea maximă

public void setMaximumSize(Dimension maximumSize)

a componentei.
public Dimension getMaximumSize()

- întoarce dimensiunea maximă a componentei. - setează dimensiunea minimă a

public void setMinimumSize(Dimension minimumSize)

componentei.
public Dimension getMinimumSize()

- întoarce dimensiunea minimă a componentei.

- indică dacă această componentă conţine punctul de coordonate (x, y). Este utilă la prelucrarea evenimentelor de mouse.
public boolean contains(int x, int y) public void setBorder(Border border) public Border getBorder()

- setează bordura componentei.

- întoarce bordura componentei. - întoarce contextul grafic al acestei componente. - setează dacă această componentă este sau nu - setează dacă această componentă poate sau

public Graphics getGraphics()

public void setVisible(boolean aFlag)

vizibilă.
public void setEnabled(boolean enabled)

nu primi intrări de la utilizator.
public void setForeground(Color fg) public void setBackground(Color bg) public void setFont(Font font)

- setează culoarea de prim-plan a componentei. - setează culoarea de fond a componentei.

- setează fontul componentei.

- indică dacă această componentă poate fi traversată la focalizare (este printre cele care sunt focalizate una după alta, când se apasă tasta Tab).
public boolean isFocusTraversable() public void setToolTipText(String text)

- setează textul volant (ToolTip) pentru

această componentă.

429

Severin Bumbaru
public String getToolTipText()

- întoarce textul volant (ToolTip) ataşat acestei - întoarce locaţia textului volant

componente.
public Point getToolTipLocation(MouseEvent event)

(ToolTip) ataşat acestei componente. - indică dacă această componentă este "uşoară", adică nu are ca suport o componentă nativă de pe platforma respectivă.
public static boolean isLightweightComponent(Component c) public boolean isOpaque()

- indică dacă această componentă este complet opacă. - setează dacă această componentă este sau - întoarce ascultătorii de

public void setOpaque(boolean isOpaque)

nu opacă.
public EventListener[] getListeners(Class listenerType)

evenimente ataşaţi acestei componente.
public void repaint(long tm, int x, int y, int width, int height)

-

redesenează dreptunghiul specificat, după tm milisecunde.
public void repaint(Rectangle r)

- redesenează dreptunghiul r. - redesenează imediat

public void paintImmediately(int x, int y, int w, int h)

dreptunghiul specificat.
public void paintImmediately(Rectangle r)

- redesenează imediat dreptunghiul

specificat.
public JRootPane getRootPane()

- întoarce JRoorpane care este ancestor (ascendent) al acestei componente, sau null, dacă nu există.
protected String paramString()

- întoarce un şir, conţinând parametri utili la depanare.

Clasa JDialog
public class JDialog extends Dialog implements WindowConstants, Accessible, RootPaneContainer Instanţele acestei clase sunt ferestre de dialog. Componentele nu se adaugă direct la fereastra JDialog, ci la un container numit contentPane, conţinut în aceasta şi care poate fi obţinut prin metoda getContentPane(). Fereastra JDialog poate avea şi bară de meniu.

Constructori
public JDialog()

- construieşte o fereastră de dialog fără titlu şi fără a se specifica

proprietarul.

430

Programarea orientata pe obiecte în limbajul Java - construieşte o fereastră de dialog fără titlu, având ca proprietar cadrul (Frame) specificat.
public JDialog(Frame owner) public JDialog(Frame owner, boolean modal) - construieşte o fereastră de dialog fără titlu, având ca proprietar cadrul owner şi indicându-se dacă este sau nu modală. public JDialog(Frame owner, String title)

- construieşte o fereastră de dialog,

specificându-se proprietarul şi titlul ferestrei. - construieşte o fereastră de dialog, specificându-se proprietarul, titlul şi dacă este sau nu modală.
public JDialog(Frame owner, String title, boolean modal)

- se construieşte o fereastră de dialog, având ca proprietar altă fereastră de dialog, dată ca argument.
public JDialog(Dialog owner)

- se construieşte o fereastră de dialog, având ca proprietar altă fereastră de dialog, dată ca argument şi specificându-se dacă este sau nu modală.
public JDialog(Dialog owner, boolean modal) public JDialog(Dialog owner, String title)

- construieşte o fereastră de dialog,

specificându-se proprietarul şi titlul ferestrei. - construieşte o fereastră de dialog, specificându-se proprietarul, titlul şi dacă este sau nu modală.
public JDialog(Dialog owner, String title, boolean modal)

Metode principale
Metodele clasei Dialog, la care se adaugă: - setează operaţia implicită, care areloc când se execută comanda Close (când se apasă butonul de închidere a ferestrei dic bolţul din dreapta sus, sau se selectează opţiunea Close din meniul din colţul din stânga-sus). Operaţia dată ca argument poate fiuna din următoarele: WindowConstants.DO_NOTHING_ON_CLOSE - nu se face nimic în mod implicit. WindowConstants.HIDE_ON_CLOSE - fereasrtra este ascunsă (aceasta este opţiunea implicită). WindowConstants.DISPOSE_ON_CLOSE - fereastra este ascunsă şi disponibilizată (eliminată).
public void setDefaultCloseOperation(int operation) public int getDefaultCloseOperation() public void update(Graphics g)

- întoarce operaţia Close implicită.

- invocă metoda paint(g). - setează bara de meniu.

public void setJMenuBar(JMenuBar menu) public JMenuBar getJMenuBar()

- întoarce bara de meniu. - întoarce containerul contentPane al acestei

public Container getContentPane()

ferestre de dialog.

431

Severin Bumbaru
public void setContentPane(Container contentPane)

- setează containerul

contentPane.

Clasa JEditorPane
public class JEditorPane extends JTextComponent Clasă pentru realizarea panourilor de editare care suportă diferite formate de text, cum ar fi text simplu, text HTML sau text RTF. Are subclasa JTextPane. Se recomandă consultarea documentaţiei Java API şi a secţiunii UsingTextComponents din Tutorialul Java.

Class JFileChooser
public class JFileChooser extends JComponent implements Accessible Instanţele clasei JFileChooser sunt selectoare de fişiere. Ele vizualizează pe ecran arborele directoarelor şi fişierelor de pe disc şi permit selectarea intercactivă (cu mouse-ul) a fişierului dorit. Clasa conţine un număr mare de câmpuri, constructori şi metode. Pentru o mai bună documentare se recomandă a se consulta Java API şi capitolul How to use FileChoosers din Tutorialul Java.

Constructori principali
- construieşte un selector de fişiere cu pointer către directorul utilizatorului (users home directory).
public JFileChooser()

- construieşte un selector de fişiere cu pointer către directorul specificat prin calea dată ca argument.
public JFileChooser(String currentDirectoryPath)

- construieşte un selector de fişiere cu pointer către directorul specificat prin calea dată ca argument.
public JFileChooser(File currentDirectory)

Metode frecvent utilizate
public File getSelectedFile()

- întoarce fişierul selectat. - setează fişierul selectat.

public void setSelectedFile(File file) public File[] getSelectedFiles()

- întoarce tabloul fişierelor selectate. - setează ca selecate fişierele

public void setSelectedFiles(File[] selectedFiles)

specificate prin tabloul dat ca argument.

432

Programarea orientata pe obiecte în limbajul Java
public File getCurrentDirectory()

- întoarce directorul curent. - setează directorul curent.

public void setCurrentDirectory(File dir) public void changeToParentDirectory()

- trece de la directorul curent la părintele - asigură că fişierul dat ca argument este

acestuia.
public void ensureFileIsVisible(File f)

vizibil (visible) şi nu ascuns (hidden).
public int showOpenDialog(Component parent) throws HeadlessException

creează o fereastră de dialog pentru deschiderea fişierului. Fereastra întoarce o valoare corespunzătoare butonului pe care s-a apăsat şi care poate fi una din următoarele: JFileChooser.CANCEL_OPTION, JFileChooser.APPROVE_OPTION, JFileChooser.ERROR_OPTION (ultima în caz de eroare).
public int showSaveDialog(Component parent) throws HeadlessException

-

creează o fereastră de dialog pentru salvarea fişierului. Fereastra întoarce o valoare corespunzătoare butonului pe care s-a apăsat şi care poate fi una din următoarele: JFileChooser.CANCEL_OPTION, JFileChooser.APPROVE_OPTION, JFileChooser.ERROR_OPTION (ultima în caz de eroare).
public void addActionListener(ActionListener l)

- adaugă un ascultător de - elimină un ascultător de

evenimente de acţiune.
public void removeActionListener(ActionListener l)

evenimente de acţiune. - întoarce tabloul ascultătoarelor de evenimente de acţiune înregistrate la acest JFileChooser.
public ActionListener[] getActionListeners()

Clasa JFrame
public class JFrame extends Frame implements WindowConstants, Accessible, RootPaneContainer Este varianta Swing a clasei Frame din AWT, fiind o subclasă a acesteia. O deosebire importantă între cele două clase este că, în clasa JFrame, componentele nu se mai adaugă direct la fereastra (la frame), ci la un panou conţinut de aceasta, numit contentPane. O referinţă la contentPane se obţine prin metoda getContentPane(). Operaţiile cu acest contentPane (adăugarea şi eliminarea de componente, setarea gestionarului de poziţionare etc) se fac folosind metodele clasei Container. In JFrame se poate pune, de asemenea, o bară de meniu.

433

Severin Bumbaru Pentru o mai bună cunoaştere a structurii şi utilizării clasei JFrame, recomandăm să se studieze capitolul UsingTop-Level Containers din Tutorialul Java.

Constructori
public JFrame()

- construieşte un JFrame, iniţial invizibil şi fără titlu. - construieşte un JFrame, iniţial invizibil, cu titlul - construieşte un JFrame fără titlu, cu - construieşte un JFrame

public JFrame(String title)

specificat.
public JFrame(GraphicsConfiguration gc)

configuraţia grafică specificată, iniţial invizibil.
public JFrame(String title, GraphicsConfiguration gc)

cu titlul şi configuraţia grafică specificate, iniţial invizibil.

Metode frecvent utilizate
- se setează acţiunea care va fi efectuată, dacă se solicită operaţia "close" pentru acest JFrame (se apasă cu mouse-ul pe butonul de închidere din dreapta-sus, sau se selectează opţiunea Close din meniul din stîngasus). Ca argument al metodei se poate da unul din următoarele: WindowConstants.DO_NOTHING_ON_CLOSE - nu se face nimic WindowConstants.HIDE_ON_CLOSE - fereasrtra este ascunsă (aceasta este opţiunea implicită). WindowConstants.DISPOSE_ON_CLOSE - fereastra este ascunsă şi disponibilizată (eliminată). JFrame.EXIT_ON_CLOSE - fereastra este închisă şi se incheie executarea aplicaţiei (opţiune permisă numai în aplicaţii, nu şi în appleturi).
public void setDefaultCloseOperation(int operation) public int getDefaultCloseOperation() public void update(Graphics g)

- întoarce operaţia de închidere implicită.

- invocă metoda paint(g). - pune în JFrame bara de meniu

public void setJMenuBar(JMenuBar menubar)

specificată.
public JMenuBar getJMenuBar()

- întoarce bara de meniu (sau null, dacă nu există)

public Container getContentPane()

- întoarce containerul contentPane al acestui JFrame. La acest container se adaugă componentele ferestrei.
public void setContentPane(Container contentPane)

- setează containerul

contentPane pentru această fereastră.

434

Programarea orientata pe obiecte în limbajul Java

Clasa JLabel
public class JLabel extends JComponent implements SwingConstants, Accessible Componentă de afişare needitabilă, care poate să conţină un text şi/sau o imagine (o pictogramă).

Constructori
public JLabel(String text, Icon icon, int horizontalAlignment)

- se construieşte un JLabel, care conţine textul şi imaginea specificate. Al treilea parametru specifică modul în care se face alinierea pe orizontală şi poate fi una din următoarele: SwingConstants.LEFT - aliniere la stânga SwingConstants.CENTER - aliniere la centru SwingConstants.RIGHT - aliniere la dreapta. - se construieşte un JLabel, care conţine textul specificat şi respectă alinierea orizontală indicată.
public JLabel(String text, int horizontalAlignment) public JLabel(String text)

- se construieşte un JLabel, conţinând textul specificat,

aliniat la stânga. - construieşte un JLabel, care conţine imaginea şi respectă alinierea orizontală date ca argumente.
public JLabel(Icon image, int horizontalAlignment) public JLabel(Icon image) public JLabel()

- construieşte un JLabel care conţine imaginea specificată.

- construieşte un JLabel care nu conţine nimic.

Metode principale
public String getText()

- întoarce textul. - setează textul.

public void setText(String text) public Icon getIcon()

- întoarce imaginea. - setează imaginea. poate fi una

public void setIcon(Icon icon)

public int getVerticalAlignment() - întoarce alinierea pe verticală (aceasta din următoarele: SwingConstants.TOP, SwingConstants.CENTER sau SwingConstants.BOTTOM). public void setVerticalAlignment(int alignment)

- setează alinierea pe verticală

(vezi mai sus).
public int getHorizontalAlignment()

- întoarce alinierea pe orizontală. - setează alinierea pe

public void setHorizontalAlignment(int alignment)

orizontală. 435

Severin Bumbaru - întoarce poziţia pe verticală a textului în raport cu imaginea (aceasta poate fi una din următoarele: SwingConstants.TOP, SwingConstants.CENTER sau SwingConstants.BOTTOM).
public int getVerticalTextPosition() public void setVerticalTextPosition(int textPosition)

- setează poziţia pe în

verticală a textului, în raport cu imaginea.
public int getHorizontalTextPosition() - întoarce poziţia pe orizontală a textului raport cu imaginea (aceasta poate fi una din: SwingConstants.LEFT, SwingConstants.CENTER, SWingConstants.RIGHT). public void setHorizontalTextPosition(int textPosition)

- setează poziţia pe

orizontală a textului în raport cu imaginea.

Clasa JMenu
public class JMenu extends JMenuItem implements Accessible, MenuElement Instanţele acestei clase sunt meniuri, adică ferestre verticale, care conţin articole de meniu. Întrucât clasa JMenu este o subclasă a JMenuItem, înseamnă că un JMenu poate fi el insuşi un articol al altui menu (un JMenuItem). În acest fel, se pot crea meniuri cu structură ierarhică (arborescentă). Clasa conţine multe metode, dintre care se dau aici câteva mai frecvent utilizate. Pentru documentare completă se va folosi Java API.

Constructori
public JMenu()

- construieşte un meniu fără text. - construieşte un meniu cu inscripţia s. - construieşte un meniu cu proprietăţile acţiunii a.

public JMenu(String s) public JMenu(Action a)

Metode frecvent utilizate
public boolean isSelected()

- indică dacă meniul este selectat. - setează dacă acest meniu este sau nu selectat. - indică dacă fereastra cu articolele de meniu este

public void setSelected(boolean b)

public boolean isPopupMenuVisible()

vizibilă. public void setPopupMenuVisible(boolean b) - setează dacă fereastra cu articole de meniu este sau nu vizibilă.
public JMenuItem add(JMenuItem menuItem)

- adaugă articolul de meniu specificat.

436

Programarea orientata pe obiecte în limbajul Java
public JMenuItem add(String s) public void addSeparator()

- adaugă un nou articol de meniu cu inscripţia s.

- adaugă un separator. - înserează pe poziţia pos un nou srticol de - înserează pe poziţia pos articolul

public void insert(String s, int pos)

meniu cu inscripţia s.
public JMenuItem insert(JMenuItem mi, int pos)

de meniu mi. public void insertSeparator(int index) - înserează un separator pe poziţia de indice dat.
public JMenuItem getItem(int pos) public int getItemCount()

- întoarce articolul de pe poziţia pos.

- întoarce numărul de articole din acest meniu, inclusiv - elimină articolul de meniu specificat.

separatorii.
public void remove(JMenuItem item) public void remove(int pos) public void removeAll()

- elimină articolul de meniu de pe poziţia pos.

- elimină toate articolele din acest meniu.

- indică dacă acest meniu este de cel mai înalt nivel, adică este ataşat direct la bara de meniu.
public boolean isTopLevelMenu() public void addMenuListener(MenuListener l)

- adaugă ascultătorul de evenimente de - elimină ascultătorul de

meniu specificat.
public void removeMenuListener(MenuListener l)

evenimente de meniu specificat.

Clasa JMenuBar
public class JMenuBar extends JComponent implements Accessible, MenuElement Instanţele aceste clase sunt folosite ca bare de meniu. Pentru a obţine un meniu cu bară, la bara de meniu se adaugă diferite meniuri (instanţe ale clasei JMenu). Vezi şi explicaţiile din secţiunea Meniuri cu bară a acestui curs. Indicăm aici numai câteva metode mai frecvent utilizate. Pentru o documentare completă se va consulta Java API.

Constructor
public JMenuBar()

437

Severin Bumbaru

Metode principale
public JMenu add(JMenu c)

- se adaugă la această bară un nou meniu. - întoarce meniul cu indicele specificat.

public JMenu getMenu(int index) public int getMenuCount()

- întoarce numărul de meniuri din această bară. - setează meniul de ajutor (Help)

public void setHelpMenu(JMenu menu) public JMenu getHelpMenu()

- întoarce meniul de ajutor (Help). - setează componenta selectată

public void setSelected(Component sel) public boolean isSelected()

- indică dacă bara de meniu conţine o componentă

selectată.

Clasa JMenuItem
public class JMenuItem extends AbstractButton implements Accessible, MenuElement Instanţele acestei clase sunt articole de meniu. Un astfel de obiect se comportă la fel ca un buton, numai că este plasat într-un meniu. Din această cauză, clasa JMenuItem extinde clasa AbstractButton. Are ca subclase JCheckBoxMenuItem, JMenu şi JRadioButtonMenuItem. Instanţele acestei clase se comportă ca butoane obişnuite (au o singură stare stabilă, iar la apăsare generează un eveniment de acţiune).

Constructori
public JMenuItem()

- construieşte un articol de meniu fără text sau pictogramă. - construieşte un articol de meniu cu pictogra