+ All Categories
Home > Documents > Program Area Orientata Spre Obiecte - Limbajul Java

Program Area Orientata Spre Obiecte - Limbajul Java

Date post: 03-Jul-2015
Category:
Upload: florin-scripcaru
View: 923 times
Download: 5 times
Share this document with a friend
129
CRENGUŢA BOGDAN LUCA DAN ŞERBĂNAŢI PROGRAMAREA ORIENTATĂ SPRE OBIECTE (LIMBAJUL JAVA) NOTE DE CURS 2006
Transcript

CRENGUŢA BOGDAN LUCA DAN ŞERBĂNAŢI

PROGRAMAREA ORIENTATĂ SPRE OBIECTE

(LIMBAJUL JAVA)

NOTE DE CURS

2006

2

CONŢINUT 1. INTRODUCERE IN PROGRAMAREA ORIENTATA SPRE OBIECTE...3

OBIECTE .....................................................................................................................................3 CLASE DE OBIECTE......................................................................................................................6

2. RELATII DE ASOCIERE INTRE CLASE ................................................9 CARACTERISTICILE ABORDARII ORIENTATE PE OBIECTE...............................................................14

3. RELAŢIA DE GENERALIZARE/SPECIALIZARE ÎNTRE CLASE.........16 MOŞTENIREA ÎN JAVA ................................................................................................................17

4. ARRAY-URI ..........................................................................................23 INIŢIALIZAREA UNUI ARRAY.........................................................................................................23 ARRAY-URI MULTIDIMENSIONALE................................................................................................24 LUCRUL CU ARRAY-URI ..............................................................................................................25 ARRAY ÎN METODE.....................................................................................................................26

5. CLASE ABSTRACTE............................................................................28 6. INTERFETE..........................................................................................32 7. CLASE INTERNE .................................................................................36

DEFINIREA CLASELOR INTERNE ..................................................................................................36 CLASE INTERNE STATICE ...........................................................................................................38 CLASE INTERNE ŞI MOŞTENIREA.................................................................................................38 UTILIZAREA CLASELOR INTERNE ÎN APLICAŢII OO .......................................................................39

8. INPUT/OUTPUT ÎN JAVA.....................................................................42 9. GESTIUNEA EXCEPŢIILOR ................................................................55 10. COLECTII .............................................................................................62

STRUCTURI DE DATE DINAMICE ..................................................................................................62 TIPOLOGII DE COLECŢII ..............................................................................................................62 COLECŢII ÎN JAVA ......................................................................................................................76

11. APPLET ................................................................................................91 12. INTERFETE GRAFICE.........................................................................99 13. GESTIUNEA EVENIMENTELOR .......................................................105

13.1 MODELUL GESTIUNII EVENIMENTELOR ÎN PROIECTAREA COMPONENTELOR GRAFICE.......105 13.2 EVENIMENTE SEMANTICE .............................................................................................107 13.3 EVENIMENTE DE NIVEL COBORÂT..................................................................................113 13.4 CLASE ADAPTER ALE INTERFEŢELOR DE ASCULTARE.....................................................118

14. PROGRAMAREA INTERFETELOR GRAFICE CU SWING...............122 FERESTRE INTERNE.................................................................................................................124 FERESTRE INTERNE.................................................................................................................125 CLASA JVIEWPORT..................................................................................................................126 JSCROLLPANE ........................................................................................................................127

15. BIBLIOGRAFIE...................................................................................129

3

1. INTRODUCERE IN PROGRAMAREA ORIENTATA SPRE OBIECTE

OBIECTE D. Un obiect este un un mod simplificat de a identifica într-un program un lucru, o entitate din lumea reală sau imaginată. Din punctul de vedere al paradigmei pe obiecte, un obiecte este o combinatie de: - informatii de structura, descrise de o multime de atribute ale obiectului, si - functionalitate descrisa de o multime de operatii ce actioneaza asupra atributelor obiectului si

eventual, asupra altor obiecte.

D. Un atribut este o abstractizare a unei proprietati a unui obiect din lumea reala. Un atribut se caracterizeaza prin: nume, tip, valoare si eventual constrangeri. De exp, magazinul Tomis, ca orice magazin se caraterizeaza prin urmatoarele proprietati: denumire, numele proprietarului, ora de deschidere si ora de inchidere. In plus, am putea avea si un numar de identificare care sa diferentieze unic magazinul Tomis de orice alt magazin.

D. Valorile curente ale tuturor atributelor unui obiect formeaza starea obiectului. De exemplu, starea magazinului Tomis ar putea fi urmatoarea:

numar de identificare= 10 denumire= Tomis numele proprietarului= Ionescu Pop ora de deschidere= 10 ora de inchidere= 18

Atributele unui obiect sunt descrise in momentul analizei si proiectarii programului OO folosind limbajul UML si apoi sunt implementate cu ajutorul variabilelor intr-un limbaj de programare (in particular, Java): Nivelul specificarii (limbajul UML) Nivelul implementarii (limbajul Java) - nrIdentificare: Integer = 10 {valoare unica pentru fiecare obiect}

private int nrIdentificare;

- denumire: String private String denumire; - numeProprietar: String private String numeProprietar; - oraDeschidere: Integer = 9 private int oraDeschidere= 9; - oraInchidere: Integer = 18 private int oraInchidere= 18;

D. O operatie este un algoritm privat al obiectului, adica se executa in mediul său si care opereaza asupra valorilor atributelor sale sau ale altor obiecte pentru a furniza un serviciu unui alt obiect numit client. Operatiile se deduc din responsabilitatile obiectului pe care trebuie sa le indeplineasca. De exemplu, obiectul magazinul 10 trebuie sa furnizeze acces la ora de inchidere a magazinului, adica sa furnizeze informatii de stare a obiectului, deoarece proprietarul magazinului vrea sa stie la ce ora se inchide magazinul. In plus, proprietarul magazinului (ca orice client al obiectului) poate sa modifice ora de inchidere a magazinului, modificand astfel starea obiectului Magazin. Sau proprietarul ar vrea sa stie care este intregul orar al magazinului. Nivelul specificarii (limbajul UML) Nivelul implementarii (limbajul Java) + obtineOraInchidere(): Integer public int getOraInchidere(){

return oraInchidere; }

+ modificaOraInchidere(nouaOra: Integer) public void setOraInchidere(int oraNoua){ oraInchidere=oraNoua; }

4

Nivelul specificarii (limbajul UML) Nivelul implementarii (limbajul Java) + obtineOrar(): String public String getOrar(){

return oraDeschidere+”:00-” +oraInchidere+”:00”;}

D. Implementarea unei operatii in limbajul Java se numeste metoda. O metoda se caracterizeaza prin urmatoarele elemente: - prototipul metodei = contine elementele de declarare a metodei - semnatura metodei = combinatie intre numele metodei si tipul parametrilor in ordinea declararii

lor - definitia metodei = corpul metodei ce contine instructiuni ce implementeaza algoritmul operatiei.

Elementele de structura si functionalitate ale unui obiect pot fi proiectate pentru a fi accesibile din afara obiectului. Pana acum, orice client (inclusiv proprietarul) are acces la operatiile obiectului 10 deoarece operatiile respective sunt publice.

D. Totalitatea elementelor ce pot fi accesate din exteriorul obiectului formeaza interfata acestuia. Rezulta ca starea unui obiect poate fi modificata numai prin intermediul interfetei lui. Observatie. Desi obiectele se diferentiaza prin stare, aceasta din urma nu asigura identitatea obiectelelor. Adica, puteam avea doua obiecte cu aceeasi stare, dar care sa fie diferite. In cazul obiectului 10, identitatea acestuia este data de valoarea atributului numar de identificare. In acest caz, spunem ca atributul (proprietatea) numar de identificare furnizeaza un criteriu de identitate pentru obiectele de acelasi tip.

Principiul OO care sta la baza definirii interfetei unui obiect este cel al incapsularii informatiilor (“information hiding”). Conform acestui principiu, trebuie sa ascundem in interiorul obiectului (folosind modificatorul de acces private) toate elementele (atribute sau operatii) care nu sunt utile altor obiecte (numite obiecte client: se afla in exteriorul obiectului si comunica cu el prin mesaje) si sa lasam numai acele informatii in care clientii sunt interesati.

De exemplu, interfata obiectului 10 este formata din metodele publice fara sa dam acces direct la variabilele instanta. In acest caz ele sunt variable private.

Un alt exemplu il constituie cazul in care proprietarul, ca orice client al obiectului 10, vrea sa stie daca magazinul cu numarul de identificare 10 este deschis sau nu. Solutia este ca obiectul 10 sa aiba o metoda publica care sa-i spuna proprietarului ca este deschis sau nu in functie de momentul apelarii metodei esteDeschis(): Nivelul specificarii (limbajul UML)

Nivelul implementarii (limbajul Java)

+ obtineRaspuns(): String public String getMesaj(){ if (esteDeschis()) return “Este deschis!”; else return “Este inchis!”; } private boolean esteDeschis(){ Calendar c=Calendar.getInstance(); int oraCurenta=c.get(Calendar.HOUR_OF_DAY); if (oraCurenta>=oraDeschidere && oraCurenta<=oraInchidere) return true; return false; }

CONCEPTE Un obiect este o instanta (sau exemplificare) a unui concept. D. Un concept este o idee sau o notiune care se aplica: - unor entitati concrete: lucruri care ne inconjoara sau fapte care se petrec de-a lungul timpului; sau

5

- unor entitati abstracte cum ar fi constructiile noastre mentale.

Un concept se caracterizeaza prin trei elemente: - nume: cuvantul pe care-l folosim cand ne referim la acel concept; - o intentie (semantica): ideea sau notiunea la care se refera numele; - o extensie data de multimea instantelor conceptului.

Conceptele pot fi clasificate in mai multe categorii: Categorie Exemple de concepte Instante de concepte tangibil persoana, masina, scaun colegul Ionescu, masina

mea intangibil timp, firma 17:23, Torent Computers fiinta din lumea reala persoana, pisica, caine colegul Ionescu, Azor lucru din lumea reala artefact, masina, casa, copac descriere a unui alt obiect marca de masina, specificatia unei

aplicatii OO, documentatie de proiect

Mercedes

organizatie firma, agentie de voiaj, banca, fundatie

Fundatia Oamenilor de afaceri

loc magazin, port, oras Magazinul Tomis, portul Constanta

rol in scenarii reale sau imaginare

pilot, administrator, profesor, medic, proprietar, profesor, autor, student, angajat

relational casatorie, asociatie familiala tranzactie vanzare, rezervare, cumparare,

contractare, inchiriere

elementele ce compun o tranzactie

articol dintr-o vanzare de mai multe produse, rezervarea pentru o persoana in rezervarea unei excursii in grup

eveniment trimitere mesaj, revizie, vizita medicala, inregistrare automobil

proces achizitie on-line a unor produse, înscriere la universitate, admitere

agregat clasa de elevi, fereastra de componente grafice, masina, masa, roi de albine

entitate continuta intr-un agregat

elev intr-o clasa, buton intr-o fereastra, motor, picior de masa, albina

dispozitiv vitezometru, modem, inregistrator de casa

sistem extern sistemul informatic al serviciului personal, sistemul de contabilitate, sistemul de autorizare a utilizarii unui card bancar

inregistrari de contracte sau evenimente

factura, log-are, memorarea sosirii unui mesaj in sistem, comanda de

6

Categorie Exemple de concepte Instante de concepte achizitie

concepte abstracte calendar gregorian, integrala, multime, ecuatie

Observatie. Categoriile de concepte nu sunt ortogonale (adica, independente intre ele), un concept putand sa apartina mai multor categorii.

CLASE DE OBIECTE D. O clasă este o descriere a unei mulţimi de obiecte care au aceleaşi atribute, aceleaşi operaţii, aceleaşi relaţii cu alte clase şi aceeaşi semantică.

Unitatea de baza a limbajului Java (ca orice limbaj orientat spre obiecte) este clasa. Din acest motiv, un program Java contine cel putin o clasa. O clasă descrie instantele unui concept.

Nivelul conceptual

Nivelul specificarii (limbajul UML) Nivelul implementarii (limbajul Java)

Magazin

public class Magazin{ private int nrIdentificare; private String denumire, numeProprietar; private int oraDeschidere, oraInchidere; public int getOraInchidere(){ return oraInchidere; } public int getOraDeschidere(){ return oraDeschidere; } public String getMesaj(){ if (esteDeschis()) return “Este deschis!”; else return “Este inchis!”; } private boolean esteDeschis(){ Calendar c=Calendar.getInstance(); int oraCurenta=c.get(Calendar.HOUR_OF_DAY); if (oraCurenta>=oraDeschidere && oraCurenta<=oraInchidere) return true; return false; } }

Observatie. Atributele si operatiile obiectelor sunt implementate in Java ca variabile instanta, respectiv metode instanta.

O clasa poate defini variabile si metode de clasa. O variabila de clasa implementeaza un atribut al clasei si nu al obiectelor sale. Asadar, valoarea unei variabile de clasa este partajata de toate obiectele clasei respective. De exemplu, pentru a implementa in Java constrangerea ca valoarea variabilei nrIdentificare este unica avem nevoie de o variabila statica care sa numere instantele (obiectele) clasei Magazin: private static int idCurent=1. Valoarea reprezinta numarul de identificare al noului obiect.

O metoda de clasa este o metoda care apartine clasei si din acest motiv este definita ca fiind statica si la care au acces toate obiectele clasei.

MagazinnrIdentificare : Integerdenumire : StringnumeProprietar : StringoraDeschidere : IntegeroraInchidere : Integer

obtineOraInchidere() : IntegerobtineOraDeschidere() : IntegermodificaOraDeschidere(nouaOra : Integer)modificaOraInchidere(nouaOra : Integer)obtineOrar() : StringobtineRaspuns() : String

7

Se pot proiecta si implementa clase care au numai metode statice. Se numesc clase utilitare si furnizeaza servicii globale. De exemplu, clasa Arrays contine metode de sortare a elementelor unui array de numere intregi, numere reale, caractere, inclusiv obiecte dupa diferite criterii.

O clasa este un model pentru obiectele sale. De exemplu, toate obiectele clasei Magazin, vor avea aceeasi structura: atribute si operatii.

O clasa este o fabrica pentru obiectele sale, deoarece orice clasa are cel putin un constructor.

D. Un constructor este o metoda cu ajutorul careia sunt create obiecte din care clasa in care este definit constructorul si initializeaza variabilele instanta ale noului obiect.

Alte caracteristici ale unui constructor: - are acelasi nume ca numele clasei - nu are tip al rezultatului - este apelat prefixandu-l cu operatorul new. Folosirea acestui operator determina crearea unei zone

de memorie in Heap in care se aloca spatiu pentru fiecare variabila de instanta a obiectului nou creat.

Asadar, clasa Magazin are un constructor. Se numeste constructor implicit deoarece este furnizat de Java. Acesta nu are parametri si initializeaza cu valori implicite sau cu valorile definite in sectiunea de declarare a variabilelor instanta. O clasa poate defini explicit unul sau mai multi constructori care sa realizeze initializarea variabilelor instanta si eventual modificarea variabilelor de clasa, daca este necesar. In cazul in care o clasa are mai multi constructori, acestia vor fi spuraincarcati, adica au acelasi nume si difera prin numarul si/sau tipul parametrilor. Sau altfel supus, semnatura lor difera numai prin numarul si/sau tipul parametrilor. De exemplu, clasa Magazin ar putea avea doi constructori: public Magazin(String numeM, String numeP, int oraD, int oraI) { //Constructor nrIdentificare=idCurent++ ; denumire=numeM; numeProprietar=numeP; oraDeschidere=oraD; oraInchidere=oraI; } public Magazin(String numeM, String numeP) { //Constructor nrIdentificare=idCurent++ ; denumire=numeM; numeProprietar=numeP; oraDeschidere=10; oraInchidere=18; } this = Cuvânt cheie care se utilizează în interiorul corpului unei metode instanţă şi care furnizează referinţa la obiectul pentru care a fost apelată metoda.

Intr-un constructor se poate apela un alt constructor al aceleaşi clase. De exemplu, al doilea constructor ar putea fi definit astfel: public Magazin(String numeM, String numeP) { this(numeM, numeP, 10, 18); }

În UML reprezentăm relaţia de instanţiere dintre o clasă şi un obiect al său ca o dependenţă

8

Diagrama 1. Relaţia de instanţiere

O clasa furnizeaza un tip de date cu care putem declara variabile referinta de acest tip.

De exemplu, vom scrie o alta clasa TestMagazin care, in metoda main(), sunt create două obiecte din clasa Magazin şi se memoreaza referinţele la ele în variabile de acelaşi tip: Magazin m1 = new Magazin(“Tomis Mall”, “Ionescu Pop”); Magazin m2 = new Magazin(“Constanta”, “Popescu”,“Ion”); Accesarea variabilelor Variabilele m1 si m2 ar putea fi folosite pentru a accesa valorile variabilelor instanţă, daca ne dau voie. In cazul in care o variabila instanta furnizeaza acces, atunci puteam folosi operatorul punct pentru a accesa valorile acestora. In cazul variabilelor de clasa, daca aceastea furnizeaza acces, ele sunt modificate sau folosite cu NumeClasa.numeVariabilaClasa.

Apelul de metodă D. Apel de metoda = mecanism oferit de un limbaj de programare prin care o entitate din program (in cazul nostru un obiect) cere executia unei metode. Aceasta metoda poate sa apartina obiectului apelant (apel intern) sau, in cazul general, poate sa apartina unui alt obiect. Pentru a putea apela o metoda M a unui obiect B, un obiect A trebuie sa aiba acces la metoda M. Accesul la o metoda este stabilit de regulile de vizibilitate din Java. De obicei un apel de metoda are loc in momentul in care obiectul apelant executa o metoda a sa. Apelul metodei intrerupe executia metodei curente si trece controlul metodei apelate. La sfarsitul executiei metodei apelate, controlul revine la metoda intrerupta, exact in punctul de intrerupere.

Daca metoda apelata restituie (intoarce) o valoare metodei apelante, aceasta valoare poate participa la calculul unei expresii devenind un operand in acea expresie. Este cazul in care apelul de metoda apare in interiorul unei expresii sau in locul unei expresii.

Apelul unei metode poate fi vazut si ca un mesaj pe care obiectul apelant il trimite obiectului apelat. In acest sens putem spune ca obiectele comunică între ele prin intermediul metodelor.

Apelarea unei metode se face cu ajutorul operatorului binar “.”. Primul operand al apelului trebuie sa fie o variabila-reference sau o expresie a carui valoare sa fie de tip reference catre un obiect in memorie. Al doilea operand este chiar numele metodei urmat de lista parametrilor efectivi. De exemplu, m1.getMesaj(). In cazul apelurilor de metode ale clasei (este cazul metodelor declarate static), regula de construire a apelului este aceeasi doar ca primul operand trebuie sa fie chiar numele clasei.

Nota. Ca si in cazul variabilelor-membru, apelul unei metode apartinand unui obiect din interiorul unei metode che apartine aceluiasi obiect se face fara a utiliza operatorul “.” si fara a utiliza referinta la obiectul destinatie. Acest obiect este subinteles a fi obiectul curent in care ne aflam cu executia. De exemplu, clasa Magazin are metoda getMesaj() care intoarce un sir care indica daca magazinul este deschis (“Este deschis!”) sau inchis (“Este inchis!”) in momentul apelarii metodei esteDeschis().

Magazin Tomis: Magazin instanţă a

obiect clasă

9

2. RELATII DE ASOCIERE INTRE CLASE Pe parcursul acestui curs vom trata problema de a gestiune a facturilor dintr-un magazin a carei descriere este prezentata in cele ce urmeaza. Specificatia proiectului

Magazinul Tomis emite facturi pe baza produselor vandute clientilor. Pentru a gestiona automat facturile emise de magazin, proprietarul magazinului a decis sa cumpere de la o firma de software o aplicatie care se realizeze acest lucru.

Cand este executata, aplicatia afiseaza fereastra principala:

Pentru a vizualiza date despre magazin, clientul (sau oricine este interesat) actioneaza butonul “Date despre magazin”, iar aplicatia va afisa urmatoarea interfata grafica:

Pentru a realiza o vanzare, clientul sau casiera trebuie sa actioneze butonul “Comandati produse” din ferestra principală. In acest moment, aplicatia va afisa urmatoarea interfata grafica:

Datele clientilor sunt memorate intr-un fisier numit “clienti.txt”, a.i. la urmatoarele operatii, acestia vor fi recunoscuti. Daca este vorba de un client “vechi”, dupa introducerea numelui si prenumelui său, si actionarea tastei Enter, adresa acestuia este citita din fisier si afisata in campul de text: Adresa. Daca este vorba de un client nou, aplicatia atentioneaza clientul ca este nou si trebuie sa introduca adresa.

10

Clientul nu poate efectua o vanzare, daca nu a fost apasat butonul Submit. In acel moment, in cazul unui client nou, datele acestuia (adica, nume, prenume si adresa) vor fi memorate in fisierul “client.txt”. In plus, butonul Adauga este activat si clientul isi poate alege produse (pe rand, dintr-o lista de produse) si trebuie sa specifice cantitatea. Daca uită sa adauge cantitatea, atunci programul afiseaza un mesaj de eroare. Aceasta operatie poate fi efectuata si de catre casieră, la sugestiile clientului.

Actionarea butonului Adauga determina afisarea informatiilor despre produsul comandat: nume, cantitate si pretul unitar al acestuia, in tabelul aflat pe interfata sistemului. Aceste informatii (numele si pretul unitar al produsului) sunt preluate dintr-un fisier numit “produse.txt”.

Dupa ce au fost alese toate produsele ce vor fi cumparate, se actioneaza butonul Executa care determina crearea si vizualizarea facturii, al carei continut va fi afisat in fereastra Factura.

Dupa cum se observa, atributele facturii sunt numar factura (care este unic) si data la care s-a emis factura. Pentru fiecare articol al facturii, se afiseaza numele, pretul unitar, cantitatea comandata si pretul articolului. La sfarsit, se face totalul (=suma pretului articolelor) din care se scade 5% din total daca cumpararea s-a facut intr-o zi de lucru (luni-vineri), la care se apoi adauga un tva de 20% din suma obtinuta si se obtine totalul general.

Factura poate fi tiparita la imprimanta, daca se actioneaza butonul Print, poate fi memorata intr-un fisier (butonul PrintToFile), respectiv poate fi arhivata (butonul Arhivare). In ultimul caz, factura memorata in fisierul “arhiva.txt” ce contine toate facturile furnizate de magazin. Operatia de vanzare este anulata daca se actioneaza butonul Cancel.

11

Paşii de rezolvare a unei probleme Pas 1. Identificarea conceptelor Magazin Vanzare Produs Interfata grafica Proprietar Fisier Data Vanzatoare Factura Lista de produse Client Program Fereastra Articol Buton Imprimanta

Pasul 2. Identificarea claselor prin analizarea conceptelor obtinute la pasul anterior.

Magazin Vanzare Produs Interfata grafica Proprietar Lista de produse Factura Client Articol Pasul 3. Identificarea atributelor obiectelor claselor Magazin: denumire, oraDeschidere, oraInchidere Factura: numar (unic), dataEmiterii SpecificatieProdus: denumire, pretUnitar Client: nume, prenume, adresa Proprietar: nume, prenume Articol (sau ElementVanzare): specificatieProdus, cantitate Vanzare: tva, total Pasul 4. Construirea diagramei de clase Pentru a putea sa construim diagrama de clase, trebuie sa identificam relatiile dintre acestea. Relatia de asociere intre clase In general, o legătură reprezintă un mod de a conecta, asocia sau raporta între ele două sau mai multe entitati. Legăturile ne oferă capacitatea de a reuni elementele în ansambluri, configuraţii sau grupuri. Putem clasifica legăturile în mai multe categorii: fizice, conceptuale, semiotice, logice şi semantice.

Legăturile fizice, concrete între entităţi sunt identificate în lumea reală. În unele cazuri, identificarea se realizează prin experimente fizice în urma cărora se deduce faptul că mobilitatea entităţilor respective este limitată, prin transmiterea de forţe sau mişcări de la o entitate la alta.

Legăturile conceptuale sau mentale sunt deduse din observarea lumii reale şi aplicarea unui proces de raţionare asupra ei. In cazul nostru, procesul de rationare este realizat prin aplicarea principiilor paradigmei OO.

Legăturile semiotice sunt deduse din descrierea în limbaj natural a elementelor din lumea reală. Ele se împart în legături sintactice, semantice şi morfologice (gramaticale). Dintre aceste categorii, ne interesează numai primele două. Legăturile semantice au loc între semne şi înţelesul lor pentru cei care le utilizează.

12

Legăturile sintactice între semne sunt induse de relaţiile dintre înţelesul semnelor ce furnizează semantica limbajului natural folosit. Astfel avem relaţii de contrarietate (bazate pe antonime), de similaritate (bazate pe sinonime), de generalizare (este_un) şi mereologice (constructii sintactice: este compus din, contine, este format din, face parte din). Dintre acestea ne intereseaza numai ultimele doua tipuri de legaturi sintactice.

Legăturile logice sunt deduse din descrierea în limbaj natural a elementelor din lumea reală, asupra căreia aplicăm un raţionament. In cazul analizei, este vorba de descrierea problemei si specificatia proiectului. D. O legatura (link) in abordarea orientata spre obiecte este o conectare fizica, conceptuala, sintactica (semantica sau sintactica) sau logica intre doua obiecte. Daca un obiect care partine unei clase trebuie sa trimita un mesaj unui obiect ce apartine unei altei clase, atunci intre cele doua obiecte trebuie sa existe un link. Link-ul poate fi: - unilateral (primul obiect il cunoaste pe al doilea, dar al doilea nu) - bilateral (cele doua obiecte se cunoasc intre ele) D. O asociere este o relatie intre doua clase ce descrie o multime de link-uri cu o structura si o semantica comune intre obiectele claselor implicate.

O asociere intre doua clase poate indica: - o cale de comunicare intre obiectele celor doua clase; - o cunoastere unilaterala sau bilaterala, - un raport de tip client-furnizor intre obiectele celor doua clase; - obiectele unei clase partajeaza cu alte obiecte obiectele celei de-a doua clase.

Orice clasa are implicit o relatie de asociere cu ea insasi, de aceea exista intotdeauna posibilitatea ca un obiect sa-si trimita sieşi un mesaj.

Relatiile de asociere (ca orice tip de relatie) se identifica din analiza verbelor din descrierea problemei sau/si specificatia de proiect sau/si raspunzand la urmatoarele intrebari: - Un obiect trimite mesaje unui alt obiect, cerandu-i acestuia din urma sa-si modifice starea? - Un obiect foloseste un alt obiect, fiindca are nevoie de informatii de la acesta din urma? - Un obiect anunta un alt obiect?

Clientadresa : String

getAdresa()

Vanzaretva : Integer

addElementVanzare(a : ElementVanzare)getElementeVanzare() : CollectiongetClient() : ClientcalculeazaTotal() : Long

1..* 11..* 1

areLoc

FacturanrFactura : Integerdata : Calendar

formatare(sir : String, latime : Integer) : String

estePentru

13

O asociere se caracterizeaza prin urmatoarele elemente: nume, directia de folosire, multiplicitate, numarul de obiecte care participa in relatia de asociere si roluri.

Nivelul de implementare Orice asociere va fi implementata cu ajutorul unui atribut de tip clasa B, in clasa A. Numele atributului este dat de rolul asocierii, daca acesta a fost specificat. public class SpecificatieProdus{ private String denumire; private int pretUnitar; public SpecificatieProdus(String denumire, int pret){ this.denumire=denumire; this.pretUnitar=pret; } public String getDenumire(){ return denumire; } public int getPret(){ return pretUnitar; } } public class ElementVanzare{ private SpecificatieProdus produs; private int cantitate; public ElementVanzare(SpecificatieProdus p, int c){ produs=p; cantitate=c; } public SpecificatieProdus getProdus(){ return produs; } public int getCantitate(){ return cantitate; } public long calculeazaCost(){ return produs.getPret()*cantitate; } }

ElementVanzarecantitate : Integer

ElementVanzare(p : SpecificatieProdus, c : Integer)getProdus() : SpecificatieProdusgetCantitate() : IntegercalculeazaCost() : Long

foloseste

1*

SpecificatieProdusdenumire : StringpretUnitar : Integer

SpecificatieProdus()getDenumire()getPret()

produs

Persoananume : Stringprenume : String

create()getNume()getPrenume()

MagazinnrIdentificare : Integerdenumire : StringoraDeschidere : IntegeroraInchidere : Integer

*+proprietaresteCondus

*

14

public class TestArticol{ public static void main(String[] args){ ElementVanzare a1=new ElementVanzare(new SpecificatieProdus("tastatura", 1000), 3); System.out.println(a1.calculeazaCost()); } } Principiile paradigmei orientarii spre obiecte

Orice aplicaţie orientată pe obiecte trebuie să fie construită folosind patru mecanisme conceptuale: 1.Abstractizare. Proces care capteaza caracteristicile esentiale ale unui obiect, caracteristici ce

diferentiaza acest obiect de toate celelalte tipuri de obiecte. 2.Incapsulare. Proces mental executat pe o abstractizare în care elementele structurale ale

abstractizarii sunt izolate de cele ce constituie comportamentul sau. Serveste la separarea interfetei vizibile a unei abstractizari de implementarea sa. Din faptul ca clasele si obiectele derivate din clase incapsuleaza într-un singur “pachet” datele si operatiile, deriva avantaje importante: • Detaliile interne de implementare si ale procedurilor sunt invizibile in exterior (information

hiding). Aceasta reduce propagarea efectelor in cazul modificarilor. • Structurile de date si operatiile sunt definite intr-o entitate cu nume: clasa. Aceasta

faciliteaza reutilizarea componentelor. • Interfetele intre obiecte sunt simplificate. Un obiect care trimite un mesaj nu trebuie sa se

preocupe de structurile interne de date ale obiectului destinatar. Se reduce astfel incarcarea sistemului.

3.Modularitate. Proprietatea unei aplicaţii care a fost descompusă într-un ansamblu de module (pachete) cu o mare coeziune interna si cu legaturi slabe intre ele.

4.Ierarhie. Este o relaţie de ordine între abstractizari care sunt tipuri abstracte de date. Există două tipuri de ierarhii: bazate pe relaţia de generalizare/specializare şi cele bazate pe relaţia de agregare.

Alte două concepte de importanta mai mica caracterizeaza obiectele intr-o modelare pe obiecte: 1.Typing. Este o constrângere pentru clasele de obiecte care le obliga sa schimbe între ele obiecte

de tipuri diferite sau sa le schimbe dar într-un mod controlat (este cazul polimorfismului de exemplu). În general, polimorfismul este un concept in teoria tipurilor in care un unic identificator poate denumi obiecte ce apartin unor clase diferite si care sunt inrudite la o superclasa.

1. Persistenta (persistence). Reprezinta proprietatea unui obiect de a continua sa existe dupa ce creatorul său a incetat sa mai existe sau de a fi mutat din locul în care a fost creat în altul.

Caracteristicile abordarii orientate pe obiecte Proprietatilor obiectelor si a mecanismelor de structurare a universului OO au drept consecinte urmatoarele caracteristici ale abordarii OO: 1. Identitate. Proprietate a unui obiect care il diferentiaza de alte obiecte. Toate obiectele au o

identitate proprie: obiectele sunt distincte intre ele. 2. Clasificare. Metoda utilizata pentru ordonarea cunostintelor despre obiecte. Cunostintele sunt

descrise în termeni de clase, instante, atribute, operatii. 3. Polimorfism. Proprietatea unei operatii de a putea fi aplicata în moduri diferite mai multor clase.

Polimorfismul este două tipuri: static (prin supraincarcarea metodelor) şi dinamic (prin redefinirea metodelor).

4. Generalizare/Specializare. Specializarea este o relatie între clase în care o clasa (superclasa) îsi transmite structura si functionalitatea uneia sau mai multor clase (subclase). Generalizarea este o

15

relatie intre clase in care structura si comportamentul comun a doua sau mai multor clase este incapsulat intr-o singura clasa. Aceasta din urma va fi supraclasa pentru primele clase. Generalizarea/specializarea reprezinta un al doilea mecanism de clasificare: obiectele se clasifica în clase, iar clasele se clasifica prin intermediul superclaselor.

16

X

Y

3. RELAŢIA DE GENERALIZARE/SPECIALIZARE ÎNTRE CLASE Am văzut că a program spre obiecte înseamnă a raţiona în termeni de concepte. De exemplu, in aplicatia Magazin clientul este o persoana care are o adresa. Mai general, vom spune că Y este un tip de X care …, unde X şi Y sunt concepte. Această relaţie particulară dintre concepte se numeşte relaţia de generalizare/specializare şi indică faptul că toate instanţele lui Y sunt şi instanţe ale lui X, dar au ceva în plus (sau ceva specific) faţă de X. În această relaţie, X este conceptul de bază, iar Y este conceptul derivat. Se mai spune că X este generalizarea lui Y, iar Y este specializarea lui X. Specializarea tipului client determina aparitia a inca 3 concepte: Angajat, Somer, Sef. Relaţia de generalizare/specializare se păstrează şi între clasele care reprezintă cele două concepte. În UML, această relaţie între clase este arătată astfel:

X se mai numeşte superclasă (sau supraclasă), iar Y subclasă.

Într-o relaţie de generalizare/specializare un obiect al unei subclase este şi obiect al supraclasei clasei repsective, având aceeaşi structură şi aceleaşi operaţii cu cele ale obiectelor din supraclasă, dar au ceva diferit sau în plus. Dar, ce poate fi diferit în subclasă faţă de supraclasă? Una sau mai multe proprietăţi (variabile) specifice subclasei respective. De exp, in urmatoarea ierarhie de clase, clasa Angajat este o subclasă

Persoananume : Stringprenume : String

Clientadresa : String

Angajatsalariu : DoublecostOra : DoublecatVechime : Integer

calculeazaSalariu(nrOreLucrate : Integer)

SomerprocentCalcul : Integer

calculeazaAjutorSomaj()

SefsporDeConducere : Double

17

a clasei Client şi faţă de aceasta din urmă, are următoarele variabile: salariu, costOra si catVechime. Apoi, subclasa poate adăuga unul sau mai multe comportamente specifice sau să le modifice pe cele moştenite din supraclasă. In cazul clasei Angajat, aceasta contine metoda calculeazaSalariu().

MOŞTENIREA ÎN JAVA

Relaţia de generalizare/specializare dintre concepte sau clase induce ideea unei moşteniri pe care o subclasă o primeşte de la supraclasa sa. Această moştenire are două aspecte: structura obiectelor şi modul lor de comportament; şi aproape întotdeauna în aceste situaţii, subclasele pot adăuga ceva specific. Pentru a reprezenta relaţia de generalizare/specializare în limbajele de programare spre obiecte, precum Java, se propune un mecanism numit moştenire. Acest mecanism permite unei noi clase să beneficieze de structura şi comportamentul definite într-o clasă deja existentă prin declararea că moştenim acea clasă. In Java, mecanismul de moştenire se numeşte extensia clasei existente şi cere subclasei să declare ce clasă extinde. Apoi, subclasa respectivă trebuie să definească propriile variabile şi metode. Declararea că o clasă extinde o altă clasă este indicată în Java prin cuvântul cheie extends în declaraţia clasei respective:

class Identificator extends NumeSuperClasa { //definim variabile şi metode specifice subclasei } unde Identificator este numele subclasei, iar NumeSuperClasa este superclasa sa. Regulă. În Java, o clasă poate extinde o singură clasă. Aşadar, moştenirea este simplă. În aplicaţia noastră, clasa Manager este o subclasă a clasei Angajat. Iată structura celor două clase: public class Persoana{ private String nume, prenume; public String getNume(){return nume;} public String getPrenume(){return prenume;} } public class Client extends Persoana{ private String adresa; public void setAdresa(String n){adresa=n; } public String getAdresa(){return adresa; } } public class Angajat extends Client{ private double costOra, salariu; private int catVechime; public void setCostOra(double c){ costOra=c;} public void setCatVechime(int c){ catVechime=c;} public void calculeazaSalariu(int nr){ salariu=nr*costOra; if(catVechime<3) salariu+=5*salariu/100; else if(3<=catVechime && catVechime<7) salariu+=7*salariu/100; else if (7<=catVechime && catVechime<10) salariu+=10*salariu/100; else salariu+=15*salariu/100; } public double getSalariu(){return salariu;} } public class Somer extends Client{

18

private int procentDeCalcul; public void setProcent(int p){procentDeCalcul=p;} public double calculeazaAjutor(double salariuMediu){ return salariuMediu*procentDeCalcul/100.0; } }

Vizibilitatea membrilor în subclase

Variabilele instanţă private ale unei supraclase nu sunt moştenite în subclasele ei. De aici putem concluziona că:

O subclasă moşteneste toate variabilele şi metodele neprivate ale supraclasei pe care o extinde.

Rămâne posibilitatea accesării variabile private prin intermediul metodelor set şi get. Redefinirea membrilor în subclase O subclasă care doreste sa se deosebeasca de superclasa sa (pentru ca altfel ce rost ar avea sa o introducem?) are două posibilităţi: - să adauge membri noi si/sau - să redefinească membrii superclasei. Dacă o metodă este redefinită în subclasă, trebuie să îndeplinească următoarele condiţii: - metoda redefinită trebuie sa aibă exact aceeaşi semnătură; - metoda redefinită trebuie să aibă acelaşi tip al rezultatului întors; - dacă metoda redefinită îşi schimbă modificatorul de acces, acesta trebuie să dea cel puţin la fel

de mult acces ca metoda moştenită din supraclasă. Exemplu. super = Cuvânt cheie care se utilizează în interiorul corpului unei metode a unei instanţe a unei clase pentru a indica obiectul superclasei ce intra in structura instantei curente.

Nota. Un obiect al unei subclase este alocat in memorie intr-o zona care include atat spatiul alocat obiectului superclasei, cat si spatiul necesar variabilelor declarate in subclasa.

Variabila salariu este protected in Angajat. public class Sef extends Angajat{ private double sporConducere; public void setSporConducere(double s){ sporConducere=s;} public void calculeazaSalariu(int nr){ salariu=super.calculeazaSalariu(nr); salariu+=sporConducere; } }

În cazul variabilelor instanţă, redefinirea este folosită mai puţin şi este vorba de fapt de ascunderea variabilelor din supraclase. Ierarhia claselor Java În Java, clasele formează o structură de arbore în care rădăcina este clasa Object, a cărei definiţie apare în pachetul java.lang. Orice clasă extinde direct (implicit sau explicit) sau indirect clasa Object. Aşadar, toate clasele definite de programator care nu extind o altă clasă sunt automat subclase ale clasei Object. Dacă doreşte, o clasă poate redefini una sau mai multe metode moştenite din clasa Object. Structura clasei Object este următoarea:

19

public class Object { public java.lang.Object(); public java.lang.String toString(); protected native java.lang.Object clone() throws CloneNotSupportedException; public boolean equals(java.lang.Object); public native int hashCode(); protected void finalize() throws Throwable; public final native java.lang.Class getClass(); public final native void notify(); public final native void notifyAll(); public final void wait() throws InterruptedException; public final native void wait(long) throws InterruptedException; public final native void wait(long, int) throws InterruptedException; }

Datorită ierarhiei claselor Java, metodele clasei Object sunt moştenite de toate clasele Java. În consecinţă, oricare din aceste metode poate fi apelată asupra oricărui obiect.

Cunoaştem deja unele metode din clasa Object, cum ar fi de exemplu toString() sau equals(). Metoda toString() furnizează o “reprezentare” sub formă de şir a unui obiect. Dacă această metodă nu este redefinită, atunci va fi afişată numele clasei din care face parte obiectul respectiv împreună cu un şir ce reprezinta codul hash al obiectului în memoria heap. De aceea, oridecâte ori se doreşte afişarea unor informaţii despre un obiect (cum ar fi starea obiectului) se va rescrie metoda toString().

Metoda equals() permite compararea oricăror două obiecte. Rezultatul este true numai dacă cele două obiecte sunt de fapt acelaşi obiect. Dar, se poate redefi această metodă dacă dorim ca metoda să realizeze un alt tip de comparare. De exemplu, clasa String redefineşte această metodă pentru a compara lexicografic şirurile conţinute în cele două obiecte care se “compară”. Constructori şi moştenirea După cum ştim, constructorii încapsulează algoritmi de iniţializare a obiectelor ce vor fi create. Când este vorba de un obiect al unei subclase este important să ştim cum va fi iniţializată partea obiectului ce aparţine structurii supraclasei, mai ales când variabilele instanţă ale supraclasei nu sunt moştenite, deoarece sunt private. Presupunem că clasa Persoana contine următorul constructor: public Persoana(String n, String prenume){ nume=n; this.prenume=prenume; } Datorită observaţiei anterioare, nu putem scrie un constructor în clasa Client care să iniţializeze direct variabilele nume şi prenume. O soluţie ar fi să apelăm metode setNume() şi setPrenume() care sunt moştenite de clasa Client şi am avea următorul constructor: public Client (String nume, String prenume, String adresa){ setNume(nume); setPrenume(prenume); this.adresa=adresa; } Codul este corect, dar la compilare vom avea eroare: nu cunoaşte constructorul fără parametri al supraclasei Persoana. Chiar dacă schimbăm modificatorul de vizibilitate al variabilelor nume şi prenume, vom avea aceeaşi eroare de compilare. O soluţie bună ar fi să folosim cuvântul cheie super şi apelăm constructorul cu doi parametri din clasa Persoana: public Client (String nume, String prenume, String adresa){

20

super(nume, prenume); this.adresa=adresa; }

Regulă. Dacă intr-un constructor al unei subclase se apelează un constructor al supraclasei sale, atunci acest apel (folosindu-se super) se face pe prima linie a corpului constrcutorului respectiv.

Mecanismul de iniţializare a obiectelor Apelul unui constructor nu este un apel al unei metode oarecare, deoarece înainte de a executa algoritmul respectiv, mediul Java apelează automat constructorul fără parametri al supraclasei dacă acesta poate fi apelat. Dacă supraclasa nu are un constructor fără parametri, atunci caută constructorul fără parametri al supraclasei clasei respectiv. Şi tot aşa, până când clasa Object este supraclasa clasei respective. Aceasta are un singur constructor, care nu are parametri. Cu această clasă este creat obiectul şi apoi este iniţializat pe drumul invers al apelului cu executarea

algoritmilor din constructorii supraclaselor până la clasa respectivă (Figura 6-2). Aşadar avem două soluţii: - folosim super şi apelăm un anumit constructor din supraclasă - în supraclasa Angajat introducem încă un constructor, fără parametri. Utilizarea obiectelor ale subclaselor Având clasa Sef definită ca subclasă a clasei Angajat, putem crea obiecte ale clasei Sef şi apela metodele lor astfel: Sef s1=new Sef("Ionescu", „Pop”, „Aleea rozelor”, 7);//constructorul s1.setSporConducere(100);//metoda instanta definita in clasa Sef s1.setCatVechime(25);//metoda mostenita din Angajat s1.calculeazaSalariu(200);//se apeleaza metoda definita in clasa Sef

Apelurile de metode sunt în regulă pentru că un obiect al clasei Sef este automat un obiect al clasei Angajat şi din această cauză pot fi apelate metodele moştenite din supraclasa Angajat. Din acelaşi motiv, putem memora într-o variabilă de tip Angajat o referinţă la un obiect al clasei Sef:

Clasa Object

Clasa X

Clasa A

new X(…)

2: A()

4: Object()

1: X(…)

….

….

Constructor predefinit A()

Constructor X(…)

Constructor Object()

5: initializeaza obiect

6: algoritmul lui A()

7: algoritmul lui X(…)

Crearea unui obiect în Java

21

Angajat s2=new Sef("Marinescu", „Marin”, „Aleea stejarilor”, 10); s2.setNume("Georgescu");//metoda mostenita din Persoana s2.calculeazaSalariu(160);//se apeleaza metoda definita in clasa Sef s2.setSporConducere(150);//eroare la compilare!

După cum se observă, dacă apelăm o metodă ce aparţine numai clasei Sef şi nu clasei Angajat, compilatorul va genera o eroare pentru că variabila s2 a fost declarată de tip Angajat, adică de tip supraclasă.

Regulă. Dacă într-o variabilă de tip supraclasa unei clase va fi memorată o referinţă la un obiect al subclasei respective, atunci obiectul accesat prin intermediul varibilei referinţă, pierde temporar specificul subclasei, comportându-se ca un obiect al supraclasei.

Atunci, dacă avem o variabilă de tip supraclasă nu suntem siguri că obiectul referit de variabilă este de tip supraclasă. În acest caz, putem folosi operatorul instanceof pentru a accesa variabilele şi metodele obiectului referit de variabilă. De exemplu, expresia s2 instanceof Sef are valoarea true deoarece referinţa memorată în variabila s2 indică un obiect al clasei Sef. Altfel, valoarea expresiei ar fi fost false. Aşadar, dacă vrem să folosim pe deplin obiectul respectiv (adică, conform clasei din care face parte) folosim operatorul instanceof împreună cu operatorul cast la clasa originală: if (s2 instanceof Sef){ Sef s3=(Sef) s2; … }

Mecanismul apelarii metodelor într-o ierarhie de clase Am văzut că în cazul moştenirii, un obiect al unei subclase poate fi folosit pentru a apela metode definite în clasa respectivă, dar şi metode definite în supraclasa sa directă sau în supraclasele sale îndirecte şi moştenite de clasa sa. În plus, subclasele pot redefini aceeaşi metodă de mai multe ori, în funcţie de propriile necesităţi. Atunci ar trebui să vedem cum va fi rezolvat un apel de metodă aplicat unui obiect dintr-o subclasă. Astfel presupunem că un obiect a apelează o metodă a obiectului b din Clasa5 din figura următoare. După cum se observă avem două cazuri: - metoda respectivă este moştenită din clasa Clasa1 şi nu a fost redefinită în ierarhia sa directă:

Clasa5 şi Clasa3. Atunci metoda care va fi executată va fi metoda din Clasa1 (Figura 6-3 a). - metoda este definită iniţial în clasa Clasa1 şi redefinită în Clasa3. Atunci metoda care va fi

executată, va fi metoda din Clasa3.

22

Observăm că, apelul unei metode a unui obiect aparţinând unei subclase, dacă metoda apelată nu este definită in subclasă, este transmis în sus în ierarhia de clase până când este găsită o primă definiţie a metodei apelate.

Clasa1

Definiţia metodei

1

Clasa2

Obiect a

2

3

4

Clasa3

Clasa4 Clasa5

Obiect b

Clasa1

Definiţia iniţială a metodei

1

Clasa2

2

3

Clasa4 Clasa5

Obiect

Clasa3

Metoda redefinită

Obiect a Obiect b

23

4. ARRAY-URI Array = O colecţie liniară de elemente în care fiecare element este accesibil prin intermediul unui indice. Un array furnizează spaţiu pentru valorile de tip primitiv sau referinţă la obiectele de un acelaşi tip. Aceste valori se numesc elementele array-ului. Elementele unui array sunt ordonate dupa unul sau mai multe criterii numite dimensiuni. O dimensiune a unui array este indicata printr-o pereche de []. Dupa numarul de dimensiuni, un array este uni- sau multidimensional. Referinţa la un array va fi memorată într-o variabilă array. Sintaxa declararii unei variabile array unidimensional este urmatoarea: TipDeDate[] id; sau TipDeDate id[]; De exemplu, putem declara două variabile array numere şi personal astfel: int[] numere; Persoana personal[]; Nota. Dupa executarea acestor declaratii, array-urile propriu-zise nu exista in memorie, ci numai variabilele array asociate. Pentru a exista într-un program, un array trebuie să fie creat (alocat) cu new. Sintaxa alocarii memoriei unui array este: id = new tip[expresie] unde tip este tipul de date al variabilei array cu identificatorul id, iar expresie este fie un literal intreg, fie o expresie cu valoare dreapta un numar intreg. De exemplu, numere = new int[5]; personal = new Persoana[1000]; In acest moment, toate elementele array-ului numere sunt initializate cu 0, iar cele ale array-ului personal cu valoarea null. După creare, elementele array-ului se pot umple cu valori, cu observatia ca indicele unui array începe de la 0. Astfel un array cu 10 elemente va avea indicele de la 0 la 9. De exemplu, daca array-ul numere contine primele 5 numere pare naturale, umplerea array-ul cu aceste valori se face astfel: for (int i=0; i<5;i++) numere[i]=2*i; In cazul array-ului de tip referinta personal, valorile lui sunt referinte catre obiecte de tip Persoana, care trebuia sa fie create. Astfel, for (int i=0; i<1000;i++) personal[i]=new Persoana(); Grafic, cele doua array-uri arata astfel:

Prin definitie, lungimea unui array este egala cu numărul de elemente ale array-ului. Pentru a determina lungimea unui array se foloseste variabila length. De exemplu, valoarea expresiei personal.length este 1000.

INIŢIALIZAREA UNUI ARRAY Am vazut ca la momentul creării unui array, elementelor sale le sunt atribuite valoarea zero (tipuri primitive numerice), \u0000 (tipul char), false (tipul boolean) sau null (tipuri referinţă). Elementele unui array pot fi initializate in declararea variabilei array printr-o lista de initializatori cuprinsa intre {} ce le contine si care este atribuita variabilei array asociata. Sintaxa este urmatoarea: TipDeDate[] id={listaDeInitializatori}

0 2 4 6 8 0 1 2 3 4

array-ul numere

array unidimensional de 5 întregi

array-ul personal

… 0 1 2 … 999

array unidimensional de 1000 obiecte Persoana

obiecte Persoana

24

Aceasta este de fapt o forma prescurtata pentru TipDeDate[] id=new TipDedate[]{listaDeInitializatori}; unde listaInitializatori este o multime de elemente de acelasi tip cu tipul array-ului, ce sunt valorile elementelor array-ului id. Numarul de elemente din lista de initializatori funrizeaza valoarea variabilei length pe variabila array asociata. De exemplu, elementele array-ul indicat prin variabila numere pot fi initializate de la declararea variabilei numere astfel: int[] numere={0,2,4,6,8};

ARRAY-URI MULTIDIMENSIONALE Array-urile multidimensionale sunt declarate prin adaugarea dimensiunilor. De exemplu, declararea unei matrici m cu elemente de tip intreg se realizeaza astfel: int[][] m; Alocarea memoriei necesare memorarii matricii m se face astfel: m=new[3][3]; In memorie, elementele lui m nu vor fi memorate ca intr-o matrice ci liniar adica, un array multidimensional este memorat ca un array de array-uri (si la randul lor pot fi de array-uri), astfel ca fiecare dimensiune este memorata ca un array diferit. De aceea, putem declara si construi array-uri multidimensionale de dimensiuni diferite. De exemplu, int[][] b=new int[2][]; //b are 2 linii b[0]=new int[3];// prima linie are 3 coloane sau elemente b[1]=new int[4];// a doua linie are 4 elemente Nota: Daca numarul coloanelor poate sa difere, tipul elementelor trebuie sa fie acelasi. In cazul array-urilor multidimensionale variabila length are valori diferite. De exemplu, b.length furnizeaza numarul de linii ale lui b, adică 2, pe cand b[1].length=4. Similar, un array multidimensional poate fi initializat si la declarare folosind o listă ce contine un numar de liste de initializatori egal cu numarul de dimensiuni ale array-ului, liste separate prin virgula. De exemplu, int[][] a={{1,0}, {0,1}} memoreaza matricea unitate bidimensionala. Exemplu. Următorul exemplu arată cum pot fi create, iniţializate şi afişate diferite array-uri multidimesionale: import java.util.*; public class ArrayMultiDimensionale{ static Random aleator= new Random(); static int pRand(int mod) { return Math.abs(aleator.nextInt())%mod; } public static void main(String[] args){ //array 3-D cu dimensiuni fixe: int[][][] a2 = new int[2][2][4]; for(int i = 0; i < a2.length; i++) for(int j = 0; j< a2[i].length; j++) for(int k=0; k<a2[i][j].length;k++) afiseaza("a2[” + i+"]["+j+"]["+k+"] = " + a2[i][j][k]); //array 3-D cu vectori de lungime diferită pentru cele trei dimensiuni int[][][] a3 = new int[pRand(7)][][]; for(int i = 0; i<a3.length; i++){ a3[i] = new int[pRand(5)][]; for(int j=0; j<a3[i].length; j++)a3[i][j] = new int[pRand(5)]; } for(int i=0; i<a3.length; i++) for(int j=0; j<a3[i].length; j++) for(int k=0; k<a3[i][j].length; k++) afiseaza("a3["+i+ "]["+j+"]["+k+"]= "

25

+a3[i][j][k]); //array 3x2 de obiecte Integer Integer[][] a4={{new Integer(1), new Integer(2)}, {new Integer(3), new Integer(4)}, {new Integer(5), new Integer(6)}}; for(int i = 0; i < a4.length; i++) for(int j = 0; j < a4[i].length; j++) afiseaza("a4[" + i + "][" + j + "] = “ + a4[i][j]); } static void afiseaza(String s){ System.out.println(s); }}

LUCRUL CU ARRAY-URI

Cand lucram cu array-uri putem folosi urmatoarea metodă statică a clasei System ce permite copierea unui număr de elemente consecutive ale unui array într-un alt array:

static void arrraycopy(arraySursă, pozSursă, arrayDest, pozDest, nrElemente) In mod obisnuit, array-urile sunt folosite in probleme de sortare si de cautare. De exemplu, sortarea elementelor unui array folosind metoda QuickSort se face astfel in Java: public class Ordonare{ static int[] a; public static void initializeaza(){ a=new int[20]; int nr; for(int i=0; i<a.length;i++){ nr=(int)(Math.random()*30); a[i]=nr; } afiseaza(); } public static void quickSort(int p, int u){ int i=p, j=u; int temp=a[(p+u)/2]; do{ while(a[i]<temp)i++; while(a[j]>temp)j--; if (i<j){ int t=a[i]; a[i]=a[j]; a[j]=t; } if (i<=j) {i++;j--;} }while (i<j); if(p<j) quickSort(p,j); if(i<u) quickSort(i,u); } public static void afiseaza(){ for(int i=0;i<a.length;i++) System.out.print(a[i]+" "); System.out.println(); } public static void main(String[] args){ initializeaza(); quickSort(0,19); afiseaza(); } } Cautarea unei valori intr-un array folosind metoda Divide et Impera se realizeaza astfel:

26

public class Cautare{ static int[] a; public static void initializeaza(){ a=new int[20]; int nr; for(int i=0; i<a.length;i++){ nr=(int)(Math.random()*30); a[i]=nr; } afiseaza(); } public static boolean cauta(int val){ int p=0, u=a.length-1, m=(p+u)/2; while(p<u && val!=a[m]){ if(val<a[m]) u=m-1; else p=m+1; m=(p+u)/2; } return a[m]==val; } public static void afiseaza(){ for(int i=0;i<a.length;i++) System.out.print(a[i]+" "); System.out.println(); } public static void main(String[] args){ initializeaza(); int nr=(int)(Math.random()*30); if(cauta(nr)) System.out.println("A gasit="+nr); else System.out.println("Nu a gasit="+nr); } }

ARRAY ÎN METODE Daca parametrul unei metode este o variabila array, atunci transferul se face prin referinta, adica orice modificare efectuata asupra elementelor array-ului respectiv se memoreaza. Elementele unui array de tip primitiv se transmit prin valoare, exact ca niste variabile simple. De exemplu, daca in programul Ordonare anterior am fi avut metoda public static void schimba(int i, int j){ int t=i; i=j; j=t; } apelul acestei metode schimba(a[i], a[j]) nu ar fi realizat operatia respectiva. Pentru a realiza într-adevăr schimbarea două valori transmise prin valoare, metoda schimba() trebuie să fie scrisă astfel: public static int[] schimba(int i, int j){ Integer n1=new Integer(i); Integer n2=new Integer(j); int[] n=new int[2]; Integer n3=n1; n1=n2; n2=n3;

27

n[0]=n1.intValue(); n[1]=n2.intValue(); return n; }

In acest caz, metoda quickSort ar putea apela metoda schimba() astfel:

public static void quickSort(int p, int u){ int i=p, j=u; int temp=a[(p+u)/2]; do{ while(a[i]<temp)i++; while(a[j]>temp)j--; if (i<j){ int[] n=schimba(a[i],a[j]); a[i]=n[0]; a[j]=n[1]; } if (i<=j) {i++;j--;} }while (i<j); if(p<j) quickSort(p,j); if(i<u) quickSort(i,u); }

Lucrul cu colectii de obiecte legate intre ele prin relatia de moştenire Presupunem ca vrem sa completam programul Angajaţi cu o clasă de test a cărei metodă main contine un array de doi angajaţi pentru a memora un angajat şi un manager. Apoi, afişăm date despre cei doi angajaţi, după care le mărim salariul cu 2% şi le afişăm din nou datele pentru a verifica creşterea salariului. public class TestAngajati { static Angajat[] personal; public static void main(String[] args) { .... // creaza si memoreaza 1 angajat si un manager ca personal al intreprinderii // afiseaza date despre fiecare membru al personalului // creste salariul cu 2% // afiseaza date despre fiecare membru al personalului } }

Cum afişăm de două ori date despre angajaţi, ar trebui să scriem algoritmul de afişare într-o metoda statică şi s-o apelăm din metoda main, când avem nevoie. public static void afiseazaDate() { System.out.println("\nLISTA PERSONAL"); for (int i = 0; i < personal.length; i++) System.out.println(personal[i]); } public static void main(String[] args) { personal= new Angajat[2]; personal[0] = new Angajat(" ", 2500.00); personal[1] = new Manager(" ", 3000.00, " "); afiseazaDate(); for (int i = 0; i < personal.length; i++) personal[i].maresteSalariu(0.02); afiseazaDate(); } În metoda main, am utilizat o variabilă array personal pentru a crea un array de doi angajaţi, dintre care unul este Manager. Putem să facem acest lucru, datorită relaţiei de generalizare/specializare dintre Angajat şi Manager.

28

5. CLASE ABSTRACTE O clasa abstractă este o clasă fără instanţe directe, dar are subclase care sunt clase concrete, adică pot fi instanţiate direct. O clasă abstractă este declarată cu modificatorul abstract. Sintaxa declarării unei clase abstracte:

ClasăAbstractă ::= [modificator de vizibilitate] abstract class Identificator{…}

unde Identificator este numele clasei abstracte. Fiind fără instanţe directe, constructorul unei clase abstracte nu poate fi apelat. De aceea, chiar dacă o clasă abstractă conţine unul sau mai mulţi constrcutori, dacă incercăm să-I apelăm vom avea eroare de compilare. În schimb, putem declara variabile de tip o supraclasă abstractă care să conţină referinţe la obiectele subclaselor concrete ale clasei respective. Aşadar, rolul unei clase abstracte este de a facilita elaborările polimorfice asupra obiectelor subclaselor sale. Atunci, metodele abstracte dintr-o clasa abstractă oferă o interfaţă comună tuturor subclaselor clasei abstracte. O clasă abstractă trebuie să conţină cel puţin o metodă abstractă adică metodă care nu defineşte algoritmul de implementare. Sintaxa declarării unei metode abstracte este următoarea:

MetodaAbstractă::=[Modificator de vizibilitate] abstract Tip numeMetoda([parametri formali]);

Exemple: abstract void metoda(); public abstract int read() throws IOException;

Aşadar, o clasă abstractă poate avea metode concrete, adică declarate şi definite în clasa respectivă. În plus, o clasă abstractă poate avea variabile ce pot fi moştenite de subclasele clasei respective. Cum comportamentul obiectelor unei clase este dat de mulţimea tuturor metodelor clasei, atunci obiectele unei clase abstracte au un comportament parţial definit, deoarece el diferă în funcţie de fiecare subclasă a clasei respective. O subclasă a unei clase abstracte are obligaţia de a defini (implementa) metodele abstracte ale supraclasei. În caz contrar, subclasa respectivă trebuie să fie declarată abstractă şi este responsabilitatea subclaselor ei să implementeze metodele care au rămas abstracte din supraclasă. 5.1.1 Dezvoltarea unei aplicatii pe obiecte

Aplicaţia Cont bancar Se consideră definirea unei clase care descrie comportamentul unui cont bancar. 1. Definirea numelui clasei: ContBancar. 2. Determinarea proprietăţilor clasei, care caracterizează obiectele clasei. Se inserează variabilele: sold de tip long şi proprietar de tip String. 3. Înţelegerea modului în care se comportă obiectele clasei. Care sunt operaţiile care se pot face asupra unui cont bancar? Responsabilitatea clasei ContBancar: Depune o sumă de bani ⇒ depune(suma:long) Scoate o sumă de bani ⇒ scoate(suma:long) Cere soldul curent ⇒ getSold():long Cere numele proprietarului ⇒ getProprietar():String 4. Definirea modurilor in care vor fi create obiecte ContBancar. Un cont bancar poate fi creat vid, numai cu numele proprietarului ⇒ ContBancar(numeProp: String)

29

Un cont bancar poate fi creat iniţializând soldul şi numele proprietarului ⇒ ContBancar(sold: Long sold, numeProp:String) Un cont bancar este de două tipuri: cont de economii sau cont curent. Un cont de economii este un cont bancar caracterizat de o dobândă ce poate fi vizualizată la cerere. Dobânda este utilizată la anumite intervale de timp, când trebuie să fie calculată şi adăugată la soldul curent. Această operaţie este realizată de metoda aplicăDobânda(). Un cont curent este un cont bancar caracterizat de un număr de operaţii sau tranzacţii efectuate asupra contului într-o anumită perioadă de timp.Fiecare tranzacţie efectuată are un cost constant exprimat în euro. Costul unei tranzacţii este constant, este iniţializat la crearea unui cont curent şi memorat în constanta COST_TRANZACTIE. Un cont curent are un număr de tranzacţii gratuite. Acest număr este memorat în constanta NR_TRANZACTII_GRATUITE, iniţializată la crearea contului curent. După efectuarea acestui număr de tranzacţii, restul tranzacţiilor trebuie să fie plătit. La anumite intervale de timp, costul tranzacţiilor va fi extras din cont şi numărul tranzacţiilor porneşte de la zero. Orice persoană care vrea să-şi deschidă un cont bancar trebuie să specifice tipul contului: de economii sau curent. Tinând cont de această cerinţă, clasa ConBancar este o clasă abstractă cu metodele abstracte depune şi scoate. După ce am analizat problema, obţinem următoarea diagramă de clase în UML:

Implementarea în Java public abstract class ContBancar{ protected long sold; protected String proprietar; public ContBancar(String numeProp){ proprietar=numeProp; } public ContBancar(long sold, String numeProp){ this.sold=sold; proprietar=numeProp; } public abstract void depune(long suma);

ContBancarsold : Longproprietar : String

ContBancar(numeProp : String)ContBancar(sold : Long, numeProp : String)depune(suma : Long)scoate(suma : Long)getSold() : LonggetProprietar() : String

ContEconomiirataDobanda : Double

ContEconomii(prop : String, rata : Double)ContEconomii(prop : String, sold : Long, rata : Double)aplicaDobanda()

ContCurentnrTranzactii : IntegerNR_TRANZACTII_GRATIS : IntegerCOST_TRANZACTII : Integer

ContCurent(prop : String, suma : Long, ntg : Integer, ct : Integer)ContCurent(prop : String, ntg : Integer, ct : Integer)descarcaCheltuieli()

30

public abstract void scoate(long suma); public long getSold(){ return sold; } public String getProprietar(){ return proprietar; } public String toString(){ return "Cont cu proprietarul: "+proprietar+" si suma curenta: "+sold; } } public class ContEconomii extends ContBancar{ private double rataDobanda; public ContEconomii(String prop, double rata){ super(prop); rataDobanda=rata; } public ContEconomii(String prop, long sold, double rata){ super(sold, prop); rataDobanda=rata; } public void depune(long suma){ sold+=suma; } public void scoate(long suma){ if (suma<=sold) sold-=suma; } public void aplicaDobanda(){ sold+=sold*rataDobanda; } public String toString(){ return super.toString()+", dobanda: "+rataDobanda; } } public class ContCurent extends ContBancar{ private int nrTranzactii; private final int NR_TRANZACTII_GRATIS; private final int COST_TRANZACTIE; public ContCurent(String prop, long suma, int ntg, int ct){ super(suma, prop); NR_TRANZACTII_GRATIS=ntg; COST_TRANZACTIE=ct; } public ContCurent(String prop, int ntg, int ct){ this(prop, 0, ntg, ct); } public void depune(long s){ sold+=s; nrTranzactii++; } public void scoate(long s){

31

if(s<=sold){ sold-=s; nrTranzactii++; } } private void descarcaCheltuieli(){ if (nrTranzactii>NR_TRANZACTII_GRATIS){ scoate((nrTranzactii-NR_TRANZACTII_GRATIS)*COST_TRANZACTIE); nrTranzactii=0; } } public String toString(){ return super.toString()+", numar de tranzactii gratis: "+ NR_TRANZACTII_GRATIS+", cost tranzactie: "+COST_TRANZACTIE; } } Pentru a testa ierarhia de clase construită vom scrie clasa TestContBancar care crează un cont de economii şi un cont curent, executăm 2-3 tranzacţii pentru fiecare cont şi vizualizăm situaţia, la început şi după efectuarea tranzacţiilor. public class TestContBancar{ public static void main(String[] args){ ContEconomii ce=new ContEconomii("Ionescu",10000, 0.15); ContCurent cc=new ContCurent("Popescu", 20000, 5, 2000); ce.depune(5000); System.out.println(ce); cc.depune(15000); System.out.println(cc); cc.scoate(2000); System.out.println(cc); ce.scoate(12000); System.out.println(ce); } }

32

6. INTERFETE Conceptul de interfaţă Presupunem că avem următoarele două probleme: 1. “Să se ordoneze conturile de economii după dobânda pentru a decide care dintre ele este mai bun pentru a depune o sumă de bani disponbila.” 2. “Să se ordoneze angajaţii unei întreprinderi după salariu pentru a putea face o statistică legată de taxele de plătit.” Pentru rezolvarea acestor probleme avem două soluţii: 1. ContEconomii sau Angajat au obiecte care nu sunt comparabile. Ordonarea se poate face în

mod personalizat, pentru fiecare clasă. 2. Cele două clase implementează interfaţa Comparable care are o metodă

compareTo(Object). Fiecare clasă trebuie să implementeze în mod personalizat metoda: public class ContEconomii implements Comparable { . . . . public in compareTo(Object o) { return (this.dobanda > o.dobanda)?1:(this.dobanda < o.dobanda)?-1:0; } } Atunci, se poate scrie simplu, având două conturi cont122 şi cont304: if(cont122.compareTo(cont304)>0) then . . . Interfeţe în Java Interfaţă = Listă de metode publice şi abstracte. O interfaţă este o colecţie de comportamente abstracte care pot fi implementate de o clasă pentru adăugarea unui comportament care nu este furnizat de superclasa clasei respective. Interfeţele reprezintă, ca şi clasele, tipuri de date, numai că aceste tipuri, în loc să prezinte date+servicii, prezintă numai servicii. Interfaţa în Java este o entitate distinctă de clase şi array-uri. O interfaţă poate conţine şi membri-date constante. O interfaţă poate moşteni de la o altă interfaţă. Interfeţele în Java trebuie să fie declarate. Declaraţia unei interfeţe: [Modificator] interface Identificator [extends NumeInterfata...] { Tip Identificator= . . . // date-membru [Modificator] SemnaturaMetoda; . . . // metode abstracte } Exemplu. public interface Comparable {public int compareTo (Comparable b);} Pentru fiecare declaraţie de interfaţă compilatorul crează un fişier .class. Implementarea unei interfeţe O clasă care decide să implementeze o interfaţă trebuie să declare acest lucru în definiţie. Sintaxa: class Identificator implements NumeInterfata,...{ CorpClasa } O clasă care doreşte să implementeze o interfaţă, trebuie să implementeze toate metodele interfeţei. Exemplu. Dacă vrem sa ordonăm angajaţii unei întreprinderi după salariu pentru a putea face o statistică legată de taxele de plătit, clasa Angajat va implementa interfaţa Comparable care are o metodă compareTo(Object). Clasa trebuie să implementeze în mod personalizat metoda: public class Angajat implements Comparable { . . . .

33

public int compareTo(Object o) { return (int)(getSalariu() – ((Angajat)o).getSalariu()); } . . . . } O clasă poate implementa mai multe interfeţe. O interfaţă poate fi implementată de mai multe clase. Variabile-interfaţă Intr-un program se pot declara variabile de tip interfaţă, ca de exemplu: Comparable s; O variabilă interfaţă poate memora referinţa la o instanţă a oricărei clase care implementează acea interfaţă, la fel cum face şi o variabilă a unei clase. s = new Angajat(. . . ); Angajat d = new Angajat(. . .); O variabilă interfaţă poate participa în calcule la fel ca orice variabilă referinţă. if (s.compareTo(d) < 0) . . . Variabilele interfaţă sunt în special utilizate ca parametri în metode pentru a permite upcasting al obiectelor ce provin din clase diferite între ele. if (s.compareTo(d) < 0) . . //Angajat promovat ca tip Comparable O variabilă interfaţă poate fi accesată numai cu metodele interfeţei. String nume = s.getNume(); // Eroare!!

Cum utilizăm interfeţele 1. Utilizaţi o interfaţă pentru a separa un comportament care este implementat în orice clase-

furnizor ale claselor client care le utilizează. 2. Utilizaţi interfeţele ca parametri ai metodelor. În acest caz:

2.1. în interiorul unei astfel de metode putem invoca orice metodă a interfeţei; 2.2. la execuţie, se poate transmite ca argument orice obiect al unei clase care implementează

interfaţa 2.3. la invocarea unei metode pentru acel obiect determină executarea unei implementări

particulare a metodei ce aparţine clasei obiectului; 2.4. transmiţând unui alt apel un obiect al unei alte clase care implementează interfaţa se

detrmină o execuţie care poate fi complet diferită de prima. Interfeţe Java standard Interfaţa Cloneable trebuie să fie implementată de toate clasele care se clonează. protected native Object clone() //clone restituie un Object generic throws CloneNotSupportedException; . . . . . . Vector v = new Vector(); Vector v2; //Pentru a-l putea utiliza, rezultatul v2 = (Vector) v.clone(); // va fi convertit la tipul dorit Interfaţa Serializable nu are metode: package java.io; public interface Serializable {};

Exemplu. In cazul proiectului nostru, introducem interfaţa OperatiiObiecte ce va fi realizată de către clasele OperatiiSpecificatiiProdus si OperatiiClient. public interface OperatiiObiecte{ public void scrieObiect(Object o); public Object citesteObiect(String cheie); }

34

import java.io.*; import java.util.*; public class OperatiiSpecificatiiProdus implements OperatiiObiecte{ private PrintWriter pw; public OperatiiSpecificatiiProdus(){ try{ pw=new PrintWriter(new FileWriter("produse.txt"), true); }catch(IOException io){io.printStackTrace();} } public void scrieObiect(Object o){ if (!(o instanceof SpecificatieProdus)) { System.out.println("instanta invalida"); return; }else { SpecificatieProdus p=(SpecificatieProdus)o; pw.println(p.getDenumire()+"_"+p.getPret()); pw.flush(); } } public Object citesteObiect(String cheie){ SpecificatieProdus sp=null; try{ BufferedReader br=new BufferedReader(new FileReader("produse.txt")); StringTokenizer st; String linie; while((linie=br.readLine())!=null){ st=new StringTokenizer(linie, "_"); if(st.nextToken().equals(cheie)) { sp=new SpecificatieProdus(cheie, Integer.parseInt(st.nextToken())); break; } } }catch(IOException io){io.printStackTrace();} return sp; } } import java.io.*; import java.util.*; public class OperatiiClient implements OperatiiObiecte{ private ObjectOutputStream oos; private ObjectInputStream ois; public OperatiiClient(){ try{ oos=new ObjectOutputStream(new FileOutputStream("clienti.txt")); ois=new ObjectInputStream(new FileInputStream("clienti.txt")); }catch(IOException io){} } public void scrieObiect(Object o){ if (!(o instanceof Client)) {System.out.println("instanta invalida");return;} else {try{oos.writeObject(o);}catch(IOException io){io.printStackTrace();}} } public Object citesteObiect(String cheie){ Client c; try{ c=(Client)ois.readObject();

35

while (c!=null){ if (cheie.equals(c.getNume()+" "+c.getPrenume()+" "+c.getAdresa()))return c; c=(Client)ois.readObject(); } }catch(IOException io){io.printStackTrace();} catch(ClassNotFoundException io){io.printStackTrace();} return null; } } public class TestOperatii{ public static void main(String args[]){ OperatiiObiecte oo=new OperatiiSpecificatiiProdus(); oo.scrieObiect(new SpecificatieProdus("flori", 120000)); oo.scrieObiect(new SpecificatieProdus("miere", 150000)); SpecificatieProdus sp=(SpecificatieProdus)oo.citesteObiect("flori"); System.out.println("pret= "+sp.getPret()); oo=new OperatiiClient(); oo.scrieObiect(new Client("Ionescu", "Pop", "alea rozelor")); oo.scrieObiect(new Client("Popescu", "Ion", "alea stejarilor")); oo.scrieObiect(new Client("Marin", "Pop", "alea crizantemelor")); Client c=(Client)oo.citesteObiect("Marin Pop alea crizantemelor"); System.out.println("date client= "+c.getNume()+" "+c.getPrenume()); } }

36

7. CLASE INTERNE

DEFINIREA CLASELOR INTERNE

Într-un program Java este posibil să plasăm o definiţie a unei clase în spaţiul de nume al altei clase. În acest caz, spunem că am definit clasa internă celei de-a doua clase, care este clasă externă pentru aceasta. Clasele interne ne permit să grupăm clasele care sunt conectate logic între ele şi să controlăm vizibilitatea acestora. Exemplu. Continuând aplicaţia GestiuneAngajaţi din ultimele două cursuri, presupunem că clasa Manager are o clasă internă numită Secretara: public class Manager extends Angajat{ private int vechime; public Manager(String nume, long salariu, int v){ super(nume, salariu); vechime=v; } public class Secretara{ private String nume; public Secretara(String nume){ this.nume=nume; } String getNume(){ return nume; } }

Dintr-o clasă externă putem crea fără probleme obiecte ale oricărei clase internă a sa. Din afara clasei externe, instanţierea clasei interner depinde de modificatorul de vizibilitate a acesteia din urmă. Astfel, dacă clasa internă oferă acces, putem crea obiecte din această clasă, cu ajutorul unei variabile instanţă de tip clasa externă şi tipul noii variabile instanţă este un tip compus: NumeClasaExterna.NumeClasaInterna, ca în exemplul următor: public static void main(String[] args){//in clasa Manager Manager m=new Manager("Ionescu", 500, 7); Manager.Secretara s=m.new Secretara("Maria"); System.out.println(s.getNume()); }

Dacă clasa internă este privată şi vrem să dăm acces clienţilor clasei externe la obiectele primei clase, clasa externă trebuie să conţină o metodă ce returnează o referinţă la un nou obiect al clasei interne: public Secretara obtineObiectSecretara(String nume){//in clasa Manager return new Secretara(nume); }

Notă. O clasă internă privată furnizează o modalitate de a ascunde informaţii despre implementarea realizată de clasa externă. Orice obiect al unei clase interne este legat automat de un obiect din clasa externă din care face parte şi din acest motiv, acesta poate accesa orice variabilă şi pot apela orice metodă (indiferent ce modificator de vizibilitate au) ale obiectului înconjurător. De exemplu, în clasa Secretara putem defini următoarea metodă:

37

public int getVechime(){return vechime;} care poate fi apelată din metoda main: System.out.println(“vechime manager= ”+ s.getVechime());

Într-o clasă internă putem scrie o metodă care să returneze o referinţă la obiectul înconjurător. Acest obiect poate fi folosit pentru a acesa, respectiv apela, toate variabilele, respectiv metodele, neprivate ale clasei externe: public Manager getManager(){return Manager.this;}

Clasele interne pot fi definite într-o metodă sau chiar într-un bloc arbitrar clasei din care fac parte. O astfel de decizie apare din două motive: - clasa internă extinde o clasă abstractă sau implementează o interfaţă şi astfel putem crea şi

returna o referinţă la aceasta - este o decizie de proiectare sau chiar de implementare, deoarece clasa externă rezolvă o

problemă mai grea şi avem nevoie de o clasă ajutătoare pe care să o folosim local.

De exemplu, putem defini clasa SecretaraE în corpul metodei obtineObiectSecretara(String): public Secretara obtineObiectSecretara(String nume){ class SecretaraE extends Secretara{ private String nume; private SecretaraE(String nume){ this.nume=nume; } public String getNume(){return nume;} }//clasa interna return new SecretaraE(nume); }

Mai mult, o clasă internă definită în corpul unei metode poate fi facută clasă anonimă în următoarele condiţii: - clasa internă anonimă nu defineşte un constructor propriu - putem declara variabile instanţă şi le putem iniţializa cu valorile transmise prin parametrii

metodei, numai că aceştia trebuie să fie declaraţi finali: public Secretara obtineObiectSecretara(final String n){ return new Secretara(){ private String nume=n; public String getNume(){return n;} }; }

Iniţializarea variabilelor instanţă ale clasei interne poate fi facută într-un bloc de iniţializare: public Secretara obtineObiectSecretara(final String n){ return new Secretara(){ private String nume; { nume=n; } public String getNume(){ return n; } }; }

38

CLASE INTERNE STATICE

O clasă internă statică se declară folosind modificatorul static: private static class SecretaraE extends Secretara{//in clasa Manager private String nume; private SecretaraE(String nume){ this.nume=nume; } public String getNume(){ return nume; } }//clasa interna

Clasele interne statice îndeplinesc următoarele condiţii: 1. Pentru a crea un obiect al clasei interne statice nu avem nevoie de un obiect al clasei externe 2. Fiind statică, dintr-un obiect al unei astfel de clase nu putem accesa un obiect al clasei

înconjurătoare şi astfel nu putem accesa, respectiv apela nici o variabilă, respectiv metodă instanţă a clasei înconjurătoare, numai variabile şi metode statice.

De exemplu, putem crea în metoda main un obiect al clasei SecretaraE astfel: Secretara s=new SecretaraE("Maria");//in main System.out.println(s.getNume());

Mai departe, clasele interne statice pot fi incorporate în interfeţe: interface ISecretara{ public String getNume(); static class SecretaraEI implements ISecretara{ private String nume; public SecretaraEI(String nume){ this.nume=nume; } public String getNume(){ return nume; } }//clasa interna }//interfata public static void main(String[] args){//din Manager ISecretara s=new ISecretara.SecretaraEI("Maria"); System.out.println(s.getNume()); }

CLASE INTERNE ŞI MOŞTENIREA

Un motiv pentru care putem folosi clase interne este de a obţine în Java moştenirea multiplă: o clasă internă extinde o clasă concretă sau abstractă sau implementează o interfaţă. De exp, putem declara următoarea clasa abstractă: abstract class Secretara{ public abstract String getNume(); }

În acest caz, putem declara clasa internă SecretaraE privată şi atunci este o clasă ascunsă oricărui client al clasei externe, Manager. În schimb, se poate obţine o referinţă la clasa de bază Secretara (sau la interfaţa implementată) care furnizează funcţionalitatea declarată. private class SecretaraE extends Secretara{

39

... private SecretaraE(String nume) } public Secretara obtineObiectSecretara(String nume){...} In metoda main, de exemplu, putem obţine un obiect din clasa SecretaraE astfel: Secretara s=m.obtineObiectSecretara("Maria");//in main

O clasă internă poate extinde orice clasă concretă sau abstractă. Mai mult, o clasă A internă clasei B poate extinde o clasă internă a supraclasei lui B. De exemplu, clasa ManagerE extinde clasa Manager: public class ManagerE extends Manager{ public ManagerE(String nume, long salariu, int v){ super(nume, salariu, v); } public class SecretaraE extends Manager.Secretara{ public SecretaraE(String nume){ (ManagerE.this).super(nume); } } public static void main(String[] args){ Manager m=new ManagerE("Ionescu", 500, 7); Manager.Secretara s=m.obtineObiectSecretara("Maria"); System.out.println(s.getNume()); } }

O altă soluţie ar fi ca constructorul clasei interne SecretaraE să conţină o variabilă referinţă la clasa înconjurătoare a clasei externe extinsă. Cu acest constructor putem apela constructorul supraclasei extinse: public class ManagerE extends Manager{ public ManagerE(String nume, long salariu, int v){ super(nume, salariu, v); } public class SecretaraE extends Manager.Secretara{ public SecretaraE(Manager m, String nume){ m.super(nume); } }

În acest caz putem introduce o nouă metodă (overloaded cu cea moştenită) care să returneze un obiect al subclasei interne SecretaraE: public SecretaraE obtineObiectSecretara(ManagerE m, String nume){ return new SecretaraE(m,nume); } public static void main(String[] args){ ManagerE m=new ManagerE("Ionescu", 500, 7); ManagerE.SecretaraE s=m.obtineObiectSecretara(m,"Maria"); System.out.println(s.getNume()); }

UTILIZAREA CLASELOR INTERNE ÎN APLICAŢII OO

Clasele interne au fost introduse pentru a trata evenimentele gestionate de aplicaţiile programate orientat spre obiecte. Un eveniment este o entitate conceptuală care spune că ceva are loc în timp şi

40

spaţiu şi are consecinţe asupra unei aplicaţii. Cu alte cuvinte, în momentul în care apare un eveniment, aplicaţia noastră trebuie să execute o acţiune. Acţiunea depinde de tipul evenimentului apărut.

Evenimentele sunt în general de două tipuri: - evenimente externe, cele generate de utilizator în interacţiunea sa cu aplicaţia noastră şi - evenimente interne, cele generate în interiorul aplicaţiei.

Atunci putem defini o clasă abstractă Eveniment ca o clasă generică de evenimente: public abstract class Eveniment{ private long moment; public Eveniment(long mT){ moment=mT; } public boolean aAparut(){ return System.currentTimeMillis()>=moment; } public abstract void executaActiune(); public abstract String descriere(); } şi un gestor generic de evenimente: public class Controller{ private Eveniment e; public void setEveniment(Eveniment e){ this.e=e; } public void gestioneaza(){ while(true) if (e.aAparut()){ e.executaActiune(); System.out.println(e.descriere()); e=null; break; } } }

Exemplu. Să scriem de exemplu o aplicaţie care simulează funcţionarea unui semafor de circulaţie. Vom avea clasa Semafor care conţine o variabilă instanţă privată culoare, ce reprezintă culoarea curentă aratată de semafor: public class Semafor{ private String culoare; public void setCuloare(String c){culoare=c;} public String getCuloare(){return culoare;} }

Semaforul este controlat de clasa GestorSemafor care extinde clasa Controller şi tratează fiecare eveniment produs de schimbarea culorii la semafor. Aşadar, toate evenimentele tratate de clasa GestorSemafor sunt de acelaşi tip SchimbareCuloare: public class GestorSemafor extends Controller{ private Semafor s; public GestorSemafor(Semafor s){ this.s=s; }

41

public class SchimbareCuloare extends Eveniment{ int culoare; public SchimbareCuloare(long timp, int culoare){ super(timp); this.culoare=culoare; } public void executaActiune(){ switch(culoare){ case 1: s.setCuloare("rosu");break; case 2: s.setCuloare("galben");break; case 3: s.setCuloare("verde"); } } public String descriere(){ return "Culoarea aratata de semafor este "+s.getCuloare(); } } } Observăm că clasa SchimbareCuloare este o clasă internă şi este o subclasă a clasei Eveniment. Clasa Test crează un obiect Semafor în metoda main şi produce la intervale scurte de timp evenimente generate de schimbarea culorii la semafor: public class Test{ public static void main(String[] args){ Semafor s=new Semafor(); GestorSemafor gs=new GestorSemafor(s); for (int i=1; i<=10; i++){ long timp=System.currentTimeMillis(); gs.setEveniment(gs.new SchimbareCuloare(timp+1000*i, 1)); gs.gestioneaza(); gs.setEveniment(gs.new SchimbareCuloare(timp+2000*i+1000, 2)); gs.gestioneaza(); gs.setEveniment(gs.new SchimbareCuloare(timp+3000*i+2000, 3)); gs.gestioneaza(); } } }

42

8. INPUT/OUTPUT ÎN JAVA Pachetul java.io Furnizează suport pentru citirea şi scrierea datelor de la şi la dispozitive diferite. Conţine 4 categorii de clase: - clase de fluxuri de intrare şi clase de citire - clase de fluxuri de ieşire şi clase de scriere - clase de gestiune a fişierelor - clasa StreamTokenizer 1. Clase de fluxuri de octeţi: BufferedInputStream, DataInputStream, FileInputStream şi StringBufferInputStream. Derivate din clasa abstractă InputStream. Clase de citire a caracterelor Unicode: Reader, BufferedReader, FileReader şi StringReader sunt derivate din clasa abstractă Reader. 2. Clase de fluxuri de ieşire: OutputStream, FileOutputStream, BufferedOutputStream, PrintStream şi StringBufferOutputStream. Sunt derivate din clasa OutputStream. Clase de scriere a caracterelor în Unicode: Writer, BufferedWriter, FileWriter şi PrintWriter. Sunt derivate de clasa abstractă Writer. 3. Claselor FileInputStream şi FileOutputStream se adaugă alte două clase destinate gestiunii fişierelor: File şi RandomAccessFile. 4. Clasa StreamTokenizer permite convertirea unui flux de intrare a datelor într-un flux de token-i şi conţine metode pentru definirea sintaxei token-ilor.

43

Ierarhia claselor I/O DataInput File

FileDescriptor ByteArrayInputStream Serializable FileInputStream BufferedInputStream

FilterInputStream DataInputStreamExternalizable InputStream ObjectInputStream LineNumberInputStream

PipedInputStreamObjectInputValidation SequenceInputStream ObjectInput StringBufferInputStream BufferedReader LineNumberReaderObjectOutput CharArrayReader

Object InputStreamReader FileReaderReader FilterReader PushbackReader

ByteArrayOutputStream PipedReader BufferedOutputStreamDataOutput

OutputStreamFileOuptputStream StringReader DataOutputStreamFilterOutputStream PrintStream

Throwable ObjectOutputStream BufferedWriterRandomAccessFile PipedOutputStream CharArrayWriter

Writer FilterWriter FileWriterFilenameFilter StreamTokenizer OutputStreamWriter

PipedWriterPrintWriter EOFExceptionStringWriter FileNotFoundException

Exception IOException InterruptedIOExceptionObjectInputValidation SyncFailedExceptionInvalidClassException UTFDataFormatExceptionInvalidObjectException ObjectStreamException CharConversionExceptionNotActiveException UnsupportedEncodingExceptionNotSerializableException WriteAbortedExceptionOptionalDataExceptionStreamCorruptedException

45

Clasa InputStream InputStream = clasă abstractă din care derivează toate clasele fluxului de intrare. Metodele clasei InputStream sunt prezentate în următorul tabel:

Semnătura metodei Semnificaţie –Valoarea returnată abstract int read() throws IOEXception

returnează un octet citit de pe fluxul de intrare. Dacă se detectează sfârşitul fluxului atunci metoda intoarce –1.

int read(byte[] b) throws IOEXception

citeşte octeţi de pe fluxul de intrare şi îi memorează în array-ul b. Dacă b este mai mic decât numărul de octeţi citiţi, se lansează o exceptie. Metoda returnează numărul de octeţi citiţi efectiv.

int read(byte[] b, int poz, int nr) throws IOEXception

citeşte maxim nr octeţi de pe fluxul de intrare şi îi memorează în array-ul b, de la poziţia poz. Dacă b este mai mic decât numărul de octeţi citiţi, se lansează o exceptie. Metoda returnează numărul de octeţi citiţi efectiv.

long skip(long n) throws IOException ignoră maxim n octeţi din fluxul de intrare şi întoarce numărul de octeţi cu care s-a avansat in flux.

int available() throws IOException returnează numărul de octeţi disponibili pentru citire fără blocare.

void mark(int poz) marchează poziţia poz în flux pentru ca ulterior să se poată reveni la ea. Nu toate fluxurile de intrare oferă această operaţie.

void reset() throws IOException repoziţionează fluxul la ultima poziţie marcată. boolean markSupported() întoarce true dacă fluxul suportă operaţia de

marcare şi revenire la poziţia marcată. void close() throws IOException închide fluxul de intrare şi eliberează resursa

alocată. Clasa Reader Aceeaşi structură are clasa abstractă Reader, specializată în citirea caracterelor. De aceea, sintaxa metodelor read() a fost modificată astfel:

Semnătura metodei Semnificaţie –Valoarea returnată int read() throws IOEXception

citeşte un caracter de pe fluxul de intrare şi returnează codul său Unicode. Dacă se detectează sfârşitul fluxului atunci metoda intoarce –1.

int read(char[] b) throws IOEXception

citeşte caractere de pe fluxul de intrare şi îi memorează în array-ul b. Dacă b este mai mic decât numărul de caractere citite, se lansează o exceptie. Metoda returnează numărul de caractere citite efectiv.

abstract int read(char[] b, int poz, int nr) throws IOEXception

citeşte maxim nr caractere de pe fluxul de intrare şi îi memorează în array-ul b, de la poziţia poz. Dacă b este mai mic decât numărul de caractere citite, se lansează o exceptie. Metoda returnează numărul de caractere citite efectiv.

System.in = variabilă statică a clasei System care memorează o instanţă a clasei InputStream. De obicei, această instanţă reprezintă fluxul standard de intrare (tastatura), dar poate fi setată cu System.setIn(InputStream) astfel: FileInputStream fi = new FileInputStream(“input.txt”); System.setIn(fi);

Crearea unui flux de date de intrare

46

Crearea unui flux de date de intrare respectă modelul pipeline Unix: face parte dintr-o sursă de date cu interfaţa InputStream şi se crează succesiv cu filtri ai aceleaşi interfeţe. Caracteristicile citirii: - este un flux continuu. - când nu mai sunt date disponibile, instanţa clasei se blochează în aşteptarea ca datele să devină

disponibile. Pentru a citi date în Java procedura este întotdeauna aceeaşi: 1. Se crează o instanţă a unei clase de flux de intrare. 2. Se indică locul din care vor fi citite datele.

Exemplu. Metoda urmatoare citeşte un caracter de la tastatură şi-l returnează: public char citeste(){ char c='0'; try{ c=(char)System.in.read(); System.in.skip(2);//nu se citeşte caracterul generat de apăsarea lui Enter }catch(IOException e){System.out.println(e.getMessage());} return c; }

Clase de flux de octeţi de intrare 1. DataInputStream (InputStream in): - utilizată pentru citirea datelor primitive (int, char, long etc.); - moşteneşte atât interfaţa DataInput cât şi clasa abstractă FilterInputStream; - la metodele furnizate de InputStream se adaugă metodele interfeţei DataInput readxxx()

(xxx = Boolean, Byte, Short, UnsignedShort, Char, Int, Long, Float sau Double) care citesc un număr de octeţi din fluxul de intrare necesar pentru a obţine o valoare de tip primitiv specificat în numele metodei. De exemplu, metoda readInt() citeşte patru octeţi din fluxul de intrare şi întoarce o valoare de tip int.

2. BufferedInputStream (InputStream in): - furnizează un flux de intrare buffer-izat permiţând astfel o citire mult mai rapidă; - include numai metodele furnizate de InputStream; - are unele variabile membru: byte buf[], int count, int pos, int markpos şi

int marklimit, utile dacă se extinde clasa BufferedStream. 3. FileInputStream (String nume): - execută operaţii de intrare simple asupra fişierelor (pentru operaţii mai avansate se utilizează

RandomAccessFile). - construieşte fluxul şi cu un obiect File sau FileDescriptor. - funcţionează ca un InputStream, dar operează asupra unui fişier. 4. PushBackInputStream (InputStream in) - construieşte un flux cu un caracter lookahead. - are o metodă unread(int ch). - funcţionează ca InputStream.

File

byte int char float long

BufferedInputStream

Bytes Buffer de bytes Date formatateFile system (Sursă date)

Memoria

FileInputStream DataInputStream

47

Clasa abstractă Reader are subclase asemănătoare cu cele ale clasei abstracte InputStream: BufferedReader, FileReader, StringReader, PushBackReader. BufferedReader are o metoda String readLine(). In plus, există clasa InputStreamReader care face legătura dintre o clasa de tip InputSream şi un Reader. Următorul exemplu arată mai multe modalitaţi de a face o citire folosind clasele derivate din InputStream şi Reader: import java.io.*; public class FluxDeIntrareIesire{ public static void main(String[] args) { try { //1. Citirea unei linii de la tastatura BufferedReader in1 = new BufferedReader( new InputStreamReader(System.in)); String s=in1.readLine(); in1.close(); //2. Citirea dintr-un fisier BufferedReader in2 = new BufferedReader( new FileReader("intrare.txt")); String s1, s2 = new String(); while((s1 = in2.readLine())!= null) s2 += s1 + "\n"; in2.close(); //3. Citirea din memorie StringReader in3 = new StringReader(s2); int c; while((c = in3.read() ) != -1) System.out.print((char)c); in3.close(); //4. Citirea din memorie formatata DataInputStream in4 = new DataInputStream(

new FileInputStream("intrare.txt")); while(true) System.out.print((char)in4.readByte()); } catch(EOFException e){ System.out.println("Am ajuns la sfarsitul fisierului"); } catch (FileNotFoundException e){ System.out.println("Fisier inexistent"); } catch (IOException e){ System.out.println("Exceptie IO"); } } }

Clasa OutputStream OutputStream = clasă abstractă din care derivează toate clasele fluxului de ieşire. Metodele clasei OutputStream sunt prezentate în următorul tabel:

Semnătura metodei Semnificaţie –Valoarea returnată abstract void write(int n) throws IOEXception

scrie valoarea lui n pe fluxul de ieşire.

void write(byte[] b) throws IOEXception scrie pe fluxul de ieşire octeţii din array-ul b void write(byte[] b, int poz, int nr) throws IOEXception

scrie nr octeţi pe fluxul de ieşire începând de la poziţia poz a array-ului b.

void flush() throws IOException goleşte fluxul de ieşire void close() throws IOException închide fluxul de intrare şi eliberează resursa

alocată. Clasa Writer Clasa Writer specializată în scrierea fluxurilor de caractere are o ierarhie similară cu ierarhia lui OutputStream. Semnătura metodelor write() se modifică şi apar două forme noi ale acestei metode:

Semnătura metodei Semnificaţie –Valoarea returnată void write(int n) throws IOEXception

scrie valoarea lui n pe fluxul de ieşire.

void write(char[] b) throws scrie pe fluxul de ieşire caracterele din array-ul b

48

Semnătura metodei Semnificaţie –Valoarea returnată IOEXception abstract void write(char[] b, int poz, int nr) throws IOEXception

scrie nr caractere pe fluxul de ieşire începând de la poziţia poz a array-ului b.

public void write(String str) throws IOException

scrie şirul str pe fluxul de ieşire.

public void write(String str, int poz, int nr) throws IOException

scrie pe fluxul de ieşire subşirul de lungime nr pe care-l extrage de la poziţia poz a şirului str.

System.out = variabilă statică a clasei System care memorează o instanţă a clasei PrintStream ce reprezintă de obicei ecranul monitorului. Este setată cu System.setOut(OutputStream): FileOutputStream fs = new FileOutputStream(“log.txt”); System.setOut(new printStream(fs)); Caracteristicile scrierii: - este un flux continuu. - instanţa clasei de flux trimite date cât timp acestea sunt disponibile. Procedura de scriere a datelor cu o clasă de fluxuri de ieşire este întotdeauna aceeaşi: 1. Se crează o instanţă a clasei fluxului de ieşire specificând fluxul final, la destinaţie. Instanţa se

poate crea plecând de la destinaţie şi compunând fluxul din obiecte-flux încuibate, deoarece obiectul obţinut are caracteristicile dorite: buffer-izat, în stare să scrie date primitive etc.

2. Se indică locul unde vor fi scrise datele.

Clase de flux de caractere în ieşire 1. PrintWriter - derivează din Writer - afişează date-text (şiruri sau numere). - metodele sale, altele decât cele furnizate de Write, include: print(tip) şi

println(tip) unde tip ia una din valorile: Object, String, boolean, char, char[], int, long, float şi double.

PrintWriter out= PrintWriter(new FileWriter(“personale.txt”), true); 2. BufferedWriter - derivează din Writer, - furnizează un flux de ieşire buffer-izat, - constructorul are ca argument un obiect Writer, - nu conţine alte metode în afară de cele furnizate de Writer, - are două variabile membru utile: byte buf[] şi int count. 3. FileWriter - execută operaţii simple de ieşire de caractere pe fişiere (pentru operaţii mai avansate pe fişiere

se utilizează RandomAccessFile), - construieşte un obiect numai cu un şir, un obiect File sau un FileDescriptor, - funcţionează ca Write, dar operează pe fişiere. Scrierea cu clasele Output Stream şi Writer Primul exemplu din această subsecţiune arată cum se poate determina numărul de linii ale unui fişier şi scrierea fiecărei linii citite într-un alt fişier, împreună cu numărul liniei curente.

BufferedInputStream

Buffer de bytes

File

File system (Sursă date)

byte int char float long

Memoria

Flux de bytes Date formatate

DataInputStream FileInputStream

Bytes

49

Observaţie. Pentru a afla numărul liniei curente vom folosi un cititor buffer-izat ce păstrează numărul liniei citite. Adică vom folosi o instanţă a clasei LineNumberReader, subclasă a lui BufferedReader.

Următorul exemplu arată cum scriem şi citim date formatate într-un/dintr-un fişier. Vom folosi instanţe ale claselor DataOutputStream şi DataInputStream pentru a scrie numărul 3.14159 şi şirul “Valoarea lui PI”:

Clasa următoare furnizează două metode care citesc, respectiv scriu date formatate pe linii de lungime fixă: import java.io.*; public class DataIO{ public static String citesteSirFix(int size, DataInput in)throws IOException{ StringBuffer buffer=new StringBuffer(size); int i=0; boolean more=true; while(more&& i<size){ char c=in.readChar(); i++; if(c==0) more=false; else buffer.append(c); } in.skipBytes(2*(size-i)); return buffer.toString(); } public static void scrieSirFix(String s, int size, DataOutput out) throws IOException{ for(int i=0;i<size;i++){ char c=0; if(i<s.length()) c=s.charAt(i); out.writeChar(c); } } }

try{ LineNumberReader lnr = new LineNumberReader(new FileReader("intrare.txt")); PrintWriter out=new PrintWriter(new BufferedWriter(

new FileWriter("iesire.txt"))); String s=""; while((s = lnr.readLine()) != null) out.println("Linia " +lnr.getLineNumber()+

" "+ s); lnr.close(); out.close(); }catch(EOFException e){System.out.println("Sfarsitul fisierului");} catch(IOException ioe){System.out.println("Eroare de intrare/iesire");}

try{ DataOutputStream out=new DataOutputStream(new BufferedOutputStream(

new FileOutputStream("Data.txt"))); out.writeDouble(3.14159); out.writeBytes("Valoarea lui PI"); out.close(); DataInputStream in = new DataInputStream(new BufferedInputStream(

new FileInputStream("Data.txt"))); BufferedReader inbr = new BufferedReader(new InputStreamReader(in)); System.out.println(in.readDouble()); System.out.println(inbr.readLine()); }catch(EOFException e) {System.out.println("Sfarsitul fluxului");} catch(IOException ioe){System.out.println("Eroare de intrare/iesire");}

50

Această clasă poate fi folosită de exemplu, pentru a scrie un scrie un program care salveaza intr-un fisier datele (id, nume, salariu) a trei angajati ai unei companii şi apoi le afişeaza în ordinea inversă scrierii lor: import java.io.*; class Angajat{ public static final int SIZE_NUME=40; public static final int RECORD_SIZE=2*SIZE_NUME+4+8; private String nume; private int id; private double salariu; public Angajat(){} public Angajat(int id, String nume, double salariu){ this.id=id; this.nume=nume; this.salariu=salariu; } public void writeData(DataOutput out) throws IOException{ out.writeInt(id); DataIO.scrieSirFix(nume, SIZE_NUME,out); out.writeDouble(salariu); } public void readData(DataInput in)throws IOException{ id=in.readInt(); nume=DataIO.citesteSirFix(SIZE_NUME,in); salariu=in.readDouble(); } public String toString(){ return id+" "+nume+" "+salariu; } } public class TestAngajat{ public static void main(String[] arg){ Angajat[] a=new Angajat[3]; a[0]=new Angajat(1, "popescu ion", 45.899); a[1]=new Angajat(2, "ionescu pop", 435.877); a[2]=new Angajat(2, "marinescu marin", 235.877); try{ DataOutputStream out=new DataOutputStream(new FileOutputStream("angajat.dat")); a[0].writeData(out); a[1].writeData(out); a[2].writeData(out); out.close(); }catch(IOException ioe){} try{ RandomAccessFile in=new RandomAccessFile("angajat.dat", "r"); int n=(int)(in.length()/Angajat.RECORD_SIZE); System.out.println("n="+n); Angajat[] an=new Angajat[n]; for(int i=n-1; i>=0;i--){ an[i]=new Angajat(); in.seek(i*Angajat.RECORD_SIZE);

51

an[i].readData(in); System.out.println(an[i]); } } catch(IOException e){System.out.println(e.getMessage());} } }

Clasa File Clasa File prezintă o vedere abstractă a numelor fişierelor şi a directoarelor, precum si locaţiilor unde se găsesc acestea într-un sistem de fişiere. Locaţia unui fişier reprezintă calea în sistemul de fişiere unde este memorat acesta. Calea poate fi absolută sau relativă la directorul în care se găsesc class-urile programului. Separatorul unei căi depinde de sistemul de operare pe care rulează programul si anume: - “\\” pentru sistemele Windows - “/” pentru sistemele Unix. Acesta este memorat în constantele String separator şi char separatorChar, văzut ca un String, respectiv ca un caracter. Constructori: File(String cale)//crează un obiect File ce contine o cale absolută sau relativă şi care poate conţine numele fişierului File(String cale, String nume)//crează un obiect File ce conţine separat numele fişierului şi calea directorului în care se află fişierul File(File dir, String nume)//calea directorului părinte este dat ca un obiect File Metodele mai importante ale clasei File sunt prezentate în următorul tabel:

Semnătura metodei Semnificaţie –Valoarea returnată String getName() returnează numele fişierului sau directorului curent String getPath() returnează calea unde se află fişierul sau directorul curent pe

disc String getAbsolutePath() returnează calea absolută unde se află fişierul sau directorul

curent pe disc String getParent() întoarce directorul părinte a directorului sau în care se găseşte

fişierul curent boolean exists() returnează true dacă fişierul indicat in constructor există pe

disc. boolean canWrite() returnează true dacă fişierul respectiv poate fi modificat boolean canRead() returnează true dacă fişierul respectiv poate fi citit boolean isFile() returnează true dacă şirul indicat ca fişier există pe disc şi este

un fişier boolean isDirectory() returnează true dacă şirul indicat ca director există pe disc şi

este un director long lastModified() returnează data şi ora ultimei modificări long length() returnează mărimea fişierului, în biţi boolean mkdir() returnează true dacă a fost creat directorul specificat in cale boolean mkdirs() returnează true dacă au fost create

directoarele specificate in cale boolean renameTo(File dest) redenumeşte fişierul cu curent cu numele dest şi returnează

true boolean delete() şterge fişierul sau directorul curent String[] list() întoarce într-un array de şiruri numele fişierelor şi

directoarelor din directorul curent. String[] list(FilenameFilter filtru) întoarce într-un array de şiruri numele fişierelor şi

directoarelor din directorul curent care îndeplinesc criteriul filtru

52

De exemplu, pentru a crea un filtru trebuie să scriem o clasă care implementează interfaţa FilenameFilter. De exemplu, următoarea clasa filtreaza fişierele dintr-un director si le acceptă numai pe cele care sunt programe Java: import java.io.*; public class Filtru implements FilenameFilter{ public boolean accept(File dir, String nume){ String sufix=""; int i=nume.lastIndexOf('.'); if(i>0 && i<nume.length()-1) sufix=nume.substring(i+1); return sufix.equals("java"); } public String getDescription(){ return "fisiere java(*.java)"; } }

Următorul exemplu citeşte numele unui fişier/director de la tastatură şi-l scrie informaţii despre el, cum ar fi: dacă este fişier sau director, locaţia sa pe disc, data ultimei modificări, etc, în fişierul analiza.txt: import java.io.*; public class Fisier{ BufferedReader br; PrintWriter pw; public Fisier(){ try{ br=new BufferedReader(new InputStreamReader(System.in)); pw=new PrintWriter(new FileWriter("analiza.txt"), true); scrie(analizeaza(citeste())); }catch(IOException ioe){} } public String citeste()throws IOException{ System.out.println("Dati numele fisierului/directorului"); return br.readLine(); } public String analizeaza(String sir){ File nume=new File(sir); StringBuffer buffer=new StringBuffer(); if(nume.exists()){ buffer.append(nume.getName()+" exista\r\n"); if(nume.isFile()) buffer.append("este un fisier\r\n"); if(nume.isDirectory())buffer.append("este un director\r\n"); if(nume.isAbsolute())buffer.append("este o cale absoluta\r\n"); else buffer.append("nu este o cale absoluta\r\n"); buffer.append("data ultimei modificari "+nume.lastModified()+"\r\n"); buffer.append("lungimea "+nume.length()+"\r\n"); buffer.append("calea "+nume.getPath()+"\r\n"); buffer.append("calea absoluta "+nume.getAbsolutePath()+"\r\n"); buffer.append("parinte "+nume.getParent()+"\r\n"); if(nume.isFile()){

53

buffer.append("Continutul fisierului\r\n"); try{ RandomAccessFile in=new RandomAccessFile(nume, "r"); String s=""; while((s=in.readLine())!=null) buffer.append(s+"\r\n"); in.close(); }catch(IOException ioe){} } else if(nume.isDirectory()){ buffer.append("Continutul directorului\r\n"); String[] dir=nume.list(); for(int i=0;i<dir.length; i++) buffer.append(dir[i]+"\r\n"); } } else buffer.append("acest nume nu exista"); return buffer.toString(); } public void scrie(String sir) throws IOException { pw.print(sir); pw.close(); } public static void main(String[] a){ new Fisier(); } }

Clasa StreamTokenizer Ca şi StringTokenizer, această clasă imparte un flux in token-i, dar spre deosebire de prima clasă, diferenţiază între tipuri de token-i: şiruri, numere, sfârşitul unei linii, sfârşitul fişierului, etc. Constructorul clasei: public StreamTokenizer(Reader r)//creaza un tokenizer care imparte fluxul de intrare r într-un şir de token-i. Pentru a sti daca s-a ajuns la sfarsitul liniei curente se foloseste constanta: public static finale int TT_EOL //token-ul citit conţine sfarşitul liniei curente (“\r” sau “\n”) dacă acesta este tratat ca un token, adică a fost apelată metoda void eolIsSignificant(boolean val) cu val=true. Pentru a trata sfarsitul fluxului de intrare folosim constanta public static finale int TT_EOF //token-ul citit conţine sfârşitul fluxului de intrare Deosebirea faţă de clasa StringTokenizer este ca aceasta clasă diferenţiaza token-ii obtinuti din fragmentare. Astfel, clasa ofera urmatoarele constante: public static finale int TT_NUMBER //token-ul citit este un număr public static finale int TT_WORD //token-ul citit este un cuvânt Pentru a obtine numărul, respectiv cuvântul citit se folosesc următoarele variabile instanţă: public double nval //conţine valoarea numărului întors ca token public String sval //conţine caracterele cuvântului întors ca token Pentru a obtine un token din fluxul analizat se apelează metoda instanţă: public int nextToken() care returnează tipul token-ului citit ca un numar intreg. Acesta este memorat dupa apelul metodei si în variabila instanţă public int ttype. Clasa oferă si alte metode utile în analizarea unui flux de intrare specializat: public void parseNumbers()//sunt analizate numerele si caracterele ‘.’ si ‘–‘.

54

public int lineno()//întoarce numărul liniei curente. public void ordinaryChar(int ch) //trateaza caracterul dat la apelul metodei ca unul oarecare, fara sa mai verifice tipul sau si-l intoarce ca un token de tip TT_WORD. public void ordinaryChars(int min, int max) //toate caracterele ce au codul Unicod in intervalul [min, max] sunt tratate ca oarecare. public void commentChar(int ch) //caracterul ch indica inceputul unui comentariu. In acest caz, token-ii fluxului de intrare aflati pana la sfarsitul liniei vor fi ignoraţi. public void quoteChar(int ch) //caracterele gasite intre două apariţii a lui ch sunt tratate ca un literal String si intoarse ca un token de tip TT_WORD. Dacă a doua apariţie a caracterului ch nu este găsită pana la sfarşitul liniei, se iau caracterele pana la acesta. Daca ch apare o singură dată in fluxul de intrare, se iau caracterele de la ch pana la sfarsitul fisierului. public void whitespaceChars(int min, int max) // toate caracterele ce au codul Unicod in intervalul [min, max] sunt tratate ca spatii. public void wordChars(int min, int max) // toate caracterele ce au codul Unicod in intervalul [min, max] sunt tratate ca parte din cuvinte. public void pushBack()//token-ul curent va fi obtinut si la urmatorul apel al metodei nextToken() Exemplu. Sa scriem un program care citeste continutul fisierului “intrare.txt” si afiseaza tipul fiecarui token si valoarea sa, precum şi numarul de cuvinte, respectiv numere, din fisierul respectiv. import java.io.*; public class Tokenizer{ private StreamTokenizer st; private int w, n; public Tokenizer(){ String linie; try{ st=new StreamTokenizer(new BufferedReader(new FileReader("intrare.txt"))); }catch(FileNotFoundException nf){System.out.println(nf.getMessage());} catch(IOException io){System.out.println(io.getMessage());} } public void imparte(){ if(st==null) System.exit(0); try{ while(st.nextToken()!=StreamTokenizer.TT_EOF){ switch(st.ttype){ case StreamTokenizer.TT_WORD: System.out.print("cuvant= "+st.sval+" "); w++; break; case StreamTokenizer.TT_NUMBER: System.out.print("numar= "+st.nval+" "); n++; break; } System.out.println(); } System.out.println("Total cuvinte= "+w); System.out.println("Total numere= "+n); }catch(IOException io){System.out.println(io.getMessage());} } public static void main(String[] args){ Tokenizer t=new Tokenizer(); t.imparte(); } }

55

9. GESTIUNEA EXCEPŢIILOR O excepţie este o condiţie exceptională care întrerupe execuţia unui program şi care poate fi: - condiţie de eroare provocată de erori interne sau lipsa resurselor mediului de executie Java, - situaţie particulară apărută în timpul execuţiei programului care poate fi de unul din

următoarele tipuri: - excepţii necontrolate cauzate de erori de programare: cast greşit, cast care depăşeşte limita

unui array, referinţă la un obiect null, - excepţii controlate: erori de intrare/ieşire precum continuarea operaţiei de citire după ce s-a

ajuns la sfârşitul fişierului, încercarea de a deschide un URL greşit. Când apare o excepţie este important să captăm cauza ce a dus la apariţia ei, împreună cu alte informaţii, cum ar fi valoarea variabilelor folosite, ce vor ajuta la tratarea exceptiei. De aceea, toate excepţiile în Java sunt reprezentate de obiecte ale subclaselor clasei Throwable din pachetul java.lang: public class Throwable{ public Throwable(); public Throwable(String mesaj); public String getMessage(); public String toString(); public void printStackTrace(); public void printStackTrace(java.io.PrintStream ps); public void printStackTrace(java.io.PrintWriter pw); public Throwable fillInStackTrace(); } După cum observăm, clasa Throwable implementează mai multe metode printre care şi metoda getMessage() care returnează un şir care descrie situaţia care a cauzat excepţia. De exemplu, clasa RunTimeException conţine tipuri de excepţii lansate automat de JVM. Dacă nu sunt tratate de programator, dacă apare o astfel de excepţie se va apela metoda printStackTrace() a clasei Throwable. De exemplu, executarea următorului program public class Exceptie{ static void metoda1(int[] a){ metoda2(a); } static void metoda2(int[] b){ System.out.println(b[0]); } public static void main(String[] args){ metoda1(null); } } determină afişarea mesajului: java.lang.NullPointerException at Exceptie.metoda2(Exceptie.java:6) at Exceptie.metoda1(Exceptie.java:3) at Exceptie.main(Exceptie.java:9) Mesajul afişat conţine următoarele informaţii: - tipul de exceptie aruncat, - stiva apelurilor metodelor, fiecare cu clasa şi unitatea de compilare de care aparţine, împreuna

cu numărul liniei pe care se găseşte declaraţia metodei. Metoda din vârful stivei este cea care a aruncat excepţia respectivă.

Toate excepţiile ce pot fi generate sunt împărţite în două clase: Exception şi Error, ambele derivate din clasa Throwable. Subclasele lui Exception, cu excepţia lui RuntimeException şi a subclaselor sale, indică situaţii particulare care nu sunt erori. Error, RuntimeException şi subclasele lor indică condiţii care în general nu sunt corecte şi care de obicei generează terminarea unui program. Toate

56

pachetele incluse în JDK generează diferite tipuri de excepţii. O parte din acestea sunt prezentate în figurile de mai jos şi unele din acestea sunt explicate în următorul tabel: Exceptie Cauzată de ArithmeticException Erori matematice cum ar fi imparţirea la zero. ArrayIndexOutOfBoundsException Indicele unui array este în afara array-ului. ArrayStoreException Se memorează într-un array o valoare de tip diferit de tipul de date al

array-ului. ClassNotFoundException Se încearcă folosirea unei clase pe care compilatorul nu o cunoaşte.

Clasa respectivă trebuie să fie importată sau scrisă de către programator.

FileNotFoundException Se accesează un fişier care nu există în locaţia specificată de program.

IOException Erori generale de I/O, cum ar fi incapacitatea de citi dintr-un fişier. NullPointerException Referirea unui obiect null. NumberFormatException O conversie eşuată între şiruri şi numere. OutOfMemoryException Memorie insuficientă creării unui obiect nou. SecurityException Un applet înacearcă să realizeze o acţiune care nu este permisă de

browser. StackOverflowException Depăşirea stivei de execuţie. StringIndexOutOfBoundsException Programul incearcă să acceseze un caracter de pe o poziţie care nu

există în şir.

57

Excepţii şi erori

Object Throwable Error

LinkageError

ThreadDeathError

VirtualMachineError

ClassCircularityError

ClassFormatError

IncompatibleClassChangeError

ExeptionInInitializerError

NoClassDefFoundError

UnsatisfiedLinkError

VerifyError

AbstractMethodError

IllegalAccessError

InstantiationError

NoSuchFieldError

NoSuchMethodError

InternalError

OutOfMemoryError

StackOverflowError UnknownError

IllegalAccessException Object Throwable Exception

ClassNotFoundException

CloneNotSupportedException

InstantiationException

InterruptedException

NoSuchMethodException

NoSuchFieldExceptionRunTimeException

ArithmeticException

ArrayStoreException

ClassCastException

IllegalArgumentException

IllegalStateException

IllegalMonitorStateException

IndexOutOfBoundsException

NegativeArraySizeException

NullPointerException

SecurityException

IllegalThreadStateException

NumberFormatException

ArrayIndexOutOfBoundsException

StringIndexOutOfBoundsException

IOException

58

Cum utilizăm excepţiile? Există mai multe alternative pentru programator: - corectarea problemei şi apelarea metodei care a cauzat excepţia; - se încearcă repararea unei greşeli şi se continuă fără să se reapeleze metoda; - elaborarea unui rezultat alternativ raportat la cel produs în mod normal de metodă; - se face tot ce se poate în contextul curent şi se relansează aceeaşi excepţie în contextul superior; - se face tot ce se poate în contextul curent şi se relansează o excepţie diferită în contextul

superior; - se termină programul. Tratarea excepţiilor prezintă următoarele avantaje: - simplifică dacă schema de elaborare a excepţiilor aleasă complică lucrurile, - face mai fiabile programul şi librăriile utilizate. Aceasta e desigur o investiţie bună pe termen

scurt (pentru debugging) şi pe termen lung (pentru robusteţea programului). Mecanismul de gestiune a excepţiilor furnizează reguli care impun cine este capabil să genereze excepţii (throws) şi când (throw), cine va încerca excepţiile (try..catch) şi ce trebuie să facă pentru a împiedica apariţia unei excepţii.

Interceptarea şi tratarea excepţiilor

Interceptarea şi tratarea excepţiilor se realizează de către gestorii de excepţii (exception handler). Când în interiorul unei metode va fi lansată o excepţie sau apelul unei metode lansează o excepţie, în mod normal, controlul abandonează metoda. Dacă nu se doreşte ca metoda respectivă să fie abandonată, instrucţiunile ce pot genera cel puţin o excepţie se includ într-un bloc try care “încearcă” să le execute. Blocul crează un mediu (spaţiu de nume) în interiorul metodei. Dacă apare o excepţie ca rezultat al executării uneia din instrucţiunile blocului, ea poate fi “prinsă” (catch) de gestorul excepţiei respective. Gestorii excepţiilor pentru un bloc try sunt introduşi cu cuvântul cheie catch şi urmează blocul try. Fiecare gestor de excepţii este ca o metodă care primeşte un unic argument care are tipul unei excepţii şi va fi executată în cazul în care apare o excepţie de tipil respectiv. Argumentul metodei poate să nu fie utilizat în codul gestorului, iar alegerea gestorului este în întregime bazată pe tipul excepţiei. Pentru acelaşi bloc try putem avea mai mulţi gestori. In acest caz este importantă ordinea gestorilor, deoarece în cazul apariţiei unei excepţii, se verifică tipul excepţiei cu fiecare gestor în ordinea în care apar dupa blocul try. Se va executa primul gestor care verifică excepţia, iar restul se ignoră. In exemplul următor, acest lucru înseamnă că al doilea gestor niciodată nu va trata nici o excepţie, deoarece primul este mai general şi va trata şi excepţiile de intrare/ieşire (deoarece IOException este o subclasă a lui Exception). Această situaţie va fi detectată de compilatorul Java şi va reporta o eroare. void metoda1(){ try{ //cod care poate produce o exceptie }catch(IOException ioe){ //cod care gestioneaza exceptia ioe } catch(Exception e){ //cod care gestioneaza exceptia e } } Un bloc try poate fi urmat eventual şi de un bloc declarat cu finally. Un bloc finally conţine instrucţiuni ce vor fi executate întotdeauna, chiar dacă apare o excepţie şi furnizează un spaţiu util de a scrie cod care să fie executat indiferent ce se întâmplă. De exemplu, următoarea metodă gestionează interceptarea unei excepţii şi a unei erori şi indiferent dacă acestea apar sau nu, se va executa codul din blocul finally: void metoda2(){ try{ //cod care poate produce o exceptie }catch(Exception e){

59

//cod care gestioneaza exceptia e } catch(Error er){ //cod care gestioneaza eroarea er } finally{ //cod ce va fi executat indiferent daca apar sau nu exceptia e sau eroarea er } } Gestorul poate prinde o excepţie de tip: - Exception pentru orice excepţie din bibliotecile Java; - specializat pentru un pachet Java (de exemplu IOException); - definit de programator.

Declaraţii throws

Dacă o metodă nu tratează toate excepţiile ce pot apare atunci când este executat corpul său, atunci ea trebuie să declare în header-ul său toate tipurile de excepţii care pot să apară şi nu le tratează. Pentru aceasta se inserează o declaraţie throws după parametrii metodei şi înainte de corpul său: modificatori numeTip numeMetoda(listaParametri) throws listaNumeTip{ //lista de instructiuni } unde listaNumeTip::=numeTip [, numeTip]* Excepţiile declarate cu throws se vor propaga metodei superioare în stivă, aceleia care a apelat-o. In acest caz, metoda apelantă trebuie să se afle în una din următoarele situaţii: - metoda este apelată dintr-un bloc try care are gestori capabili să prindă toate tipurile de

excepţii ce pot fi aruncate de metoda apelată. - metoda este apelată dintr-un bloc try care are gestori capabili să prindă numai unele din

tipurile de excepţii ce pot fi aruncate de metoda apelată. Pentru celelalte tipuri de excepţii, metoda apelantă trebuie să le declare cu throws şi ele vor fi propagate mai departe.

- metoda este apelată din afara unui bloc try şi atunci toate tipurile de excepţii ce pot fi aruncate trebuie să apară în declaraţia throw a metodei apelante. Notă: Excepţiile de tip Error, RuntimeException şi a subclaselor sale nu trebuie să fie declarate cu throws.

Următoarea metodă returnează un caracter citit de la tastatură: public char citesteCaracter() throws IOException{ return (char)System.in.read(); }

Lansarea excepţiilor

În timpul proiectării programului sau în timpul depanării, dacă se consideră necesar ca o metodă să lanseze o excepţie, se execută următorii paşi: - căutarea clasei de excepţii corespunzătoare; - dacă metoda nu tratează excepţia apărută, se declară că apelul metodei va lansa o excepţie (cu

ajutorul lui throws şi clasa de exceptie găsită la pasul anterior); - crearea unui obiect al acelei clase; - lansarea obiectului creat imediat după punctul în care se verifică excepţia, folosind

instrucţiunea throw; Exemplu. Metoda următoare citeşte caracter cu caracter o linie de la tastatură pe care o returnează metodei apelante care trebuie să trateze excepţia de tip IOException sau s-o arunce mai departe metodei apelante:

60

import java.io.*; public class Linie{ StringBuffer sb=new StringBuffer(); void readLine() throws IOException{ char c; while(true){ c=(char)System.in.read(); if (c==13) throw new IOException("Caracterul citit este CR"); sb.append(c); } } public static void main(String[] args){ Linie f=new Linie(); try{ f.readLine(); }catch(IOException e){System.out.println(e.getMessage());} System.out.println(f.sb.toString()); } } Procesul de reportare a unei excepţiei în sus continuă până când excepţia va fi interceptată de o metodă activă care se angajează să o rezolve sau până când ajunge în vârful stivei şi aplicaţia se termină. Următorul exemplu arată cum se poate citi conţinutul unui fişier şi să detectăm sfârşitul fişierului aruncându-se o excepţie de tip EOFException: String readData(BufferedReader in){ StringBuffer sb=new StringBuffer(); String s; try{ while(true){ s=in.readLine(); if (s==null){ in.close(); throw new EOFException("Am ajuns la sfarsitul fisierului"); } sb.append(s); } }catch(IOException ioe){ System.out.println("Exceptie de citire: "+ioe.getMessage()); } finally{ return sb.toString(); } }

Observaţie: Cum EOFException este o subclasă a lui IOException, metoda readData() tratează excepţia apărută, deci ea nu va fi aruncată metodei apelante. Important: lansând o excepţie, metoda nu o returnează apelului. Se pot crea clase de excepţii noi. Trebuie să avem o clasă care extinde clasa Exception sau un alt tip apropiat ca semantică cu excepţia noastră, în care să redefinim constructorii. De exemplu, ne definim o clasă excepţie numită FileFormatException: public class FileFormatException extends IOException{ public FileFormatException(){} public FileFormatException(String mesaj){ super(mesaj); } } Atunci, metoda readData() de mai sus ar putea fi rescrisă astfel:

61

String readData(BufferedReader in){ StringBuffer sb=new StringBuffer(); String s; try{ while(true){ s=in.readLine(); if (s==null){ in.close(); throw new FileFormatException("Am ajuns la sfarsitul fisierului"); } sb.append(s); } }catch(IOException ioe){ System.out.println("Exceptie de citire: "+ioe.getMessage()); } finally{ return sb.toString(); } }

O excepţie poate fi rearuncată şi dintr-un bloc catch. Atunci putem avea mai multe metode din lanţul de metode active care să trateze excepţia, poate fiecare din acestea indeplinindu-si partea sa din restaurarea stării programului astfel încât acesta din urmă să-şi poată continua execuţia după ce excepţia a fost tratată.

62

10. COLECTII STRUCTURI DE DATE DINAMICE Presupunem că avem nevoie să utilizăm grupuri de obiecte care au unele proprietăţi comune, cum ar fi personalul unei întreprinderi, conturile bancare ale clienţilor unei bănci sau comenzile făcute în ultima perioadă. Numărul componentelor din grup nu este cunoscut de obicei cunoscut de la început şi poate varia după necesităţile aplicaţiei. Utilizarea unui astfel de grup este necesară din următoarele motive: - verificarea că un anumit obiect există în grup, - adăugarea unui nou obiect la grup, - eliminarea unui obiect existent din grup. Deseori trebuie să executăm operaţii care privesc întregul grup, cum ar fi: - conţinutul obiectelor conţinute, - verificarea dacă grupul este vid sau conţine cel puţin un obiect, - adăugarea la grup sau eliminarea din grup a unui grup de obiecte, - eliminarea tuturor obiectelor din grup. Astfel de grupuri de obiecte se numesc colecţii şi pentru a le putea elabora, trebuie să proiectăm algoritmi şi structuri de date corespunzătoare.

TIPOLOGII DE COLECŢII Pentru a găsi soluţii standard la problema colecţiilor este mai întâi necesar să identificăm tipurile de colecţii după care modul în care se doreşte elaborarea colecţiei. Pentru fiecare tip de colecţie, pot fi oferite servicii care satisfac exigenţele programatorilor. Diferenţele dintre colecţii se datorează fie din exigenţele ce privesc navigarea într-o colecţie, fie din natura sa: colecţia de obiecte sau relaţii de tipuri funcţionale între două colecţii, care formează întodeauna o colecţie. De exemplu, programatorul poate fi interesat de o colecţie în care fiecare componentă se găseşte într-o poziţie bine stabilită în raport cu alte componente sau colecţia nu impune nici o ordine poziţională, dar atunci când este navigată se pot întâlni duplicate care pot crea confuzie. În primul caz avem tipul listă (List) de colecţie, în schimb, în al doilea caz, avem tipul mulţime (Set). În schimb, nu este unica posibilitate de a găsi un element fie prin navigarea componentelor sau prin identificarea lui prin intermediul un număr de ordine. În anumite aplicaţii poate fi util să găsim o componentă prin intermediul proprietăţilor ei, sintetizate de o dată cheie. Este cazul relaţiilor care sunt colecţii de legături între o cheie şi componenta asociată (tipul Map). În alte situaţii, mult mai complexe, relaţiile dintre componentele unei colecţii nu mai sunt de ordine totală, ca în cazul listelor, ci de oridne parţială. Este cazul arborilor şi a grafurilor care necesită modalităţi sofisticate de abordare. Error! Reference source not found. reaminteşte principalele structuri de date, cu avantajele şi dezavantajele lor de utilizare. Structura de date Avantaje Dezavantaje

Array Inserare rapidă, acces rapid dacă indexul este cunoscut

Căutare lentă, eliminare lentă, capacitate limitată

Array ordonat Căutare mai rapidă decât într-un array neordonat Căutare lentă, eliminare lentă, capacitate limitată

Listă înlănţuită Inserare şi eliminare rapide Căutare lentă Stivă Acces “last-in, first-out” Acces lent la componentele care nu sunt în

vârf Coadă Acces “first-in, first-out” Acces lent la componentele care nu sunt în

vârf Arbore binar Căutare, inserare şi eliminare rapidă (dacă

arborele rămâne balansat) Algoritm de eliminare complex pentru ca arborele să rămână balansat

Arbore binar de Căutare, inserare şi eliminare rapidă. Arborele Este complex.

63

Structura de date Avantaje Dezavantaje căutare este întotdeauna balansat. Poate fi utilizat pentru

file systems

Arbore rosu-negru Căutare, inserare şi eliminare rapidă. Arborele este întotdeauna balansat.

Este complex.

Tabel hash Acces foarte rapid dacă se cunoaşte cheia. Inserare rapidă.

Eliminare lentă, acces lent dacă cheia nu este cunoscută, utilizează ineficient memoria.

Heap Inserare şi eliminare rapide Acces rapid la componente mai mari. Acces lent la alte componente.

Graf Modelează situaţii frecvente în lumea reală Orice algoritm este complex şi lent

10.1.1 Structura de date listă

O listă este o secvenţa finită de zero sau mai multe elemente. Dacă toate elementele sunt de acelaşi tip T, spunem că lista este de tip T. Notatie. De obicei, o lista este este scrisă specificandu-se elementele sale ai: (a1, a2, …, an) Exemple: Lista numerelor prime 10 numere prime naturale: (2,3,5, 7,11, 13, 17, 19, 23, 29) O linie de text este o lista de caractere. Un document este o lista de linii de text. Lungimea unei liste este numărul de apariţii a elementelor listei. Daca numarul este zero, atunci lista este vidă. Notatie () sau ε. Daca o lista este nevidă, atunci primul element se numeste capul listei, iar restul listei formează coada listei. Daca L=(a1, a2, …, an) este o listă, atunci pentru orice i şi j a.i. 1≤i≤j≤n, (ai, ai+1, …, aj) este o sublista a lui L. Exp. Prefixul unei liste este orice sublista care incepe la inceputul unei liste. Sufixul unei liste este o sublista care se termină la sfarşitul listei. Exp. fiecare element din listă are asociată o poziţie. Dacă L=(a1, a2, …, an) şi n≥1 atunci a1 este primul element, a2 al doilea, samd, an este ultimul element in lista. De asemenea, putem spune că aI apare la poziţia i in lista. Asadar, numarul de poziţii ale unei liste formeaza lungimea listei. Operaţii asupra listelor

1. Inserarea unui element x într-o listă L. In principiu x ar putea să apară oriunde in listă şi nu contează dacă x apare de mai multe ori. Ca un caz special, putem insera pe x la inceputul listei şi atunci x va fi capul noii liste, iar lista initiala va deveni coada, L=(x, a1, a2, …, an).

2. Stergerea unui element din listă. Se va sterge prima apariţie a elementului x în listă. Daca x nu apare in lista, atunci operatia de stergere nu are nici un efect.

3. Stergerea tuturor elementelor din lista. 4. Putem cauta un element intr-o lista L. Aceasta operatie este logică, returnand true daca x se

gaseste in lista. 5. prim(L) intoarce primul element al listei şi ultim(L) intoarce ultimul element al listei .

Ambele genereaza o eroare daca lista este vida. 6. elementAt(i, L) returneaza elementul de pe pozitia i a listei L. Avem eroare daca L este de

lungime mai mica decat i. 7. lungime(L) returneaza lungimea listei L. 8. esteVida(L) returnează true daca L nu contine nici un element. 9. concateneaza(L1, L2) două liste L1 şi L2.

Implementarea listelor Modul cel mai uşor de a implementa o lista este să folosim o lista de noduri legate între ele. Fiecare nod contine doua campuri: – primul contine un element al listei, – al doilea contine o referinta la urmatorul nod al listei. Implementarea in Java a unui nod al listei este: public class Nod{

64

private Object element; private Nod next; public Nod(Object o){ element=o; } public Nod(Object o, Nod n){ element=o; next=n; } }

Atunci, pentru fiecare element al listei avem cate un nod; elementul ai apare în campul element al nodului i. Variabila next din nodul i contine o referintă la nodul (i+1), pt i=1, n-1. Variabila next al ultimul nod este null, indicând sfarşitul listei. De exemplu: lista L=(a1, a2, …, an) este reprezentata ca o listă linked-itata astfel: Implementarea în Java a operaţiilor asupra listelor linked-itate este realizată în următorul program: public class Lista{ private Nod cap; private Lista coada; private int nr; public boolean esteVida(){ return lungime()<=0; } public int lungime(){ return nr; } //adauga un element la sfarsitul listei public void adaugaElement(Object o){ Nod n=new Nod(o); if (esteVida()){cap=n;nr++;} else insereazaElement(o, nr); } //inserarea unui element la o anumita pozitie public void insereazaElement(Object o, int index){ if (esteVida() && index==0){adaugaElement(o); return;} if (verificaPozitie(index)){ Nod n=new Nod(o); if(index==0){ n.next=cap; cap=n; } else { Nod curent=nodLa(index-1); n.next=curent.next; curent.next=n; } nr++; } } //verificarea pozitiei unui element private boolean verificaPozitie(int index){ if (index>lungime() || index<0) return false; return true;

a1 a2 an …

65

} //returneaza nodul de la o anumita pozitie public Nod nodLa(int index){ if (verificaPozitie(index)){ if (index==0) return cap; Nod n=cap; for(int i=0; i<index;i++) n=n.next; return n; } return null; } //returneaza elementul de la o anumita pozitie public Object elementulDeLa(int index){ Nod n; if ((n=nodLa(index))!=null) return n.element; return null; } //cauta un element in lista public int cauta(Object o){ Nod n=cap; for (int i=0;i<nr;i++){ if (n.element.equals(o)) return i; else n=n.next; } return -1; } //returneaza primul element din lista public Object prim(){ return cap.element; } //returneaza ultimul element din lista public Object ultim(){ return nodLa(nr-1).element; } //sterge un element din lista daca acesta exista public boolean stergeElement(Object o){ int poz=cauta(o); if(poz==-1) return -1; Nod curent=nodLa(poz); if(poz==0) cap=curent.next; else{ Nod anterior=nodLa(poz-1); anterior.next=curent.next; } curent=null; nr--; return true; } //sterge toate elementele listei public void stergeToateElementele(){ if (!esteVida()){ cap=null; coada=null; nr=0; }

66

} //afiseaza lista sub forma (o1, o2, ..., on) public String toString(){ StringBuffer sb=new StringBuffer(); sb.append("["); IteratorLista it=new IteratorLista(cap); while (it.hasNext()){ sb.append(it.next().toString()); if (it.hasNext()) sb.append(", "); } sb.append("]"); return sb.toString(); } //converteste lista la un array public Object[] getListaArray{ Object[] o=new Object[lungime()]; int poz=0; IteratorLista it=new IteratorLista(cap); while(it.hasNext()){ o[poz]=it.next(); poz++; } return o; } //concateneaza lista curenta cu o alta lista public Lista concateneaza(Lista l){ Lista rez=this; IteratorLista it=new IteratorLista(new Nod(l.prim())); while (it.hasNext()) rez.adaugaElement(it.next()); return rez; } }//clasa Lista public class IteratorLista{ private Nod curent; public IteratorLista(Nod c){ curent=c; } public boolean hasNext(){ return curent!=null; } public Object next(){ Object ret=curent.element; curent=curent.next; return ret; } } public class TestLista{ public static void main(String[] args){ Lista l=new Lista(); Lista p=new Lista(); Student[] s=new Student[3]; s[0]=new Student(“Ionescu”, “Ion”, “Informatica”); s[1]=new Student(“Popescu”, “Mircea”, “Matematica-Informatica”); s[2]=new Student(“Marinescu”, "Ciorica", “Informatoca”);

67

l.adaugaElement(s[0]); l.adaugaElement(s[1]); System.out.println(l); l.stergeElement(s[0]); System.out.println("sterge= "+l); l.insereazaElement(s[2],0); System.out.println(l); System.out.println("primul element= "+l.prim()); System.out.println("ultimul element= "+l.ultim()); if(l.cauta(s[0])) System.out.println("elementul se gaseste in lista"); else System.out.println("elementul nu se gaseste in lista"); p.adaugaElement(s[1]); Lista rez=l.concateneaza(p); System.out.println(rez); Object o=rez.elementulDeLa(1); if (o!=null) { ((Student)o).setMedie(9.35); System.out.println(o); } System.out.println(rez); l.stergeToateElementele(); System.out.println(l); } }

10.1.2 Structura de date stivă

O stivă este un model de date bazat pe modelul listă în care toate operaţiile sunt realizate la un capăt al listei, care se numeşte vârful stivei. Stiva este o structură de date LIFO adică “last-in first-out (ultimul intrat, primul ieşit)”. Modelul abstract al unei stivei este acelaşi cu cel al unei liste – adică o secvenţă de elemente a1, a2,…, an de un anumit tip. Ceea ce deosebeşte o stivă de o listă este setul de operaţii permise aupra unei stive. Operaţii asupra unei stive Inserarea unui element x într-o stivă S, push(x, S), determină punerea elementului x în vârful stivei S. Dacă stiva are vârful capătul stâng al stivei, operaţia push(x) aplicată listei (a1, a2,…, an) generează lista (x, a1, a2,…, an). Stergerea elementului din vârful stivei este realizată de metoda pop(S). Aceasta va sterge elementul din vârful stivei şi va fi returnat de către metodă. Dacă stiva este vidă, metoda va genera o eroare. Stergerea tuturor elementelor din stivă va fi realizată de metoda stergeToateElementele(S). Metoda lungime(S) va returna lungimea stivei S, iar metoda esteVida(S) returnează true dacă S nu contine nici un element. Implementarea stivelor Pentru a implementa static o stivă vom folosi un array de 10 obiecte: public class StivaArray{ private Object[] lista; private Object varf; private static int nr=10;

68

private int poz; public StivaArray(){ lista=new Object[nr]; } public void push(Object x){ if (esteVida()) lista[0]=x; else { System.arraycopy(lista, 0, lista, 1, poz); lista[0]=x; } varf=lista[0]; poz++; } public Object pop() throws ListaException{ if (esteVida()) throw new ListaException("Stiva este vida"); Object o=varf; System.arraycopy(lista, 1, lista, 0, poz-1); poz--; return o; } public void stergeToateElementele(){ for (int i=0; i<poz; i++) lista[i]=null; varf=null; poz=0; } //verifica daca stiva este vida public boolean esteVida(){ return lungime()<=0; } //returneaza lungimea stivei public int lungime(){ return poz; } public String toString(){ String s="("; if(poz!=0){ for (int i=0; i<poz-1; i++) s+=lista[i]+", "; s+=lista[poz-1]+")"; } else s+=")"; return s; } } In continuare, vom implementa dinamic o stivă cu ajutorul listelor simplu înlănţuite. public class Stiva{ private Lista l; private Object varf; private int nr; public Stiva(){ l=new Lista(); } public void push(Object x){ if (esteVida()) l.adaugaElement(x); else l.insereazaElement(x, 0);

69

try{ varf=l.prim(); }catch(ListaException e){} nr++; } public Object pop() throws ListaException{ if (esteVida()) throw new ListaException("Stiva este vida"); Object o=varf; try{ varf=l.elementulDeLa(1); }catch(ListaException le){varf=null;} l.stergeElement(l.elementulDeLa(0)); nr--; return o; } public void stergeToateElementele(){ l.stergeToateElementele(); varf=null; nr=0; } //verifica daca stiva este vida public boolean esteVida(){ return lungime()<=0; } //returneaza lungimea stivei public int lungime(){ return nr; } public String toString(){ return l.toString(); } }

10.1.3 Structura de date coadă

O coadă este un model de date bazat pe modelul listă în care elementele sunt inserate pe la un capăt al listei, numit sfârşit, şi şterse pe la celălalt capăt al listei numit început. Mai precis, o coadă este o structură de elemente în care putem insera şi sterge elemente după politica FIFO: first-in, first-out. Dacă coada începe din capătul stâng al listei, operaţia extrage() aplicată listei (a1, a2,…, an) generează lista (a2,…, an), iar operaţia introduce(x) generează lista (a1, a2,…, an, x). Operaţii asupra unei cozi

- introduce(C, x) adaugă elementul x la sfârşitul cozii. - extrage(C) extrage elementul de la începutul cozii. Dacă coada este vidă, atunci operaţia va

genera o eroare. - stergerea tuturor elementelor din coadă, stergeToateElementele(C). - lungime(C) returneaza lungimea coadă C. - esteVida(C) returnează true dacă C nu contine nici un element

Implementarea unei cozi Vom implementa operaţiilor ce pot fi executate asupra unei cozi cu ajutorul unei liste simplu înlănţuită. public class Coada{ private Lista l; private Object inceput, sfarsit;

70

private int nr; public Coada(){ l=new Lista(); } public void introduce(Object x){ l.adaugaElement(x); nr++; try{ if (nr==1) inceput=l.prim(); sfarsit=l.ultim(); }catch(ListaException e){} } public Object extrage() throws ListaException{ if (esteVida()) throw new ListaException("Stiva este vida"); Object o=inceput; inceput=l.elementulDeLa(1); l.stergeElement(l.elementulDeLa(0)); nr--; return o; } public void stergeToateElementele(){ l.stergeToateElementele(); inceput=null; sfarsit=null; nr=0; } //verifica daca stiva este vida public boolean esteVida(){ return lungime()<=0; } //returneaza lungimea stivei public int lungime(){ return nr; } public String toString(){ return l.toString(); } } public class TestStivaSiCoada{ public static void main(String[] args) throws ListaException{ Student[] s=new Student[3]; s[0]=new Student(“Ionescu”, “Ion”, “Informatica”); s[1]=new Student(“Popescu”, “Mircea”, “Matematica-Informatica”); s[2]=new Student(“Marinescu”, "Viorica", “Informatica”); Stiva st=new Stiva(); st.push(s[0]); st.push(s[1]); System.out.println(st); try{ Student o=(Student)st.pop(); System.out.println("scoate= "+o); st.push(s[2]); System.out.println(st); st.stergeToateElementele();

71

System.out.println(st); }catch(ListaException e){System.out.println(e.getMessage());} Coada c=new Coada(); c.introduce(s[0]); c.introduce(s[1]); System.out.println(c); try{ Student o=(Student)c.extrage(); System.out.println("scoate= "+o); c.introduce(s[2]); System.out.println(c); c.stergeToateElementele(); System.out.println(c); }catch(ListaException e){System.out.println(e.getMessage());} } }

10.1.4 Structura de date arbore

Un arbore este format dintr-o mulţime de noduri legate prin muchii ce îndeplinesc următoarele condiţii: - conţine un nod special, numit nod rădăcină. De aceea, un arbore A se poate defini ca fiind

format dintr-un nod rădăcină de care este legată o mulţime finită de arbori. Aceştia sunt numiţi subarborii lui A, datorită relaţiei de “subordonare” faţă de rădăcină.

- orice muchie leagă două noduri distincte. O muchie este indicată prin perechea de noduri pe care le leagă.

- orice nod c diferit de rădăcină este legat de un alt nod p al arborelui printr-o muchie. In acest caz, nodul c este părintele lui p, iar p este un fiu al lui c. Aşadar, orice nod, cu excepţia nodului rădăcină, are un singur nod părinte şi poate avea mai mulţi fii. In cazul în care un nod nu are noduri fii, se numeşte nod frunză (sau terminal).

- un arbore este conectat, în sensul că accesul de la rădăcina unui (sub)arbore nevid la oricare al nod presupune parcurgerea unei căi formate din a muchii (a≥0) pe care se găsesc n noduri (n=a+1). Valoarea n reprezintă nivelul pe care se găseşte nodul faţă de rădăcină, al cărei nivel este, prin convenţie, 1.

Aşadar, o cale într-un arbore este o succesiune de muchii adiacente două cate două, în sensul că orice două muchii succesive din cale au un nod comun. In plus, putem defini înălţimea unui arbore ca fiind maximul dintre nivelurile nodurilor frunză. De exemplu, putem caracteriza următorul arbore astfel:

- rădăcina arborelui este n1, - are trei subarbori, fiecare cu rădăcina n2, n3 şi n4, - nodul n3 este părinte pentru nodurile n5 şi n6 şi fiu pentru rădăcină, - nodurile n2, n5, n7 şi n4 sunt noduri frunză, - (n1, n3),(n3, n6) este calea de la rădăcină la nodul n6, - arborele este de nivel 4,

n1

n2 n3 n4

n5 n6

n7

72

- adăncimea arborelui este egală cu 4. In continuare, vom lucra numai cu arbori binari, adică cu arbori ai căror noduri au cel mult doi fii (direcţi). In acest caz, cei doi potenţiali subarbori ai unui arbore nevid se numesc subarbore stâng şi subarbore drept. Similar, cei doi potenţiali fii ai unui nod al unui arbore binar se numesc fiu stâng, respectiv fiu drept. Implementarea in Java a unui nod al unui arbore binar este dată în continuare: public class Nod{ private int info; private Nod stang, drept; public Nod(int inf){ info=inf; } public Nod(int inf, Nod l, Nod r){ info=inf; stang=l; drept=r; } public void setInformatie(int info){ this.info=info; } public int getInformatie(){ return info; } public void setNodStang(Nod s){ stang=s; } public Nod getNodStang(){ return stang; } public void setNodDrept(Nod s){ drept=s; } public Nod getNodDrept(){ return drept; } }

Operaţii asupra aborilor binari Asupra unui arbore binar putem executa următoarele operaţii: 1. modificarea informaţiei memorată în rădăcina arborelui; 2. inserarea unui element în arbore; 3. căutarea unui element în arbore. Operaţia va începe cu rădăcina arborelui şi va returna valoarea

false dacă elementul nu a fost găsit. 4. stergerea unui element din arbore cu returnarea unei valori logice care ia valoarea true dacă

elementul există în arbore. 5. ştergerea întregului arbore. 6. afişarea arborelui. După cum ştim, informaţia din nodurile unui arbore va fi afişată în funcţie de

ordinea în care arborele binar a fost parcurs, începând cu rădăcina arborelui: - preordine, adică vizitarea nodului curent si apoi a subarborelui stâng, respectiv drept al

acestuia, - inordine presupune parcurgerea subarborelui stâng al nodului curent, apoi a acestuia din urmă

şi pe urmă subarborele drept, - postordine, adică nodul curent este vizitat după ce au fost parcurşi cei doi subarbori.

73

In continuare vom exemplifica implementarea operaţiilor de inserare, cautare şi ştergere a unui element in/dintr-un arbore binar ce îndeplineşte următoarea proprietate: informaţiile nodurilor sunt ordonate astfel încăt informaţia unui nod oarecare este “mai mare” decât informaţia oricărui nod al subarborelui stâng al său şi este “mai mică” decât informaţia oricărui nod al subarborelui drept al său. Ordonarea este cea obişnuită dacă informaţiile sunt numerice sau este de tip lexicografică dacă avem de-a face cu caractere sau şiruri de caractere.

Inserarea unui element într-un arbore binar de căutare

Următorul algoritm recursiv descrie paşii necesari pentru ca un element x să fie inserat în arbore A:

Baza. Dacă A este vid, atunci se crează un nod ce conţine elementul x şi acesta va fi rădăcina arborelui. Altfel, dacă un nod conţine o informaţie egală cu x, atunci x este deja în arbore şi operaţia se termină.

Pas recursiv. Dacă A nu este vid şi x nu se găseşte ca înformaţie a unui nod oarecare al arborelui, atunci se inserează x în subarborele stâng al rădăcinii dacă x este mai mic decât informaţia rădăcinii sau se inserează în subarborele drept al rădăcinii dacă x este mai mare decât informaţia conţinută în rădăcină. Dacă unul din subarbori nu există şi ne aflăm pe un nivel nou al arborelui, atunci înălţimea arborelui este incrementată cu o unitate. Pentru aceasta, avem nevoie să ştim nodul părinte al nodului curent.

Metoda următoare implementează în Java acest algoritm: private void insereaza(int element, Nod n, Nod p, boolean nou){ if (esteVid()){ rad=new Nod(element); inaltime=1; nou=false; } else { if(n.getInformatie()==element) return; else if(element<n.getInformatie()) { Nod n1=n.getNodStang(); if (n1!=null) insereaza(element, n1, n, false); else { n.setNodStang(new Nod(element, null, null)); if (p==null || p.getNodDrept()==null) nou=true; } } else{ Nod n1=n.getNodDrept(); if (n1!=null) insereaza(element, n1, n, false); else { n.setNodDrept(new Nod(element, null, null)); if (p==null || p.getNodStang()==null) nou=true; } } if (nou) inaltime++; } }

Căutarea unui element într-un arbore binar de căutare

Următorul algoritm recursiv descrie paşii necesari căutării unui element x în arborele A, începând bineînţeles cu rădăcina acestuia:

Baza. Dacă A este vid, adică nodul curent (care iniţial va fi rădăcina) analizat este null atunci x nu se găseşte în arbore.

74

Pas recursiv. Dacă nu suntem în condiţiile bazei, fie y informaţia nodului curent. Dacă x<y, atunci căutăm x numai în subarborele stâng al nodului curent. Altfel, îl căutăm în subarborele drept.

Metoda următoare implementează în Java acest algoritm: private boolean cauta(int element, Nod n){ if (n==null) return false; else if (n.getInformatie()==element) return true; else if(element<n.getInformatie()) return cauta(element, n.getNodStang()); else return cauta(element, n.getNodDrept()); }

Stergerea unui element într-un arbore binar de căutare

Următorul algoritm descrie paşii necesari ştergerii unui element x în arborele A:

1. Dacă A nu contine nici un nod ce conţine x, atunci algoritmul se termină cu valoarea false.

2. Altfel, ne aflăm în unul din următoarele cazuri:

- dacă nodul curent este nod frunză, atunci acesta se şterge şi referinţa nodului părinte la nodul respectiv este setată la null. De aceea, trebuie să ştim ce tip de fiu era pentru nodul părinte: stâng sau drept.

- dacă nodul curent are un singur nod fiu, atunci primul este înlocuit cu al doilea nod.

- dacă nodul curent este un nod interior, atunci se caută nodul y ce conţine cea mai mică informaţie din subarborele drept al nodului curent, informaţia mai mare decât x. Apoi nodul y se şterge din arbore, după ce informaţia conţinută de el este memorată în nodul curent.

Metoda ce determină nodul cu informaţia cea mai mică, dar mai mare decât x este: int stergeMinim(Nod n, Nod p, String poz){ int min; if(n.getNodStang()==null){ min=n.getInformatie(); n=n.getNodDrept(); if (poz=="s") p.setNodStang(n); else p.setNodDrept(n); return min; } else return stergeMinim(n.getNodStang(), n, "s"); } iar metoda următoare implementează întregul algoritm descris mai sus: private boolean sterge(int element, Nod n, Nod p, String poz){ if(n!=null){ if(element<n.getInformatie()) return sterge(element, n.getNodStang(), n, "s"); else if(element>n.getInformatie()) return sterge(element,n.getNodDrept(),n,"d"); else //nodul este un nod frunza if(n.getNodStang()==null && n.getNodDrept()==null) { if (poz=="s") p.setNodStang(null); else p.setNodDrept(null); if (p.getNodStang()==null && p.getNodDrept()==null) inaltime--; } else if(n.getNodStang()==null) n=n.getNodDrept(); else if(n.getNodDrept()==null) n=n.getNodStang(); else //aici ambele noduri fii nu sunt null

75

n.setInformatie(stergeMinim(n.getNodDrept(),n, "d")); return true; } return false; }

Afişarea informaţiilor unui arbore binar de căutare

Următoarele metode recursive arată diferite modalităţi de a afişa informaţiile unui arbore binar de căutare prin parcurgerea acestuia din urmă în preordine, inordine sau postordine. public void afiseaza(String forma){ if (forma.equals("preordine")) preordine(rad); else if (forma.equals("inordine")) inordine(rad); else if (forma.equals("postordine")) postordine(rad); else afiseaza(rad, 0); } private void preordine(Nod n){ if (n==null) return; System.out.println(n.getInformatie()); preordine(n.getNodStang()); preordine(n.getNodDrept()); } private void inordine(Nod n){ if (n==null) return; inordine(n.getNodStang()); System.out.println(n.getInformatie()); inordine(n.getNodDrept()); } private void postordine(Nod n){ if (n==null) return; postordine(n.getNodStang()); postordine(n.getNodDrept()); System.out.println(n.getInformatie()); } }

Celelalte operaţii nu necesită explicaţii, iar metodele asociate furnizează interfaţa clasei Arbore: public class Arbore{ private Nod rad; private int inaltime; public Arbore(){} public Arbore(int inf, Nod st, Nod dr){ rad=new Nod(inf, st, dr); inaltime=1; } public void modificaInformatie(int info){ rad.setInformatie(info); } public boolean esteVid(){ return rad==null; } public void stergeArbore(){ rad=null; } public Nod getRadacina(){ return rad;

76

} public int getInaltime(){ return inaltime; } public void insereaza(int element){ insereaza(element, rad, null, false); } public boolean cauta(int element){ return cauta(element, rad); } public boolean sterge(int element){ return sterge(element, rad, null, null); } public void afiseaza(String forma){ if (forma.equals("preordine")) preordine(rad); else if (forma.equals("inordine")) inordine(rad); else if (forma.equals("postordine")) postordine(rad); else afiseaza(rad, 0); } }

COLECŢII ÎN JAVA Java furnizează o structură de clase şi interfeţe numită Collections Framework specializată în memorarea şi elaborarea colecţiilor de obiecte. Collections Framework furnizează un API convenabil oricăror tipuri de date abstracte utilizate frecvent în informatocă: relaţii, mulţimi, liste, arbori, array, etc. Datorită orientării spre obiecte, clasele din Collections Framework încapsulează atât structura de date cât şi algoritmii asociaţi acestor abstractizări. 10.1.5 Teoria de bază a colecţiilor

La baza teoriei colecţiilor stă conceptul matematic de mulţime, chiar dacă colecţia a fost definită având o semnificaţie mai generală, de grup de elemente, fără nici o legătură între ele. În schimb, în Collections Framework o mulţime este o colecţie de elemente unice, adică o colecţie fără elemente duplicat. Exemple de mulţimi sunt numeroase. Iată unele: - mulţimea literelor mari de la 'A' la 'Z' - mulţimea numerelor nenegative: {0, 1, 2 ...} - mulţimea cuvintelor cheie din Java: {'import', 'class', 'public', 'protected'...} - mulţimea angajaţilor unei întreprinderi - mulţimea obiectelor Component dintr-un Container - mulţimea vidă {} Un caz particular de mulţime este relaţia funcţională sau maparea (în engleză, mapping). Este o mulţime de legături în care fiecare legătură reprezintă o conexiune de la un element la un altul. Exemple de mapări sunt: - maparea adreselor IP la nume dintr-un domeniu de nume DNS, - dicţionar (maparea cuvintelor la înţelesurile lor), - conversia unui sistem de numeraţie la un alt sistem de numeraţie (sistemul arabic la cel roman).

77

O mapare poate servi la navigarea de la un element al unei legături la un alt element. Pentru mapare, biblioteca Java conţine clase ce furnizează rapid obiectele căutate indicând numai cheia asociată.

10.1.6 Collections Framework

Collections Framework este furnizat în pachetul java.util şi conţine interfeţe care se găsesc în relaţii de generalizare/specializare ca cea prezentată în figura următoare. Unele interfeţe sunt implementate de clase Java ce aparţin aceluiaşi pachet java.util. Aceste clase reprezintă colecţii de obiecte sau relaţii între obiecte şi sunt prezentate în tabelul următor.

Interfaţa Implementare Precedenţi istorici

Set HashSet

SortedSet TreeSet

List ArrayList LinkedList Vector Stack

Map HashMap Hashtable Properties

SortedMap TreeMap

Cheie1 Valoare1 legatura1

Cheie2 Valoare2 legatura2

Cheie3 Valoare3 legatura3

Cheie4 Valoare4 legatura4

Cheie5 Valoare5 legatura5

Collection

Set List

SortedSet

Map

SortedMap

78

Schema completă a lui Collections Framework este prezentată în figura următoare. Schema prezintă în diferite nuanţe de gri interfeţele, clasele abstracte şi clasele concrete din Collections Framework şi relaţiile dintre ele. În plus, sunt marcate clasele care provin din versiuni mai vechi cum ar fi Vector, Stack, Hashtable şi Properties. Collections Framework mai conţine şi alte interfeţe cum ar fi Iterator, ListIterator, Comparator şi Comparable care permit navigarea colecţiilor şi compararea elementelor dintr-o colecţie.

Interfaţa Collection java.util.Collection este o interfaţă standard ce reprezintă un grup de elemente, numite elementele colecţiei. Biblioteca Java nu conţine o implementare directă a interfeţei Collection. Această interfaţă este utilizată ca numitor comun al altor interfeţe, pentru a permite transferul colecţiilor de la un tip de colecţie la un alt tip şi elaborarea lui cu generalitate maximă. Operaţiile interfeţei Collection sunt prezentate în tabelul următor.

Declaraţie metodă Semnificaţie

Operaţii de bază

boolean add(Object o) Verifică dacă colecţia conţine elementul o. În caz pozitiv, nu mai adaugă elementul o şi întoarce false. În caz negativ, adaugă elementul al colecţie şi returnează true.

boolean remove(Object o) Dacă elementul o se găseşte in colecţie, este eliminat şi meoda returnează true. Altfel, metoda returnează false.

boolean contains(Object o) Returnează true dacă colecţia conţine elementul o

Iterator iterator() Întoarce un obiect de tip Iterator care se poate utiliza pentru navigarea colecţiei.

int size() Întoarce numărul de elemente ale colecţiei

boolean isEmpty() Returnează true dacă colecţia nu are elemente.

Operaţii pe grupuri de obiecte

boolean addAll(Collection c) Adaugă toate elementele colecţiei c. Dacă operaţia are loc cu succes, metoda întoarce true.

boolean removeAll(Collection c) Elimină toate elementele colecţiei c. Dacă operaţia are loc cu succes, metoda întoarce true.

<interface> Collection

<interface> Map

<interface> Iterator

<interface> Set

<interface> ListIterator

<interface> List

<interface> Comparable

<interface> Comparator

<abstract> AbstractCollection

<abstract> AbstractSet

<abstract> AbstractList

<abstract> AbstractMap

TreeMap HashMap

Hashtable

TreeSet HashSet

Vector ArrayList

Stack

<abstract> AbstractSequentialList

LinkedList

produce produce

Legenda Clase din versiunile 1.0, 1.1 Interfeţe fundamentale Clase concrete utile

Hashtable

<interface> Map

HashMap

<interface> SortedSet

<interface> SortedMap

<abstract> Dictionary

Properties

79

Declaraţie metodă Semnificaţie

Boolean retainAll(Collection c) Elimină toate elementele care nu fac parte din colecţia transmisă ca argument.

void clear() Goleşte colecţia

Operaţii cu array

Object[] toArray() Returnează un array ce conţine toate elementele colecţiei.

Object[] toArray( Object []) Returnează un array ce conţine toate elementele colecţiei. Array-ul returnat este de acelşi tip cu array-ul transmis ca parametru.

Iterfaţa Iterator Iterator este o interfaţă implementată de un obiect obţinut prin invocarea metodei iterator() a unui obiect din clasa Collection. Interfaţa conţine trei metode: boolean hasNext()//verifică dacă colecţia mai mai are elemente de parcurs Object next() //returnează următorul element al colecţiei void remove() //poate fi apelată numai o singură pentru fiecare obiect

//obţinut cu metoda next() Iteratorul ne permite să navigăm într-o colecţie pănă la terminarea elementelor conţinute. În timpul navigării putem, de exemplu, să filtrăm o colecţie, adică să eliminăm din colecţie acele elemente care nu îndeplinesc o anumită condiţie. Presupunem că avem o metodă verificaEliminare(Object o) care întoarce true dacă obiectul transmis ca parametru va fi efectiv eliminat. Folosind interfaţa Iterator, filtrul va fi implementat astfel: Collection colectie = ...; Iterator iterator = colectie.iterator(); while (iterator.hasNext()) { Object element = iterator.next(); if (verificaEliminare(element)) { iterator.remove(); } }

Interfaţa Set Collections Framework include o interfaţă Set şi numeroase clase concrete care implementează Set. Interfaţa Set extinde interfaţa Collection şi împiedică crearea de elemente duplicat în colecţie. Nu introduce alte metode diferite de cele din Collection. Verificarea claselor de implementare se bazează pe metoda equals() a obiectului care este adăugat la colecţie. Interfaţa Set este implementată de două clase concrete Java: java.util.HashSet şi java.util.TreeSet. Obiectele din clasa HashSet sunt utilizate pentru memorarea colecţiilor fără duplicate. Obiectele ce vor fi adăugate la un HashSet trebuie să implementeze metoda hashCode() astfel încât obiectele vor fi egal distribuite în mod uniform cu ajutorul codurilor hash. Majoritatea claselor standard redefinesc implementarea predefinită de clasa Object. Clasa TreeSet este utilizată când vrem să lucrăm cu elementele unei colecţii ordonate. Obiectele care se adaugă la un TreeSet trebuie să fie ordonabile, adică să poată fi comparate între ele după o relaţie de ordine totală. Clasa HashSet Clasa implementează interfaţa Set şi este construită intern de un “hash table”. Figura următoare prezintă schematic ideea care stă la baza unei structuri de de date de tip hash table. În centrul structurii se găseşte un array ale cărui elemente conţin indicatori la lista înlănţuită ale cărei noduri indică elemente ale colecţiei. Indexul tabelului variază între 0 şi un k-1. Căutarea într-o astfel de structură începe cu calculul indicelui hash al obiectului care este căutat. Acest cod este un număr întreg calculat după un algoritm particular care implică toate câmpurile obiectului văzute ca nişte combinaţii de biţi. Odată găsit codul, el va fi folosit ca indice al array-ului central. Elementul identificat poate conţine null, şi atunci obiectul căutat nu se găseşte în colecţie şi, adcă este necesar, se poate adăuga, sau este vorba de un indicator la o listă “bucket” în care obiectul va fi căutat

80

secvenţial. Dacă nu este găsit în listă, înseamnă că nu se găseşte în colecţie şi poate fi adăugat la lista de bucket. Într-o tabelă hash nu se poate prevede care va fi ordinea de navigare între elemente, dar această ordine rămâne întotdeauna aceeaşi. Clasa permite utilizarea unui element null în colecţie.

Constructorii clasei HashSet sunt: HashSet() //creaza o colectie HashSet vida HashSet (Collection c) //creaza o colectie de tip HashSet ce contine

//elementele lui c HashSet(int capacitate)//creaza o colectie HashSet de capacitate initiala

//transmisa ca argument HashSet(int capacitate, int incr)//creaza o colectie HashSet de capacitate initiala şi factor de încărcare transmise ca argumente Exemplu. Vom scrie un program care preia cuvinte din linia de comandă şi vizualizează cuvintele duplicat, numărul de cuvinte distincte, având într-o listă cuvintele unice: import java.util.*; public class Duplicate { public static void main(String args[]) { Set s = new HashSet(); for (int i=0; i<args.length; i++) if (!s.add(args[i])) System.out.println("Duplicat: "+args[i]); System.out.println(s.size()+" cuvinte distincte identificate: "+s); } }

Exemplu. Presupunem că vrem să ştim care din cuvintele din linia de comandă au apărut o singură dată şi care au apărut de mai multe ori, dar le afişăm numai o singură dată. Putem utiliza două obiecte din clasa Set. Într-un obiect memorăm cuvintele liniei de comandă şi în al doilea obiect memorăm numai cuvintele duplicat. Cuvintele care apar numai o singură dată se deduc din diferenţa celor două mulţimi. import java.util.*; public class Duplicate1 { public static void main(String args[]) { Set unice = new HashSet(); Set dupl = new HashSet(); for (int i=0; i<args.length; i++) if (!unice.add(args[i])) dupl.add(args[i]); unice.removeAll(dupl); // Diferenta dintre multimi System.out.println("Cuvinte unice: " + unice); System.out.println("Cuvinte duplicat: " + dupl); } }

f i

81

Clasa TreeSet Clasa implementează SortedSet care este o subinterfaţă a lui Set şi garantează că mulţimea este ordonată în ordine crescătoare. Ordonarea este cea naturală între elemente (care trebuie să implementeze Comparable) sau va fi specificată prin intermediul unui obiect comparator (face parte dintr-o interfaţă Comparator) la crearea lui TreeSet. Constructorii clasei TreeSet sunt: TreeSet() TreeSet (Collection c) TreeSet(Comparator comp) TreeSet(SortedSet ss)

Al doilea constructor va fi utilizat în următorul program în care transmit elementele unei colecţii HashSet unui TreeSet: import java.util.*; public class ExempluSet { public static void main(String args[]) { Set set = new HashSet(); set.add("Andreea"); set.add("Maria"); set.add("George"); set.add("Maria"); set.add("Clara"); System.out.println(set); Set sortedSet = new TreeSet(set); System.out.println(sortedSet); } }

Un alt exemplu de utilizare a clasei TreeSet este prezentat în continuare şi ilustrează utilizarea elementelor ce pot fi comparate (Comparable) şi pot fi păstrate ordonate în interiorul unui TreeSet. Fiind şi un Set, un TreeSet poate fi configurat la creare pentru accesarea obiectelor care respectă o anumită relaţie între ele: import java.util.*; class ComparatorArticole implements Comparator { public int compare(Object a, Object b){ Articol articolA = (Articol)a; Articol articolB = (Articol)b; String descrA=articolA.getDescriere(); String descrB=articolB.getDescriere(); return descrA.compareTo(descrB); } } class Articol implements Comparable{ private String descriere; private int numarParte; public Articol(String oDescriere,int unNumarParte){ descriere = oDescriere; numarParte = unNumarParte; } public String getDescriere(){ return descriere; } public String toString(){ return "[descriere=" + descriere + ", numarParte=" + numarParte + "]"; } public boolean equals(Object alt){

82

if (alt instanceof Articol){ Articol altArticol = (Articol)alt; return descriere.equals(altArticol.descriere) &&

numarParte == altArticol.numarParte; } else return false; } public int hashCode() { return 13 * descriere.hashCode() + 17 * numarParte; } public int compareTo(Object alt){ Articol altArticol = (Articol)alt; return numarParte - altArticol.numarParte; } } public class TestTreeSet { public static void main(String[] args) { SortedSet parti = new TreeSet(); parti.add(new Articol("Monitor", 1234)); parti.add(new Articol("Imprimanta", 4562)); parti.add(new Articol("Modem", 9912)); System.out.println(parti); SortedSet ordoneazaDupaDescriere=new TreeSet(new ComparatorArticole()); ordoneazaDupaDescriere.addAll(parti); System.out.println(ordoneazaDupaDescriere); } }

Interfaţa List List este o interfaţă care reprezintă o colecţie ordonată (numită şi secvenţă). Prin intermediul acestei interfeţe, utilizatorul are un control exact al locului în care este inserat fiecare element. Utilizatorul poate accesa elementele prin intermediul iteratorilor şi a indicilor întregi care reprezintă poziţia în listă. În acest mod căutăm mai uşor în lista. Listele acceptă elemente duplicat (adică, elemente e1 şi e2 care verifică e1.equals(e2)) şi acceptă şi elemente null. În afara declaraţiilor de metode din interfaţa Collection, List are următoarele metode:

Declaraţie metodă Semnificaţie

Acces după poziţie Object get(int indice) Întoarce obiectul de la indicele transmis ca parametru boolean add(int indice, Object o) Inserează obiectul o la poziţia indice Object remove(int indice) Elimină obiectul de la poziţia indice void set(int indice, Object o) Înlocuieşte obiectul de la poziţia indice cu obiectul o boolean addAll(int indice, Collection c)

Inserează începând cu poziţia indice elementele colectiei c

Căutarea unui obiect int indexOf(Object o) Returnează poziţia obiectului o din listă. Dacă nu-l

găseşte, returnează -1. int lastIndexOf(Object o) Returnează poziţia ultimei apariţii a obiectului o din

listă. Dacă nu-l găseşte, returnează -1.

Iterare ListIterator listIterator() Returnează un obiect de tip ListIterator al lui List ListIterator listIterator(int Returnează un obiect de tip ListIterator al lui List. Cei

83

Declaraţie metodă Semnificaţie indice) doi iteratori sunt folosiţi de LinkedList.

Operaţie globală List subList(int in, int sf) Crează o sublistă formată din elementele listei între cei

doi indici transmişi ca parametri.

Ca întotdeauna când este vorba de o interfaţă, putem implementa în mod polimorfic algoritmi valizi pentru toate obiectele claselor care implementează interfaţa. De exemplu, pentru a schimba locul între două elemente ale unei liste poate fi utilizată următoarea metodă: private void schimba(List a, int i, int j) { Object t = a.get(i); a.set(i, a.get(j)); a.set(j, t); }

Vom utiliza metoda schimba() pentru a permuta elementele unei liste. Algoritmul polimorfic utilizat parcurge lista şi generează numere aleatoare pentru a identifica locuri noi din care putem lua elementul cu care înlocuim elementul curent: public void permuta(List list, Random rnd) { for (int i=list.size(); i>1; i--) schimba(list, i-1, rnd.nextInt(i)); }

O variabilă de tip List poate conţine referinţe la obiecte ArrayList sau LinkedList care sunt implementări ale interfeţei. Aceşti algoritmi sunt independenţi de clasa utilizată pentru implementarea listei. Interfaţa ListIterator Interfaţa List furnizează un iterator mai bogat: ListIterator, care ne permite să traversăm lista în ambele direcţii, să o modificăm în timpul traversării şi să obţinem poziţia curentă a iteratorului. Interfaţa ListIterator este prezentată în continuare: interface ListIterator extends Iterator { void add(Object elem); // adaugă un element în poziţia curentă Object previous(); //poziţionează cursorul şi returnează elementul //precendent boolean hasPrevious(); // verifica dacă există un element precedent void set(Object elem); // suprascrie elementul curent cu elementul //transmis ca parametru int nextIndex(); // returnează indicele elementului succesiv int previousIndex(); // returnează indicele elementului succesiv }

Spre deosebire de metoda Collection.add(), metoda ListIterator.add() nu returnează o valoarea de tip boolean. Metoda nextIndex() returnează întotdeauna elementul care va fi restituit de un apel succesiv al lui next(), în schimb previousIndex() returnează poziţia elementului întors la un apel al lui previous(). În consecinţă, nextIndex() == previous()+1. Apoi, un alt fapt: la primul apel al lui previous() după o secevnţă de apeluri next() întoarce acelaşi element al ultimului apel al lui next. În mod similar, primul apel al lui next() după o serie de apeluri ale lui previous() returnează acelaşi element al utlimului apel al lui previous() . Exemplu următor este cel al unei metode statice care implementează un algoritm polimorfic care înlocuieşte într-o listă oarecare toate apariţiile unui obiect val cu un obiect nou: public static void replace(List l, Object val, Object nou) { for (ListIterator i = l.listIterator(); i.hasNext(); ) if (val==null ? i.next()==null : val.equals(i.next())) i.set(nou); }

84

In schimb, metoda următoare implementează un algoritm polimorfic care returnează toate apariţiile valorii val specificată ca parametru, cu o secvenţă de valori noi conţinute într-o listă: public static void replace(List l, Object val, List valoriNoi) { for (ListIterator i = l.listIterator(); i.hasNext(); ) { if (val==null ? i.next()==null : val.equals(i.next())) { i.remove(); for (Iterator j = nuoviValori.iterator(); j.hasNext(); ) i.add(j.next()); } } }

Clasa ArrayList ArrayList este o clasă concretă care implementează interfaţa List şi se sprijină pe un array fără limite de creştere. Modelează o listă cu elemente enumerate, deoarece are un acces obişnuit, direct, relativ rapid. La metodele interfeţei List, clasa ArrayList adaugă următoarele metode: ArrayList(Collection)//construieşte un ArrayList dintr-un Collection Object clone() //clonează obiectul ArrayList void ensureCapacity(int) //garantează un număr minim pentru listă void trimToSize() //Elimină spaţiile libere din array

În continuare, prezentăm un program care extrage virtual dintr-un pachet de cărţi – reprezentat ca o listă - n cărţi pentru un jucător şi le furnizează întotdeauna ca o listă: public static List furnizeazăCarte(List pachet, int n) { int dimPachet = pachet.size(); List listaNoua = pachet.subList(dimPachet-n, dimPachet); List cartiJucator = new ArrayList(listaNoua); listaNoua.clear(); return cartiJucator; }

Clasa LinkedList LinkedList este o clasă concretă care furnizează un acces secvenţial optim cu operaţii economice de eliminare şi inserare în interiorul listei. Are un acces aleator relativ lent (dacă accesul aleator este frecvent trebuie să fie utilizat ArrayList). Cu un obiect LinkedList putem implementa cod deoarece garantează o politică FIFO în elaborarea listei. LinkedList adaugă următoarele metode la cele declarate de interfaţa List : void addFirst( Object o) //adaugă o la începutul listei void addLast(Object o) //adaugă o la sfârşitul listei Object getFirst( ) //furnizează primul element din listă Object getLast( ) //furnizează ultimul element al listei Object removeFirst( ) //elimină primul element al listei Object removeLast( ) //elimină ultimul element al listei

Următorul program ilustrează ilustrează trasferul uşor de la o implementare a unei liste al o alta şi modul diferit în care cele două tipologii de liste îşi gestionează elementele: import java.util.*; public class ExempluLista { public static void main(String[] args) { List lista = new ArrayList(); lista.add("Andreea"); lista.add("Eliza"); lista.add("George"); lista.add("Eliza"); lista.add("Clara");

85

System.out.println(lista); System.out.println("2: " + lista.get(2)); System.out.println("0: " + lista.get(0)); LinkedList coada = new LinkedList(); coada.addFirst("Andreea"); coada.addFirst("Eliza"); coada.addFirst("George"); coada.addFirst("Eliza"); coada.addFirst("Clara"); System.out.println(coada); coada.removeLast(); coada.removeLast(); System.out.println(coada); } } Rezultat: [Andreea, Eliza, George, Eliza, Clara] 2: George 0: Andreea [Clara, Eliza, George, Eliza, Andreea] [Clara, Eliza, George]

Cum utilizăm interfaţa Collection 1. Ştim că utilizarea interfeţelor este obligatorie atunci când obiectele client nu trebuie să ştie

provenienţa obiectului (clasa care l-a generat) şi în general orice detalii de implementare care su fost utilizate. În acest mod, pentru un client, dacă două obiecte a şi b implementează aceiaşi interfaţă, se poate utiliza obiectul b în locul obiectului a, fără să necesite nici o modificare din parte clientului. Este de ajuns să utilizeze o variabilă interfaţă în care să memoreze referinţa la unul din cele două obiecte (figura următoare).

În consecinţă, nu contează ce obiect Java este utilizat pentru a reprezenta o colecţie şi dacă în mod succesiv, acest obiect este înlocuit cu un altul care implementează aceeaşi interfaţă. 2. Implementările colecţiilor (ArrayList, LinkedList, HashSet, TreeSet) au toate un constructor cu

un parametru de tip Collection. Acest parametru este un obiect dintr-o clasă care implementează interfaţa Collection şi care va fi utilizat pentru a iniţializa noua colecţie. În acest mod, noua colecţie va fi iniţializată cu elementele colecţiei utilizată ca parametru, dar va avea un comportament diferit.

În continuare, va fi prezentat un scenariu posibil de utilizare a varaibilelor de tip Collection: 1. Declarăm o variabilă colectie de tip Collection (poate fi un List sau un Set): Collection colectie;

2. colectie memorează o referinţă la un obiect care implementează o colecţie, precum un LinkedList, ArrayList, HashSet sau TreeSet: colectie = new HashSet();

3. Se adaugă obiecte la colectie cu metoda add(): colectie.add(o1); colectie.add(o2); . . . 4. Se crează o lista de tip List ce se bazează pe un array, construită pe baza lui colecţie: List lista = new ArrayList(colectie); 3. lista conţine elementele din colecţie memorate în HashSet, schimbând numai punctul de

vedere. Într-un List, elementele unice ale lui HashSet pot fi căutate în ordine şi poate accepta duplicate.

86

Interfaţa Map Map este o interfaţă care păstrează o asociere chei-valoare şi care permite căutări bazate pe chei.

Declaraţie metodă Semnificaţie

Operaţii de bază Object get(Object cheie) Întoarce obiectul cu cheia transmisă ca parametru void put(Object cheie, Object o) Inserează o legătură cheie-valoare Object remove(Object cheie) Elimină legătura indicată de cheie Boolean containsKey(Object cheie) Verifică existenţa cheii cheie în Map Boolean containsValue(Object o) Verifică existenţa valorii o în Map Int size() Returnează numărul de legături Boolean isEmpty() Verifică dacă mulţimea legăturilor este vidă sau nu

Operaţii globale Boolean putAll(Map m) Adaugă legăturile găsite în m Void clear() Goleşte un Map

Vederi ca colecţii Set entrySet() Furnizează Set-ul tuturor legăturilor cheie-valoare Set keySet() Furnizeaza un Set compus din chei Collection values() Furnizează o colecţie a tuturor valorilor din Map

Vederile (views) ale unui Map ne permit să utilizăm interfetele Collection (pentru valori) sau Set (pentru chei) pentru eleborarea întregului Map: for (Iterator i=map.keySet().iterator(); i.hasNext(); ) { Object cheie = i.next(); System.out.println(cheie + ": " + map.get(cheie)); }

Map.Entry este o interfaţă statică internă a lui Map care reprezintă toate legăturile cheie-valoare din Map. Map.Entry are următoarele trei metode: Object getKey() Object getValue() Object setValue(Object val)

Fiecare element din Set-ul de legături returnat de metoda entrySet()este văzut ca un element de tip Map.Entry. De aceea, putem parcurge o astfel de mulţime şi să afişăm fie cheia, fie valoarea fiecărei legături. Acest lucru este realizat de următoarea secvenţă de cod: Map m=…//crearea unui obiect al unei clase ce implementează interfaţa Map for (Iterator i=m.entrySet().iterator(); i.hasNext(); ) { Map.Entry e = (Map.Entry) i.next();

a b

Variabilă de tip Collection

Client Înlocuirea unei implementări a lui Collection cu o alta

a – implementare a lui Collection b- o altă implementare a lui Collection

87

System.out.println(e.getKey() + ": " + e.getValue()); }

Interfaţa Map are două implementări principale: HashMap şi TreeMap şi una moştenită din prima versiune de Java: Hashtable. Clasa HashMap Următorul exemplu verifică numerele aleatoare furnizate de metoda Math.random(). import java.util.*; class Counter { //Obiectele acestei clase vor fi folosite ca numărători int i = 1; public String toString() { return Integer.toString(i); } } public class FreqHashMap { public static void main(String[] args) { HashMap ht = new HashMap(); for(int i = 0; i < 10000; i++) { Integer r = new Integer((int)(Math.random()*20)); //Produce un număr între //0 şi 19 inclusiv if(ht.containsKey(r)) ((Counter)ht.get(r)).i++; else ht.put(r, new Counter()); } System.out.println(ht); } }

Clasa TreeMap Clasa TreeMap furnizează o implementare a interfeţei Map în care colecţia de legături chei-valoare este ordonată după chei, implementând şi interfaţa SortedMap. Implicit, ordonarea este crescătoare (conform interfeţei Comparable), dar criteriul de comparare poate fi specificat prin constructor: TreeMap (Comparator c)

Alţi constructori: TreeMap () TreeMap (Map m) TreeMap (SortedMap m) Exemplu. Următorul program ilustrează diferenţele dintre HashMap şi TreeMap: import java.util.*; public class FreqTreeMap { public static void main(String args[]) { Map map=new HashMap(); Integer unu=new Integer(1); for (int i=0, n=args.length; i<n; i++) { String cheie=args[i]; Integer frecventa=(Integer)map.get(cheie); if (frecventa==null) frecventa = unu; else { int valoare = frecventa.intValue(); frecventa = new Integer(valoare + 1); } map.put(cheie, frecventa); } System.out.println(map.size()+" cuvinte distincte gasite:"); System.out.println(map);

88

Map mapSortat = new TreeMap(map); System.out.println(mapSortat); } }

Clasa Properties Furnizează o colecţie persistentă de legături chei-valori in care cheile şi valorile sunt String-uri. Din acest motiv, valorile se numesc proprietăţi. O legătură cheie-proprietate este adăugată la un Properties cu metoda Object setProperty(String key, String value) şi se poate obţine cu metoda overloaded getProperty(): String getProperty(String cheie) String getProperty(String cheie, String proprietateImplicita)

Persistenţa colecţiei permite memorarea şi încărcarea pe şi de pe suport persistent (sistem de fişiere). În acest sens, clasa Properties furnizează următoarele metode instanţă: public void store(OutputStream out, String header) public void list(PrintStream out) public void list(PrintWriter out) public void load(InputStream in)

De exemplu, memorarea într-un fişier a conţinutului unui obiect Properties se realizează prin următoarea secvenţă de cod: Properties p=new Properties(); p.setProperty("1", "Ioana"); p.setProperty("4", "Maria"); try{ p.store(new FileOutputStream("proprietati.txt"), " Colectie de

proprietati"); }catch(IOException e){System.out.println(e.getMessage());}

Ieşire: # Colectie de proprietati #Tue Jan 04 10:03:53 EET 2005 4=Maria 1=Ioana Colecţia de legături şi proprietăţi memorată într-un obiect Properties poate fi afişată şi pe ecran prin apelarea metodei list, astfel: p.list(System.out);, unde p este variabila referinţă din exemplul anterior. Exemplu. import java.util.*; public class Catalog extends TreeSet{ private static Catalog c; private OperatiiSpecificatiiProdus osp; private Catalog(){osp=new OperatiiSpecificatiiProdus(); } public static Catalog getInstanta(){ if(c==null) c=new Catalog(); return c; } public void add(SpecificatieProdus sp){super.add(sp);} public SpecificatieProdus getProdus(String nume){ Iterator it=iterator(); SpecificatieProdus sp=null; while(it.hasNext()){ sp=(SpecificatieProdus)it.next();

89

if(sp.getDenumire().equals(nume)) return sp; } sp=(SpecificatieProdus)osp.citesteObiect(nume); if (sp!=null) add(sp); return sp; } public void descarca(){ Iterator it=iterator(); while(it.hasNext()) osp.scrieObiect(it.next()); } } public class TestCatalog{ public static void main(String[] args){ Catalog c=Catalog.getInstanta(); c.add(new SpecificatieProdus("Imprimanta", 250)); c.add(new SpecificatieProdus("Calculator", 1300)); SpecificatieProdus sp=c.getProdus("miere"); System.out.println(sp); sp=c.getProdus("Imprimanta"); System.out.println(sp); } } import java.util.*; public class Vanzare extends ArrayList{ private Client c; public static int tva=20; private double total; public Vanzare(Client c){this.c=c;} public Client getClient(){return c;} public void addProdus(ElementVanzare ev){super.add(ev);} public double calculeazaTotal(){ ListIterator li=listIterator(); ElementVanzare ev; while(li.hasNext()) { ev=(ElementVanzare)li.next(); total+=ev.calculeazaCost(); } Calendar cl=Calendar.getInstance(); if(cl.get(Calendar.DAY_OF_WEEK)>=2&&cl.get(Calendar.DAY_OF_WEEK)<=6)total-=5*total/100.0; return total+calculeazaTVA(); } public ArrayList getProduse(){return this; } public double calculeazaTVA(){return total*tva/100.0; } } import java.util.*;import java.text.*; public class Factura{ private static int id=1; private int nrFactura; private Calendar data; private Vanzare v; private Client c; private ArrayList articole; public Factura(Vanzare v){ nrFactura=id++; data=Calendar.getInstance();

90

this.v=v; c=v.getClient(); articole=v.getProduse(); } public String formatare(String sir, int latime) { String temp=sir; for (int i=latime-sir.length(); i>0; --i) temp += " "; return temp; } public String toString(){ String sir="Factura: "+nrFactura+"\r\n"; sir+="Data emiterii: "+ DateFormat.getDateInstance(DateFormat.LONG).format(data.getTime())+"\r\n" sir+="------------------------------------------------------------------------\r\n\r\n"; sir+="Cumparator: "+c.getNume()+" "+c.getPrenume()+"\r\n"; sir+="Adresa: "+c.getAdresa()+"\r\n"; sir+="------------------------------------------------------------------------\r\n\r\n"; sir+=formatare("Denumire", 15)+ formatare("Pret unitar", 15)+formatare("Cantitate", 20)+formatare("Pret/articol", 25)+"\r\n"; Iterator it=articole.iterator(); ElementVanzare el; double t=v.calculeazaTotal(); while(it.hasNext()){ el=(ElementVanzare)it.next(); sir+=formatare(""+el.getProdus().getDenumire(), 15)+formatare(""+el.getProdus().getPret(), 15)+formatare(""+el.getCantitate(), 20)+formatare(""+el.calculeazaCost(), 25)+"\r\n";} sir+="------------------------------------------------------------------------\r\n\r\n"; sir+="TOTAL\t\t"+t+" lei \r\n"; sir+="Din care TVA 20%\t"+v.calculeazaTVA()+" lei \r\n"; sir+="\r\n\r\nTOTAL GENERAL\t"+t+" lei \r\n"; return sir; } public class TestVanzare{ public static void main(String[] args){ Vanzare v=new Vanzare(new Client("Ionescu", "Pop", "aleea stejarilor")); v.addProdus(new ElementVanzare(new SpecificatieProdus("Calculator", 123), 6)); Factura f=new Factura(v); System.out.println(f); }}

91

11. APPLET Browser – program cu urmatoarele functiuni: - vizualizează resursele codificate în HTML. - Suport pentru orice mecanisme de scripting. - Context pentru executarea applet-urilor Java. - Suportă plug-ins. - Cere resurse de la server-ele din reţea. - Utilizează protocolul HTTP. - Identifică resursele utilizând adrese unice - URI (Uniform Resource Identifier) sau URL. Pagini Web Paginile Web sunt resurse Web care au o adresă URL (Uniform Resource Locator). Pagină Web = Document de text cu extensia .html sau .htm al cărui conţinut va fi citit şi interpretat de un browser. Documentul poate fi scris cu word processor oarecare, este de ajuns să fie salvat în format text cu extensia .html sau .htm. HTML = Limbaj de comenzi cu care se scriu paginile Web. Utilizează tag-uri (comenzi între < şi >) care au un înţeles bine precizat pentru browser. Un fişier HTML simplu Un tag poate avea modificatori. Modificatorii sunt atribute, de obicei perechi nume=valoare: <BODY BGCOLOR=“yellow”> <HTML> <HEAD> <TITLE>Arhitectura Internet</TITLE> </HEAD>

<BODY BACKGROUND="" BGCOLOR="#ffffff" TEXT="#000000" LINK="#0000ff" VLINK="#800080" ALINK="#ff0000"> <P>Bine ati venit în site-ul nostru!</P> </BODY> </HTML>

Tag-uri de formatare a textelor Titluri de secţiune: <H1>text</H1>, <H2>text</H2>,. . ., <H6>text</H6> Alinearea textului: <CENTER>text</CENTER> sau ca atribut: <H1 ALIGN=CENTER>text</H1> Trecerea pe linia următoare în paragraful curent: <BR> Text afisat asa cum este scris în pagină: <pre>text</pre> Separarea paragrafelor: <P>text</P> sau cu un atribut: <P ALIGN=LEFT> Liste ale căror elementele sunt prefixate de un punct: <UL> introduce o lista <LI> text introduce un element în listă <LI> text </UL> termină lista Liste ale căror elemente sunt numerotate: <OL> <LI> text introduce un element în listă

<HTML>

</HTML>

<HEAD> Header </HEAD>

<BODY> Corp </BODY>

Structura unei pagini Web: Header + Corp Structura header-ului: Structura corpului:

<HEAD>

<TITLE>text </TITLE> </HEAD>

<BODY TEXT=” “ LINK=” “ ALINK=” “ VLINK=” “ BACKGROUND=” “>

tag-uri +text </BODY>

92

<LI> text </OL> termină lista Linie de separare: <HR> Tag-ul APPLET

Clasa Applet Applet-urile sunt programe Java care se pot descarca din internet; aceasta descarcare este realizata de browser; apare intr-o fereastra a broser-ului ca un dreptunghi (panel-panou) in care avem o prezentare grafica cu un fond (background), putem desena caractere de un anumit font si o anumita culoare, precum si alte forme precum dreptunghiuri, linii poligonale, elipse, cercuri. Asadar, un applet are urmatoarele proprietati: - funcţioneaza ca parte integrantă a unui browser; - prezinta utilizatorului o interfaţă grafică şi poate capta evenimente declanşate de utilizatori. Orice applet este un obiect al unei clase definită de utilizator care extinde clasa Applet a pachetului java.applet sau clasa JApplet a pachetului javax.swing. Clasa Applet este un Container (componentă grafică Java utilizată pentru a include, gestiona şi vizualiza alte componente grafice) care utilizează metoda paint pentru vizualizarea desenelor şi componentelor grafice pe o suprafaţă dreptunghiulară. Exemplu: import java.awt.*; import java.applet.Applet; public class PrimulApplet extends Applet { public void init(){setBackground(Color.magenta);} public void paint(Graphics g){ g.drawString(“Buna ziua!”,20,30); } } În pagina HTML: <applet name=”primul applet" code=”PrimulApplet.class” codebase=”classes" width=”100” height="60”> </applet>

Când întâlneşte tag-ul APPLET, browser-ul execută următorii paşi: 1. Incarcă fişierul PrimulApplet.class din directorul classes al directorului curent. 2. Alocă aria în care se vizualizează applet-ul. 3. Instanţiază clasa PrimulApplet. 4. Cere executarea metodelor standard implementate în applet: init, paint etc. Structura unui applet:

<APPLET [ALT=TEXT] CODE=“NUMEAPPLET.CLASS” [CODEBASE=URL-UL FIŞIERULUI .CLASS] HEIGHT=ÎNĂLŢIMEA ÎN PIXELI [HSPACE=SPAŢIUL ORIZONTAL DINTRE APPLET ŞI TEXTUL CARE-L ÎNCONJOARĂ] WIDTH=LĂRGIMEA ÎN PIXELI [VSPACE=SPAŢIUL VERTICAL ÎNTRE APPLET ŞI TEXTUL CARE-L ÎNCONJOARĂ] [NAME=NUMELE APPLET-ULUI] > [<PARAM NAME=PARAMETRU1 VALUE=VALOARE PARAMETRU1>] [<PARAM NAME=PARAMETRU2 VALUE=VALOARE PARAMETRU2>] </APPLET>

93

import java.applet.Applet; public class NumeApplet extends Applet{ public void init(){…} public void start(){…} public void stop(){…} public void destroy(){…} public void paint(Graphics g){…}//mostenita din supraclasa Container }

Desenarea cu paint() - paint(Graphics g), repaint() şi update(Graphics g) sunt utilizate pentru desenarea pe un Container, în particular într-un applet vizualizat în pagina HTML. În aceste metode trebuie să descriem cum trasăm în dreptunghiul applet-ului un text, o linie, un fond de o anumită culoare sau o imagine. - pentru a vizualiza orice lucru avem nevoie să redefinim metoda paint mostenita de clasa Applet din clasa Container: public void paint(Graphics g){ //cod pentru desenat } - parametrul g este iniţializat cu un obiect al clasei Graphics care este creat şi transmis lui paint() de către browser. Acest obiectul reprezintă contextul grafic al applet-ului. Pentru a putea utiliza metoda paint() trebuie importată clasa Graphics din pachetul java.awt.

Browser Applet

1. Accesează pagina care conţine un applet

2. Applet-ul a fost încărcat în sistem şi iniţializat Browser-ul cheamă

apoi cheamă

start()

init()

paint()

stop()

Cere încărcarea applet-ului Applet-ul este încărcat în sistem Pentru a-l iniţializa, browser-ul cheamă

3. ... vizualizează întreaga pagină

applet-ul rulează…

4. Browser-ul lansează pagina şi cheamă applet-ul se opreşte din rulare

5. Browser-ul revine în pagina applet-ului sau browser-ul eliberează definitiv pagina conţinută

6. ... va fi apelată metoda destroy()

94

Metoda paint() va fi apelată automat de către browser când se desenează în applet. Când acelaşi applet trebuie să fie redesenat (de exemplu când am schimbat culoarea fondului cu setBackground()), se utilizează repaint(). Un applet care desenează import java.applet.Applet; import java.awt.Graphics; import java.awt.Color; public class Dreptunghiuri extends Applet{ public void paint(Graphics g) { g.drawRect (10, 15, 20, 30); g.translate(20, 35); g.setColor(new Color(0, 0, 255)); g.fillRect (10, 15, 40, 65); } }

Graphics Graphics = Clasă abstractă, bază pentru toate contextele grafice care aparţin unei aplicaţii de desenat. Informaţiile de stare necesare pentru desenarea în Java sunt: - obiectul Component pe care se desenează (un applet, de exemplu); - coordonatele originii translatării pentru desenat şi pentru operaţia de clipping; - clip-ul curent; - culoarea curentă; - font-ul curent; - funcţia logică curentă a operaţiei în pixeli (XOR sau Paint). Cine furnizează obiecte Graphics? Obiectele Graphics sunt întoarse de metodele getGraphics() existente în clasele Component, Image şi PrintJob. Cele mai importante metode ale clasei Graphics sunt prezentate în tabelul urm. Metoda Semnificaţie dispose() Elimină contextul grafic. getColor() Întoarce culoarea curentă. setColor(Color) Setează culoarea curentă ca fiind culoarea

specificată. getFont() Întoarce font-ul curent. setFont(Font) Setează font-ul contextului curent ca fiind font-ul

specificat. getFontMetrics() Întoarce dimensiunile tipului curent de font. getFontMetrics(Font) Întoarce dimensiunilr tipului de font specificat. translate(int, int) Pune originea contextului grafic în punctul (x, y) al

sistemului curent de coordonate. draw3DRect(int, int, int, int, boolean)

Desenează un dreptunghi în 3-D.

drawArc(int, int, int, int, int, int)

Desenează un arc înscris în dreptunghiul specificat.

drawImage(Image, int, int, Color, ImgObs)

Desenează atâta cât din imagine specificată este disponibilă.

drawLine(int, int, int, int) Desenează o linie între două puncte ale sistemului de coordonate al contextului grafic.

drawOval(int, int, int, int) Trasează o elipsă în interiorul dreptunghiului specificat.

drawPolygon(int[], int[], int) Trasează un poligon definit de doua array-uri de puncte x şi y.

drawPolyline(int[], int[], int) Trasează o secvenţă de linii conexe specificate de array-urile de coordonate x si y. Fiecare pereche

95

Metoda Semnificaţie (x[i], y[i]) defineşte un punct.

drawRect(int, int, int, int) Trasează dreptunghiul specificat. drawRoundRect(int, int, int, int, int, int)

Trasează un dreptunghi cu colţurile rotunjite specificate.

drawString(String, int, int) Desenează şirul specificat cu culoarea contextului grafic curent.

fill3DRect(int, int, int, int, boolean)

Colorează interiorul dreptunghiului 3D cu culoarea curentă.

fillArc(int, int, int, int, int, int)

Umple un sector de cerc continut in dreptunghiul specificat cu culoarea curenta.

fillOval(int, int, int, int) Umple cu culoarea curentă o elipsă continută în dreptunghiul specificat.

fillPolygon(int[], int[], int) Umple cu culoarea curentă un poligon definit de două array-uri de coordonate x şi y.

fillRect(int, int, int, int) Umple dreptunghiul specificat cu culoarea curentă. fillRoundRect(int, int, int, int, int, int)

Umple dreptunghiul cu colţuri rotunde specificat cu culoarea curentă.

Shape getClip() Întoarce aria de lucru curentă ca un obiect de tip interfata Shape

getClipBounds() Intoarce dreptunghiul circumscris ariei de lucru. setClip(int, int, int, int) Setează clip-ul curent dreptunghiul specificat cu

coordonatele date ca parametri actuali ai metodei. Color Color este o clasă care reprezintă o culoare. Culorile pot fi construite cu constructori ca: Color (xxx r, xxx g, xxx b) Color (xxx r, xxx g, xxx b, xxx alpha) xxx=int sau float Color (int rgb) Color (int rgb, boolean hasAlpha) Clasa Color conţine câmpuri constante iniţializate cu culori fundamentale: Color.red, Color.yellow, Color.blue, Color.cyan, Color.white, Color.green, Color.magenta etc

Font static Font getFont(String nm)//returneaza un obiect de tip Font cu numele specificat static Font getFont(String nm, Font f) int getSize() int getStyle() boolean isBold() boolean isItalic() boolean isPlain() static final int BOLD=1; static final int ITALIC=2; static final int PLAIN=0; Exemple de font-uri din JDK 1.4.1: Dialog, SansSerif, Serif, Monospaced, Helvetica, TimesRoman, DialogInput. Dimensiunile sunt specificate în puncte tipografice (1/72”): Font (String nume, int stil, int dim) Font f=new Font(“SansSerif”,Font.BOLD,12); Se poate impune unui context grafic un font nou astfel: void Graphics.setFont(Font f)

Applet pentru ora exactă

96

import java.awt.*; import java.util.*; public class Ceas extends java.applet.Applet { private Color beige=new Color(255, 204, 102); private String oraPrec=“”; public void init() { setBackground(Color.black); } public void paint(Graphics g) { Graphics2D g2D = (Graphics2D) g; Font tip=new Font (“Monospaced”, Font.BOLD, 20); g2D.setFont(tip); GregorianCalendar azi=new GregorianCalendar(); g2D.setColor(Color.black); g2D.drawString(oraPrec, 5, 25); g2D.setColor(beige); String ora=azi.getTime().toString(); g2D.drawString(ora, 5, 25); try {Thread.sleep(1000); }catch(InterruptedException e){ } oraPrec = ora; repaint(); } } Applet pentru ora exactă cu parametri import java.awt.*; import java.util.*; public class CeasNou extends java.applet.Applet{ private Color beige = new Color(255, 204, 102); private String oraPrec = “”; Color culoareFond; public void init() { String in=getParameter(“fond”); culoareFond=Color.black; if (in != null) { try{ culoareFond=Color.decode(in); }catch(NumberFormatException e) {showStatus(“Parametru eronat “ + in);} } setBackground(Color.black); } public void paint(Graphics g) { Graphics2D g2D=(Graphics2D) g; Font tip=new Font(“Monospaced”, Font.BOLD, 20); g2D.setFont(tip); GregorianCalendar azi=new GregorianCalendar(); g2D.setColor(culoareFond); g2D.drawString(oraPrec, 5, 25); g2D.setColor(beige); String ora=azi.getTime().toString(); g2D.drawString(ora, 5, 25); try {Thread.sleep(1000); }catch(InterruptedException e) {} oraPrec=ora; repaint(); } }

<html><applet code="CeasNou" width=350 height=50> <param name="fond" value="#996633"> </applet> </html>

97

FontMetrics FontMetrics Component.getFontMetrics(Font) FontMetrics Graphics.getFontMetrics() FontMetrics Graphics.getFontMetrics(Font) FontMetrics Toolkit.getFontMetrics(Font) int charsWidth(char data[],int off,int len) int bytesWidth(byte data[],int off,int len) int charWidth(char ch) int charWidth(int ch) int getAscent() getDescent() getLeading() getHeight() getMaxAscent() getMaxDescent() int stringWidth(String s) Cum se determină lungimea unui şir pe ecran? Font f=new Font(“SansSerif”, Font.BOLD+Font.ITALIC, 12); FontMetrics fm=g.getFontMetrics(f); cx=fm.stringWidth(“Un şir lung de 31 de caractere.”);

Programarea cu obiecte Font Exercitiu. Cum să scriem textul:

Acesta este un program pentru testarea font-urilor poziţionat în mijlocul unui frame utilizând două font-uri diferite? Solutia o gasiti în Error! Reference source not found.. public class TestFont2 extends Frame{ private Font f; private Font fi; private FontMetrics fm; private FontMetrics fim; private boolean fontsSet = false; public void setFonts(Graphics g){ if (fontsSet) return; f=new Font("SansSerif",Font.BOLD, 14); fi=new Font("SansSerif",Font.BOLD + Font.ITALIC,14); fm=g.getFontMetrics(f); fim=g.getFontMetrics(fi); fontsSet = true; } public void paint(Graphics g){ setFonts(g); String s1 = "Acesta este"; String s2 = " un program pentru"; String s3 = " testarea font-urilor."; int w1 = fm.stringWidth(s1); int w2 = fim.stringWidth(s2); int w3 = fm.stringWidth(s3); Dimension d = getSize(); Insets in = getInsets(); int clientWidth=d.width-in.right-in.left; int clientHeight =d.height-in.bottom-in.top; int cx=(clientWidth-w1-w2-w3)/2 + in.left; int cy=clientHeight/2+in.top; g.drawRect(in.left,in.top,clientWidth-1, clientHeight-1); g.setFont(f); g.drawString(s1, cx, cy);

98

cx += w1; g.setFont(fi); g.drawString(s2, cx, cy); cx += w2; g.setFont(f); g.drawString(s3, cx, cy); } public static void main(String args[]){ Frame f = new TestFont2(); f.show(); } }

99

12. INTERFETE GRAFICE AWT-Abstract Window Toolkit Serie completă de componente ale interfeţei grafice utilizator (GUI) Suport pntru “container-e” de componente grafice. Fiecare componentă are ciclul său de viaţă, independent de al altora. Un mecanism de evenimente care gestionează evenimentele sistemului şi evenimentele utilizatorului. Mecanisme pentru aranjare a componentelor într-un mod care permite obţinerea unui GUI independent de platformă. Gestionează ferestrele, menu-urile şi ferestrele de dialog. Grafica - Desenează figuri în 2D - Controlează culorile - Controlează font-urile Java 2D API - Posibilitatea de grafică mai laborioasă

- Desenează figuri în 2D personalizate - Umple figurile cu culori şi modele

Container-e Ideea fundamentală: O fereastră Java este un container care conţine o colecţie de componente încuibate. Container-ele sunt gestori ai componentelor conţinute. Ierarhia de container-e poate avea orice nivel de complexitate. Frame = Fereastra de nivel cel mai înalt într-o aplicaţie. Applet = Container de componente grafice care poate fi executat de un browser. Ierarhia claselor AWT

java.awt.Component java.awt.Component = Clasă abstractă care generalizează toate componentele AWT, exceptând menu-urile. Unei componente i se asociază următoarele elemente: - un obiect Graphics - localizare - dimensiune - un peer nativ - un container părinte - font şi dimensiunile font-ului (font metrics)

Object

Component

Canvas Container List Button TextComponent CheckBox ScrollBar Label Choice

Panel Window

Frame DialogApplet

FileDialog

TextField TextArea

100

- culori pentru foreground şi background - specific lingvistic - dimensiuni maxime şi minime Component⇒Container ⇒Window⇒Frame java.awt.Component boolean isVisible() void setVisible(boolean b) boolean isShowing() boolean isEnabled() void setEnabled(boolean b) Font getFont() void setFont(Font f) Point getLocation() void setLocation(int x, int y) void setLocation(Point p) Point getLocationOnScreen() Graphics getGraphics() void setBackground(Color c) void setForeground(Color c) Dimension getSize() void setSize(int w, int h) void setSize(Dimension d) java.awt.Container Component add(Component c) Component add(String name, Component c) Component getComponentAt(int x, int y) Component getComponentAt(Point p) Component[] getComponents() LayoutManager getLayout() void setLayout(LayoutManager mgr) Insets getInsets() void paint(Graphics g) void print(Graphics g) java.awt.Window void toFront() void toBack() Toolkit getToolkit() void show() java.awt.Frame void setResizable(boolean b) void setTitle(String s)

Adăugarea componentelor grafice import java.applet.Applet; import java.awt.*;// importa toate clasele AWT public class Primul extends Applet { TextField text;//declara campul de text ca TextField Button buton1, buton2;//declara doua Button public void init(){ text = new TextField(20);//creaza un TextField add(text);//adauga TextField-ul la fereastra buton1=new Button ("Apasa");//creaza un Button add(buton1);//adauga Button-ul la fereastra buton2=new Button("Cancel"); //creaza un Button add(buton2); //adauga Button-ul la fereastra } }

ImageObserver<<Interface>>

Serializable<<Interface>

Componentvizible : booleanenabled : booleanvalid : booleanx : inty : intwidth : intheight : int

1

2+fg, bg

1

1

*

+parinte

ComponentPeer+peer

+panelLayout

Font

Color

Graphics

Container

Panel

Applet

FlowLayout

LayoutManager<<Interface>>

Window

Frame

MenuContainer<<Interface>>

101

TextArea, TextField şi Checkbox

List, Scrollbar, Canvas şi Label

Menu In diagrama urmatoare se prezinta conceptele principale pe care trebuie sa le cunoastem pentru a programa menu-uri cu java.awt .

import java.applet.Applet; import java.awt.*; public class ContainerX extends Applet{ //declaratii/alocari de componente public void init() { //declaratii/alocari de componente //adaugarea componentelor in applet } }

import java.applet.Applet; import java.awt.*; public class Container1 extends Applet{ TextField text1; TextField text2; public void init() { text1=new TextField(20); add(text1); text2=new TextField(“Buna”,10); add(text2); } }

import java.applet.Applet; import java.awt.*; public class Container2 extends Applet{ TextArea tArea; public void init() { tArea=new TextArea(10,20); add(tArea); } }import java.applet.Applet; import java.awt.*; public class Container3 extends Applet{ CheckboxGroup cBG=new CheckboxGroup(); Checkbox c1, c2: public void init() { c1=new Checkbox(“Primul”,cBG, true); add(c1); c2=new Checkbox(“AlDoilea”); c2.setCheckboxGroup(cBG); c2.setState(false); add(c2); } }

O arie de text

un grup de butoane radio

import java.applet.Applet; import java.awt.*; public class Container4 extends Applet{ List list1; public void init(){ list1=new List(2, true); list1.addItem(“Rosu”); list1.addItem(“Verde”); list1.addItem(“Albastru”); add(list1); } }

import java.applet.Applet; import java.awt.*; public class Container5 extends Applet { Scrollbar hSB, vSB; public void init(){ hSB=new Scrollbar(Scrollbar.HORIZONTAL,0,1, 1,100); add(hSB); vSB=new Scrollbar(Scrollbar.VERTICAL,0,1,1,

100); add(vSB); } }import java.applet.Applet; import java.awt.*; public class Container6 extends Applet{ Label etic=new Label(“Nume si Prenume”); text1=new TextField(“Ion Pop”,20); public void init(){ add(etic); add(text1); } }

Etichetă

Bare de defilare verticală şi orizontală

import java.applet.Applet; import java.awt.*; public class Container7 extends Applet{ Canvas c; public void init() { c=new Canvas(); c.resize(40,40); c.setBackground(Color.black); add(c); } }

Canvas

Lista

MenuComponent

MenuBar MenuItem

CheckboxMenuItem Menu PopupMenu

102

Din programul urmator se poate vedea care este în esenta modalitatea de programare a menu-urilor. import java.awt.*; public class MenuFrame extends Frame { MenuItem fileNew=new MenuItem(“Nou”); MenuItem fileOpen=new MenuItem(“Deschide”); MenuItem fileSave=new MenuItem(“Salveaza”); MenuItem fileExit=new MenuItem(“Iesire”); MenuItem editUndo=new MenuItem(“Cancel”); MenuItem editCut=new MenuItem(“Cut”); MenuItem editCopy=new MenuItem(“Copy”); MenuItem editPaste=new MenuItem(“Paste”); MenuItem helpContents=new MenuItem(“Rezumat”); MenuItem helpAbout=new MenuItem(“Info...”); public MenuFrame() { super(“Exemplu de menu”); MenuBar menubar=new MenuBar(); Menu fileMenu=new Menu(“Fisier”); Menu editMenu=new Menu(“Modifica”); Menu helpMenu=new Menu(“?”); fileMenu.add(fileNew); fileMenu.add(fileOpen); fileSave.setEnabled(false); fileMenu.add(fileSave); fileMenu.addSeparator(); fileMenu.add(fileExit); editUndo.setEnabled(false); editMenu.add(editUndo); editMenu.addSeparator(); editCut.setEnabled(false); editMenu.add(editCut); editCopy.setEnabled(false); editMenu.add(editCopy); editPaste.setEnabled(false); editMenu.add(editPaste); helpMenu.add(helpContents); helpMenu.addSeparator(); helpMenu.add(helpAbout); menubar.add(fileMenu); menubar.add(editMenu); menubar.add(helpMenu); menubar.setHelpMenu(helpMenu); setMenuBar(menubar); setSize(new Dimension(400,300)); show(); } public static void main(String[] args){ MenuFrame meu=new MenuFrame(); } }

Grafica cu LayoutManager Layout Manager = Interfaţă utilizată pentru asezarea diferitelor componente într-un container după un şablon de aranjare. LayoutManager2 extinde LayoutManager şi este folosit pentru asocierea de constrângeri aşezării componentelor.

103

Cele mai folosite clase de gestori de layout care implementează interfaţa LayoutManager sunt: FlowLayout, BorderLayout, GridLayout, GridBagLayout, CardLayout.

Exemplu. import java.awt.*; public class FacturaFrame extends Frame {Button print, cancel,printF,arhivare; TextArea arie; Panel panel; private Factura factura; private GridBagLayout gb; private GridBagConstraints gbc; public FacturaFrame(Factura f) {super("Factura"); factura=f; gb=new GridBagLayout(); gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.anchor=GridBagConstraints.NORTH; panel=new Panel(); panel.setLayout(gb); arie=new TextArea(factura.toString(),20, 50); arie.setEditable(false); addComponent(arie,0,0,1,5); print=new Button("Print"); addComponent(print,1,1,1,1); printF=new Button("PrintToFile"); addComponent(printF,2,1,1,1); arhivare=new Button("Arhivare"); addComponent(arhivare,3,1,1,1); cancel=new Button("Cancel"); addComponent(cancel,4,1,1,1);

setLayout(new FlowLayout()); AppletViewer: GestoreLayoutApplet

setLayout(new BorderLayout()); AppletViewer: GestoreLayoutApplet

West

South

North

CenterEast

setLayout(new GridLayout(3,2)); AppletViewer: GestoreLayoutApplet

Clasa GridBagLayout este un gestor flexibil care aliniază componentele pe verticală şi orizontală, fără să fie nevoie ca componetele să aibă aceleaşi dimensiuni. Fiecare obiect GridBagLayout păstrează o grilă dreptunghiulară dinamică de celule în care fiecare componentă ocupă una sau mai multe celule, care formează aria de vizualizare a componentei.

CardLayout aşează componentele (de obicei paneluri) în straturi, precum cărţile într-un pachet de cărti. În orice moment, este vizibilă numai o componentă, dar componentele pot fi “răsfoite” şi să controlăm care din ele să devină vizibilă. Acest layout este util căn o aceeaşi suprafaţă trebuie partajată de mai multe paneluri care trebuie să fie vizualizate în momente diferite de timp.

104

add(panel); } void addComponent(Component c, int linie,int col, int lat, int inal) { gbc.gridx=col; gbc.gridy=linie; gbc.gridwidth=lat; gbc.gridheight=inal; gb.setConstraints(c,gbc); panel.add(c); } public static void main(String[] args){ Vanzare v=new Vanzare(new Client("Ionescu", "Pop", "alea rozelor")); v.addProdus(new ElementVanzare(new SpecificatieProdus("mousepad", 11), 2)); v.addProdus(new ElementVanzare(new SpecificatieProdus("mouse", 50), 3)); Frame f=new FacturaFrame(new Factura(v)); f.setSize(480, 400); f.setVisible(true);}}

105

13. GESTIUNEA EVENIMENTELOR Programarea pe evenimente este o paradigmă de programare diferită de programarea procedurală sau de cea orientată spre obiecte. După această paradigmă, colaborarea între participanţii dintr-o elaborare va fi realizată prin intermediul mesajelor transmise nu unui unic participant ca în celelalte două cazuri, ci mai multor participanţi interesaţi în primirea informaţiilor conţinute de mesajele respective. Orice aplicaţie poate fi mai bine proiectată dacă se utilizează paradigma orientată de evenimente. Java utilizează această paradigmă pentru a oferi o mai bună comunicare între obiecte autonome, implicate ocazional într-o activitate de grup. Este cazul interfeţelor grafice în care evenimentele verificate sunt generate de componente grafice (click al mouse-ului asupra unei componente, selectarea unui element dintr-o listă sau apăsarea unei taste când o componentă este selectată) şi trebuie să fie anunţate altor componente care trebuie să ia anumite decizii. În continuare vom prezenta paradigma pe evenimente, aşa cum este utilizată în interfeţele grafice Java.

13.1 MODELUL GESTIUNII EVENIMENTELOR ÎN PROIECTAREA COMPONENTELOR GRAFICE Într-un scenariu de evenimente există trei protagonişti (Figura 13-1): - sursă de evenimente: orice componentă grafică. O componentă grafică este specializată în

trimiterea anumitor tipuri de evenimente (Tabelul 13-1); - eveniment: obiect lansat de o sursă de evenimente. Evenimentul este de un tip care

specializează clasa AWTEvent. Între aceste tipuri de evenimente există un tip ActionEvent care reprezintă evenimente rezultate în urma efectuării unui clic de mouse într-o interfaţă grafică sau un tip MouseEvent care prezintă acţiuni efectuate de mouse asupra unei componente oarecare. Toate evenimentele conţin informaţii asupra sursei evenimentului şi un mesaj care ilustrează evenimentul apărut. Sursa evenimentului se obţine cu metoda getSource() aplicată pe eveniment. Metoda întoarce un Object. Mesajul se obţine apelând evenimentul cu toString();

- ascultător (listener): obiect care implementează un gestor de evenimente de un anumit tip (event handler - EH). Gestorul respectiv va indica acţiunile ce vor fi executate când apare evenimentul pentru care s-a înregistrat. Clasa trebuie să implementeze o interfaţă de tip EventListener.

Există interfeţe pentru toate tipurile de evenimente. De exemplu, pentru ActionEvent şi MouseEvent avem interfeţele ActionListener şi MouseListener. Metodele interfeţelor diferenţiază diferite situaţii în care se pot verifica pentru un anumit tip de eveniment. De exemplu, ActionListener are o unică declaraţie de metodă: void actionPerformed(ActionEvent e)

iar interfaţa MouseListener are următoarele declaraţii de metode: public void mouseClicked(MouseEvent e); public void mouseEntered(MouseEvent e); public void mouseExited(MouseEvent e); public void mousePressed(MouseEvent e); public void mouseReleased(MouseEvent e);

Figura 13-1. Protagonişti într-un scenariu de evenimente

Pentru a vedea cum colaborează cei trei protagonişti, vom considera cazul evenimentelor de tip ActionEvent lansat de un buton. Un click de mouse asupra butonului transformă butonul într-o sursă de evenimente care generează mai multe obiecte-eveniment între care unul este de tip ActionEvent. Evenimentul va fi primit de toţi ascultătorii care s-au înregistrat la acest tip de eveniment şi produs de acest buton.

Sursă de evenimente

Eveniment

Ascultător

106

În programele care utilizează evenimente, sunt obligatorii trei secţiuni de cod: - clasa ascultătoare declară că implementează o interfaţă Listener pentru tipul de eveniment de care este interesată. Aceasta este o clasă internă a applet-ului sau ferestrei ce conţine componenta grafică ce lansează evenimente de un anumit tip: class AscultatorEveniment implements ActionListener { ... }

- ascultătorul unui anumit tip de evenimente se înregistrează la sursă. Codul se găseşte de obicei într-o metodă a unei instanţe a container-ului ce conţine sursa (deseori în constructor) sau într-o metodă a unui alt obiect. De exemplu, pentru oComponenta, sursă de evenimente de tip ActionEvent, ascultătorul instantaAscultatorEveniment se înregistrează astfel: oComponenta.addActionListener(instantaAscultatorEveniment);

- ascultătorul implementează metodele interfeţei de ascultare. Cu fiecare din aceste metode ascultătorul va fi anunţat de apariţia unui eveniment: public void actionPerformed(ActionEvent e) { . . . }

În metode trebuie să identificăm sursa eveneimentului pentru a şti de unde a apărut evenimentul: public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof Button) {...} . . . }

Ascultătorul poate fi chiar applet-ul sau fereastra ce conţine sursa de evenimente şi atunci acest container trebuie să implementeze interfaţa ascultător pentru tipul de eveniment de care este interesat şi să se înregistreze la sursa de evenimente: public class Fereastra extends Frame implements ActionListener { ... public Fereastra(){ ... oComponenta.addActionListener(this); ... } }

Tipuri de evenimente generate Componentă AWT action adjus-

tment compo-

nent container focus item key mouse text window

Button X X X X X Canvas X X X X Ceckbox X X X X X CeckboxMenuItem X Choice X X X X X Component X X X X Container X X X X X Dialog X X X X X X Frame X X X X X X Label X X X X List X X X X X X MenuItem X Panel X X X X X Scrollbar X X X X X ScrollPane X X X X X TextArea X X X X X TextComponent X X X X X TextField X X X X X X Window X X X X X X

Tabelul 13-1. Tipuri de evenimente generate de componente grafice

107

13.2 EVENIMENTE SEMANTICE Evenimentele se împart în două categorii: evenimente semantice şi cele de nivel jos. În această secţiune ne vom ocupa numai de evenimentele din prima categorie. Evenimentele semantice (Diagrama 13-1) apar datorită interacţiunii utilizatorului cu componenta grafică, adică cu sursa de evenimente. De exemplu: - un utilizator face un clic asupra unui buton: ActionEvent; - un utilizator modifică valoarea unei bare de defilare: AdjustmentEvent; - un utilizator selectează un element dintr-un grup de elemente (dintr-o listă): ItemEvent; - un utilizator modifică textul dintr-un câmp de text sau dintr-o arie de text: TextEvent.

AdjustmentEvent

getAdjustable() : AdjustablegetAdjustmentType() : intgetValue() : int

EventObject

getSource() : ObjecttoString() : String

AWTEvent

ActionEvent

getActionCommand() : StringgetModifiers() : intgetWhen() : long

ItemEvent

getItem() : ObjectgetItemSelectable() : ItemSelectablegetStateChanged() : int

TextEvent

Diagrama 13-1. Tipuri de evenimente semantice

13.2.1 Gestiunea evenimentelor de tip ActionEvent

Exemplul 1. Considerăm cazul unui applet care vizualizează un câmp de text şi unul din mesajele “Bine ati venit!” sau “La revedere” în funcţie de butonul apăsat: (Figura 13-2).

Figura 13-2. Un applet care gestionează evenimente de tip ActionEvent import java.applet.Applet; import java.awt.*; // importă clase AWT la cerere import java.awt.event.*; public class Primul extends Applet { private TextField text; //declară un câmp de text ca TextField private Button buton1, buton2; //declară două Button private AscultatorEveniment ae; //declară un ascultător public void init(){ text = new TextField(20); //crează un TextField add(text); //adaugă la fereastră TextField-ul buton1 = new Button (”Bine ati venit"); //crează un Button add(buton1); //adaugă la fereastră Button-ul ae=new AscultatorEveniment();

108

buton1.addActionListener(ae); // adăugarea ascultătorului la buton1 si //obiectul a cărui referinţă este memorată în ae tratează aparitia //evenimentului ActionEvent generat de buton1 buton2 = new Button (”La revedere"); //crează un Button add(buton2); //adaugă la fereastră Button-ul buton2.addActionListener(ae); // adăugarea ascultătorului la buton2 si //obiectul a cărui referinţă este memorată în ae rezolvă //evenimentul ActionEvent cand va fi generat de buton2 } class AscultatorEveniment implements ActionListener{ public void actionPerformed (ActionEvent evt) { //tratarea evenimentului if (evt.getSource()==buton1) text.setText(“Bine ati venit!”); else text.setText(“La revedere!”); } } } Exemplul2. Considerăm o fereastră numită “Jurnal” care are o arie de text şi două butoane: Salvează şi Anulează. Utilizatorul introduce un text în aria de text, după care apasă unul din cele două butoane. Dacă apasă butonul Salvează, conţinutul ariei de text va fi salvat în fişierul “jurnal.txt”. Dacă apasă pe butonul Anulează, fereastra se închide şi conţinutul ariei de text se pierde (Figura 13-3).

Figura 13-3. Fereastra Jurnal

Programul care implementează fereastra Jurnal este următorul: import java.awt.*; import java.awt.event.*; import java.io.*; public class AlDoilea extends Frame{ private Button b1, b2; private TextArea ta; private Ascultator a; public AlDoilea(){ super("Jurnal"); a=new Ascultator();

109

ta=new TextArea(50,75); add(ta, BorderLayout.CENTER); Panel p = new Panel(); b1 = new Button ("Salveaza"); p.add(b1); b1.addActionListener(a); b2 = new Button ("Anuleaza"); p.add(b2); b2.addActionListener(a); add(p, BorderLayout.SOUTH); } class Ascultator implements ActionListener{ public void actionPerformed (ActionEvent e){ if (e.getSource()==b1){ String text=ta.getText(); try{ PrintWriter pw=new PrintWriter(new FileWriter("jurnal.txt")); pw.println(text); pw.close(); }catch(IOException io){System.out.println(io.getMessage());} System.exit(0); } else System.exit(0); } } public static void main(String[] args) { Frame f = new AlDoilea (); f.setBounds(40, 60, 450, 350); f.setVisible(true); } }

Programul 13-1. Programul Jurnal

13.2.2 Gestiunea evenimentelor de tip AdjustmentEvent

Exemplu. Fie un program care vizualizează pe ecran fereastra (Figura 13-4) ce conţine trei bare de defilare, fiecare corespunzând unei culori din sistemul RGB, şi trei câmpuri de text ce afişează valorile culorilor roşu, verde şi albastru din care este creată culoarea curentă. Cu această culoare, programul desenează un disc pe un canvas aflat în partea dreaptă a ferestrei. Acesta va fi redesenat de fiecare dată când utilizatorul deplasează butonul de defilare sau execută un click de mouse pe unul din butoanele de incrementare sau decrementare a valorii oricărei bare de defilare. Codul programului este prezentat în continuare. import java.awt.*; import java.awt.event.*; import java.applet.Applet; public class ColorChooser extends Frame{ private Scrollbar r,g,b; private TextField rt,gt,bt; private Panel p; private Canvas canvas; private GridBagLayout gb; private GridBagConstraints gbc; private Ascultator a; public ColorChooser(){

110

super("ColorChooser"); a=new Ascultator(); p=new Panel(); gb=new GridBagLayout(); gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.NONE; gbc.anchor=GridBagConstraints.WEST; gbc.insets=new Insets(10,10,0,0); p.setLayout(gb); r=new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 255); adaugaComp(r,0,0,3,1); r.addAdjustmentListener(a); rt=new TextField(5); adaugaComp(rt,0,3,1,1); g=new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 255); adaugaComp(g,4,0,3,1); g.addAdjustmentListener(a); gt=new TextField(5); adaugaComp(gt,4,3,1,1); b=new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 255); adaugaComp(b,7,0,3,1); b.addAdjustmentListener(a); bt=new TextField(5); adaugaComp(bt,7,3,1,1); canvas=new Canvas(); add(p, BorderLayout.WEST); add(canvas, BorderLayout.EAST); } public void adaugaComp(Component c, int l, int col, int lat, int inal){ gbc.gridx=col; gbc.gridy=l; gbc.gridwidth=lat; gbc.gridheight=inal; gb.setConstraints(c,gbc); p.add(c); } class Ascultator implements AdjustmentListener{ public void adjustmentValueChanged(AdjustmentEvent e){ int rosu=r.getValue(); int verde=g.getValue(); int albastru=b.getValue(); rt.setText(String.valueOf(rosu)); gt.setText(String.valueOf(verde)); bt.setText(String.valueOf(albastru)); Color c=new Color(rosu,verde,albastru); deseneaza(c); } public void deseneaza(Color c){ Graphics g=getGraphics(); g.setColor(c); g.fillOval(135,85,75,75); canvas.paint(g); }

Figura 13-4.Fereastra ColorChooser

111

} public static void main(String[] arg){ Frame f=new ColorChooser(); f.setBounds(350, 240, 250, 220); f.show(); } }

Programul 13-2. Programul ColorChooser

13.2.3 Gestiunea evenimentelor de tip ItemEvent

Un obiect de tip ItemEvent este creat de fiecare dată când un utilizator alege sau selectează un element dintr-o listă, moment în care este lansat un eveniment de acelaşi tip. Exemplu. Vom îmbunătăţi programul anterior (Programul 13-2) pentru a permite utilizatorului să aleagă dintr-o listă, forma grafică: disc, oval, patrat sau dreptunghi plin care vrea să fie desenată. Pentru aceasta, introducem o componentă grafică de tip Choice numită lista ce conţine numele formelor grafice în ordinea menţionată anterior, la care asociem un ascultător de tip ItemListener. Codul necesar este următorul: al=new AscultatorLista(); lista=new Choice(); lista.add("Disc"); lista.add("Oval"); lista.add("Patrat"); lista.add("Dreptunghi"); lista.addItemListener(al); add(lista, BorderLayout.NORTH); Instrucţiunile din metoda adjustmentValueChanged() a interfeţei AdjustmentListener sunt mutate într-o metodă getCuloare() a clasei Ascultator: public Color getCuloare(){ int rosu=r.getValue(); int verde=g.getValue(); int albastru=b.getValue(); rt.setText(String.valueOf(rosu)); gt.setText(String.valueOf(verde)); bt.setText(String.valueOf(albastru)); return new Color(rosu,verde,albastru); }

metodă ce va fi apelată de ambii ascultători: cel al barelor de defilare şi cel al listei când apelează metoda desenează(): class AscultatorLista implements ItemListener{ public void itemStateChanged(ItemEvent e){ a.deseneaza(a.getCuloare(), lista.getSelectedItem()); } } Modificăm şi metoda desenează()introducând un parametru ce reprezintă numele formei geometrice ce va fi desenată pe canvas cu o anumită culoare, şi anume cea aleasă de utilizator: public void deseneaza(Color c, String forma){ Graphics g=getGraphics(); g.clearRect(135, 85, 100, 100); g.setColor(c); if(forma.equals("Disc")) g.fillOval(135,85,75,75); if(forma.equals("Oval")) g.fillOval(135,85,85,75); if(forma.equals("Patrat")) g.fillRect(135,85,85,85); if(forma.equals("Dreptunghi")) g.fillRect(135,85,85,75); canvas.paint(g); } }

13.2.4 Gestiunea evenimentelor de tip TextEvent

112

Un eveniment de tip TextEvent are loc când textul unei componente grafice a fost modificat. În acel moment este apelată metoda public void textValueChanged(TextEvent e) a ascultătorului de tip TextListener, dacă acesta s-a înregistrat la componenta respectivă. Exemplu. Să scriem un program care furnizează funcţionalitatea unei case de bilete a unui cinematograf. Programul vizualizează cumpărătorilor de bilete interfaţa grafică din Figura 13-5 şi calculează, respectiv afişează suma pe care trebuie să o platească un client dacă cumpără un număr de bilete. Se ştie că persoanele cu vârsta între 7 şi 14 plătesc jumătate de bilet (50000 lei), iar copiii au intrare gratuită. Astfel, un client alege categoria de vârstă la care vrea să cumpere un număr de bilete pe care-l introduce în câmpul de text asociat categoriei. În acest moment, programul calculează şi afişează cât îl costă pe client biletele respective. Dacă alege să cumpere şi bilete din cealalta categorie, programul îi va recalcula, respectiv reafişa, costul total al biletelor. Codul programului este prezentat în continuare. import java.awt.*; import java.awt.event.*; public class CasaDeBilete extends Frame { private Checkbox c1,c2; private TextField tf1, tf2; private Panel p; private long cost; private AscultatorCamp ac; public CasaDeBilete(){ super("Casa de bilete"); ac=new AscultatorCamp(); p=new Panel(); p.setLayout(new GridLayout(5,2, 10,10)); p.add(new Label("Categorie de varsta")); p.add(new Label("Numar de bilete")); c1=new Checkbox("7<=x<=14", false); p.add(c1); tf1=new TextField(10); tf1.addTextListener(ac); p.add(tf1); c2=new Checkbox("x>14", false); p.add(c2); tf2=new TextField(10); tf2.addTextListener(ac); p.add(tf2); p.add(new Label("Cost bilete")); add(p); } class AscultatorCamp implements TextListener{ private Label costEt; private int nr; public void textValueChanged(TextEvent e){ nr=0; if (costEt!=null) {p.remove(costEt);p.validate();} if (e.getSource()==tf1){ nr=Integer.parseInt(tf1.getText()); cost+=nr*50000; if (c2.getState()){ cost+=100000*Integer.parseInt(tf2.getText()); } } if (e.getSource()==tf2){ nr=Integer.parseInt(tf2.getText()); cost+=nr*100000;

Figura 13-5. Fereastra Casa de bilete

113

if (c1.getState()){ cost+=50000*Integer.parseInt(tf1.getText()); } } costEt=new Label(String.valueOf(cost)); p.add(costEt); p.validate(); cost=0; } } public static void main(String[] arg){ Frame f=new CasaDeBilete(); f.setBounds(250, 250, 250,200); f.show(); } }

13.3 EVENIMENTE DE NIVEL COBORÂT Mai există o categorie de evenimente, evenimentele de nivel coborât (Diagrama 13-2), ce apar datorită modificării componentelor şi sunt de mai multe tipuri: - Evenimente ale componentelor raportează modificarea poziţiilor, dimensiunilor şi a vizibilităţii

componentelor: ComponentEvent; - Evenimente de intrare generate de folosirea tastaturii sau a mouse-ului: InputEvent; - Evenimente ale focusului când o componentă primeşte sau pierde focusul de la tastatură:

FocusEvent; - Evenimente produse de tastatură: KeyEvent; - Evenimente ale ferestrelor prezintă starea fiecărei ferestre: WindowEvent; - Evenimente ale container-elor informează de adăugarea sau eliminarea unei componente dintr-

un container: ContainerEvent.

Diagrama 13-2. Tipuri de evenimente de nivel coborât

13.1.1 Gestiunea evenimentelor de tip FocusEvent

Un eveniment de tip FocusEvent este generat atunci când o componentă este selectată sau deselectată cu ajutorul tastei Tab. Exemplu. Să scriem o clasă care afişează o fereastră DateAngajat cu interfaţa grafică din Figura. Observăm că fereastra conţine un câmp de text în care utilizatorul trebuie să introducă data naşterii

FocusEvent

getOppositeComponent() : ComponentisTemporary() : boolean

AWTEvent

ComponentEvent

getComponent() : Component

InputEvent

isShiftDown() : booleanisAltDown() : booleanisAltGraphDown() : booleanisControlDown() : boolean

MouseEvent

getClickCount() : intgetX() : intgetY() : intgetPoint() : Point

WindowEvent

getNewState() : intgetOldState() : intgetWindow() : WindowgetOppositeWindow() : Window

KeyEvent

<<static>> getKeyText(car : char) : StringsetKeyCode(cod : int)getKeyCode() : intsetKeyChar(car : char)getKeyChar() : char

ContainerEvent

getChild() : ComponentgetContainer() : Container

114

în momentul în care câmpul respectiv este activat, adică are focus-ul. Când câmpul de text pierde focus-ul, programul verifică dacă data de naştere introdusă respectă formatul “zz/ll/aa”. În caz afirmativ, focusul trece pe următoarea componentă, adică butonul Inchide şi dacă este apăsat Enter, fereastra Angajat este închisă. Dacă data de naştere nu este validă, programul afişează o fereastră de dialog ce conţine un mesaj de atenţionare şi plasează cursul în campul de text corespunzător datei de naştere. Programul este prezentat în continuare. import java.awt.*; import java.awt.event.*; import java.io.*; import java.text.*; import java.util.*; public class FereastraAngajat extends Frame{ private TextField nume, prenume, dataNastere; private Button b; private AscultatorCamp ac; private AscultatorButon ab; public FereastraAngajat(){ super("Date angajat"); setLayout(new GridLayout(4,2, 10,10)); add(new Label("Nume")); nume=new TextField(10); add(nume); add(new Label("Prenume")); prenume=new TextField(10); add(prenume); add(new Label("Data nastere")); dataNastere=new TextField(10); add(dataNastere); ac=new AscultatorCamp(); dataNastere.addFocusListener(ac); b=new Button("Inchide"); add(b); ab=new AscultatorButon(); b.addKeyListener(ab); } class AscultatorCamp extends FocusAdapter{ Dialog d; public void focusLost(FocusEvent e){ String data=dataNastere.getText(); DateFormat df=DateFormat.getDateInstance(DateFormat.SHORT); try{ df.parse(data); }catch(ParseException nf){ d=new Dialog(FereastraAngajat.this, "Eroare"); d.setLayout(new GridLayout(2,1)); d.add(new Label("Format data invalid!! (ll/zz/aa)")); Button ok=new Button("OK"); ok.addKeyListener(new KeyAdapter(){ public void keyPressed(KeyEvent e){ dataNastere.setText(""); d.dispose(); dataNastere.requestFocus(); } }); d.add(ok);

Figura 13-6. Fereastra DateAngajat

115

d.setBounds(250,250, 100, 100); d.setVisible(true); }//catch }//metoda focusLost } class AscultatorButon extends KeyAdapter{ public void keyPressed(KeyEvent e){ if(e.getKeyCode()==KeyEvent.VK_ENTER)System.exit(0); } } public static void main(String[] args){ Frame f=new FereastraAngajat(); f.setBounds(200,200,200,175); f.setVisible(true); f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0); } }); } }

Programul 13-3. Programul DateAngajat

13.3.1 Gestiunea evenimentelor de tip MouseEvent

Un obiect MouseEvent poate executa 5 metode ale interfeţei MouseListener după cum urmează: - dacă evenimentul este de tip MOUSE_PRESSED, va fi apelată metoda mousePressed() - dacă evenimentul este de tip MOUSE_RELEASED, va fi apelată metoda mouseReleased() - dacă evenimentul este de tip MOUSE_CLICKED, va fi apelată metoda mouseClicked() - dacă evenimentul este de tip MOUSE_ENTERED, va fi apelată metoda mouseEntered() - dacă evenimentul este de tip MOUSE_EXITED, va fi apelată metoda mouseExited(). De exemplu, când utilizatorul face un clic cu mouse-ul, Java apelează metoda mouseClicked() al obiectului ascultător. Utilizând metodele getX() şi getY() pe argumentul MouseEvent, se pot obţine coordonatele x şi y ale cursor-ului mouse-ului. Pentru a diferenţia între un singur clic şi dublu clic se utilizează getClickCount(). Această metodă returnează valoarea 2 dacă au fost efectuate două click-uri în timpul evenimentului de tip MOUSE_CLICKED. Exemplu. Vom prezenta un program numit EvenimenteMouse care utilizează evenimente de tip MouseEvent. Programul va prezenta într-o fereastră două componente grafice: un Canvas la stânga şi un TextArea la dreapta ().Obiectul Canvas va fi utilizat ca sursă de evenimente generate de mouse. Ascultătorul este însăşi fereastra. De fiecare dată când mouse-ul intră în spaţiul canvas-ului sau iese din această arie sau când utilizatorul face un click sau un dublu click pe canvas, obiectul Canvas lansează evenimente de tip MouseEvent care vor fi primite şi gestionate de fereastra EvenimenteMouse. Gestiunea evenimentelor presupune scrierea în aria de text a unui text ce conţine coordonatele punctului în care s-a efectuat o acţiune cu mouse-ul şi a fost lansat evenimentul, precum şi tipul evenimentului.

116

Figura 13-7. Fereastra programului EvenimenteMouse Programul EvenimenteMouse este prezentat în continuare. import java.awt.*; import java.awt.event.*; public class EvenimenteMouse extends Frame implements MouseListener{ TextArea ta; Canvas c; public EvenimenteMouse(){ super("Evenimente generate de mouse"); c = new Canvas(); c.setSize(250, 200); c.setBackground(Color.red); c.addMouseListener(this); add(c, BorderLayout.CENTER); ta = new TextArea(20, 40); add(ta, BorderLayout.EAST); } public void mouseEntered(MouseEvent e){ ta.append("Mouse intrat in canvas in punctul "+e.getX()+","+e.getY()+")

\n"); } public void mousePressed(MouseEvent e){ ta.append("Buton mouse apasat in punctul "+e.getX()+","+e.getY()+") \n"); } public void mouseReleased(MouseEvent e){ ta.append("Buton mouse eliberat in punctul ("+e.getX()+","+e.getY()+")\n"); } public void mouseClicked(MouseEvent e){ if(e.getClickCount()==2) ta.append("Dublu click in punctul

("+e.getX()+","+e.getY()+")\n"); else ta.append("Un click in punctul ("+e.getX()+","+e.getY()+")\n"); } public void mouseExited(MouseEvent e){ ta.append("Mouse iesit din canvas in punctul ("+e.getX()+","+e.getY()+ ")\n"); } public static void main(String[] args){

117

Frame f=new EvenimenteMouse(); f.setBounds(320, 240, 500, 250); f.show(); } }

Programul 13-4. Programul EvenimenteMouse

13.3.2 Gestiunea evenimentelor de tip KeyEvent

Un eveniment de tip KeyEvent este lansat ori de câte ori o tastă este apăsată, eliberată sau este apăsată o tastă caracter. Dacă suntem în ultimul caz, caracterul transmis poate fi obţinut prin folosirea getKeyChar() din clasa KeyEvent. Pentru evenimente generate de apăsarea sau eliberarea unei taste, putem folosi metoda getKeyCode() ce returnează codul Unicode al tastei implicate în eveniment. Aceste cod este furnizat şi de o constantă a clasei KeyEvent. De exemplu, codul Unicode al tastei direcţionale ← este memorat în constanta VK_LEFT şi poate fi folosită pentru a şti dacă tasta ← a fost apăsată de către utilizator. Exemplu. Programul următor vizualizează o fereastră vidă pe ecran şi permite utilizatorului să apese tastele săgeţi pentru a muta fereastra pe ecranul monitorului. În plus, utilizatorul poate folosi tastele C, S şi D pentru a plasa fereastra în centrul, stânga sau dreapta ecranului. import java.awt.*; import java.awt.event.*; import java.util.*; public class AscultatorFereastra implements KeyListener{ private Frame f; private int x, y; public AscultatorFereastra(Frame f){ this.f=f; Point p=f.getLocation(); x=(int)p.getX(); y=(int)p.getY(); } public void keyPressed(KeyEvent e){ switch(e.getKeyCode()){ case KeyEvent.VK_UP: y-=10;break; case KeyEvent.VK_DOWN: y+=10;break; case KeyEvent.VK_LEFT: x-=10;break; case KeyEvent.VK_RIGHT: x+=10; } f.setLocation(x,y); } public void keyReleased(KeyEvent e){ Toolkit toolkit=Toolkit.getDefaultToolkit(); Dimension dimEcran=toolkit.getScreenSize(); switch(e.getKeyCode()){ case KeyEvent.VK_S: f.setLocation(0,y);break; case KeyEvent.VK_C: x=(int)(dimEcran.getWidth()/2-f.getWidth()/2); y=(int)(dimEcran.getHeight()/2-f.getHeight()/2);

f.setLocation(x,y); break;

case KeyEvent.VK_D: f.setLocation((int)(dimEcran.getWidth()- f.getWidth()),y);

} }

118

public void keyTyped(KeyEvent e){} public static void main(String[] args){ Frame f=new Frame("Fereastra"); f.addKeyListener(new AscultatorFereastra(f)); f.setSize(100, 200); f.setVisible(true); } }

13.3.3 Gestiunea evenimentelor de tip WindowEvent

Exemplu. Vom scrie codul ce permite crearea unei ferestre care se închide de la butonul cu această funcţionalitate din bara de titlu a ferestrei. Când utilizatorul execută un clic de mouse pe acest buton, are loc un eveniment de tip WindowEvent, adică este creat un obiect din această clasă. Având acest obiect, putem implementa interfaţa WindowListener, care conţine 7 declaraţii de metode: public void windowClosed(WindowEvent e) ; public void windowIconified(WindowEvent e); public void windowOpened(WindowEvent e); public void windowClosing(WindowEvent e); public void windowDeiconified(WindowEvent e); public void windowActivated(WindowEvent e); public void windowDeactivated(WindowEvent e);

şi fereastra se poate înregistra ca ascultător al evenimentelor apărute de tip WindowEvent: void addWindowListener(WindowListener)

Dintre declaraţiile de metode ale lui WindowListener, ne interesează numai windowClosing(WindowEvent e), care va apelată în cazul închiderii ferestrei. Bineînţeles că şi restul metodelor vor implementate, dar corpul acestora va fi vid. Programul este prezentat în continuare. import java.awt.*; import java.awt.event.*; public class FereastraCareSeInchide extends Frame implements WindowListener { public FereastraCareSeInchide() { setSize(300, 200); addWindowListener(this); } public void windowClosed(WindowEvent e) {} public void windowIconified(WindowEvent e){} public void windowOpened(WindowEvent e) {} public void windowClosing(WindowEvent e) { System.exit(0); } public void windowDeiconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public static void main(String[] args) { Frame f = new FereastraCareSeInchide(); f.show(); } }

13.4 CLASE ADAPTER ALE INTERFEŢELOR DE ASCULTARE După cum am văzut până acum, interfeţele ascultător ce tratează evenimentele de nivel coborât conţin mai multe metode ce trebuie să fie implementate de orice clasă care se înregistrează la o

FereastraCareSeInchide

Frame WindowListener

119

sursă ce generează evenimente din această categorie. Biblioteca Java furnizează în pachetul java.awt.event clase adapter, câte una pentru fiecare din aceste interfeţe ascultător ce conţine mai multe metode. Un adapter este o clasă care doar implementează una din aceste interfeţe şi atât. Cu alte cuvinte, metodele oricărui adapter sunt vide. De exemplu, clasa WindowAdapter , adapter al interfeţei WindowListener, are urmatoarea structură: public class WindowAdapter implements WindowListener{ public void windowClosed(WindowEvent e){} public void windowIconified(WindowEvent e){} public void windowOpened(WindowEvent e){} public void windowClosing(WindowEvent e){} public void windowDeiconified(WindowEvent e){} public void windowActivated(WindowEvent e){} public void windowDeactivated(WindowEvent e){} } Atunci, orice clasă ascultător ar putea extinde o clasă adapter şi să redefinească numai metoda sau metodele pe care o interesează. De exemplu, putem modifica programul anterior folosind o clasă internă sau nu, numită InchidereFereastra care extinde clasa adapter WindowAdapter şi redefineşte numai metoda windowClosing(). Programul rezultat este prezentat în continuare. import java.awt.*; import java.awt.event.*; public class FereastraCareSeInchide extends Frame{ public FereastraCareSeInchide() { InchidereFereastra if = new InchidereFereastra(); addWindowListener(if); setSize(300, 200); setTitle(getClass().getName()); } public static void main(String[] args){ Frame f = new FereastraCareSeInchide(); f.show(); } class InchidereFereastra extends WindowAdapter( void windowClosing(WindowEvent e){ System.exit(0); } } } În tabelul următor (Tabelul 13-2) prezentăm toate interfeţele ascultător din pachetul java.awt.event împreună cu metodele lor şi clasa adapter asociată, precum şi tipurile de componente grafice la care se pot înregistra obiecte ale claselor care implementează interfaţa ascultător respectivă sau extind clasa adapter corespunzătoare interfeţei respective.

Interfaţă Listener Clasă adapter Metode Evenimente generate de:

ActionListener nu există actionPerformed Button, List, MenuItem, TextField

AdjustmentListener nu există adjustmentValueChanged Scrollbar ComponentListener ComponentAdapter componentHidden

componentMoved componenResized componentShown

Component

ContainerListener ContainerAdapter componentAdded componentRemoved

Container

FocusListener FocusAdapter focusGained focusLost

Component

ItemListener nu există itemStateChanged Checkbox, CheckboxMenuItem,

120

Interfaţă Listener Clasă adapter Metode Evenimente generate de: Choice, List

KeyListener KeyAdapter keyPressed keyReleased keyTyped

Component

MouseListener MouseAdapter mouseClicked mouseEntered mouseExited mousePressed mouseReleased

Component

MouseMotionListener MouseMotionAdapter mouseDragged mouseMoved

Component

TextListener nu există textValueChanged TextComponent WindowListener WindowAdapter windowActivated

windowClosed windowClosing windowDeactivated windowDeiconified windowIconified windowOpened

Window

Tabelul 13-2. Clase adapter ale interfeţelor ascultător

13.1.2 Clase anonime şi adaptori

Am văzut că orice interfaţă xxxxListener cu mai multe metode are o clasă xxxxAdapter care introduce o implementare vidă pentru metodele interfeţei. Obiectul ascultător poate fi al unei subclase a adaptorului care redefineşte numai metodele necesare. În practică, obiectul ascultător face parte dintr-o clasă internă anonimă definită ca subclasă a ascultătorului. De exemplu, putem modifica programul anterior şi să facem clasa InchidereFereastra clasă anonimă. Atunci obiectul referit de variabila if va fi creat local în metoda addWindowListener unde vom defini şi structura clasei anonime: import java.awt.*; import java.awt.event.*; public class FereastraCareSeInchide extends Frame{ public FereastraCareSeInchide(){ addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0); } });//inchiderea metodei addWindowListener setSize(300, 200); setTitle(getClass().getName()); } public static void main(String[] args){ Frame f = new FereastraCareSeInchide(); f.show(); } } Exemplu. class Controller implements ActionListener{ private PrintWriter out; public void actionPerformed(ActionEvent e){ if (e.getSource()==print){ PrinterJob imprimanta=PrinterJob.getPrinterJob(); Book bk=new Book(); bk.append(new ContinutPanel(), imprimanta.defaultPage()); imprimanta.setPageable(bk); if(imprimanta.printDialog()){

121

try{imprimanta.print();} catch (PrinterException pe){arie.append("Imprimanta nu exista");arie.repaint();} catch(ArrayIndexOutOfBoundsException ae){System.out.println("Ce se printeaza???");} } } else if (e.getSource()==printF){ try{ out=new PrintWriter(new FileOutputStream("factura.dat")); out.write(factura.toString()); out.flush(); out.close(); }catch(FileNotFoundException fe){} catch(IOException ioe){} } else if (e.getSource()==arhivare){ facturi.adaugaFactura(factura); } else if (e.getSource()==cancel) setVisible(false);} } class ContinutPanel extends Panel implements Printable { private BufferedReader br; private String sir=""; public int print(Graphics g, PageFormat pf,int pageIndex) throws PrinterException{ g.setColor(Color.black); try{ StringReader continut=new StringReader(arie.getText()); br=new BufferedReader(continut); int i=0; while((sir=br.readLine())!=null) { if (sir.length()==0) sir=" "; g.drawString(sir,100,100+i);i+= 20; } }catch(IOException io){} catch (IllegalArgumentException ie){} return Printable.PAGE_EXISTS; } }

122

14. PROGRAMAREA INTERFETELOR GRAFICE CU SWING Swing este un tehnologie API care extinde AWT-ul pentru construirea interfetelor grafice. Swing este o multime de componente usoare aflate in pachetul javax.swing.*. Spre deosebire de componentele grele nu sunt incadrate intr-o fereastra (window) proprie, opaca, nativa (1), ci in fereastra container-ului lor greu (2). Din (1) rezulta ca, componentele usoare pot avea background-uri transparente. Aproape toate componentele Swing-ului sunt usoare; exceptiile sunt container-ele de nivel inalt: - JFrame – implementeaza o fereastra principala, - JApplet- implementeaza o arie dreptunghiulara in fereastra browser-ului - JWindow – o fereastra externa, - JDialog – implementeaza o fereastra secundara Din (2) rezulta ca componentele usoare trebuie sa stea in ultima instanta intr-un container greu, adica o subclasa a clasei java.awt.Container.

Container-e usoare: 1. JPanel – succesorul lui Panel si Canvas. De aceea, are un rol dublu: este un simplu container si

un canvas pentru afisarea graficelor. 2. JRootPane – este continut in orice container greu. - furnizeaza o ierarhizare a continutului:

JFrame

Content pane

JPanel

JButton JLabel

public class Fereastra extends JFrame{ public Fereastra(){ Container container=getContentPane(); JPanel panel=new JPanel(); panel.add(new JButton(“Buton”)); panel.add(new JLabel(“Label”)); container.add(panel, BorderLayout.NORTH); } }

Componenta Clasa Descriere stratPanel JLayeredPane contine continutPanel

si menuBar continutPanel JPanel contine componentele

aplicatiei sau applet menuBar JMenuBar sta deasupra lui

continutPanel sticlaPanel JPanel capteaza evenimentele

mouse-ului si poate fi transparent

Panel radacina

stratPanel

continutPanel

menuBar

sticlaPanel

123

Exemplu. Sa scriem un program care contine un buton care determina afisarea/ascunderea unui panel de sticla ce afiseaza textul “Buna ziua” pe toata suprafata sa.

3. JLayeredPane –nu are un layout manager implicit si permite plasarea componentelor pe diferite straturi:

Strat Valoare Descriere FRAME_CONTENT_LAYER -3000 Stratul de baza, unde bar menu-ul si content pane sunt

asezate DEFAULT_LAYER 0 Implicit, pe acest strat sunt plasate componentele PALETTE_LAYER 100 Folosit pentru palete si toolbar-uri MODAL_LAYER 200 Folosit de catre ferestrele de dialog POPUP_LAYER 300 Pentru menu-urile popup DRAG_LAYER 400 Util in deplasarea componentelor sau a celor care

trebuie sa stea deasupra tuturor celorlalt Obs. Straturile cu valori mai mari sunt plasate deasupra straturilor cu valori mai mici. Plasarea componentelor poate fi controlata si in cadrul unui acelasi strat prin urmatoarele proprietati: Proprietate Descriere Index -getIndexOf(comp)

Indexul intr-un vector de componente pastrat de panelul stratificat. Indicele are legatura cu ordinea componentelor: cele cu indice mai mic sunt plasate deasupra celor cu indice mai mare.

Strat -getLayer(comp)

Stratul pe care se afla componenta respectiva. Componentele de pe un strat cu numar mai mic sunt afisate sub componentele ce se afla pe straturi cu numar mai mare.

Pozitie -getPosition(comp)

Pozitia unei componente relativa la celelalte componente din acelasi strat. Componentele cu pozitii mai mici sunt afisate deasupra celor cu pozitii mai mari.

4. JTabbedPane – panel ce contine pagini (tab-uri) de paneluri.

import javax.swing.*; import java.awt.event.*; import java.awt.*; public class Prob1 extends JFrame implements ActionListener { private Component panelSticla; private JButton buton; public Prob1(){ panelSticla=new PanelSticla(); setGlassPane(panelSticla); buton=new JButton("", new ImageIcon("swing.small.gif")); buton.addActionListener(this); Container content=getContentPane(); content.add(buton); } public void actionPerformed(ActionEvent e){ panelSticla.setVisible(true); } public static void main(String[] arg){ JFrame f=new Prob1(); f.setSize(200,200); f.setVisible(true); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }}

class PanelSticla extends JPanel{ public PanelSticla(){ setOpaque(false); addMouseListener(new MouseAdapter(){ public void mousePressed(MouseEvent e){ setVisible(false); }}); } public void paintComponent(Graphics g){ String sir="Buna ziua"; Dimension d= getSize(); FontMetrics f=g.getFontMetrics(); int l=f.stringWidth(sir); int i=f.getHeight(); g.setColor(Color.blue); for (int li=i; li<d.height; li+=i)

for (int c=0; c<d.width; c+=l) g.drawString(sir, c, li);

} }

124

Exemplu. Fereastra cu 2 pagini de panel-uri ce contin cate un buton.

5. JSplitPane – afiseaza doua componente (initial, butoane) separate de un despartitor (divider).

Componentele pot fi orientate vertical sau orizontal (JSplitPane.HORIZONTAL_SPLIT sau VERTICAL_SPLIT). Metoda: setOrientation(constanta).

Panelul foloseste doua proprietati booleene:

- layout continuu – controleaza daca componentele continute sunt incontinuu actualizate in timp ce separatorul este deplasat. Metoda: setContinuousLayout(boolean)

- redimensionare la comanda (one-touch expandable) determina daca afiseaza un control in separator. Controlul permite marirea sau micsorarea componentelor cand este click-at. Metoda: setOneTouchExpandable(boolean)

Exemplu.

import javax.swing.*; import java.awt.event.*; import java.awt.*; public class Prob3 extends JFrame{ private JTabbedPane tp; public Prob3(){ tp=new JTabbedPane(); JPanel panel1=new JPanel(); panel1.add(new JButton("buton in panelul 1")); JPanel panel2=new JPanel(); panel2.add(new JButton("buton in panelul 2")); tp.add(panel1, "Primul panel"); tp.addTab("Al doilea panel", new ImageIcon("ok.gif"), panel2, "Panelul 2"); Container contentPane=getContentPane(); contentPane.add(tp); }

public class Prob4 extends JFrame{ private JSplitPane sp; public Prob4(){ sp=new JSplitPane(); sp.setContinuousLayout(true); //sp.setOneTouchExpandable(true); sp.setDividerSize(15); sp.setOrientation(JSplitPane.HORIZONTAL_SPLIT); Container contentPane=getContentPane(); contentPane.add(sp); }

125

FERESTRE INTERNE Swing ofera functionalitate MDI (Multiple Documente Interface) prin ferestre interne- clasa JInternalFrame, care stau pe desktop -clasa JDesktopPane. Ferestrele interne sunt: - ferestre deoarece furnizeaza multe din caracteristicile unei ferestre (inchidere, deschidere,

maximizare, minimizare, iconificare, redimensionare), - interne deoarece stau, fiind componente usoare, intr-un alt container Swing, de obicei un

desktop pane. Proprietati:

Nume Proprietate

Tip de data Acces

Valoarea implicita

Descriere

closable boolean C3SG1

false Indica daca fereastra interna poate fi inchisa de catre utilizator

closed boolean SG false indica daca fereastra este inchisa la momentul respectiv

contentPane Container SG instanta de JPanel container-ul ce contine componentele din fereastra

defaultCloseOperation

int SG WindowConstants.HIDE_ON_CLOSE

indica operatia care are loc cand fereastra este inchisa

desktopIcon JDesktopIcon SG L&F Icon-ul afisat pe desktop cand fereastra este redusa la icon

desktopPane JDesktopPane G - O instanta a lui JDesktopPane ce contine una sau mai multe ferestre interne

frameIcon Icon SG L&F Icon-ul afisat in bara de titlu al ferestrei

glassPane Component SG instanta JPanel Panelul de sticla asociat cu panelul radacina al unei ferestre interne

icon boolean SG false Determina daca fereastra interna este (poate fi) redusa la icon – setIcon(boolean)

iconifiable boolean C5SG

false Determina daca fereastra interna poate fi redusa la un icon prin apelul metodei setIcon(boolean)

layer Integer SG DEFAULT_LAYER

Stratul pe care sta fereastra interna. Implicit este DEFAULT_LAYER

layeredPane JLayeredPane SG JLayeredPane Panelul stratificat asociat panelului radacina al ferestrei interne

maximizable boolean C4SG

false Indica daca fereastra poate fi maximizata printr-un clic pe butonul corespunzator

maximum boolean SG false Determina daca fereastra este maximizata

jMenuBar JMenuBar SG null Bara de menu asociata panelului radacina al ferestrei interne

resizable boolean C2SG

false Determina daca fereastra poate fi redimensionata. Daca este maximizata nu poate fi redimensionata

rootPane JRootPane SG JRootPane Panelul radacina asociat ferestrei 1 C-poate apare in constructor, S-setter, G-getter

126

selected boolean SG false Selecteaza sau deselecteaza fereastra interna

title String C1SG

null Titlul afisat in bara de titlu al ferestrei

Exemplu. Sa cream o fereastra care la actiunea unui buton vor fi create ferestre interne (ce vor contine o eticheta)

Scroll-area componentelor Este necesara in cazul in care o componenta (tabele, texte, trees, imagini, liste) este mai mare decat spatiul de afisare. Pentru aceasta putem folosi urmatoarele clase: - container-ele usoare JViewport si JScrollPane (inlocuieste si imbogateste componenta grea

AWT ScrollPane) - interfata Scrollable - JScrollBar folosit pentru implementarea manuala a barelor de scroll-are

CLASA JVIEWPORT - instantele sale furnizeaza un obiectiv (porthole) prin care se afiseaza o anumita regiune a

vederii. Pozitia vederii poate fi manevrata pentru a afisa regiuni diferite ale vederii astfel: - pentru a muta vederea in sus: crestem coordonata Y a vederii - pentru a muta vederea in jos: descrestem coordonata Y a vederii - pentru a muta vederea la stanga: crestem coordonata X a vederii - pentru a muta vederea la dreapta: descrestem coordonata X a vederii

Proprietati Nume Proprietate

Tip de data Acces

Valoarea implicita

Descriere

scrollMode int SG SIMPLE_SCROLL_MODE

moduri de a realiza scroll-area: BLIT_SCROLL_MODE, BACKINGSTORE_SCROLL_MODE, SIMPLE_SCROLL_MODE

extentSize Dimension SG - Este o instanta a clasei Dimension care reprezinta partea vizibila a vederii

view Component SG - Componenta afisata in viewport

public class Prob1 extends JFrame implements ActionListener{ private JDesktopPane dp; private JInternalFrame fi; private JButton buton; public Prob1(){ Container contentPane=getContentPane(); buton=new JButton("creaza Frame"); buton.addActionListener(this); contentPane.add(buton, BorderLayout.NORTH); dp=new JDesktopPane(); dp.setLayout(new FlowLayout()); contentPane.add(dp, BorderLayout.CENTER); } public void actionPerformed(ActionEvent e){ fi=new JInternalFrame("O noua fereastra interna", true,true, true, true); fi.setPreferredSize(new Dimension(250,150)); //fi.setFrameIcon(new ImageIcon("ok.gif")); /*Container content=fi.getContentPane(); JPanel p=new JPanel(); p.add(new JLabel(new ImageIcon("swing.small.gif"))); content.add(p);*/ dp.add(fi); fi.setVisible(true); dp.revalidate();}

127

viewPosition Point SG - Este un punct ce reprezinta coordonatele vederii din coltul stanga sus al obiectivului

viewRect Rectangle G - O instanta a lui Rectangle care reprezinta dimensiunea si pozitia partii vizibile a vederii. Latimea si inaltimea dreptunghiului sunt egale cu extent

viewSize Dimension SG - O instanta a lui Dimension care reprezinta dimensiunea vederii. Daca nu este setata explicit, este egala cu dimensiunea preferred a vederii

JSCROLLPANE - este container uşor ce contine un viewport cu scrollbar-uri si header-e de coloane si linie

optionale. Elementele unui JScrollPane:

Proprietati Nume Proprietate Tip de data Acc

es Valoarea implicita

Descriere

columnHeader JViewport SG null o instanta a lui JViewport pentru header-ul de coloane

columnHeaderView

Component S null o instanta a lui Component folosita ca header de coloane pentru vederea viewport-ului

corner Component SG null o componenta care este afisata in unul din cele patru colturi: ScrollPaneConstants.UPPER_LEFT_CORNER ScrollPaneConstants.LOWER_LEFT_CORNER ScrollPaneConstants.UPPER_RIGHT_CORNER ScrollPaneConstants.LOWER_RIGHT_CORNER

horizontalScrollbar

JScrollBar SG - Scrollbar-ul orizontal folosit de panel

horizontalScrollbarPolicy

int CSG JScrollPane.HORIZONTAL_SCROLLBAR_

Politica folosita pentru detrminaea circumstantelor in care scrollbar-ul orizontal este afisat. Constante: ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED

128

AS_NEEDED

ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS

rowHeader JViewport SG null o instanta a lui JViewport pentru header-ul de linii

rowHeaderView Component G null o instanta a lui Component folosita ca header de linii pentru vederea viewport-ului

verticalScrollbar JScrollBar SG - verticalScrollbarPolicy

int CSG JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED

ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS

viewport JViewport SG JViewport

O instanta a lui JViewport folosita pentru afisarea componentei scroll-ata de panel

viewportBorder Border SG null O bordura pentru viewport viewportView Component CSG null componenta afisata in viewport-ul panelului

129

15. BIBLIOGRAFIE I. Athanasiu si colectiv, Limbajul Java. O perspectivă pragmatică. Editura Teora, 1998

B. Eckel, Thinking in Java. Prentice Hall, 1998

C. S. Horstmann, Computing Concepts with Java 2 Essentials, Second Edition. John Wiley&Sons, 2000

S. Tănasă, C. Olaru, S. Andrei, Java de la 0 la expert. Editura Polirom, 2003


Recommended